Давайте сначала вернемся к тому времени, когда Apple только что запустила Swift, преемника Objective-C. Это был необыкновенный момент. Siri еще не открыла дверь в ад, Prezi еще не поддерживает подписки, а Северная Корея на тот момент еще не взломала чью-либо электронную почту. Я лично очень взволнован появлением нового языка, тем более, что это скриптовый язык с типобезопасностью. Хотя Swift все еще находится в стадии быстрой разработки, нам не нужно беспокоиться о его стабильности. Когда этот момент наступит, я уже должен знать, как создавать чистый, тестируемый код. Нужен очень гибкий и плавный пользовательский интерфейс? Если вас интересуют вещи, выходящие за рамки Objective-C и MVC, продолжайте читать.

МВК и МВВМ

Начнем с нуля. Когда мы разрабатываем приложение, вы можете сначала рассмотреть архитектуру приложения. Фреймворк Cocoa основан на Model-View-Controller (также известном как MVC), и его структура показана на рисунке ниже.

Хотя вы можете обнаружить, что эта архитектура не делает ваш дизайн более эффективным. Из приведенного выше рисунка видно, что обязанности роли Контроллера слишком сложны. Если у вас есть опыт использования MVC на iOS или Mac, держу пари, вы уже оценили, что View Controller берет на себя слишком много обязанностей. Чем больше у вас опыта работы с MVC, тем больше вам захочется найти другой способ решения этих проблем с помощью MVC. При использовании MVC для обработки сетевых запросов вы обнаружите, что это делает контроллер чрезвычайно раздутым. Он не только должен обрабатывать запрос данных, но также отвечает за отображение данных в пользовательском интерфейсе. Что более болезненно, так это то, что вы пытались провести модульное тестирование контроллера представления? Не терпится оглянуться! Это включает в себя тестирование View и бизнес-логики. По мере роста сложности View его становится все труднее поддерживать. Вот почему люди не тестируют MVC.

Модель-представление-представление (также называемая MVVM) — лучший выбор для вашего приложения. Это очень мощный шаблон архитектуры, выпущенный Microsoft для реализации, управляемой событиями. Как видите, разница с MVC заключается в том, что View здесь выполняет роль ViewModel.

Конечно, все это зависит от того, как вы хотите реализовать этот шаблон, несмотря ни на что, шаблон MVVM в конечном итоге принесет вам меньшую сложность и лучшую тестируемость. Можно ли достичь этих эффектов одновременно? Как это может быть? Я во всяком случае верю! Он перемещает всю логику в роль ViewModel, чтобы уменьшить обязанности View Controller. (Примечание переводчика: пожалуйста, обратитесь к этой статье, чтобы узнать о разнице между MVC, MVP и MVVM.)

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

Вернемся к температуре МВВМ и винного погреба. Чтобы иметь возможность считывать значение температуры, нам нужно использовать WebSocket на нашем сервере, чтобы наши приложения, основанные на архитектуре MVC, могли его считывать и отображать на iPhone. Во-первых, нам нужно создать класс модели Brew, в котором есть поле температуры (temp).

class  Brew {
     var temp =  0.0 
}

Нам также нужно создать ViewController, который отображает температуру винного погреба в пользовательском интерфейсе.

class  BrewViewController : UIViewController {
     @IBOutlet  weak  var tempLabel: UILabel ! 
}

Через MVC нам нужно реализовать логику сетевых запросов и обновить операции пользовательского интерфейса в ViewController.

socket. on ( " temperature_changed " , callback : {( AnyObject data) ->  Void  in 
    self . brew . temp  = data[ 0 ] as  Float 
     dispatch_async ( dispatch_get_main_queue (), {
        self . updateTempLabel ()
     })
})

Функция обновления пользовательского интерфейса:

func  updateTempLabel () {
        self . brewLabel . text  =   NSString ( format : " %.2f ˚C " , self . brew . temp )
    }

На данный момент мы успешно поместили все эти коды в ViewController, очень хорошо, не так ли? Этого не должно быть!

Представьте себе такой сценарий, когда вам нужно проверить личность пользователя, нарисовать диаграмму в реальном времени или обработать входящие и исходящие поля, как вы этого добьетесь? Полный пример архитектуры MVC — здесь. А теперь давайте взглянем на реализацию, основанную на архитектуре MVVM:

var brew =  Brew ()
 dynamic  var temp: Float  =  0.0 {
     didSet {
         self . brew . temp  = temp
    }
}

Выше приведены переменные, хранящиеся в ViewModel. Хотя они выглядят немного странно, я не знаю, как сделать их менее странными. Независимо от этого, давайте сначала посмотрим на Socket:

socket. on ( " temperature_changed " , callback : {( AnyObject data) ->  Void  in 
   self . temp  = data[ 0 ] as  Float 
})

Таким образом, логика сетевого запроса переносится во ViewModel. Но как мы можем изменить пользовательский интерфейс? KVO — наш хороший друг, мы используем его для достижения, вот фрагмент кода в viewDidLoad:

self . brewViewModel . addObserver ( self , forKeyPath : " temp " , options :. New , context : & brewContext)

Нам нужно наблюдать за температурой в винном погребе, поэтому я добавил в View Model отдельное поле. Вы можете спросить, какое нам дело до того, что модель Brew находится в модели представления? Я хочу сказать, что модель Brew может иметь разные поля, и вам может потребоваться сериализовать ее, передать по сети или выполнить какие-то другие операции. Поэтому нам нужен его статус.

