Все новости с меткой: swift


Последнее время стало модно использовать всеми горячо любимый фреймворк Swinject для внедрения зависимостей , о чем не стесняются писать в резюме. Для чего? Если коротко, то ваш код не должен быть сильно связным, все зависимости каждого компонента должны зависеть лишь от абстракций, а не от конкретной реализации. По крайней мере этого просит буква D из принципов SOLID. А внедрять эти зависимости можно из так называемого контейнера для внедрения зависимостей, напоминающий фабрику. Этот контейнер возвращает определенный инстанс определенного класса, отвечающему заданному протоколу, в том числе и с другими вложенными зависимостями. Получается, что пользователь не знает, каким образом и какой конкретный объект ему был возвращен контейнером, т.к. работает с объектом строго через заданный протокол. Таким образом можно еще и удобно протестировать работу отдельного компонента, сделав контейнер с заданной конфигурацией, в которой будут отдаваться классы-заглушки с законсервированными данными для зависимостей тестируемого компонента. Еще прикольно, что Swinject умеет задавать стратегию создания объекта, например, либо каждый раз новый объект, либо один раз в виде синглтона (+ это еще не все)


После обновления до Swift 5.1 я его выбросил нахрен и написал свой Dependency Injection Container, причем ровно с таким же интерфейсом как и у Swinject, даже переписывать ничего не пришлось в уже существовавшем контейнере. Вроде бы дело нехитрое, запомнить стратегию создания объекта для заданного протокола в кложуре и когда тебя попросят - просто зарезолвить. Кроме того, добавилась еще и потокобезопасность! Да-да, в Swinject все разваливалось при резолвинге из разных потоков. Ну и еще я поддержал стратегию создания объектов в виде синглтона или каждый раз нового. Больше мне ничего от огромного фреймворка не нужно было.  Вышел компактный класс, который живет в хелперах проекта, можно забыть про отдельную перекомпиляцию рядом лежащего модуля. Ну и опять же, в очередной раз избавились от зависимости от модуля внедрения зависимостей, ведь когда-нибудь это могло бы сыграть злую шутку при очередном обновлении языка Swift

Подробнее




Наверняка у каждого iOS-разработчика про запас лежит свой кастомный контрол, который умеет получать и отображать картинку из интернетика по URL. Некоторые любители зависимостей cocoapods также используют сторонний SDWebImage. Впрочем, я решил поиспражняться поупражняться и сделать свой велосипед. Ведь всяко может обновиться iOS SDK, пацаны могут выкатить новый Swift, и тут выяснится, что все пропало, а контрибьютер стороннего компонента уже срубил кучу бабла и где-нибудь чиллит на Мальдивах с безлимитным куба-либре в руке, а релиз у вас уже завтра, и что делать - хз.

Кстати, сама идея использовать URL для UIImageView породила вот этот доклад, в котором ребята из ВКонтакте в сам урл запихивают допустим GPS-координаты - а в ответ получают картинку с местоположением в Google Maps, либо накладывать локально фильтры на изображение из галереи, очень гибко переопределив работу URLProtocol

Мой велосипед умеет:

  • Async load of images from the given url
  • Save already loaded images in NSCache
  • Persist already loaded images in app caches directory and restore it back after app relaunch
  • Ability to set placeholder while image is loading
  • Create only one network request when trying to load 1000 images with the same URL at the same time. Other copies are waiting the network request result

А пользоваться еще проще.

Листинг кода можно посмотреть на гитхабе, особенно может быть интересным, как сделано ожидание загрузки у остальных картинок с одинаковым урлом, пока первая не скачается, а остальным нотифицирует, что пора бы обновить картинку локально

Подробнее




Существует довольно-таки распространенная  необходимость сигнализировать о каких-либо событиях от одного к нескольким объектам. В Swift это решается двумя способами из коробки:

  1. KVO, которое тянет за собой обязательство наследоваться от NSObject
  2. NotificationCenter, который до iOS 11 SDK хранил сильную ссылку на объект-наблюдатель, что приводило к утечке памяти, если вовремя не отписаться. Еще он неудобен тем, что необходимо отписываться явным образом от конкретной сигнатуры нотификации, чтобы случайно не отписать от других важных событий текущий объект, если использовать removeObserver(self)

