Лекция 1 CS193P Fall 2017 — Введение в iOS 11, Xcode 9 и Swift 4. Часть 2.

На сайте представлен полный неавторизованный хронометрированный конспект Лекции 1 на русском языке курса  Разработка iOS 11 приложений с помощью Swift 4.

Первая часть — 0 — 50 минута находится здесь,
Вторая часть — 50 — 82 минута — находится  в этом посте.

Код демонстрационного примера для Лекции 1 находится на Github в папке Concentration L1.

———   ПРОДОЛЖЕНИЕ КОНСПЕКТА  ——————-
——- 50 -ая минута лекции ———
Но метод touchCard вызывается ОБЕИМИ картами:

И в этом проблема.
Почему так произошло? Это произошло из-за того, что я копировал (⌘C Copy) и вставлял (⌘V Paste) карту с “привидением” 👻 для создания второй карты.  При проведении операции копирования и вставки происходит копирование всех методов. Это очень распространенная ошибка, которую очень легко сделать.
Конечно, я намеренно создал новую карту копированием, чтобы показать вам, как избавиться от вышеупомянутой  ошибки.
Для этого вам нужно кликнуть правой клавишей “мышки” (или CTRL -кликнуть) на элементе UI, и он вам покажет все свои связи (connections).
Давайте CTRL -кликнем на карте с “тыквой” 🎃:

Мы видим, что “тыква” 🎃 посылает два сообщения: touchCard и touchSecondCard, а нам нужно только одно — touchSecondCard. Я хочу отсоединить touchCard, кликая на маленьком креcтике x.

Теперь я все исправил.
Теперь сообщение touchSecondCard посылает только карта с “тыквой” 🎃:

А сообщение touchCard посылает только карта с “привидением” 👻:

Очень важно знать этот прием с кликом правой клавишей (CTRL -клик) “мыши” на элементе UI, если вы попадаете в ситуацию, когда кажется, что происходит что-то странное и вы думаете, что “что-то” не так подсоединено, или ваше приложение заканчивается аварийно, пытаясь послать какие-то незапланированные сообщения. В этом случае кликаете правой клавишей (CTRL -клик) “мыши” на элементе UI, и вам покажут, почему это могло произойти. Давайте запустим приложение, и убедимся, что мы действительно исправили нашу проблему.

Карта с “привидением” 👻 переворачивается и  карта с “тыквой” 🎃 также переворачивается.
Все работает прекрасно.
Теперь, прежде, чем добавить еще карт, я хочу иметь элемент UI, который мне скажет, сколько раз переворачивались карты. Потому что вы, наверное, заметили, что в этой игре чем меньшее число раз я переворачиваю карты, тем лучше. Если я все время буду переворачивать карты “лицевой” стороной, обратной, “лицевой” стороной, обратной, тем хуже для результатов игры, ибо это не означает, что я “концентрируюсь”.
Мне нужен некоторый UI, который сообщил бы мне, сколько раз я переворачивал карты.
Но вначале удалим печать из нашего метода  
flipCard — мне больше не нужна отладка:

Я начну с добавления переменной экземпляра класса (instance variable) в мой класс, которая бы отслеживала число переворотов  карт. Это очень легко. Как выглядит добавление  переменной экземпляра класса?

Мы используем ключевое слово var, что является сокращением от слова “variable” (переменная). Затем следует имя flipCount, затем тип : Int, и это все. Я добавил переменную с именем flipCount, которая отслеживает число переворотов карт, которые произошли к этому моменту времени.
Но посмотрите, что произошло… Похоже, у нас ошибка. Она подсвечивается КРАСНЫМ цветом. Если у вас есть ошибка, подсвеченную КРАСНЫМ цветом, то ваше приложение не сможет компилироваться и запускаться. Вы можете также получить ошибку, подсвеченную ЖЕЛТЫМ цветом. В этом случае ваше приложение будет компилироваться и запускаться, но вы не должны мне посылать такое приложение на оценку, даже если это просто предупреждения. Все равно они указывают на то, что что-то не так, и вы не должны предоставлять мне ваши домашние Задания в таком виде.
Но у нас КРАСНАЯ ОШИБКА, и мы должны ее исправить.
Забавно то, что КРАСНАЯ ОШИБКА появляется на другой строке в коде.
Я напечатал строку:

var flipCount :Int

А КРАСНАЯ ОШИБКА появляется на совершенно другой строке:

