[Из песочницы] 14 вещей, которые обязан знать iOS-разработчик
С разрешения автора выкладываю перевод статьи Norberto Gil Vasconcelos »14 must knows for an iOS developer» (ссылка на оригинал).
Как iOS-разработчик (в данный момент абсолютно зависимый от Swift), я создавал приложения с нуля, поддерживал приложения, работал в различных командах. За все время работы в этой индустрии я не раз слышал фразу: «Не можешь объяснить — значит не понимаешь». Так что, в попытке понять, чем именно я занимаюсь каждый день, я создаю список того, что, на мой взгляд, важно для любого iOS-разработчика. Я постараюсь максимально ясно объяснить каждый момент. [Пожалуйста, не стесняйтесь исправлять меня, высказывать свое мнение или предлагать свои дополнения в этот список.]
Темы: [ контроль версий | архитектурные паттерны | Objective-C против Swift | React | менеджер зависимостей | хранение информации | CollectionViews & TableViews | UI | протоколы | замыкания | схемы | тесты | геолокация | локализуемые строки ]
Вот мой список, без лишних слов, в произвольном порядке.
1 — Контроль версий
Поздравляю, вы приняты! Извлеките код из репозитория и приступайте к работе. Стоп, что?
Контроль версий необходим любому проекту, даже если вы только разработчик. Наиболее часто используемые системы — это Git и SVN.
SVN основан на централизованной системе управления версиями. Это репозиторий, где создаются рабочие копии, и для доступа к ним нужно соединение по сети. Авторизация изменений осуществляется по конкретному пути; система отслеживает изменения путем регистрации каждого файла, полную историю изменений можно посмотреть только в репозитории. Рабочие копии содержат лишь новейшую версию.
Git использует распределенную систему управления версиями. У вас будет локальный репозиторий, в котором вы сможете работать, сетевое подключение необходимо только для синхронизации. При изменении рабочей копии сохраняется состояние всего каталога, но регистрируются только внесенные изменения; и репозиторий, и рабочие копии имеют полную историю изменений.
2 — Архитектурные паттерны
Ваши пальцы дрожат от волнения, вы разобрались с контролем версий! Или это из-за кофе? Неважно! Вы в ударе, и настал час программирования! Не-а. А чего еще ждать?
Прежде, чем вы сядете за клавиатуру, необходимо выбрать архитектурный паттерн, которого вы будете придерживаться. Если не вы начали проект, вы должны соответствовать уже существующему паттерну.
Существует широкий спектр паттернов, используемых при разработке мобильных приложений (MVC, MVP, MVVM, VIPER и т. д.). Дам краткий обзор наиболее часто используемых в разработке для iOS:
Для более глубокого понимания и получения информации о других паттернах я рекомендую прочесть следующую статью.
Может показаться, что это не так уж и важно, но хорошо структурированный и организованный код может предотвратить много головной боли. Большая ошибка, которую каждый разработчик совершает в какой-то момент, состоит в том, чтобы просто получить желаемый результат и отказаться от организации кода, ошибочно полагая, что он экономит время. Если вы не согласны, послушайте старину Бенджи:
Каждая минута, потраченная на организацию своей деятельности, экономит вам целый час— Бенджамин Франклин
Наша цель состоит в том, чтобы получить интуитивно понятный и простой для чтения код, который будет легко использовать и поддерживать.
3 — Objective-C против Swift
Решая, на каком языке программирования писать свое приложение, вы должны знать, какими возможностями располагает каждый из них. Если есть возможность, я предпочитаю использовать Swift. Почему? Если честно, Objective-C имеет очень мало преимуществ по сравнению со Swift. Большинство примеров и учебных пособий написаны на Objective-C, а у Swift с каждым обновлением вносятся коррективы в парадигмы, что может приводить в уныние. Однако эти проблемы в конечном итоге исчезнут.
В сравнении с Objective-C, Swift делает скачок во многих отношениях. Его легко читать, он похож на естественный английский, и, поскольку он не построен на C, это позволяет отказаться от традиционных условностей. Для тех, кто знает Objective-C, это значит никаких больше точек с запятой, а вызовы методов и условия выражений не нужно будет заключать в скобки. Также легче поддерживать ваш код: Swift нужен только файл .swift вместо файлов .h и .m, потому что Xcode и компилятор LLVM могут определять зависимости и выполнять инкрементные сборки автоматически. В целом вам придется меньше беспокоиться о создании стандартизированного кода, и вы обнаружите, что можно достичь тех же результатов меньшим количеством строк.
Все еще сомневаетесь? Swift безопаснее, быстрее, и заботится об управлении памятью (большей ее частью!). Знаете, что происходит в Objective-C, когда вы вызываете метод с неинициализированной переменной-указателем? Ничего. Выражение становится неактивным и пропускается. Звучит здорово, потому что это не приводит к вылету приложения, однако вызывает ряд багов и нестабильное поведение, из-за которых вам захочется подумать о смене профессии. Серьезно. Идея стать профессиональным выгульщиком собак заиграла новыми красками. В это же время, счетчик Swift работает с optional-значениями. Вы не только получите лучшее представление о том, что может быть nil и поставите условия для предотвращения использования nil-значений, но и получите runtime crash, если nil optional все же будет использоваться, что упростит отладку. ARC (автоматический подсчет ссылок) помогает лучше распоряжаться памятью в Swift. В Objective-C ARC не работает с процедурным C или с API, наподобие Core Graphics.
4 — React или не React? (вот в чем вопрос)
Функциональное реактивное программирование (FRP) — новый хит. Оно предназначено для упрощения составления асинхронных операций и потоков событий/данных. Для Swift это общая абстракция вычислений, выраженная через интерфейс Observable<Element>.
Проще всего проиллюстрировать это небольшим количеством кода. Скажем, малыш Тимми и его сестра Дженни хотят купить новую игровую приставку. Тимми получает от своих родителей 5 евро каждую неделю, то же самое касается Дженни. Однако Дженни зарабатывает еще 5 евро, доставляя газеты по выходным. Если оба экономят каждый цент, мы можем каждую неделю проверять, доступна ли консоль! Каждый раз, когда значение их сбережений меняется, рассчитывается их совокупная величина. Если этого достаточно, сообщение сохраняется в переменной isConsoleAttainable. В любой момент мы можем проверить сообщение, подписавшись на него.
// Сбережения
let timmySavings = Variable(5)
let jennySavings = Variable(10)
var isConsoleAttainable =
Observable
.combineLatest(timmy.asObservable(), jenny.asObservable()) { $0 + $1 }
.filter { $0 >= 300 }
.map { "\($0) достаточно для покупки приставки!" }
// Вторая неделя
timmySavings.value = 10
jennySavings.value = 20
isConsoleAttainable
.subscribe(onNext: { print($0) }) // Не выведет ничего
// Двадцатая неделя
timmySavings.value = 100
jennySavings.value = 200
isConsoleAttainable
.subscribe(onNext: { print($0) }) // 300 достаточно для покупки приставки!
Это лишь пример того, что можно сделать с FRP, как только вы освоите его, оно откроет целый новый мир возможностей, вплоть до принятия архитектуры, отличной от MVC… Да-да! MVVM!
Вы можете посмотреть на двух основных претендентов на звание главного Swift FRP:
5 — Менеджер зависимостей
CocoaPods и Carthage являются наиболее распространенными менеджерами зависимостей для проектов Cocoa Swift и Objective-C. Они упрощают процесс внедрения библиотеки и поддержания ее в актуальном состоянии.
CocoaPods имеет множество библиотек, собран с помощью Ruby и может быть установлен с помощью следующей команды:
$ sudo gem install cocoapods
После установки вы захотите создать Podfile для вашего проекта. Вы можете запустить следующую команду:
$ pod install
или создать пользовательский Podfile с такой структурой:
platform :ios, '8.0'
use_frameworks!
target 'MyApp' do
pod 'AFNetworking', '~> 2.6'
pod 'ORStackView', '~> 3.0'
pod 'SwiftyJSON', '~> 2.3'
end
После создания пришло время установить ваши новые модули:
$ pod install
Теперь вы можете открыть .xcworkspace вашего проекта, не забудьте импортировать ваши зависимости.
Carthage — децентрализованный менеджер зависимостей, в отличие от CocoaPods. Недостатком этого является то, что пользователям становится все труднее находить существующие библиотеки. С другой стороны, такой подход требует меньше работ по поддержке и позволяет избежать сбоев по причине централизованности хранилища.
Для получения дополнительной информации об установке и использовании загляните на GitHub проекта.
6 — Хранение информации
Начнем с простейшего способа сохранения данных для ваших приложений. NSUserDefaults, названный так, потому что он обычно используется для сохранения пользовательских данных по умолчанию, отображаемых при первой загрузке приложения. По этой причине он сделан простым и легким в использовании, однако это же подразумевает некоторые ограничения. Одним из них является тип объектов, которые принимает этот способ. Его поведение очень похоже на Property List (Plist), который имеет такое же ограничение. Вот шесть типов объектов, которые они могут хранить:
- NSData
- NSDate
- NSNumber
- NSDictionary
- NSString
- NSArray
В целях совместимости со Swift, NSNumber может принять следующие типы:
- UInt
- Int
- Float
- Double
- Bool
Объекты могут быть сохранены в NSUserDefaults следующим образом (Сначала создайте константу, которая будет хранить ключ для сохраняемого объекта):
let keyConstant = "objectKey"
let defaults = NSUserDefaults.standardsUserDefaults()
defaults.setObject("Object to save", objectKey: keyConstant)
Чтобы прочитать объект из NSUserDefaults, мы можем сделать следующее:
if let name = defaults.stringForKey(keyConstant) {
print(name)
}
Есть несколько удобных методов для чтения и записи в NSUserDefaults, которые получают конкретные объекты вместо AnyObject.
Keychain представляет собой систему управления паролями и может содержать пароли, сертификаты, личные ключи или личные заметки. Keychain имеет два уровня шифрования устройства. Первый уровень использует код блокировки экрана блокировки в качестве ключа шифрования. Второй уровень использует ключ, сгенерированный и сохраненный на устройстве.
Что это значит? Это не совсем супер безопасно, особенно если вы не используете пароль на экране блокировки. Существуют также способы доступа к ключу, используемому на втором уровне, поскольку он сохранен на устройстве.
Лучшее решение — использовать собственное шифрование. (Не храните ключ на устройстве)
CoreData — это фреймворк, разработанный Apple, чтобы ваше приложение взаимодействовало с базой данных объектно-ориентированным образом. Это упрощает процесс, сокращая объем кода и избавляя от необходимости тестировать этот раздел.
Вы должны использовать CoreData, если вашему приложению требуются постоянные данные, это значительно упрощает процесс их сохранения и позволяет вам не создавать/тестировать свой собственный способ связи с БД.
7 — CollectionViews & TableViews
Почти каждое приложение имеет одну или несколько CollectionViews и/или TableViews. Знание того, как они работают и когда использовать ту или иную, предотвратит сложные изменения в вашем приложении в будущем.
TableViews отображают список элементов в одном столбце по вертикали и ограничены только вертикальной прокруткой. Каждый элемент представлен UITableViewCell, который можно полностью настроить. Их можно отсортировать по разделам и строкам.
CollectionViews также отображают список элементов, однако они могут иметь несколько столбцов и строк (к примеру, сетка). Они могут прокручиваться по горизонтали и/или по вертикали, и каждый элемент представлен UICollectionViewCell. Как и UITableViewCells, они могут быть настроены по желанию и отсортированы по разделам и строкам.
Они обе имеют схожую функциональность и используют многоразовые ячейки для улучшения подвижности. Выбор того, что вам нужно, зависит от сложности, которую вы хотите иметь в списке. CollectionView может использоваться для представления любого списка и, на мой взгляд, всегда является лучшим выбором. Представьте, что вы хотите представить список контактов. Список простой, можно реализовать его одним столбцом, поэтому вы выбираете UITableView. Все работает! Через несколько месяцев ваш дизайнер решит, что контакты должны отображаться в формате сетки, а не в виде списка. Единственный способ сделать это — изменить реализацию UITableView на реализацию UICollectionView. Я пытаюсь сказать, что, хотя ваш список может быть простым и UITableView может быть достаточно, если есть большая вероятность изменения дизайна, вероятно, лучше всего реализовать этот список с помощью UICollectionView.
Какой бы выбор вы ни сделали, хорошей идеей будет создать общий TableView/CollectionView. Это облегчает реализацию и позволяет повторно использовать большой объем кода.
8 — Storyboards VS Xibs VS программируемый UI
Каждый из этих методов может использоваться отдельно для создания пользовательского интерфейса, однако ничто не мешает вам объединить их.
Storyboards предоставляют более широкое представление о проекте, которое придется по вкусу дизайнерам, позволяя видеть поток приложения, а также его окна. Недостатком является то, что с добавлением большего количества окон соединения становятся более запутанными, а время загрузки Storyboard увеличивается. Проблемы слияния возникают гораздо чаще, потому что весь пользовательский интерфейс находится в одном файле. Их также становится гораздо сложнее решить.
Xibs обеспечивают визуальный просмотр окон или частей окна. Преимущества заключаются в простоте повторного использования, меньшем количестве конфликтов слияния, чем в Storyboards, и в простоте просмотра содержимого каждого из окон.
Программирование пользовательского интерфейса дает вам больший контроль над ним, снижает частоту конфликтов слияния, а если они и возникают, их проще устранить. Недостатком являются меньшая визуализация и дополнительное время, необходимое на написание.
Вышеуказанные подходы к созданию пользовательского интерфейса сильно разнятся. Но, по моему субъективному мнению, лучшим вариантом является сочетание всех трех. Несколько Storyboards (теперь мы можем переходить между Storyboard-ами!), с Xibs для любого визуального объекта, который не является основным окном, и, наконец, чуть-чуть программирования для дополнительного контроля, столь необходимого в определенных ситуациях.
9 — Протоколы!
В быту протоколы существуют, чтобы в определенной ситуации мы знали, как реагировать. Допустим, вы пожарный, и возникла чрезвычайная ситуация. Каждый пожарный должен следовать протоколу, который устанавливает требования для успешного реагирования. То же самое относится и к протоколам в Swift/Objective-C.
Протокол определяет эскиз методов, свойств и других требований для заданных функций. Он может быть принят классом, структурой или перечислением, которые затем будут иметь фактическую реализацию этих требований.
Вот пример создания и использования протокола:
Для моего примера мне понадобится перечисление, в котором приведены различные типы материалов, используемые для тушения пожара.
enum ExtinguisherType: String {
case water, foam, sand
}
Далее я создам протокол реагирования на чрезвычайные ситуации.
protocol RespondEmergencyProtocol {
func putOutFire(with material: ExtinguisherType)
}
Теперь я создам класс пожарного, который подчиняется протоколу.
class Fireman: RespondEmergencyProtocol {
func putOutFire(with material: ExtinguisherType) {
print("Fire was put out using \(material.rawValue).")
}
}
Отлично! А теперь используем нашего пожарного.
var fireman: Fireman = Fireman()
fireman.putOutFire(with: .foam)
В результате должно получится следующее: «Fire was put out using foam.»
Протоколы также используются при Делегировании. Это позволяет классам или структурам делегировать определенные функции экземпляру другого типа. Протокол создается с делегируемыми обязанностями, чтобы гарантировать обеспечение их функциональности соответствующим типом.
Небольшой пример!
protocol FireStationDelegate {
func handleEmergency()
}
Пожарная служба делегирует пожарному меры по ликвидации чрезвычайной ситуации.
class FireStation {
var delegate: FireStationDelegate?
func emergencyCallReceived() {
delegate?.handleEmergency()
}
}
Это означает, что пожарный должен будет также соответствовать протоколу FireStationDelegate.
class Fireman: RespondEmergencyProtocol, FireStationDelegate {
func putOutFire(with material: ExtinguisherType) {
print("Fire was put out using \(material.rawValue).")
}
func handleEmergency() {
putOutFire(with: .water)
}
}
Все, что нужно сделать — чтобы пожарный на вызовах был назначен делегатом пожарной станции, и он будет обрабатывать принятые экстренные вызовы.
let firestation: FireStation = FireStation()
firestation.delegate = fireman
firestation.emergencyCallReceived()
В результате получим: «Fire was put out using water.»
10 — Замыкания
Речь пойдет только о замыканиях Swift. В основном они используются для возврата завершающего блока или с функциями высокого порядка. Завершающие блоки используются, как понятно из названия, для выполнения блока кода после завершения задачи.
Замыкания в Swift аналогичны блокам в C и Objective-C.Замыкания являются объектами первого класса*, поэтому их можно вкладывать и передавать (как и блоки в Objective-C).
В Swift функции — это частный случай замыканий.
Источник — fuckingswiftblocksyntax.com **
Этот ресурс — отличное место для изучения синтаксиса замыканий.
* Объекты первого класса (first-class objects) — объекты, которые можно использовать без ограничений: присвоить переменной, передать/вернуть из функции, создать/уничтожить во время выполнения программы и т.д. Подробнее. (здесь и далее — прим. переводчика)
** Сайт не работает, но остались снимки в waybackmachine, пример.
11 — Схемы
Если коротко, то схемы — это любой простой способ переключения между конфигурациями. Начнем с базовой информации. Рабочая область содержит различные связанные проекты. Проект может иметь различные таргеты — таргеты определяют продукт для сборки и способ сборки. Также, проект может иметь различные конфигурации. Схема в Xcode определяет коллекцию таргетов для сборки, конфигурацию, используемую при сборке, и коллекцию тестов для выполнения.
Фиолетовым показана одна из возможных схем
12 — Тесты
Если вы выделяете время на тестирование своего приложения, вы на верном пути. Это, конечно, не панацея, вы не можете исправить каждый баг, равно как и гарантировать, что ваше приложение будет лишено каких-либо проблем; и все же я думаю, что плюсы перевешивают минусы.
Начнем с минусов модульного тестирования:
- Увеличение длительности разработки;
- Увеличение количества кода.
Плюсы:
- Необходимость создавать модульный код (для упрощения тестирования);
- Очевидно, обнаружение большинства ошибок до релиза;
- Упрощение поддержки.
В сочетании с утилитой Инструменты у вас будет все, чтобы приложение было гибким и работало без ошибок и сбоев.
Существует много инструментов, которые можно использовать для проверки работы приложения. В зависимости от того, что нужно отследить, вы можете выбрать один или несколько из них. Пожалуй, наиболее часто используемые инструменты — это Leaks, Time Profiler и Allocations.
13 — Геолокация
Во многих приложениях некоторые функции требуют определить местоположение пользователя. Так что неплохо бы иметь общее представление о том, как работает местоположение для iOS.
Существует фреймворк под названием Core Location, позволяющий получить доступ ко всему, что вам нужно:
Фреймворк Core Location позволяет определить текущее местоположение или направление движения устройства. Фреймворк использует доступное аппаратное обеспечение для определения положения и направления пользователя. Вы можете использовать классы и протоколы этого фреймворка для настройки и планирования событий, связанных с местоположением и направлением. Вы также можете использовать Core Location для отслеживания перемещения по географическим регионам. В iOS вы также можете определить расстояние до Bluetooth-маячка*.* Как я понял, речь о технологии iBeacon
Классно, не так ли? Ознакомьтесь с документацией Apple и приведенным там примером, чтобы лучше понять возможности фреймворка.
14 — Локализуемые строки
То, что должно быть реализовано в любом приложении. Это позволяет менять язык в зависимости от региона, в котором находится устройство. Даже если ваше приложение только на одном языке, в будущем может возникнуть необходимость добавить новые. Если весь текст вводится с использованием локализуемых строк, все, что нужно сделать, это добавить переведенную версию файла Localizable.strings для нового языка.
Ресурс может быть добавлен в язык через инспектор файлов. Чтобы получить строку с NSLocalizedString, необходимо написать следующее:
NSLocalizedString(key:, comment:)
К сожалению, чтобы добавить новую строку в файл Localizable, это нужно сделать вручную. Вот пример структуры:
{
"APP_NAME" = "MyApp"
"LOGIN_LBL" = "Login"
...
}
Теперь другой язык (португальский), локализуемый файл:
{
"APP_NAME" = "MinhaApp"
"LOGIN_LBL" = "Entrar"
...
}
Есть даже способы реализации множественного числа.
Всегда делись тем, чему ты научился.— магистр Йода
Надеюсь, эта статья была полезна!