override  func  observeValueForKeyPath ( keyPath : String , ofObject  object : AnyObject , change : [NSObject: AnyObject ], context : UnsafeMutablePointer < Void >) {
         if context ==  & brewContext {
             self . updateTempLabel ((change[NSKeyValueChangeNewKey]) as  Float )
        } else {
             super . observeValueForKeyPath (keyPath, ofObject : object, change : change, context : context)
        }
    }

Ничего себе, этот код слишком много? Реализация KVO генерирует много шаблонного кода, но она все же более легкая, чем реализация MVC, особенно когда мы рассматриваем возможность добавления все большего количества функций в наше приложение, эта логика будет перемещена в модель представления. Вот полный фрагмент кода, реализованный MVVM.

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

Отзывчивое программирование

«Реактивное программирование — это программирование с использованием асинхронных потоков данных». «Реактивное программирование — это программирование с использованием асинхронных потоков данных».

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

Если мы используем реактивное программирование в нашем проекте, то ситуация должна быть такой: Данные сокета рассматриваются как поток температуры, и этот поток отслеживается UILabel. Процесс их преобразования показан в функциональном программировании на рисунке выше. Другие операции, такие как сопоставление, слияние, фильтрация и т. Д., Этот процесс очень хорош! Давайте посмотрим, как эти концепции работают с Cocoa.

РеактивКакао

Когда мы планируем использовать реактивное программирование на Cocoa, лучшим вариантом будет использование ReactiveCocoa. ReactiveCocoa — это фреймворк привязки данных, вдохновленный функциональным программированием.

Он работает через сигналы, которые определяются как потоки, управляемые push-уведомлениями. Это означает, что сигнал представляет собой асинхронный рабочий процесс, который в будущем будет доставлять данные или произвольные результаты своим наблюдателям. В дополнение к сигналу, генерируемому изменениями модели данных, ReactiveCocoa также предоставляет некоторые встроенные привязки UIKit/AppKit, такие как rac_textSignal и другие вещи.

Вы можете быть немного не уверены, когда планируете начать свой следующий проект Swift, и вы также можете захотеть реализовать реактивное программирование в этом проекте. Не нужно этого делать, разработчики на github помогут вам реализовать эти функции. Они планируют выпустить версию 3.0 ReactiveCocoa под названием Great Swiftening, конечно, вы также можете использовать версию 2.4.x, потому что они также достаточно стабильны.

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

Давайте посмотрим, как мы можем создать ReactiveViewModel при использовании ReactiveCocoa в качестве системы привязки данных шаблона MVVM. Посмотрите на следующий код, выглядит ли наш контроллер представления более элегантно?

self . brewViewModel . tempChangedSignal . map {
            ( temp : AnyObject ! ) ->  AnyObject !  in 
            return  NSString ( format : " %.2f ˚C " , temp as  Float )
        } ~>  RAC ( self . TempLabel , " text " )
 }

Поскольку сложные макросы в стиле C недоступны в Swift, макросы RAC, используемые в Objective-C, были заменены. Спасибо Юсефу Напоре за простое решение, а еще одно решение — в Образце приложения Колина Эберхардта.

Теперь мы возвращаемся к проекту определения температуры винного погреба, мы готовы использовать адаптивное программирование! Давайте посмотрим на оставшийся код:

socket. on ( " temperature_changed " , callback :
    {( AnyObject data) ->  Void  in 
tempChangedSignal. sendNext (data[ 0 ]). deliverOn (RACScheduler. mainThreadScheduler ())
    })

Здесь мы только что создали сигнал данных о температуре и обеспечили выполнение обновлений UILabel в основном потоке. У меня нет сопротивления ReactiveCocoa! Теперь давайте выполним http-запрос и обработаем ошибку, возникшую при попытке инициализировать объект Brew на стороне сервера. Код выглядит следующим образом:

NSURLConnection. sendAsynchronousRequest (request, queue : NSOperationQueue (), completionHandler : {
                ( Response : NSURLResponse ! , Data : NSData ! , Error : the NSError ! ) ->  Void  in 
                IF error ==  nil {
                    subscriber. sendNext ( JSON (data))
                    subscriber. sendCompleted ()
                } else {
                    subscriber. sendError (error)
                }
})

В случае успеха мы отправим подписчику полученные данные Json, в противном случае вызовем функцию sendError, чтобы отправить сообщение об ошибке подписчику. Очень удобно, не так ли?

syncSignal. subscribeError ({ ( error : NSError ! ) ->  Void  in 
                UIAlertView ( title : " Error creating brew " , message : error. localizedDescription , delegate : nil , cancelButtonTitle : " OK " ). show ()
})

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

Вывод

Есть много других факторов, которые могут заставить вас попробовать ReactiveCocoa и Swift. Если вам интересно, как варить пиво, или просто интересны эти коды, то вы можете просмотреть все коды здесь (BrewMobile). Если у вас есть лучшие технологии, чтобы сделать это приложение лучше, пожалуйста, дайте мне знать! Конечно, вы также можете предоставить нам код.

Прежде чем закрыть, поделитесь некоторыми мыслями о будущем BrewMobile. Я планирую представить React Native в BrewMobile. Если получится, поделюсь еще раз своим опытом.