class ViewController: UIViewController {

ОШИБКА нам говорит: “ class ViewController has no initializers” (У класса  ViewController нет инициализаторов).
Почему компилятор говорит нам такое и что нам с этим делать?
Это происходит из-за того, что Swift требует, чтобы все переменные экземпляра класса (instance variables), которые, между прочим, в Swift мы называем свойствами (properties), были инициализированы.
Когда вы слышите, что я говорю “свойство”, то я имею ввиду переменные экземпляра класса (instance variable).
Итак, все  переменные экземпляра класса, все свойства, должны быть инициализированы.
Вам не разрешается иметь такие переменные как

var flipCount :Int

Где значение этого свойства? У него нет значения, и это свойство должно иметь начальное значение, не разрешается просто так “сидеть” в классе и не иметь значения.
Есть два способа инициализации переменной экземпляра класса.
Первый способ состоит в использовании инициализатора. И именно об отсутствии такого (или таких) инициализаторов говорит нам КРАСНАЯ ОШИБКА. Инициализатор — это просто метод со специальным именем init (сокращение для слова initializer”), и он может иметь любые аргументы, какие хотите. Фактически, у вас может быть множество методов с именем init, но каждый должен иметь аргументы, отличающиеся от аргументов других инициализаторов. Но любой из инициализаторов является ответственным за инициализацию всех переменных экземпляра класса, потому что в этом и заключается “работа” инициализатора и он должен ее выполнять.
Добавление инициализатора в класс class может оказаться достаточно сложным из-за наследования (inheritance), так как у вашего superclass также есть инициализаторы, и вы должны обеспечить их правильный вызов.
Я пока не рассказывал вам об инициализаторах, я совсем немного расскажу о них в Среду. На следующей неделе мы проработаем тему инициализаторов более глубоко.
Второй способ инициализации, каким я могу инициализировать переменные экземпляра класса без инициализаторов, состоит в том, что я просто назначу переменной flipCount значение равное 0:

Мы видим, что КРАСНАЯ ОШИБКА “ушла”, и жизнь — прекрасна!
Именно так мы инициализируем большинство наших переменных экземпляра класса (instance variables).
Ну, раз уж я здесь и рассказываю об экземплярах класса, я хочу немного поговорить о самом языке программирования Swift и типизации.
Swift — это чрезвычайно строгий типизированный язык программирования. Правда, у него есть тип, который подобен отсутствию типа, но он используется почти всегда исключительно для обратной совместимости с Objective-C. В большинстве случаев вы захотите определить, какой тип вы будете использовать, и многие люди жалуются на такие строго типизированные языки программирования. Это так обременительно всегда и всему назначать определенный тип.
Но Swift также является языком программирования, который способен осуществить  строгий «вывод типа из контекста» (type inference), а это означает, что если он может, то он “угадывает” тип для вас.

——- 55 -ая минута лекции ———
В нашем случае вы видите тип : Int, который абсолютно не нужен в Swift.

Не только в нем нет никакой необходимости, но мы вообще не будем указывать здесь никакой другой тип:

Почему так? Из-за 0,  который ясно дает понять, что это Int.
Swift обращается со всеми литералами подобным образом. Нет десятичной точки — значит Int. Как мы узнаем, что переменная flipCount имеет тип Int? Мы удерживаем клавишу option, как мы уже это делали, когда кликали на setTitle для получения документации, и кликаем на flipCount:

И нам сообщают, что flipCount — это Int.
Если я назначу flipCount значение 0.0, а затем, удерживая клавишу option, кликну на flipCount:

И нам сообщают, что flipCount — это Double.
Этот тип для flipCount Swift “вывел из контекста” (infer).
Давайте назначим flipCount значение «hello», и нам покажут тип String, Swift “вывел тип из контекста” (infer):

Это то, что касается “вывода типа из контекста” (infer).
В нашей ситуации очень просто осуществить “вывод типа из контекста” (infer), потому что это константа,

но Swift может “выводить тип из контекста” (infer) даже в фантастически сложных ситуациях. Может быть единственно возможный тип для некоторого объекта, и Swift “выведет этот тип из контекста”, так что вам не придется повсюда проставлять типы.
Это способность Swift просто удивляет. Единственный раз, когда вам придется точно указывать тип — это большинство аргументов в функциях, так как вам нужно четко сказать, что вы ожидаете получить. Но в других случаях вам редко придется указывать тип явно. И это действительно замечательно.
Итак, у нас есть переменная flipCount, давайте займемся ее приращением.
Каждый раз, когда кто-то кликает на карт, число переворотов карт flipCount должно возрастать на единицу:

Вторую строку

var flipCount += 1

я получил копированием ⌘C и вставкой ⌘V.
Боже мой, каждый раз, когда вы копируете и вставляете код, вы делаете что-то неправильное. Это не может быть правильным. Сейчас я все равно это делаю таким образом, но  через секунду мы это исправим, потому что не хотим усложнять код.
Каждый раз, когда мы касаемся карты, я увеличиваю flipCount на единицу. Все это хорошо, но мне нужно, чтобы число переворотов карт flipCount появилось на нашем UI. Я хочу, чтобы пользователь видел, сколько раз он перевернул карты.
Как мы будем это делать?
Мне нужен некоторый другой UI элемент, отличный от кнопки UIButton, который мог бы показывать совсем небольшой текст. И в iOS таким read-only текстовым полем является метка UILabel. Каждый раз, когда мы хотим добавить что-то на наш UI, мы обращаемся к нижней части Области Утилит, к Библиотеки Объектов. И прямо рядом с кнопкой Button, находится метка Label. Я вытягиваю ее из Палитры Объектов на наш UI и попытаюсь расположить где-нибудь в центре:

Эта метка очень-очень маленькая. Я сделаю ее побольше, потому что буду использовать шрифт большого размера через секунду, я сделаю ее выше, но пока она не видна из-за того, что цвет текста — черный, и получается черный текст на черном фоне. Я поменяю цвет текста метки на наш любимый цвет — оранжевый:

Мы видим, что текст очень мелкий, и нам действительно нужно поменять размер шрифта, который сейчас равен 17 points, такой размер шрифта выставлен по умолчанию, а нам нужен размер 40 points:

Намного лучше.
Теперь я хочу, чтобы метка для начала показывала текст Flips: 0 и была выровнена по центру :

Вы видите, как я пользуюсь Инспектором Редактирования Атрибутов для улучшения вида моего UI.
Я также могу воспользоваться голубыми пунктирными линиями, чтобы расположить мою метку посередине и вдоль нижнего края экрана:

И хотя я сказал, что пока не буду их использовать, все равно это полезно делать.
Итак, у меня есть UI, как я буду с ним разговаривать?
Как я сообщу, что у меня изменилось число переворотов карт и как мне показать это пользователю?
Для этого я создам связь между моим UI и моим кодом. И мы знаем, как это делается — с помощью CTRL-перетягивания:

Но на этот раз нам нужен НЕ Action, а Outlet, который создается как переменная экземпляра класса ( instance variable).  Action создает метод, а Outlet создает переменную экземпляра класса ( instance variable) или свойство (property). Это свойство указывает на эту метку UILabel, и я могу говорить с ней и обновлять, если происходит переворот карты:

Здесь уже стоит правильный тип в поле TypeUILabel, а не Any.
Не обращайте внимание на поле Storage, в котором стоит Weak. Я буду говорить об этом на следующей неделе.
Я кликаю на кнопке “Connect” и создается переменная var похожая на var flipCount:
Но это немного другая переменная var:

Здесь ЯВНО указан тип переменной var flipCountLabel: UILabel!
Мы не можем получить тип этой переменной с помощью “вывода из контекста” (infer), потому что эта переменная указывает на UI. Она не знает, как заглянуть на UI и “вывести из контекста” (infer) тип метки UILabel. Поэтому ЯВНО указывается ТИП метки UILabel.

——- 60-ая минута лекции ———
О ключевом слове weak я буду рассказывать на следующей неделе.
У нас присутствует директива @IBOutlet, которая принадлежит Xcode, и благодаря которой слева от переменной размещается маленький кружочек.  Директива @IBOutlet похожа на директиву @IBAction, c которой мы уже встречались.
Если мы наведем “мышку” на кружочек слева, то нам покажут элемент UI, к которому эта переменная подсоединена:

Точно также, как и метод touchCard:

И метод touchSecondCard:

Переменной var flipCountLabel: UILabel! в точности такая же переменная, как и переменная var flipСount. Но у переменной var flipCountLabel в конце стоит восклицательный ! знак. Это СУПЕР ВАЖНО. Но сегодня я не буду говорить об этом. Я не могу говорить прямо на первой Лекции сразу обо всем.
Заметьте, что у переменной var flipCountLabel есть побочный эффект и это тоже супер важно. Он состоит в том, что переменная var flipCountLabel не должна быть инициализирована.
Мы не можем ничего ей присвоить, мы не можем написать

var flipCountLabel: UILabel! = …

И более того, она не инициализирована, а у нас нет КРАСНОЙ ошибки, как прежде.
Пока не беспокойтесь об восклицательном ! знаке, я расскажу о нем все, это самая важная часть Swift, возможно, наиглавнейшая из всех новшеств Swift, и вы должны узнать о ней все.
Итак, у меня есть переменная var flipCountLabel. Все, что я хочу сделать, — это каждый раз, когда изменяется переменная flipCount, я хочу разговаривать с меткой с помощью переменной flipCountLabel и устанавливать ее текст. Я собираюсь послать ей сообщение о том, что я хочу изменить ее текст:

Так случилось, что у метки есть свойство с именем text, самое первое в списке. Нам сообщают, что оно имеет тип String, так что я кликну дважды на нем и получу свойство text в моем коде и  которое установлю с помощью нашей любой фишки /() и переменной  flipCount:

И, конечно, мне придется скопировать эту строку кода и вставить ее в метод touchSecondCard, потому что я хочу изменять метку каждый раз при изменении переменной flipCount:

Давайте посмотрим, работает ли это.

Прекрасно работает, каждый раз, когда мы кликаем на карте, она переворачивается и число переворотов растет. Фантастика!
За исключением того, что я каждый раз копирую код для обоих методов: touchCard и touchSecondCard. А если у меня будет еще одна кнопка “New Game”, которая будет возвращать переменной  flipCount значение 0? Я должен скопировать для этой кнопки все три строки:

А что, если я в один прекрасный день захочу поменять текст метки с «Flips: \(flipCount)» на «Flips count: \(flipCount)«? Я должен буду менять код во всех трех местах? Ужасный стиль программирования.
К счастью, в Swift есть способ избежать этого ужасного кода в этом случае.
Вы можете после свойства, которое собирается меняться, разместить код внутри didSet { }:

Этот код будет выполняться каждый раз, когда это свойство устанавливается.
Такая синтаксическая конструкция называется НАБЛЮДАТЕЛЬ СВОЙСТВА (Property Observer), потому что этот код наблюдает за изменениями свойства.
Мы можем перенести сюда код из методов touchCard и touchSecondCard:

Каждый раз, когда свойство flipСount изменяется, выполняется код в didSet {}, который разговаривает с меткой и обновляет ее. Так что, если я добавлю кнопку  “New Game”, в которой установлю свойство flipСount в 0, метка flipСountLabel автоматически обновится.

НАБЛЮДАТЕЛЬ СВОЙСТВА (Property Observer) — это действительно крутая фишка. Очевидно, что мы будем использовать НАБЛЮДАТЕЛЕЙ СВОЙСТВА (Property Observer) очень часто для синхронизации нашего UI с переменными экземпляра класса.
Видите?
Все работает:

Теперь пришло время добавить еще кнопок.
У нас уже есть две кнопки.
Между прочим, до сих пор мы управляли нашим UI в коде:

Но я собираюсь изменить мой UI и сделать так, чтобы игра начиналась с того, что все карты лежат «обратной стороной». Интересно, что если я выделю обе карты, то смогу воспользоваться верхней частью Области Утилит, в которой уже указано, что оба этих UI элемента — кнопки Button:

И я могу сделать заголовок кнопок пустым в поле, в котором сказано “Multiple Values” (множество значений), потому что там сейчас находятся и “привидение” 👻, и “тыква” 🎃 :

И я могу установить оранжевый фон:

Все эти установки сразу воздействуют на обе кнопки.
Если вы выбираете множество UI элементов, и если они в чем-то одинаковы, например, у них один и тот же тип, то вы можете редактировать атрибуты их всех одновременно.
Итак, у меня две карты. Давайте сделаем больше карт.
Мы будем делать это копированием ⌘C и вставкой ⌘V.
Но прежде, чем я начну добавлять новые карты, я должен заметить, что я крайне недоволен своей архитектурой, имея для каждой карты отдельный метод:

Если я начну добавлять карты, то я должен копировать код и в дополнение к методам touchCard и touchSecondCard создать метод touchThirdCard для третьей карты:

И метод touchFourthCard для четвертой карты:

И метод для пятой, шестой, седьмой, восьмой, девятой карты.
Вы получите “F” (по-нашему 1) за ваше домашнее Задание, если представите мне такой код. Это ужасная архитектура, и мы должны полностью избавиться от метода touchSecondCard, потому что я хочу разместить весь код в методе touchCard:

Я не хочу больше дублировать код и это здорово. Это даст мне простор для развития моего UI.
Я CTRL-кликаю на карте с бывшей “тыквой”

и отсоединяю метод touchSecondCard :

Вместо этого мы выполним CTRL-перетягивание от этой карты в код и “подцепим” уже существующий метод touchCard:

Я отпускаю клавишу CTRL, и теперь обе эти карты подсоединены к методу touchCard :
——- 65-ая минута лекции ———

Это здорово, но, очевидно, что внутри метода touchCard мы не должны использовать “привидение” 👻, потому что в этом случае обе карты будут  “привидениями” 👻:

Убираем эту строку кода:

Какой нам написать код в методе touchCard, чтобы он работал для всех карт?
Я создам массив всех этих карт, и если кто-то кликает на некоторой карте и срабатывает метод touchCard, то я буду искать в этом массиве карт, какая из них была выбрана. Затем, зная карту, то есть индекс выбранной карты в массиве карт, я буду просматривать другой массив, в котором находятся эмоджи, и выбирать эмоджи, соотвующий индексу выбранной карты, чтобы разместить его на карте, если это лицевая сторона. Таким образом, все будет управляться данными. Я должен добавить больше карт и создать из них массив, я также должен создать массив эмоджи.
Здорово.
Давайте добавим еще карт с помощью копирования ⌘C и вставки ⌘V :

Теперь у меня 4 карты, и я хочу создать из них массив, который включал бы все карты.
Как я буду это делать? С помощью связи (connection) между UI и кодом.
Мы используем CTRL-перетягивание для создания еще одной переменной var прямо в коде:

На этот раз, мы будем использовать Outlet Collection, который означает массив UI элементов:

Я умышленно дал имя с грамматической ошибкой сardButons, потому что я хочу показать вам, как корректно  исправить эту ошибку, если вы ее сделали:

Мы получили правильный тип UIButton в поле Type.
Я кликаю на кнопке “Connect” и она создала мне еще одну переменную var :

И посмотрите на ее тип — [UIButton]!
Это специальный синтаксис в Swift, который означает, что это массив кнопок UIButton.
Может быть для вас будет более привычным другой Swift синтаксис для массива  кнопок UIButton:

Это более знакомо из языка программирования Java?
Так что массив Array — это Generic класс, надеюсь, что все знают, что такое Generic в Java?
Это означает, что в Swift не может быть класса Array, который сам по себе, в Swift нужно обязательно указать тип элементов массива, потому что я говорил вам, что Swift очень строг относительно типов. Когда вы размещаете или достаете элементы из массива, Swift может хватить “сердечный удар”, если он не знает тип того элемента, который размещается в массиве.
Поэтому, когда вы создаете массив Array, вы обязаны определить тип его элементов, и тогда Swift сможет “дышать” спокойнее.
Массивы Array настолько распространены, что вместо использования нормального Swift синтаксиса Array<UIButton>, мы будем использовать специальный краткий синтаксис [UIButton]:

Это что касается массива Array, словарь Dictionary также имеет специальный краткий синтаксис, но его я покажу в Среду.
Итак, у меня есть массив со странным названием cardButons, к которому в данный момент подсоединена только одна карта.  Я еще не подсоединил остальные, но обязательно это сделаю:

Допустим, я понял, что сделал ошибку при наборе имени cardButons, и хочу исправить его на правильное cardButtons. Помните, я говорил вам, что не следует редактировать по месту то, что также присутствует и в нашем UI? Например, имя класса или вот имя @IBOutlet.
Давайте я переименую переменную cardButons в cardButtons, и посмотрим, что произойдет с маленьким кружком слева от этой переменной:

У кружочка внутри исчезла точка, потому что к нему никакой UI элемент не подсоединен. Я CTRL— кликаю на карте, и вижу, что она по-прежнему подсоединена к cardButons:

Она не подсоединена к cardButtons.
Как нам это исправить?
Если я опять верну имя cardButons, то связь кнопки с переменной восстановится и для того, чтобы ее переименовать, я должен использовать еще одну МАГИЧЕСКУЮ КЛАВИШУ — клавишу (command). Я уже показывал вам одну МАГИЧЕСКУЮ КЛАВИШУ — Alt (option)  для получения документации. Если вы (command), то “всплывет” меню, которое делает совершенно потрясающие вещи:

Пункт этого меню “Jump to Definition” осуществляет переход к определению того, на чем вы >-кликнули. В нашем случае, выбирая этот пункт меню, мы никуда не уйдем, потому что это и есть определение cardButons.
Пункт этого меню “Show Quick Help” работает как Alt (option) — клик для получения документации.
И также пункт меню “Rename”. Когда вы кликаете на этом пункте меню, то смотрите, что происходит. Наш UI немного свернется, Xcode начнет искать cardButons по всему проекту, включая  storyboard, и результат предстанет перед нами в компактном свернутом виде:

Теперь, если я изменю cardButons на cardButtons, то эти изменения произойдут повсюду, включая  storyboard, и ничего не сломается:

Если мы CTRL —  кликнем на карте, то увидим, что она подсоединена к cardButtons:

Итак, -клик — для переименования (rename). Именно так мы переименовываем элементы, которые задействованы как в коде, так и в UI нашего приложения.
Итак, у меня есть cardButtons.
Теперь мне нужно что-то сделать в методе touchCard, мне нужно просмотреть массив карт cardButtons  и найти эту кнопку.
Прежде чем мы будем это делать, мне необходимо оставшиеся карты подсоединить к cardButtons. И я покажу вам другой способ подсоединения вашего UI к коду. Это делается с помощью желтой кнопки, расположенной в самом верху нашего экранного фрагмента на storyboard.

Видите эту желтую кнопку на самом верху? На ней подсказка “View Controller”. Эта желтая кнопка представляет весь наш код. Если вы выполните CTRL-перетягивание от этой желтой кнопки к любому элементу UI, то вы также можете выполнить  подсоединение кода к вашему UI и “подцепить” cardButtons:

Я намеренно не “подцепил” последнюю четвертую карту к cardButtons, чтобы показать вам, что произойдет, если карта не “подцеплена” к cardButons.
Давай посмотрим на cardButtons:

К cardButtons “подцеплены” три карты, кроме одной.
Как я буду искать в массиве cardButtons мою карту?

——- 70-ая минута лекции ———
Конечно, Swift массив Array — это фантастический класс. У него такая замечательная функциональность, что, конечно, он знает как искать внутри себя нужную карту и каков ее индекс с помощью метода, который называется index(of:). Давайте просто вызовем этот метод. Я создам переменную var с именем cardNumber, в которой я хочу разместить номер карты : 0,1,2,3,4 …, то есть тот, который карта имеет в массиве cardButtons:

Я после cardButtons набираю точку “.”, чтобы послать сообщение index массиву cardButtons. Посмотрите, сколько у массива Array методов с именем index. Множество !! В Swift это совершенно законно. В Swift у вас может быть 100 методов, которые имеют одно и то же имя index, но все они должны различаться аргументами. Вы видите все методы с одинаковым именем index, которые имеют различные аргументы? В этом списке есть методы index (after:), index (before:) и другие.
Мне нужен метод index (of: UIButton), расположенный немного ниже и возвращающий Int?:

С Int? могут быть проблемы, и мы это увидим через секунду, но я просто кликаю дважды на методе index (of: UIButton),  и получаю метод index (of: UIButton), который использую для sender, потому что sender — это карта, которой я коснулся. Затем я хочу просто распечатать полученный номер карты cardNumber и использую для этого нашу “волшебную” синтаксическую конструкцию \(cardNumber):


Прежде чем мы начнем просматривать массив с эмоджи, давайте убедимся, что мы нашли с помощью нашего кода правильную карту.
Но у меня появились два предупреждения. Мы можем их полностью увидеть, если разместим код на полном экране.
Первое предупреждение говорит: “Variable ‘cardNumber’ was never mutated. Consider changing to ‘let’ constant.” — “ Переменная ‘cardNumber’ никогда не изменяется. Рассмотрите использование ‘let’ для константы.” Что оно означает? Мы действительно дали cardNumber начальное значение, которое представляет собой номер искомой карты в массиве cardButtons, и мы никогда не будем его менять. По своей сути  cardNumber — это не переменная, это константа, и в Swift мы всегда метим константы как константы. Но мы не используем ключевое слово const как в C и других языках, мы используем let, L-E-T.
И здесь есть еще одна крутая возможность. Видите желтый треугольник? Если я кликну на нем, то в большинстве случаев Swift сам исправит ошибку вместо меня:

Вы видите, что там написано “Fix>”, это означает “Исправить”?  И поясняется, какое исправление имеется ввиду: замена ключевого слова var на let. Мы кликаем на “Fix”, и получаем исправленный код:

Почему мы используем ключевое слово let вместо const? Потому что мы хотим, чтобы Swift говорил правильно по-английски.
Код let cardNumber = cardButtons.index (of:sender) звучит по-английски так  “let cardNumber = cardButtons.index of the sender” (Пусть cardNumber равно cardButtons.index sender ). В любом случае это лучше, чем “const cardNumber =…”.
Вы привыкните к использованию let, всегда используйте let, если что-то является константой, и никогда не используйте var в этом случае.
У меня есть еще предупреждение, но я его проигнорирую, просто не буду обращать на него внимания. Давайте просто посмотрим, что будет происходить, если мы запустим приложение:

Мы попробуем кликнуть на некоторых картах и получить на консоли значения cardNumber.
Готовы? Кликаем!
Что-то странное. Взгляните на это. Optional(0), Optional(1), Optional(0), Optional(2).
Что такое Optional? Мы ожидали увидеть номера 0, 1, 2, а нам говорят Optional.
Почему Optional?
Это супер важная вещь, о которой я вскользь упомянул.
Вот она.
Давайте option-кликнем на методе index (of:sender) и посмотрим документацию:

Мы видим, что возвращаемое значение индекса в методе index (of:) вовсе НЕ Int, возвращаемое значение индекса — Optional, и об этом говорит вопросительный ? знак. Мы уже видели  вопросительные ? знаки в документации. Все они означают Optional. И Optional — это совершенно другой тип, чем Int. Он вообще ничего не может делать с Int.
Optional — это тип, который имеет только два состояния: set (“установлен”) и not set (“не установлен”). Optional — это перечисление (enumerations). Возможно, вы видели перечисления в других языках. Перечисления — это вещи, которые имеют дискретный набор значений. Перечисление Optional имеет только два значения: set (“установлен”) и not set (“не установлен”). Только два значения и больше никаких других.
Но замечательно, что перечисления (enumerations) в Swift, этого нет в других языках программирования, для каждого дискретного значения (case) могут иметь ассоциированные данные (associated data), которые сопровождают это дискретное значение.
Если Optional находится в состоянии set (“установлен”), оно имеет ассоциированные данные (associated data), которыми в нашем случае является Int.

Метод index (of:) возвращает нам значение, которое означает, смогли мы найти заданную кнопку в массиве кнопок или нет, то есть значение set(“установлен”) или not set (“не установлен”). И если эту кнопку удалось найти, то нам даются ассоциированные данные в виде Int.
Именно это мы видим при печати на консоли:

Нам сообщают, что это Optional, которое имеет значение set (“установлен”) и показывают ассоциированное значение, которое имеет тип Int.
Понятно?
Теперь давайте кликнем на четвертой кнопке, которой нет в массиве cardButtons. Давайте посмотрим, что распечатает она.
Я кликаю на четвертой кнопке, которой нет в cardButtons:

Индекс четвертой кнопки не найден в массиве cardButtons и возвращается значение not set (“не установлен”).   Мы видим nil, N-I-L. В Swift слово nil означает значение not set (“не установлен”) в случае с Optional. И это единственное, что оно означает. В других языках программирования это означает нулевой указатель или что-то еще. Но в Swift >nil всегда означает Optional, который not set (“не установлен”). Понятно?
——- 75-ая минута лекции ———
Тем не менее, для меня не очень хорошо, если у меня будет Optional. Я не смогу его использовать для поиска в другом словаре — словаре с эмоджи, там мне реально нужен Int.
Как мне получить ассоциированное значение для состояния set (“установлен”) ?
Как я могу “вытянуть” его из Optional ?
Один способ заключается в том, что мы поставим восклицательный знак “!” в конце нашей Optional переменной:

Если мы разместим восклицательный знак “!” в конце Optional, то это даст нам ассоциированное значение при условии, что само Optional, находится в состоянии set (“установлен”).
Давайте запустим приложение и посмотрим, на что это похоже.
Мы видим, что весь синтаксис для Optional реально очень простой : вопросительный ? знак, восклицательный ! знак. Это один символ, потому что нам так часто придется их использовать с Optionals.
Смотрим, что получилось:

Теперь cardNumber = 0,1, 2, прекрасно, все работает.
Что произойдет, если я кликну на четвертой карте?
(Аварийное завершение программы).

Точно. Почему?
Потому что возвращается значение not set (“не установлен”) для Optional, которое не должно иметь ассоциированного значения и приложение завершается аварийно.
Вы будете иногда видеть такое аварийное завершение в процессе разработки из-за того, что случайно пытались получить ассоциированное значение у Optional, которое имеет значение not set (“не установлен”).

Если вы посмотрите на консоль, то увидите, что вам сообщают об ошибке :
fatal error: unexpectedly found nil, while unwrapping an optional. ( НЕисправимая ошибка: неожиданно обнаружено nil значение  при попытке “развернуть” (то есть достать ассоциированное значение) у Optional).
Привыкайте к этой ошибке, вы ее будете видеть постоянно.
Я уверен, что некоторые из вас, кто более консервативен,  подумают: “Восклицательный знак “!? Это — не для меня, я никогда его не буду использовать. Я не хочу, чтобы мое приложение завершалось аварийно. Это ужасно!”
Но в действительности, аварийное завершение приложения на этапе разработки — это благо, потому что помогает быстро найти ошибки, вы оказываетесь прямо в отладчике и можете посмотреть, а что же произошло?
Как, например, в нашем случае, когда предполагается, что четвертая кнопка должна быть в массиве cardButtons. То, что она там не находится — это ошибка, Если бы приложение не закончилось аварийно, то, возможно, я бы не заметил этого. В случае аварийного завершения приложения я определенно должен установить причину этого. Мое приложение закончилось аварийно именно при нажатии на четвертой кнопке, то есть там , где имеет место ошибка, и я попадаю прямо в отладчик и могу взглянуть на эту кнопку.
Не бойтесь аварийных завершений, потому что они вам помогают.
Допустим, вы хотите написать условный код таким образом, чтобы он не приводил к аварийному завершению, а вначале бы смотрел, установлено ли Optional значения, и только в случае, если Optional значение установлено, извлекал бы “ассоциированное значение”, а в противном случае — нет. Я покажу вам небольшой прием для “развертывания” Optional и получения ассоциативного значения, который сначала тестирует Optional с тем, чтобы убедиться, что оно находится в состоянии set (“установлен”).
Вместо размещения восклицательного ! знака в конце, я его удаляю, я ставлю оператор if в начале:

Вы опять видите, что Optional требует минимального синтаксиса в этом случае. Теперь мой оператор print (“ cardNumber …”) размещается внутри if предложения.  Вы даже можете добавить else, если хотите, и распечатать текст о том, что данная карта не найдена в cardButtons с тем, чтобы мы могли заметить, что это произошло.

Теперь мы выполняем получение  “ассоциированного значения” условно.
Давайте посмотрим, как это работает:

Первые три карты по-прежнему работают: cardNumber = 0, 1, 2, потому что я использовал синтаксическую конструкцию if let. Но теперь, если я кликаю на “плохой” карте, то получаю сообщение “chosen card was not in cardButtons” и приложение не заканчивается аварийно.
Вот как “разворачивается” (unwrap) Optional.
Супер-пупер ВАЖНО. На самом деле я знаю, что это совсем новое для вас.
Мы поговорим об восклицательных ! знаках, которые я упоминал раньше:

Они сконструированы так, что вам не нужно их инициализировать, потому что они также являются Optionals, но это чуть-чуть другого рода Optionals, потому что в конце у них вместо вопросительного ? знака стоит восклицательный ! знак.

Помните наш метод index (of:)?  Там у нас в описании метода стоял вопросительный ? знак:

А в @IBOutlet у нас стоят восклицательные !  знаки, которые немного другие Optional, но все равно Optional. Мы поговорим о них позже.
И последняя вещь, которую я хотел бы сделать, это “подцепить” номер карты cardNumber, который я нашел, к массиву эмоджи. Я создам переменную var и назову ее emojiChoices. Это будет массив Array строк String, которые представляют собой эмоджи. Мы создадим массив “на лету” с помощью квадратных скобок, внутрь которых поместим элементы массива :

Теперь я размещу эмоджи. Возвращаемся к выбору эмоджи из меню Edit -> Emoji & Symbols

и выбираем в разделе Frequently Used “тыкву” 🎃 и “привидение” >👻:

Дополняем пары карт:

Теперь мы получили массив эмоджи для индекса cardNumber равного 0, 1, 2, 3.
Пока мы здесь, давайте подсоединим оставшуюся четвертую карту к массиву cardButtons. Я опять использую CTRL-перетягивание от желтой кнопки вниз к четвертой карте:

Я хотел бы отметить еще одну вещь:

У нас нет необходимости в декларировании типа Array <String> массива emojiChoices, потому что мы задали значения этому массиву, и мы убираем этот тип:

Если мы option— кликнем на emojiChoices>, то увидим, что это массив строк [String]:

—— 80-ая минута лекции ———
Здесь опять работает механизм “вывода типа” из контекста (inferring).
Мы никогда не будем декларировать Array <String>, в этом никогда нет необходимости.
Теперь мы можем заменить печать номера карты вызовом метода flipCard:

Запускаем приложение:

Все работает, и мы можем гордиться собой, потому что мы создали архитектуру, управляемую данными. Это замечательно, и мы можем еще добавить карт прямо сейчас. Мы можем сделать 20 кнопок и добавить больше эмоджи в массив.
Но это не очень хорошая архитектура, потому что она очень “хрупкая”. Число эмоджи должно точно соответствовать числу кнопок на вашем UI, и вы размещаете их точно по парам, они не следуют в случайном порядке, так что карты всегда будут находиться на одних и тех же местах. Это не очень хорошое решение.
Нам действительно нужно сделать следующий шаг и создать реальный движок для игры Concentration. Этот движок нужен также для выявления совпадений и подсчета очков и т.д.
Для того, чтобы это сделать, мы должны использовать парадигму конструирования, о которой я говорил в самом начале и которая называется Model View Controller (MVC). Я начну Лекцию в Среду с некоторых слайдов, объясняющих, как работает Model View Controller (MVC). Затем мы применим Model View Controller (MVC) к нашей игре, и вы увидите, что есть лучший способ конструирования этой игры: более гибкий и легко расширяемый, когда добавление карт не будет приводить к аварийному завершению приложения.

Увидимся в Среду .
——— 82 -ая минута лекции ———
——- КОНЕЦ ЛЕКЦИИ 1 ———-

Лекция 1 CS193P Fall 2017 — Введение в iOS 11, Xcode 9 и Swift 4. Часть 2.: 2 комментария

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *