10.08.2016
Как проверить версию 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’ом в общем таких проблем нет.
05.08.2016
В этой статье опишу рецепт, как сделать свою кастомизацию базовых стилей фреймворка twitter bootstrap. Для начала нам понадобяться собственно исходники бутстрапа:
git clone https://github.com/twbs/bootstrap.git
И опционально переключаемся на нужный тэг:
Далее нам потребуется LESS для сборки css файлов bootstrap’а
теперь попытаемся собрать CSS:
lessc bootstrap/less/bootstrap.less > boostrap.css
Или минифицированную версию
lessc --compress bootstrap/less/bootstrap.less > boostrap.css
Теперь для создания своей конфигурации boostrap вы можете править файл bootstrap.less или его зависимости. Однако здесь таится одна небольшая проблема. Мы выше забрали исходники через гит. При выходе очередной версии вы наверняка захотите обновиться. Но в этом вам будут мешать сделанные изменения, которые надо будет куда-то деть. Да, конечно можно форкнуть репозиторий, сделать свою ветку и потом просто rebase’иться. Однако есть вариант лучше. Наши измения мы просто вынесем в отдельную папку, и они просто будут ссылаться на исходники boostrap’а. В таком случае обновление будет сводиться просто к git checkout tags/...
и изредка правкой вашей кастомизации.
Чтож, приступим.
-
Создаем папку
-
Создаем свои файлы в которые будем помещать кастомизацию
cp bootstrap/less/variables.less customization/custom-variables.less
echo "" > customization/custom-other.less
-
Далее создаем наш кастомизированный центральный файл customization/custom-boostrap.less со следующим содержимым:
@import "../bootstrap/less/bootstrap.less";
@import "custom-variables.less";
@import "custom-other.less";
@import "../bootstrap/less/utilities.less";
-
Сделаем пробную сборку кастомизированного (на самом деле пока еще нет) 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
01.08.2016
В последнее время очень часто приходится сталкиваться вот с каким вопросом. Мобильные приложения должны работать с бэкендом. Как правило, модель данных 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)
У меня есть главный вопрос - а зачем все это делается? Ради декларативности - императивный код проще и сосредоточен в одном месте - в коде запроса, в точке наиболее близкой к точке изменения контракта. Декларативный код размазывает маппинги по нескольким местам и вносит дополнительный код (мапперы), которые могут сломаться. Кроме того, декларативный фреймворки делают некоторые предположения относительно того, как работает бэкенд и если вдруг бэкенд работает не совсем так - у фреймворка возникают проблемы.
Может ли кто-нибудь привести примеры, в которой вся эта декларактивная машинерия оправдана?
03.07.2016
Решили мы тут попробовать свои силы и таки взобраться на Конжак. “Конжаком” в народе называют гору Конжаковский камень - самую высокую гору Свердловской области (не путать с Уралом, на Серверном Урале есть горы и повыше). Каждый год здесь проводится горный марафон “Конжак”, где участникам предлагается на время бегом взобраться на вершину, там отметиться и вернуться на старт. Ну а раз проводиться марафон, то по маршруту марафона организованы контрольные пункты, и на трассе присутствует огромное количество людей - не так страшно в безлюдных горах. Марафон, как правило, проводят с 10 часов (дают старт забегу) и до 8 вечера. Вообще на трассе находятся два вида участников: “спортсмены” - те, кто официально участвует в марафоне, и “туристы” - те, кто путаются под ногами забираются в гору самостоятельно и не участвуют в марафоне.
Итак, как сообщает нам википедия:
Конжаковский Камень — гора в южной части Северного Урала, на территории Свердловской области (Россия). Одна из высочайших вершин Уральских гор (1569 м). Сложена пироксенитами, дунитами и габбро. В нижней части склоны покрыты хвойными лесами, а с высоты 900—1000 м — горной тундрой и каменными россыпями[1]. Названа по имени охотника-вогула Конжакова, юрта которого некогда стояла у основания горы[2].
Вдоль склонов горы проложена трасса марафона, на 4 километре которой есть даже вот такая его карта
Рассчитывая свои силы мы решили пойти пораньше, так как мы совсем не марафонцы. Начало не предвещало того, что потом открылось нам:
По дороге нам встретился вполне горный горный перевал, а потом дорога пошла вниз (мы удивились - в гору же идем) и привела нас к плещущейся горной реке и наведенной через нее переправе:
Чуть выше по маршруту на нее можно посмотреть с высоты:
Дальше начался подъем вверх, сначала пологий, но потихоньку он становился все круче и круче. После небольшого подъема (примерно 2 км) встречается КП “Родник”, где “спортсменов” кормят бутербродами и поют чаем, а “туристы” могут пополнить запасы воды в роднике. Вода, кстати, вкусная. Далее после небольшого горизонтального участка трасса круто забирает вверх. На этом подъеме нас догнали первые “спортсмены”, которые в отличии от нас стартовали не в 8, а в 10. Далее, после еще нескольких километров крутого подъема, встречается второй контрольный пункт - “Поляна Художников”. Это отметка 14 км от начала трассы и, как правило, многие “туристы” заканчивают здесь свое восхождение (высота около 1000 м над уровнем моря). Но мы хотели взобраться совсем наверх, поэтому пошли дальше.
(спойлер - обратите внимание на муху в центре кадра)
С этой поляны открываются впечатляющие виды
Следующим впечатлящим пунктом стал ледник, который мы запреметили еще издалека
Ледник уже сам по себе находится достаточно близко к первым вершинам (еще не сам Конжак):
А вот с него уже деревья не мешают обзору, и вид вообще шикарный:
Дальше, после длительного карабканья по камням, мы попадаем на Иовское плато, где настолько высоко, что уже немного тяжело дышать, и ветер дует постоянно. А еще здесь как на болоте - почва утопает под ногами, и пройти в кросовках, не промочив ноги, практически невозможно.
Здесь начинаются обещанные альпийские луга (где, кстати, очень много растений из Красной книги)
Виды с Иовского плато
Здесь, посмотрев на почти отвесный подъем по камням на саму вершину:
Мы решили на этом закруглиться. Итого 1246 метров из 1569, не дошли чуть больше 200 метров в высоту и 2,5 км по трассе в длину.
Вид с конечной точки нашего восхождения
“Дохлости”
Далее мы пошли вниз. В итоге - 39,46 километров мы прошли (марафон был 42) сделав 36 тысяч шагов.
07.06.2016
Некоторые полезные в работе флаги
-UIViewShowAlignmentRects YES
- показывает желтыми линиями frame rect’ы, которые удобно использовать для отладки
-com.apple.CoreData.ConcurrencyDebug 1
- “стреляет” assert’ами, когда NSManagedObjectContext или другие примитивы CoreData используются в неправильном потоке
-com.apple.CoreData.SQLDebug 1
- пишет в лог все sqlite запросы, которые делает CoreData в процессе своей работы
-Name:OS_ACTIVITY_MODE disable
- отключает вывод системного лога в debug окно Xcode 8
DYLD_PRINT_STATISTICS 1
- выводит данные о загрузке внешних библиотек (и не только)
SWIFT_DEBUG_IMPLICIT_OBJC_ENTRYPOINT 1-3
- выводит лог использования @objc inference
Некоторые полезные флаги компилятора
-Wglobal-constructors
- показывает использование нетривиальных конструкторов С++ при инициализации глобальных объектов
Разные полезные штуки при работе с LLDB