Быстрый! Протокольно-Ориентированный

Всем привет! Нет, это не очередной пост в стиле «знакомьтесь со Swift и его возможностями», а, скорее, небольшой экскурс в практические приложения и тонкости, где протоколоориентированная природа нового языка Apple позволяет делать красивые и удобные вещи.



Быстрый! Протокольно-ориентированный

Особый привет тем, кто заглянул под хабра-кат. В последнее время мне пришлось много разрабатывать в Swift, в то же время у меня большой опыт работы с объектом C и некоторое желание выражать некоторые вещи в коде, что, как я понял, можно реализовать гораздо проще и элегантнее на новом языке.

.

Внимание : статья не представляет собой подробные обучающие материалы, а представляет практические моменты, которые лично меня впечатлили и я решил ими поделиться; они, вероятно, покажутся знакомыми большинству читателей, поэтому, если кто-то хочет что-то добавить, прокомментируйте.



Чего ожидать в этой статье?

  • Пара вводных предложений (плюс немного полезной библиотеки)
  • Украшение дополнительного поведения класса с помощью расширения (небольшой код)
  • Создаем повторно используемый элемент, используя протокол и реализацию по умолчанию (много кода)
  • Протоколы и перечисления — могут быть удобны (средний код)


В чем сила протоколов?

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

Наследование ограничивает нас тем, что в цепочку наследников на n-м шаге невозможно «внести» или «добавить» новое поведение, общее для какого-то другого объекта.

Во-вторых, Swift имеет возможность добавлять реализацию по умолчанию для указанного протокола.

Более того, протокол может иметь несколько реализаций по умолчанию в зависимости от класса или типа объекта, который его наследует. В-третьих, протокол может быть унаследован от протокола.

В-четвертых, протоколы могут наследоваться не только классами (Class), но и структурами (Struct) и перечислениями (Enum).

В-пятых, протоколы могут добавлять свойства.

В-шестых, вы можете добавить реализацию системных протоколов по умолчанию и при желании переопределить ее в определенном классе.

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

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

Например, в каждом проекте есть необходимость обработать клик по UIView чтобы не писать каждый раз лишний код, сделайте свой класс Тапабельный (код - здесь ) Лично мне не хватает некоторых соглашений при наследовании протокола, чтобы унаследованные методы и свойства были четко видны (я слышал, что это существует в Ruby):

  
  
  
  
  
   

protocol FCActionProtocol { var actionButton: UIButton! {get set} func showActionView() } class FCController: FCActionProtocol { var actionButton: UIButton! // FCActionProtocol convenience func showActionView() {} }

Я бы хотел, чтобы это было кнопка действия И шоуДействиеВью() были подставлены в автоматически созданную область.

Я подожду со Swift 3.0

Украшение дополнительного поведения класса с помощью Extension

Итак, от теории к практике: жизненный случай №1. Представим, что у нас есть логика цикла просмотра контроллера и логика передачи модели во представление.

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

С протоколами это легко:

class MyViewController: UIViewController { // a lot of code here } extension MyViewController: MFMailComposeViewControllerDelegate { func showMailController() { let mailComposeViewController = configuredMailComposeViewController() if MFMailComposeViewController.canSendMail() { self.presentViewController(mailComposeViewController, animated: true, completion: nil) } } func configuredMailComposeViewController() -> MFMailComposeViewController { let controller = MFMailComposeViewController() controller.mailComposeDelegate = self return controller // customize and set it here } // MARK: - MFMailComposeViewControllerDelegate func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {} }

Я очень рад, что в Swift, в отличие от obj-c, можно расширить класс Мойвиевконтроллер указать новые наследуемые протоколы и реализовать их поведение.



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

Случай №2: недавно в приложении была одна и та же кнопка на двух экранах, что привело к одному и тому же сценарию — показу листа действий с действиями, на одном из которых показывался почтовый клиент. Технической задачей было реализовать протокол с реализацией и всей логикой внутри, чтобы степень сложности его подключения и зависимостей была минимальной.

Вот как выглядит код в проекте:

protocol FCActionProtocol { var actionButton: UIButton! {get set} var delegateHandler: FCActionProtocolDelegateHandler! {get set} mutating func showActionSheet() func showMailController() } class FCActionProtocolDelegateHandler : NSObject, MFMailComposeViewControllerDelegate { var delegate: FCActionProtocol! init(delegate: FCActionProtocol) { super.init() self.delegate = delegate } func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) { controller.dismissViewControllerAnimated(true, completion: nil) } } extension FCActionProtocol { mutating func showActionSheet() { delegateHandler = FCActionProtocolDelegateHandler(delegate: self) let actionController = UIAlertController(title: nil, message: nil, preferredStyle: .

ActionSheet) actionController.addAction(UIAlertAction(title: NSLocalizedString("ActionClear", comment: ""), style: .

Default) { (action) in }) actionController.addAction(UIAlertAction(title: NSLocalizedString("ActionWriteBack", comment: ""), style: .

Default) { (action) in self.showMailController() }) if let controller = self as? UIViewController { controller.presentViewController(actionController, animated: true) {} } } func showMailController() { if MFMailComposeViewController.canSendMail() { let controller = MFMailComposeViewController() controller.mailComposeDelegate = delegateHandler (self as! UIViewController).

navigationController!.

presentViewController(controller, animated: true, completion: nil) } } }

Внимание! Идея кода заключается в том, что существует протокол FCAActionProtocol , который включает кнопку ( кнопка действия ) при нажатии отображается лист с действиями ( показатьActionSheet ).

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

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

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

В результате сложность добавления этого многоразового листа действий такова:

class FCMyController: FCActionProtocol { var actionButton: UIButton! // convenience FCActionProtocol var delegateHandler: FCActionProtocolDelegateHandler! // convenience FCActionProtocol }

Вся логика внутри.

Нам нужно только инициализировать и добавить кнопку.

На мой взгляд, получилось красиво и лаконично.



Протоколы и перечисления — может быть удобно

Кейс №3: наша команда создала сервис по продаже авиабилетов онлайн.

Мобильный клиент тесно взаимодействует с сервером, и существуют различные сценарии выполнения вызовов API. Разделим их условно на поиск, бронирование билетов и оплату.

Ошибка может возникнуть в каждом из этих процессов (на стороне сервера, на стороне клиента, протоколе связи, проверке данных и т. д.).

Если при бронировании или поиске 500-й с сервера еще ничего страшного не означает, то, например, при оплате данные с внутреннего сервера уже могли уйти на платежный шлюз и клиент не может просто так показывают ошибку, хотя его деньги могли быть списаны с банковского счета.

карты.

Здесь протоколы могут позволить создать довольно элегантный код:

protocol Critical { func criticalStatus() -> (critical: Bool, message: String) } enum Error { case Search(code: Int) case Booking(code: Int) case Payment(code: Int) } extension Error : Critical { func criticalStatus() -> (critical: Bool, message: String) { switch self { case .

Payment(let code) where code == 500: return (true, "Please contact us, because your payment could proceed") default: return (false, "Something went wrong. Please try later.") } } }

Теперь давайте попробуем наш код и оценим, насколько он плох:

let error = Error.Payment(code: 500) if error.criticalStatus().

critical { print("callcenter will solve it") }

Жаль, что на самом деле проект представлял собой огромный пласт Object-C с кучей хаков для совместимости со Swift. Надеюсь, что следующие проекты удастся реализовать, используя все возможности языка.

P.s. Я надеюсь, что кто-то новый заинтересуется Swift и подходом к разработке на основе протоколов.

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

А старшие не будут критиковать и поделятся в комментариях парочкой своих секретов и наработок.

Спасибо всем.

Теги: #Swift #протоколы #расширение #iOS #разработка мобильных устройств #Разработка мобильных приложений #Swift

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.