PreWorking 1.3: Паттерны — часть 1

P

Swift 2.2
Мы не останавливаемся и продолжаем готовить вас к будущему собеседованию по iOS разработке. Эта тема будет практической — паттерны. Тема эта самая тяжелая в разделе и самая ключевая. А так же она требует некоторых навыков создания iOS приложений. Если вы только начинаете свой путь  iOS разработчика — пропустите эту статью и пока что ознакомьтесь только с теорией по ссылкам в конце второй части урока. Спустя пару разделов вы сможете вернуться и пройти практическую часть этой статьи. Все написанное является переводом статьи Ray Wenderlich. Итак, начнем.   

1. Общая информация:

     1.1 ООП

     1.2 Принципы проектирования

     1.3 Паттерны — часть 1 — часть 2

     1.4 Системы контроля версии

     1.5 Использование third-party решений в iOS разработке

Шаблоны проектирования или паттерны — вероятно вы часто слышали эти термины, но не всегда понимали, что они значат?

Идея паттернов возникла 40 лет назад у архитектора Кристофера Александера:

Любой паттерн описывает задачу, которая снова и снова возникает в нашей работе, а также принцип её решения, причём таким образом, что это решение можно потом использовать миллион раз, ничего не изобретая заново.

Программисты это увидели и сказали:

Хотя Александр имел в виду паттерны, возникающие при проектировании зданий и городов, но его слова верны и в отношении паттернов объектно-ориентированного проектирования. Наши решения выражаются в терминах объектов и интерфейсов, а не стен и дверей, но в обоих случаях смысл паттерна – предложить решение определенной задачи в конкретном контексте.

В этой статье-уроке мы напишем программу, в которой используем наиболее распространенные паттерны:

  • Порождающий: Singleton.
  • Структурный: MVC, Decorato, Adapter, Facade.
  • Поведенческий: Observer, Memento.

Паттерны: Начало

Скачайте стартовый проект и запустите файл BlueLibrarySwift.xcodeproj.

Есть 3 момента, которые стоит отметить в приложении:

1. У ViewController есть 2 IBOutlet, соединенные с TableView и toolbar в Storyboard.

2. В Storyboard есть объекта, у которых есть констрейны для вашего удобства. Главный компонент — поле, где обложки будут выведены на экран. Ниже обложек располагается TableView, отображающее информацию, связанную с обложками. Ниже идет toolbar, у которого будет две кнопки. Одна для отмены действия и другая для удаления выбранного альбома.
Паттерны

3. Стартовый HTTP клиент-класс с пустой реализацией, которую мы заполним позже.

Перед погружением в первый паттерн, создадим два класса для хранения и вывода на экран альбома.

File\New\File > iOS > Cocoa Touch Class > Next. Назовем его Album. Наследоваться он будет от NSObject. Язык ставим Swift. Откроем Album.swift и добавим следующие свойства после объявления класса:

Вы создали 5 свойств. Класс Album будет отслеживать название, артиста, жанр, обложку и год альбома. Давайте добавим после свойств инициализатор:

Этот код создает инициализатор для класса Album. Когда придет пора создавать экземпляр альбома, вы передадите через него все описанные выше свойства. После инициализатора добавим следующий метод:

Метод description() возвращает строковое представление атрибутов альбома.
Создадайте еще один класс, так же как и Album. File\New\File > iOS > Cocoa Touch Class > Next. Назовем его AlbumView. Наследоваться он будет от UIView. Язык ставим Swift. Откроем AlbumView.swift и добавим следующие свойства после объявления класса:

CoverImage — изображение обложки альбома. Второе свойство — индикатор, который вращается, чтобы обозначить что что то происходит пока идет загрузка.
Свойства отмечены как private, потому что никакой класс за пределами AlbumView не должен знать о существовании этих свойств; они используются только в реализации внутренней функциональности класса. Это чрезвычайно важно, если вы создаете библиотеку или фреймворк для других разработчиков.

Добавим следующие инициализаторы:

Инициализатор NSCoder является обязательным (ключевое слово required), потому что UIView соответствует NSCoding. CommonInit — метод-помощник, используемый в обоих инициализаторах. В нем мы настраиваем некоторые стартовые значения для альбома.

Наконец, добавьте следующий метод:

Этот метод переключит цвет фона выбранного альбома на белый, если тот будет выделен и черный, если нет.
Все работает? Отлично =) Тогда приготовьтесь к вашему первому шаблону проектирования!

Паттерны: MVC — Король шаблонов проектирования

Паттерны

Model-View-Controller (MVC) — один из стандартов Cocoa и является, без сомнений, самым частоиспользуемым паттерном. Он класифицирует объекты согласно их ролям в приложении и поощряет чистое разделение кода на основе роли. В статье, посвященной ООП мы писали про MVC. Но там была теория от Apple. Сейчас же будет практика.

В MVC существует 3 роли:

  • Model: объект, который содержит данные приложения и определяет, как управлять ими. Например, в нашем приложении моделью является класс Album.
  • View: объекты, которые отвечают за визуальное представление модели и пользовательские манипуляции. Представлениями, в основном, являются все UIView-производные объекты. В нашем приложении представлением является класс AlbumView.
  • Controller: контроллер-посредник, который координирует всю работу. Он получает доступ к данным модели и выводит на экран с помощью представления, следит за событиями и управляет данными по мере необходимости. Вы можете предположить, какой класс — наш контроллер? Верно: ViewController.

Хорошая реализация этого шаблона разработки в вашем приложении означает, что каждый объект попадает в одну из этих групп.
Передача между Представлением к Модели через Контроллер может быть лучше всего описана со следующей схемой:

Паттерны

Модель уведомляет Контроллер о любых изменений данных, и в свою очередь, Контроллер обновляет данные в Представлениях. Представление может уведомить Контроллер относительно действий, выполняемых пользователем и Контроллер или обновит Модель, если необходимый или получит какие-либо запрошенные данные.
Это — сила MVC!

Паттерны: Как использовать MVC

Во-первых, вы должны гарантировать, что каждый класс в вашем проекте — Контроллер, Модель или Представление. Не комбинируйте функциональность двух ролей в одном классе.
Во-вторых, чтобы гарантировать, чтобы вы соответствовали этому методу работы, вы должны создать три группы кода — по одной для каждой роли.
ПаттерныТеперь проект выглядит намного чище и более упорядоченно. Без сомнения,  у вам может быть множество иных классов в будущем. Однако ядро приложения — эти три  категории. Теперь, когда наши компоненты организованы, мы должны получить данные альбома откуда то. Мы создадим класс API для использования повсюду в коде и управления данными. Как раз представим вам новый паттерн — Singleton.

Паттерны: Singleton

Паттерн Singleton (Одиночка) гарантирует, что существует только один экземпляр данного класса. Apple часто использует этот паттерн. Например NSUserDefaults.standardUserDefaults(), UIApplication.sharedApplication(), UIScreen.mainScreen(), NSFileManager.defaultManager() — все они возвращают singleton объект.

Паттерны: Как использовать Singleton

Посмотрите на диаграму ниже:

Паттерны

Это изображение показывает класс Loger с единственным свойством instance и двумя методами: sharedInstance и init. В первый раз, когда пользователь запросит sharedInstance, свойство экземпляра еще не инициализировано. Таким образом вы создаете новый экземпляр класса и возвращаете ссылку на него.
В следующий раз, когда вы вызываете sharedInstance, экземпляр сразу возвращается без любой инициализации. Эта логика гарантирует, что существует только один экземпляр.
Вы реализуете этот образец, создавая singleton-класс, чтобы управлять всеми данными альбома.
Создайте новый файл в группе API, щелкнув правой кнопкой по группе и выбрав New File. iOS > Cocoa Touch Class > Next. Имя класса LibraryAPI инаследование от NSObject. Swift в качестве языка. Нажмите Next > Create.
Теперь перейдите к LibraryAPI.swift и введите этот код в теле класса.

  1. Создаем переменную класса как свойство определенного типа.
  2. Вложенная в переменную класса структура Singleton.
  3. Внутри структуры Singleton располагается статическая константа instance с типом LibraryAPI (тип нашего класса). Ключевое слово static означает, что свойство будет существовать только один раз. Так же статические переменные неявно ленивые, что означает, что они не будут созданы, пока в этом нет необходимости. Так же обратите внимание, что хоть мы и создаем LibraryAPI внутри LibraryAPI — переменная не будет создаваться до бесконечности. Она инициализируется один раз и во второй раз его больше не создаст.
  4. Возвращает свойство определенного типа.

Теперь у нас в программе есть объект Singleton как точка входа для управления альбомами. В группе API создайте класс для обработки персистентности ваших данных. Назовите его PersistentManager и унаследуйте от NSObject. Внутри фигурных скобок напишите:

Сдесь мы объявили приватное свойство для хранения данных альбомов. Массив изменяемый, так что вы можете легко добавить или удалить альбомы. Добавьте следующий инициализатор ниже:

В инициализаторе мы заполнили массив пятью демонстрационными альбомами.

Теперь добавьте еще ниже пару функций:

Эти методы позволяют Вам получать, добавлять и удалять альбомы. Давайте раскроем отношение LibraryAPI и PersistentManager в следующем паттерне.

Паттерны: Шаблон проектирования Facade

Паттерн Фасад обеспечивает единый интерфейс для сложных систем. Вместо того что бы предоставить нам множество разных классов, Фасад объединяет их в один простой API.

Паттерны

Пользователь API не будет знать о сложном механизме, заложенном внутри. Эта модель идеальна при работе со множеством классов, особенно если они сложны и труднопонимаемы. Так же фасад позволяет проводить изменения в классах, не меняя при этом конечный API, что очень удобно.

Паттерны: Как использовать Facade

Сейчас у нас есть PersistencyManager для сохранения данных альбомов локально и HTTPClient, чтобы обработать передачу данных. Другие классы в нашем проекте не должны знать об этой логике, и они будут скрываться позади фасада LibraryAPI.

Паттерны

Мы предоставим LibraryAPI все необходимое, но сделаем это приватным, что бы больше никто о них не знал. Откройте LibraryAPI.swift и добавьте следующие свойства:

isOnline определяет, должен ли сервер быть обновлен с какими-либо изменениями, внесенными в список альбомов, такими как добавление или удаление альбома.

Теперь эти переменные нужно инициализировать.

На данный момент HTTP клиент не работает с реальным сервером и только для демонстрации использования Фасада isOnline всегда будет false.

Добавим парочку методов после инициализации.

Обратите внимание на addAlbum(_: index:). Класс сначала обновляет данные локально, и затем если есть интернет-соединение, обновляет удаленный сервер. Это — реальная сила Фасада, когда какой то класс за пределами вашей системы добавляет новый альбом, то он не знает и не должно знать о сложности, которая находится в глубине.

Запустите программу. Вы увидите пару пустых View. Паттерн Decorator поможет нам их заполнить!

Паттерны: Шаблон проектирования Decorator

Говоря умными словами, шаблон «Декоратор» динамически добавляет поведение (behaviors) и обязанности (responsibilities) к объекту, не меняя его код. Это альтернатива наследованию (при наследовании вы меняете поведение класса, обернув его в подкласс).

В Objective-C есть две очень распространенные реализации этого паттерна: расширения и делегирование.

Расширения
«Расширения» (Extensions) — очень мощный механизм, позволяющий добавлять методы к существующим классам без наследования. Новые методы добавляются при компиляции и могут быть выполнены как обычные методы расширенного класса. Это немного отличается от классического определения «Декоратора», т.к. «Расширение» не содержит экземпляр класса, который она расширяет.

Примечание: Кроме расширения собственных классов, вы также можете добавлять методы к любым классам Cocoa!

Как использовать расширения
Представьте ситуацию: у нас есть объект Album, который мы хотим показать в таблице:

Паттерны

Откуда к нам придёт информация об альбоме? Album — это объект «модель», ему без разницы, в каком виде мы показываем данные. Нам нужен некоторый внешний (по отношению к Album) код, чтобы добавить соответствующий функционал к классу Album, не трогая код Album.

Мы с вами создадим расширение класса Album. Методы расширения будут возвращать структуру данных, удобную для использования в UITableView.

Структура данных будет выглядеть так:

Паттерны

Создайте расширение, которое расширит класс Album. Это определит новый метод, который возвращает структуру данных, которая может использоваться с UITableView.
File\New\File > iOS > Source > Swift File > Next > AlbumExtension > Create. В созданном файле введите код, расширяющий Album:

