Как отлаживать приложение для iOS 10 в xCode 7.3.1

     

Случилась такая задача - отладить приложение собираемое с iOS 9 SDK на телефоне с iOS 10. Для этого собирать его надо естественно в xCode 7, но xCode 7 не видит устройства с iOS 10. Проблема относительно просто решается вот такой вот магией:

sudo ln -s /Applications/Xcode8beta4.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/10.0\ \(14A5322e\) /Applications/Xcode7.3.1.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/

После этого можно отлаживаться в обычном режиме.

Аналогичным образом можно подцепить к Xcode 8 девайс с iOS 7.1 например

sudo ln -s /Applications/Xcode7.3.1.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/7.1 /Applications/Xcode8GM.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport

Но это уже на ваш страх и риск - никто работоспособность такого хака не гарантирует

Poor Man's package management

     

Вы наверняка пользовались пакетными менеджерами а-ля CocoaPods и Carthage. Однако иногда возникают ситуации когда вы хотите подцепить себе в проект сторонний проект, но при этом по какой-то причине не хотите пользоваться этими менеджерами пакетов. В таких случаях может пригодиться техника, которую я ласково называю “Poor Man’s package management” (пакетный менеджер для бедных, уж простят меня оные).

Суть техники в следующем - зависимости мы цепляем непосредственно как часть нашего проекта с помощью git submodules. Да, техника работает для пользователей git (хотя лично я знаю как ее применить и для SVN, думается, что уж для популярных DCVS систем такую технику уж точно придумать можно).

Для того, чтобы репозиторий зависимости стал “подпапкой” вашего проекта достаточно сделать так

git submodule add http://github.com/cooluser/coolproject ./deps/coolproject

и далее ручками через xcode добавляем нужные файлы к себе в проект. Не забываем закоммитить .gitmodules себе в проект, это нужно чтобы ваши коллеги могли себе собрать тоже и также.

Чтобы другой человек (ваш коллега, например) получил полный экземпляр вашей работы кроме обычных действий при работе с подмодулями ему надо сделать

git submodule init
git submodule update

Проверяем версию iOS

     

Как проверить версию iOS? Очень просто:

NSString* requiredVersion = @"9.3.1";
NSString* currentSystemVersion = [[UIDevice currentDevice] systemVersion];
if ([currSystemVersion compare:requiredVersion options:NSNumericSearch] != NSOrderedAscending) {
    // code that is suitable for iOS >= 9.3.1
} else {
    // code for versions older that 9.3.1
}

В Swift’е это сделать несколько проще:

if #available(iOS 9.3.1, *) {
    // code that is suitable for iOS >= 9.3.1
} else {
    // code for versions older than 9.3.1
}

Важное отступление. Наверняка возникает вопрос, почему нельзя сделать так:

if (UIDevice.currentDevice.systemVersion.floatValue >= 9.3) {
    // do something for iOS greater or equal 9.3
}

Отвечаю. Пункт первый - а что если вам надо >= 9.3.1 именно с 1 обновления, а не для всей 9.3? Float сравнение уже не позволит написать такое условие, в то же время приведенный мной код строкового сравнения позволяет сделать это. Пункт второй, более важный - сравнение чисел с плавающей точкой. Пример, для вот такого кода

NSString* systemVersion = @"9.3.2";
float sysVer = systemVersion.floatValue;

в sysVer будет 9.30000019 и простое сравнение sysVer == 9.3 не сработает. В таком случае надо бы делать fabs(sysVer - 9.3) <= 0.01 что уже как проигрывает по выразительности, так и ставит вопросы - а каков эпсилон версий операционки?

Со swift’ом в общем таких проблем нет.

Собираем свою версию bootstrap'а с кастомизацией

 

В этой статье опишу рецепт, как сделать свою кастомизацию базовых стилей фреймворка twitter bootstrap. Для начала нам понадобяться собственно исходники бутстрапа:

git clone https://github.com/twbs/bootstrap.git

И опционально переключаемся на нужный тэг:

git checkout tags/v3.3.7

Далее нам потребуется LESS для сборки css файлов bootstrap’а

sudo npm i less -g

теперь попытаемся собрать CSS:

lessc bootstrap/less/bootstrap.less > boostrap.css

Или минифицированную версию

lessc --compress bootstrap/less/bootstrap.less > boostrap.css

