Это третья Лекция курса Stanford CS193p, весна 2021 года. На первых двух Лекциях мы много узнали о том, как создавать UI, используя SwiftUI на примере карточной игры «на запоминание» Memorize.
На этой неделе мы узнаем, как “подцепить” наш UI к логике, которая знает, как играть в карточную игру “на совпадение”. Но сначала профессор рассматривает две действительно важные концептуальные идеи: MVVM и системные ТИПы языка программирования Swift.
MVVM
MVVM — это, по сути, способ организации всего кода в нашем приложении.
Системные ТИПы в Swift, очевидно, позволяют нам делать все, что мы делаем в языке программирования Swift и, следовательно, в SwiftUI.
MVVM, как и MVC, разделяет весь код нашего приложения на код пользовательского интерфейса (UI), то есть то, что мы называем View, от логики нашего приложения, которую мы называем моделью Model.
Model — полностью UI НЕзависима. Model вбирает в себя все данные Data и логику Logic, которые описывают “ЧТО” делает ваше приложение. View — это то, «КАК» ваше приложение предстает перед пользователем, Model — это то, “ЧТО” ваше приложение делает на самом деле. Model — единственный источник ИСТИНЫ (“Truth”) для данных, которые представляют нашу игру.
View всегда будет отражением текущего состояния Model. View по большому счету вообще не имеет состояния State (то есть оно stateless). Ему не нужно хранить слишком много информации о состояниях State, потому что ИСТИНА (“Truth”) о состоянии игры всегда находится в Model. В демонстрационном приложении Memorize, который мы делали на прошлой неделе, всё своё время мы проводили за написанием кода для переменной var body нашего View. Этот var body всегда должен что-то возвращать на основе текущего состояния Model. Views — неизменны (immutable). Их нельзя изменить. Следовательно, нет другого способа изменить наш View, кроме как целиком перестроить var body. Следовательно, 100% того, как выглядит View, определяется исключительно тем, что находится в реализации переменной var body.
Мы называем этот вид кодирования декларативным, потому что мы декларируем в переменной var body, как выглядит пользовательский интерфейс (UI) нашего View. Конец истории. Это противоположно виду кодирования, к которому мы привыкли и который мы называем императивным.
Чтобы это было практично, у вас должна быть очень эффективная система. Она должна запрашивать переменные var body только тех Views, которые на самом деле могут измениться в результате этого последнего изменения в Model. В противном случае вы бы постоянно перерисовывали UI, весь ваш UI полностью.
В этом суть MVVM. И вот почему, помимо декларативности, мы также говорим, что SwiftUI является реактивным. View всегда автоматически эффективно реагируют (react) на изменения в Model. По своей сути, работа MVVM заключается в том, чтобы “подцепить” Model к View так, чтобы любое изменение в Model, заставляло перестраивать исключительно те части View, на которые воздействует это изменение в Model, путем доступа к их переменным var body, чтобы показать это новое измененное состояние Model.
Реактивность в MVVM это обеспечивается третьим элементом, ViewModel. Работа ViewModel состоит в том, чтобы “привязать” (bind) View к Model, поэтому изменения в Model заставляют View реагировать и заново перестраиваться. Какой бы ни была ViewModel, её первостепенная задача — отслеживать все изменения в Model. Это фундаментально. Замечая изменения, ViewModel немедленно публикует всему Миру сообщение : “Что-то изменилось…”. И любой желающий может прослушивать эти сообщения.
Почему именно так это организовано?
По очень важной причине — ViewModel не хочет иметь никакие связи ни с каким из Views, которые используют его для доступа к Model. Это очень важный момент. У нас НИКОГДА не будет указателя или какой-то другой структуры данных в ViewModel, которые знают что-нибудь о View структуре. И именно поэтому ViewModel публикует всему Миру сообщение: “Что-то изменилось…”. Views должны “подписаться” на прослушивание этих сообщений об изменениях.
A как насчет другого направления? Мы говорили о том, как View всегда обнаруживает изменения и перестраивается, чтобы показать последнее состояние Model, но как изменяется Model, с помощью выполняемых в View жестов наподобие Tap, Swipe, и т.д,? В некоторых случаях это может вызвать изменение Model.
Мы справляемся с этим, добавляя в ViewModel обработку Намерений (Processes Intends) пользователя. На самом деле в SwiftUI нет формального встроенного механизма, поддерживающего архитектуру Model—View—Intent (Модель — View — Намерение), хотя такие вещи существуют в других системах. Но думать об этих вещах как о Намерениях (Intent) настоятельно рекомендуется. Это очень понятный способ разобраться в том, что происходит в вашем View и как это должно повлиять на вашу Model.
Вот и вся архитектура MVVM.
Мы будем рассматривается на этой Лекции архитектура MVVM применительно к игре Memorize.
Системные ТИПы Swift
Сложно по-настоящему понимать код Swift без знания системных ТИПов, лежащих в его основе. По существу, в Swift есть 6 различных разновидностей ТИПов данных.
- struct
- class
- protocol
- «не важно, какой ТИП» (Generics)
- enum
- functions
Это структуры struct наподобие наших ContentView и CardView, с которыми мы уже знакомы. Кстати, наша модель Model игры “Memorize» также будет структурой struct.
Есть классы class, которые конечно, предназначены для объектно-ориентированного программирования. Наша ViewModel будет классом class.
Есть протоколы protocol. Супер важный ТИП. View подобно some View или “ведет себя” как View. Такой View — это и есть протокол protocol. A также Identifiable. Помните, с какими вещами работает ForEach, когда мы хотели разместить их в массиве Array? Они должны быть Identifiable? Identifiable — это также протокол protocol.
Есть “не важно, какой” ТИП, которые также известны как Generic. Мы будем говорить о них.
Есть enum, это просто перечисления, но очень мощные в Swift.
И, наконец, функции — это ТИПы. Да, как вы уже могли догадаться, функции — это первоклассные ТИПы в Swift. Это фундаментально, чтобы функции были первоклассными ТИПами при наличии функционального программирования в языке программирования Swift.
Это большая тема и на Лекции 3 рассматриваются только 4 ТИПа: структуру struct, класс class, “не важно, какой ТИП” и функцию.
Далее профессор добавляем MVVM в наш демонстрационный пример с игрой «Memorize«. Наша цель на этой неделе — получить приложение, способное на самом деле играть в игру «Memorize».
Далее в демонстрационном примере профессор создает инфраструктуру нашей модели Model и нашей ViewModel. При этом исследуется вопрос использования функции в качестве аргумента init, static функции и переменные и другие синтаксические конструкции Swift.
На следующей Лекции мы вернемся к нашему View и обеспечить его зависимость от модели Model.
Код демонстрационного примера для Лекции 3 находится на Github для iOS 14 в папке Memorize L3.
Русскоязычный неавторизованный конспект Лекции 3, иллюстрированный, хронометрированный и представленный в виде PDF-файла, который можно скачать и использовать offline, а также в формате Google Doc доступны на платной основе.