Только подумайте, насколько мощный паттерн:

  • Мы используем свойства прямо из Album.
  • Мы расширили функционал класса Album без наследования. (Если вы хотите наследовать от Album, это тоже можно сделать.)
  • Это простое дополнение позволяет нам UITableView’шное представление Album’а без модификации самого кода Album.

Делегирование

Другой вариант паттерна «Декоратор» — это делегирование. Механизм, при котором один объект взаимодействует от имени другого объекта (или в координации с ним). Очень похоже на то, как руководитель делегирует полномочия подчинённым.

Рассмотрим пример: при использовании UITableView один из методов, который мы должны реализовать, это tableView:numberOfRowsInSection: (число строк в разделе таблицы).

Мы не ожидаем, что UITableView сам знает, сколько строк должно быть в каждом разделе таблицы. Понятно, что это число зависит от приложения. Поэтому UITableView передаёт задачу «узнать число строк в каждом разделе» своему делегату. Это даёт важную вещь: сам класс UITableView не зависит от отображаемых данных.

Вот что происходит, когда вы создаёте новый UITableView:

Паттерны

Объект UITableView делает свою работу, показывая таблицу. Для этого ему требуется некоторая информация, которой у него нет. Он обращается к своему делегату (или делегатам), запрашивая дополнительную информацию. Кстати, класс может объявить обязательные и необязательные методы, используя протокол (мы узнаем о протоколах позже в данном уроке).

Казалось бы, проще наследоваться от объекта и переопределить необходимые методы? Но есть один момент: вы можете наследоваться только от одного класса. Язык Swift не поддерживает множественное наследование. Если вы захотите сделать объект делегатом двух (или более) других объектов, то этого нельзя достичь путём наследования.

Как использовать делегирование

Откройте ViewController.swift и добавьте эти приватные свойства к классу:

Добавьте следующий код во viewDidLoad() после super.viewDidLoad():

Пока не обращайте внимания на ошибки.

  1. Выключение полупрозрачности на панели навигации.
  2. Получите список всех альбомов через API. Помните, план состоит в том, чтобы использовать фасад LibraryAPI, а не PersistencyManager непосредственно!
  3. Здесь вы настраиваете UITableView. Вы объявляете, что контроллер представления — делегат/источник данных UITableView, поэтому, вся информация, запрошенная UITableView, будет предоставлена контроллером представления. Обратите внимание на то, что Вы можете фактически установить делегат и источник данных в Storyboard, если ваше табличное представление создается там.

Добавьте следующий метод после ViewDidLoad.swift:

showDataForAlbum() отбирает требуемые данные для альбома из массива альбомов. Когда вы хотите представить новые данные, вы просто должны вызвать reloadData. Это заставляет UITableView спрашивать свой делегат о том, сколько разделов должно появиться в табличном представлении, сколько строк в каждом разделе, и как каждая ячейка должна выглядеть.
Добавьте следующую строку в конце viewDidLoad:

Этот код загружает текущий альбом при запуске приложения.  И так как currentAlbumIndex был ранее установлен на 0, то этот код отобразит первый альбом из списка.

Теперь пора реализовать протокол источника данных. Давайте заодно уберем ошибки. Добавьте следующий код после закрытия последних фигурных скобок:

И теперь давайте реализуем обязательные функции протокола UITableViewDataSource. Добавьте следующий код в расширение UITableViewDataSource:

tableView(_:numberOfRowsInSection:) возвращает количество ячеек для отображения в tableView, которое в данном случае соответствует числу заголовков в структуре данных.

tableView(_:cellForRowAtIndexPath:) создает и возвращает ячейку с заголовком и его значением.

Запустите проект. Вы видите, что первый в списке альбом успешно выводится на экран.

Паттерны


Итак, подошла к концу первая часть урока по паттернам. Ссылку на код вы можете скачать по этой ссылке.

Полезные ссылки для углубления в тему будут приложены после 2й части.

 

Поддержите ресурс blog.justDev:

Сведения об авторе

Игорь Малеваный

1 комментарий

Instagram

Поддержите ресурс blog.justDev:

Свежие записи

Рубрики