Теперь для создания своей конфигурации boostrap вы можете править файл bootstrap.less или его зависимости. Однако здесь таится одна небольшая проблема. Мы выше забрали исходники через гит. При выходе очередной версии вы наверняка захотите обновиться. Но в этом вам будут мешать сделанные изменения, которые надо будет куда-то деть. Да, конечно можно форкнуть репозиторий, сделать свою ветку и потом просто rebase’иться. Однако есть вариант лучше. Наши измения мы просто вынесем в отдельную папку, и они просто будут ссылаться на исходники boostrap’а. В таком случае обновление будет сводиться просто к git checkout tags/... и изредка правкой вашей кастомизации.

Чтож, приступим.

  1. Создаем папку

    mkdir customization
    
  2. Создаем свои файлы в которые будем помещать кастомизацию

    cp bootstrap/less/variables.less customization/custom-variables.less
    echo "" > customization/custom-other.less
    
  3. Далее создаем наш кастомизированный центральный файл customization/custom-boostrap.less со следующим содержимым:

    @import "../bootstrap/less/bootstrap.less";
    @import "custom-variables.less";
    @import "custom-other.less";
    @import "../bootstrap/less/utilities.less";
    
  4. Сделаем пробную сборку кастомизированного (на самом деле пока еще нет) bootstrap’а

    lessc customization/custom-bootstrap.less > bootstrap.css
    

После этого можно править custom-variables.less, перегенрировать и радоваться. Однако надо где-то смотреть. Есть вот такой хороший проект где все компоненты на одной странице https://github.com/mrdekk/bootstrap-tldr

git clone https://github.com/mrdekk/bootstrap-tldr
npm install
sudo npm i bower -g
bower install
sudo npm i grunt -g
lessc customization/custom-bootstrap.less bootstrap-tldr/app/styles/bootstrap.css
grunt serve

После этого http://localhost:9000 вы сможете лицезреть страничку со всеми элементами bootstrap

Крик души о мапперах API в модель

 
     
 

В последнее время очень часто приходится сталкиваться вот с каким вопросом. Мобильные приложения должны работать с бэкендом. Как правило, модель данных API бэкенда не совсем сходится с моделью данных на самом приложении, и даже если сходится 1 в 1, библиотеки сетевых запросов возвращают объекты класса словарь (как бы он не назывался - Map<?, ?>, NSDictionary, …) и возникает необходимость конвертации вида

func map(json: [String: AnyObject]) -> SomethingModel {
    var model = SomethingModel()    
    model.some = json["some"]
    model.another = json["another"]
    model.rest = json["rest"]
    ...
    return model
}

func reverse(obj: SomethingModel) -> [String: AnyObject] {
    var res = [String: AnyObject]()
    json["some"] = model.some
    json["another"] = model.another
    json["rest"] = model.rest
    ...
    return model
}

Как видно, это большей частью простые императивные преобразования. Иногда конечно возникает необходимость дополнительной проверки (например на nil) или кастомной конвертации (NSDate -> String). Однако для этого можно написать простые хелперы и код будет выглядить как-нибудь так:

model.nillable = Conv.nillable(json, "nillable")
model.date = Conv.date(json, "date")

Что же есть на практике. А на практике каждый уважающий себя разработчик стремится для model.some = json["some"] написать кастомную обертку, и что особенно важно, декларативно. То есть вместо упомянутой строчки мы получаем нечто вроде:


class SomethingModel {
    static func jsonMap() -> [String: AnyObject] {
        let res = [String: AnyObject]()
        res["some"] = StringMapping(path: "some")
        res["another"] = NumberMapping(path: "another", default: -1)
        res["rest"] = DateMapping(path: "rest", NSDate())
        ...
        return res
    }
}

class DeclarativeMapper<ModelClass> {
    func fromJson(json: [String: AnyObject]) -> ModelClass {
        let model = ModelClass()
        let mapping = ModelClass.jsonMap()
        for (field, mapper) in mapping.enumerate() {
            model.setValue(field: field, mapper.map(json, mapper.path))
        }
        return model
    }
}

let json: [String: AnyObject] = ...
let declarativeMapper = DeclarativeMapper()
let model = declarativeMapper.fromJson(json)

У меня есть главный вопрос - а зачем все это делается? Ради декларативности - императивный код проще и сосредоточен в одном месте - в коде запроса, в точке наиболее близкой к точке изменения контракта. Декларативный код размазывает маппинги по нескольким местам и вносит дополнительный код (мапперы), которые могут сломаться. Кроме того, декларативный фреймворки делают некоторые предположения относительно того, как работает бэкенд и если вдруг бэкенд работает не совсем так - у фреймворка возникают проблемы.

Может ли кто-нибудь привести примеры, в которой вся эта декларактивная машинерия оправдана?