Еще один годный вариант - сделать свой Observable, который будет:

  1. Работать на кложурах, без использования NSObject-based классов или @objc-методов
  2. Самостоятельно больше не трекать умерших наблюдателей и не слать нотификации мертвому объекту

Идея не нова, в С++ уже давно используется boost::signal, примерно похожее и удобное будем делать на Swift


Для начала нужно научиться хранить кложуру, чтобы впоследствии можно было бы ее вызвать. Создадим некий "держатель кложуры", шаблонным параметром которого будут являтся типы параметров вызова аргументов. Забегая вперед, держатель кложуры будет сообщать о своем уничтожении к Observable, чтобы тот почистил список наблюдателей

В самом Observable будет храниться список кложур, завернутых в ClosureHolder. Но не все так просто, список держателей кложур будет держать сильную ссылку на держателя кложуры, отсюда она никогда самостоятельно не уничтожится. Для этого нужно научиться складывать объекты в массив, при этом не увеличивая счетчик ссылок на этот объект. Еще один враппер:

Теперь осталось сделать сам Observable, который будет добавлять, очищать и вызывать кложуры:

Идея такая, что при подписке нужно хранить где-то созданный ClosureHolder. От нотификаций наблюдатель будет автоматически отписываться при уничтожении, либо при явном обнулении переменной, хранящей хранителя кложуры

Как вообще этим пользоваться:

Как видим, кейсы с Test 2 и Test 4 выведены не будут, т.к. наблюдатель был отписан от нотификаций. Полный кусок кода лежит на гитхабе.

Прощай, NotificationCenter. Ну почти

Подробнее




Недавно затащил в проект SwiftLint, чтобы код как можно ближе соответствовал Swift Style Guide, ну чтобы в общем был по всем канонам. Куча ворнингов, и довольно часто встречается вот такой:

Line Length Violation: Line should be 120 characters or less: currently 139 characters (line_length)

Вылазит для куска кода, где в wireframe инстанциируется View-слой для VIPER-модуля. Аналогично и для получения ячейки по идентификатору из UICollectionView / UITableView

Казалось бы, куда уж короче, ведь строчку некуда сокращать. Нашлась достаточно удобная оптимизация, позволяющая сделать код более читаемым, а также избавиться от строковых констант, для которых автокомплит не сработает. Кроме повышения читаемости кода, исключаем риск опечататься в строковом идентификаторе, который задан в Storyboard


Глядя на код выше, мы-таки можем что-то сделать, чтобы не писать 2 раза 2 раза MyViperModuleViewController. Как минимум идея заключается в том, чтобы давать названия для вьюконтроллеров в самих сторибоардах ровно так, как они и называются

Для начала научимся получать текстовый идентификатор из имени нужного класса:

Ну и сделать укороченные версии методов для UIStoryboard, UICollectionView и UITableView:

Получаем более читаемый код, в котором опущены id вьюконтроллеров и ячеек:

??????

PROFIT

Подробнее




В общем, задача такая, сделать почти как в андроиде, чтобы плейсхолдер в UITextField в момент фокуса улетал наверх, а также само поле ввода было подчеркнуто и линия становилась полупрозрачной при потере фокуса.

Swift Custom TextField

 

Реализовано, конечно, не совсем как в гайдлайне материал дизайна, но задача была сформулирована именно таким образом. В реализации используется CoreGraphics Affine Transform для плавной анимации UILabel для плейсхолдера. Также переопределено само свойство placeholder со своими сеттерами и геттерами для текста нового UILabel. Можно задавать текст прямо из storyboard и, внимание, менять цвет текста плейсхолдера, ведь раньше приходилось хачить в рантайме через attributedPlaceholder. Исходный код лежит на гитхабе.

Подробнее




Раньше в стареньком Objective-С для локализации строк активно использовалась функция NSLocalizedString(@"myString", nil), которая затем мигрировала в свифт, но почему-то без optional параметра comment. Тягать такую конструкцию всякий раз стало крайне раздражительным и неуклюжим.

Воспользуемся прелестями нового языка и обозначим какой-нибудь префиксный оператор, который будет означать, что мы хотим получить на выходе уже локализованый литерал:

prefix func ~(s: String) -> String {
    return NSLocalizedString(s, comment: "");
}

Ну вот, теперь можно удобно определять, какие строки должны подвергнуться локализации

print(~"Hello world");

Список доступных операторов для перегрузки можно увидеть здесь
 

Подробнее