Лекция 1 CS193P Winter 2017 — Введение в iOS 10, Xcode 8 и Swift 3. Часть 2.

Лекция 1 и слайды на английском языке находятся на  iTunes название “1. Introduction to iOS 10, Xcode 8, and Swift 3.”

На сайте представлен полный неавторизованный хронометрированный конспект Лекции 1 на русском языке.

Первая часть Лекции 1 CS193P Зима 2017 — 0 — 40 минута находится здесь,
Вторая часть Лекции 1 CS193P Зима 2017 — 40 — 85 минута — находится в этом посте.

Этот же конспект можно посмотреть в PDF файле:

Лекция 1 CS193P Winter 2017 iOS 10 (1).pdf


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

———   ПРОДОЛЖЕНИЕ КОНСПЕКТА  ——————-

—————— 40-ая минута Лекции ——————
Вы впервые видите локальную переменную var digit в Swift. Ключевое слово var означает, что это локальная переменная. Ее имя — digit. Если вы хотите указать тип этой переменной, например, String, то вам нужно разместить после имени переменной тип : String точно также, как это сделано для аргумента sender.
Но в Swift мы обычно этого не делаем,

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

Это как в Java и во множестве других языков, просто объект и точка “.”, именно так вы посылаете сообщение объекту. К сожалению, Xсode помогает мне и показывает мне все методы и свойства, которые кнопка (button) может выполнять и иметь. Посмотрите на этот огромный список, я нахожусь только на Fs,  и могу прокручивать и прокручивать:

Здесь огромное множество методов. Как же всем этим можно управлять? Я хочу только получить заголовок (title) этой кнопки. Конечно, я мог бы взять документацию, начать читать ее, пытаясь найти то, что мне нужно и фактически я так и буду поступать. Но есть некоторая хитрость, которую нам предлагает Xcode. Я просто печатаю наименование того, что мне нужно, и смотрите, что будет происходить, если я просто напечатаю “title” — это не метод или свойство кнопки с именем title:

Вы видите, что Xcode показал мне все методы и свойства, начинающиеся со слова “title” ИЛИ имеющие в имени слово “title” ИЛИ даже то, что имеет в имени «t«, «it«, «le«. Xcode делает все возможное, чтобы показать вам максимально подходящее тому, что вы напечатали.
Давайте посмотрим, может быть мы найдем то, что нам надо.Что нам даст заголовок кнопки?
Как насчет title (for: UIControlState)? Выглядит подходяще и возвращает заголовок (title) для соответствующего состояния:

Однако я ничего не знаю относительно “состояния” UIControlState. Давайте продолжим поиск нужного метода. Может быть, сurrentTitle, который возвращает текущий заголовок кнопки, который в данный момент показан на экране:

Звучит в точности так, как мне нужно. Ура! Победа! Я выбираю этот метод двумя кликами.

Итак, мы посылаем сообщение currentTitle кнопке sender. Давайте узнаем побольше о сообщении currentTitle. Мы знаем, что это однострочный текст, показываемый в данный момент на кнопке. Для того, чтобы узнать больше информации о currentTitle, нажимаем клавишу option. Видите на видео в левом нижнем углу показывается, какую клавишу я нажимаю?  Когда я нажимаю эту клавишу и подвожу “мышку” к интересующему нас объекту, то появляется голубая пунктирная линия, подчеркивающая текст, со знаком “?” вопроса:

Если я кликну на слове с голубой пунктирной линией, то появится небольшое окошко с информацией:

В этом окошке у меня есть детальное описание currentTitle, я узнаю, как декларируется currentTitle. Оказывается, currentTitle — это не функция func, а свойство var. И здесь мы впервые видим, как декларируется свойство (property) в Swift. Это переменная экземпляра класса (instance variable) класса UIButton.
Понятно, что это переменная, следовательно var, имя переменной —currentTitle. Тип переменной — String?? Может быть, кнопка вообще не уверена, что за тип у нее имеет заголовок?  Я думаю, что кнопка-то знает тип своего текущего заголовка, а мы в настоящий момент можем предположить, что это строка String, а в дальнейшем увидим, если что-то не так. Далее следует маленький синтаксис { get }, означающий что мы можем только “читать” текущий заголовок кнопки с помощью свойства currentTitle. Мы не можем устанавливать (“писать”) заголовок currentTitle.
Но я бы все-таки хотел знать, а как установить заголовок кнопки? Я должен посмотреть документацию.  Как мы можем получить ее прямо сейчас? Для этого существует раздел “More” в этом маленьком справочном окошке:

Если я кликну на ссылке в этом разделе, то получу документацию для currentTitle:

Замечательно в этой документации то, что я могу кликнуть на любом объекте : String или UIButton и получить документацию по соответствующему объекту.
Кликаем на UIButton и получаем документацию по классу UIButton.

Здесь есть раздел Overview (Обзор). Он совершенно замечательный, и я настоятельно рекомендую ознакомится с этим разделом для каждого класса, который вы будете использовать — потратьте 5-10 минут для прочтения этих разделов. Тогда вы точно будете знать, как эти классы работают.
Посмотрите, например, что у нас есть для класса UIButton.
Здесь описывается как сконфигурировать внешний вид кнопки (appearance).

Этот раздел рассказывает о состояниях states кнопки. Помните? Мы рассматривали метод title (for: UIControlState)? И мы не знали, что означает состояние state. Здесь нам рассказывают об этом.
Нам рассказывают о том, из каких частей состоит кнопка. Мы можем поместить туда изображение (image) и, очевидно, текст (text). Также объясняется, что такое отступы (edge insets).

——— 45 -ая минута лекции ———

Даже есть сведения обо всем, что есть в различных инспекторах Interface Builder:

Посмотрите, все детально разъясняется. Совершенно потрясающая вещь, прочтя которую, вы будете знать все эти классы. И, конечно, есть список методов. Например, тот, который мы искали — setTitle:

Если я кликну на этом методе, то получу его полное описание.

И даже можно посмотреть, что такое состояние UIControlState и кликнуть на нем для получения:

Ну, конечно, это состояния normal (нормальное), highlighted (подсвеченная кнопка), disabled (неработоспособная кнопка), selected (выбранная кнопка) и т.д.. И в каждом из этих состояний у вас может быть свой собственный заголовок. Здорово!
Вы видите, как легко и быстро я перемещаюсь между различными типами и классами простым кликом по соответствующим ссылкам и это очень важно для того, чтобы быть профессиональным разработчиком iOS. Очень важно эффективно пользоваться документацией.
Возвращаемся к currentTitle, он имеет тип String? :

И я говорил вам, что Swift “выводит тип” (infer) из контекста. Давайте убедимся в этом и посмотрим, какой тип имеет локальная переменная digit.

Она также имеет тип String?. Конечно, потому что я установил знак равенства тому, что имеет тип String?, и компилятор знает это.
Давайте пойдем дальше и распечатаем digit.
Для печати в некоторых языках вы можете использовать следующий синтаксис:

К сожалению, вы не можете этого делать в Swift, там нет % формата и нет метода printf.
Вместо этого мы используем print, а вместо % мы используем “магические” круглые скобки с обратным слэшем \( ). Внутри круглых скобок вы можете разместить строку и все, что может быть преобразовано в строку. Тогда это будет включено в распечатываемый текст. Очевидно, сама строка может быть преобразована в строку:

Этим способом можно вставлять строки в другие строки. Или вставлять более сложные объекты, которые знают как превратить себя в строку, в другие строки.
Заметим, что у нас есть предупреждение в виде желтого маленького треугольника.

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

И нам говорят, что переменная digit никогда не изменятся (was never mutated) и советуют заменить var на let, то есть сделать переменную константой.
Более того, нам предлагают сделать эту замену автоматически. Я кликаю на выделенной в подсказке строке и замена var на let происходит автоматически:

Предупреждение исчезло. Оно говорило о том, что digit никогда не изменятся, теперь digit действительно стала константой. Мы дали этой константе начальное значение и больше она не изменяется.
Когда вы декларируете константу, вам всегда нужно использовать let.
Почему нам нужно использовать другое ключевое слово для константы, отличное от var? Потому что константа — это не переменная, она не изменяется, это действительно константа. Ключевое слово let — прекрасное слово для читаемости кода: пусть digit равняется текущему заголовку sender. Прекрасно читается. Почему мы так беспокоимся о том, чтобы различать константы и переменные? Этому две причины. Одна заключается в том, что, если вы читаете чей-то код и видит ключевое слово let, то сразу понимаете, что это константа и нигде далее в коде она не будет изменена. И если кто-то попытается изменить ее, то компилятор генерирует ошибку. Таким образом, вы не сможете изменить что-то, что является константой.
Но другая более важная причина состоит в том, что вы сообщаете Swift, что это — константа, что вы намереваетесь ее использовать как константу. И позже, если вы попытаетесь модифицировать ее, даже если это массив Array или словарь Dictionary, то есть пытаетесь что-то разместить в массиве Array или добавить что-то в словарь Dictionary, то Swift будет знать о ваших намерениях использовать их как константы и выдаст ошибку.
Это способ сказать Swift о ваших намерениях. Разница между неизменяемым (immutable) массивом Array и массивом Array, в который вы можете добавлять элементы, — огромна!
Потому что массивы Array и словари Dictionary передаются в Swift путем копирования.Это очень необычный способ передачи по сравнению с другими языками программирования, в которых объекты “сидят” в “куче” (heap) и мы передаем только указатель (pointer) на них.

В Swift каждый раз при передачи Array и Dictionary некоторой функции, мы их копируем, это очень неэффективно, если они все — изменяемые. Потому что мы действительно должны их физически копировать, если кто-то их изменил. Но Swift знает, какие из них изменяемые (mutable), а какие — нет. Когда вы передаете неизменяемые (immutable) Array и Dictionary, то не происходит физического неэффективного копирования. Фактически, до тех пор, пока вы не присвоите Array и Dictionary изменяемой (mutable) переменной явно записав var … = Array / Dictionary, у Swift нет причин беспокоиться на этот счет. Он выполняет “копирование при записи” (copy on write) только, когда происходят реальные изменения Array и Dictionary.
Итак, это что касается var / let, привыкайте к тому, чтобы всегда использовать let для констант.

——— 50 -ая минута лекции ———

Давайте запустим наше приложение и посмотрим, что будет происходить. Это должно работать. Мы получаем текущий заголовок кнопки, на которую нажимаем. Нажимаем “7”, “8”, “2”:

Похоже, что работает и каждая кнопка знает свой заголовок. Но что такое Optional (“7”)? Это о чем?
Это потому что digit — это не String, а String?.

Отличается ли тип String? от обычного String, вот в чем вопрос. Тип String? называется Optional типом. Это супер ВАЖНО!
Опять, те, у кого сейчас дневной сон, проснитесь! Это действительно супер ВАЖНО!
Очень небольшое количество языков программирования имеет эту концепцию. Это действительно крутая концепция, которую обязательно нужно понять, ибо она встречается повсюду во всех iOS APIs. Но требуется некоторое время, чтобы к ней привыкнуть.
Что собой представляет Optional ?
Optional — это просто тип. Этот тип может иметь только два значения: set (“установлен”) и not set (“не установлен”). Только два значения и больше никаких других. Тем не менее, если Optional находится в состоянии set (“установлен”) , то оно может иметь ассоциированное значение (associated value). Это значение, которое держится в стороне, но при создании Optional и при декларировании Optional вы должны определить тип этого ассоциированного значения. В нашем случае ассоциированным значением является строка, ее тип String. Мы говорим о текущем заголовке кнопки. Поэтому, типом digit будет Optional строка или String?. Это означает Optional, у которого ассоциированное значение в состоянии set (“установлен”) имеет тип String.
Все это хорошо, но нам нужно как-то получить это ассоциированное значение (associated value). Дайте мне ассоциированное значение, которое и является заголовком нашей кнопки.
Как я могу “вытянуть” его из Optional ?
Ответ заключается в том, что мы должны использовать восклицательный знак “!”:

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

Это String. Swift умеет “выводить тип” из контекста, так что “развернув” (unwrapped) Optional в правой части, мы получаем String в левой части выражения.
Что будет, если мы применим восклицательный знак “!” к Optional, которое находится в состоянии not set (“не установлен”)? В этом состоянии у Optional нет ассоциированного значения. Что произойдет с моим приложением? Кто-нибудь попытается угадать?
Правильно, ваше приложение закончится аварийно. Я уверен, что некоторые из вас, кто более консервативен,  подумают: “Восклицательный знак “!” ? Это — не для меня, я никогда его не буду использовать. Я не хочу, чтобы мое приложение заканчивалось аварийно. Это ужасно!”
Но в действительности, аварийное завершение приложения на этапе разработки — это благо, потому что помогает быстро найти ошибки, вы оказываетесь прямо в отладчике и можете посмотреть, а что же произошло?
В нашем случае, когда мы говорим о заголовке (title), это означает, что заголовок на кнопке никогда не устанавливался, а метод touchDigit вызывается, что никогда не должно происходить. Если это произойдет в приложении, которое я поставил своим пользователям, то они начнут жаловаться : “Это кнопка, на которой нет заголовка, я не знаю на что я нажимаю, когда использую такую кнопку.” Вы должны “отловить” такую ситуацию во время разработки приложения. Так что иногда аварийное завершение приложения — это хорошо. Но, конечно, я не хочу, чтобы каждый раз при “развертывании” Optional мое приложение аварийно завершалось, и я покажу вам небольшой прием для “развертывания” Optional и получения ассоциативного значения, который сначала тестирует Optional с тем, чтобы убедиться, что оно находится в состоянии set (“установлен”).
Но пока мы будем использовать восклицательный знак “!”.
Давайте запустим приложение и посмотрим, на что это похоже. Оно должно работать. Но сейчас мы “развернули”  Optional и получили ассоциативное значение. Мы знаем, что текущий заголовок currentTitle должен быть установлен для каждой кнопки и мы не будем беспокоиться об аварийном завершении приложения. Нажимаем “7”, “9”, ”3”, “5”, “6”.

Теперь мы можем коллекционировать цифры, полученные от пользователя.
Давайте разместим их на дисплее. Нам нужно добавить дисплей на наш Калькулятор.
Мы возвращаем Область Утилит на экран и смотрим внимательно на Палитру Объектов с тем, чтобы выбрать что-то для дисплея. Будьте внимательны и не захватите текстовое поле Text Field, потому что это редактируемый текст, а в Калькуляторе мы не можем кликать на дисплее и редактировать его. Мы печатаем цифры на только что созданной цифровой клавиатуре с тем, чтобы их показывать на дисплее. Мы выберем метку Label.

Между прочим, если вы кликаете на любом объекте в Палитре Объектов и оставляете на нем “мышку” в течение секунды, то получаете детальное объяснение функциональных возможностей объекта.

Я выбираю метку Label и перетягиваю ее на самый верх моего UI.

И делаю с ней то же самое, что и с первой кнопкой — изменяю размер, делаю больше шрифт, хочу чтобы на дисплее был в самом начале:

Текст на дисплее должен быть выравниваться вправо:

——— 55 -ая минута лекции ———

Может быть изменим цвета — сделаем цвет фона голубым:

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

Наш дисплей выглядит вполне достойно на данный момент. Теперь нам надо сделать так, чтобы нажимая на кнопки, мы “разговаривали” с дисплеем и сообщали ему цифры, которые нажимает пользователь.
Как мы можем это сделать? Нам нужна связь между дисплеем и нашим кодом, но это связь другого типа, потому что мы не нажимаем на дисплее, как на кнопке, вследствие чего вызывается метод. Нам необходима переменная экземпляра или свойство (property), которое бы указывало на объект UI, и мы могли бы с ним “говорить” когда захотим, потому что нам нужно размещать на нем цифры. Механизм создания связи будет тот же. Мы нажимаем клавишу CTRL и тянем в направлении от метки в код:

Отпускаем CTRL, тут же появляется диалоговое окошко, в котором на этот раз мы используем Outlet. Outlet означает свойство, которое указывает на элемент UI.

Мы называем наш Outlet display. Это наш дисплей. У меня уже есть тип — UILabel. В поле Storageweak и strong, но пока не обращайте на это внимание. Я буду рассказывать об этом на следующей неделе:

Нажимаем “Connect” и получаем нашу первую переменную экземпляра класса в нашем классе. Здорово!

И опять у нас есть некоторая часть @IBOutlet, которая принадлежит Xcode, кроме этого он размещает еще маленький кружочек здесь же.  Если вы наведете мышку на этот кружочек, то увидите к чему на UI подсоединен этот @IBOutlet.

Ключевое слово weak мы игнорируем, а дальше идет декларирование свойства (property):

Конечно, оно начинается с var или может быть также let. Если вы хотите, чтобы ваша переменная экземпляра класса (instance variable) только устанавливалась в самом начале и потом никогда бы не изменялась, вы можете использовать let. Но это бывает крайне редко, хотя вы имеете право это делать. Обычно используется var. Имя переменной экземпляра класса — display.
«: UILabel!» — это тип. Можно догадаться, что здесь что-то делается с Optionals.
Поначалу присутствие восклицательного ! знака может сбить вас с толку, ведь в нормальной ситуации он означает “развертывание” Optional. Очевидно, что мы не можем здесь что-то “разворачивать”, мы здесь просто декларируем свойство. Здесь восклицательный ! знак — это в точности то же самое, что и вопросительный ? знак. Фактически я сейчас заменю восклицательный ! знак на  вопросительный ? знак, а позже мы поменяем его обратно на восклицательный ! знак, и вы увидите в чем разница.

Он означает тот же самый Optional, что и вопросительный ? знак.
Но тип свойства display — это Optional<UILabel>.
Почему это Optional<UILabel>, а не просто UILabel? Почему вообще нужно, чтобы Outlet display был Optional? Потому что, когда ваш UI появляется в самом начале, iOS необходимо несколько наносекунд, чтобы “подцепить” все для вас. Поэтому в тот момент, когда UI появился впервые, Outlets не установлены, они являются Optional с состоянием not set (“не установлено”), затем UI “подцепляется” для вас, и после этого они являются установленными навсегда. И это очень важно, что они устанавливаются навсегда (forever), и мы увидим, что дальше они имеют дело с восклицательным ! знаком, от которого я только что избавился. Но сейчас для лучшего понимания мы будем полагать, что display — это просто Optional, которое нам необходимо “разворачивать” (unwrap) всякий раз, когда мы его используем, это просто.
Теперь вместо печати на консоли цифр, давайте разместим их на дисплее display, и каждый раз при нажатии цифры, мы будем добавлять цифру в конец дисплея. Например, если у нас на дисплее — 56 и мы хотим добавить (append) 2, то мы получим 562, если еще добавляем 8, то получаем 5268. Мы хотим постоянно добавлять цифры в конец.
Нам нужен текст, который в данный момент находится на дисплее, чтобы добавить к нему цифру. Поэтому мне нужна еще одна локальная переменная, которая также будет константой с именем textcurrentlyInDisplay и я получу ее, посылая сообщение display, который я должен “развернуть” (unwrap) с помощью восклицательного ! знака,  а потом поставить точку “.”:

Здесь представлены все сообщения, на которые реагирует метка display!, их сотни. Я применяю ту же самую хитрость, что и прежде. То есть просто напечатаю слово “text”, потому что я хочу извлечь текст из метки:

Смотрите! Первая же строка дает нам текст, который показывает метка. Это Optional <String>String?.
Прекрасно — я ее беру. Теперь у меня есть text, но он —Optional, поэтому я “разворачиваю” его с помощью восклицательного ! знака:

Итак, у меня есть textCurrentlyInDisplay. Если я option-кликну на ней, то мы увидим, что это String.

Теперь я могу просто установить текст на метке display:

Я “разворачиваю” метку display>, и беру его свойство text, которое устанавливаю в текст, который в данный момент показывает display, плюс цифра digit.
Заметьте, что когда я устанавливаю Optional равно чему-то, а свойство text для метки является Optional, то вам необходимости его “разворачивать” его и ставить  восклицательного ! знака вслед за text. Мы устанавливаем Optional, и компилятор знает, что Optional должно иметь ассоциированное значение типа String, которое и связывается с этим Optional свойством.

——— 60 -ая минута лекции ———

Кроме того, заметьте, что свойства text вслед за типом String? Приводится небольшой синтаксис {get set}, который означает возможность и чтения, и установки этого свойства в отличие текущего заголовка кнопки currentTitle, который можно только читать {get}, и нельзя устанавливать:

Давайте запустим приложение и посмотрим, как это работает. Наш UI выглядит прекрасно, в том числе и дисплей. Давайте попробуем нажимать кнопки “8”, “6” и т. д.

Прекрасно, действительно добавляет наши цифры на дисплей, но впереди остается 0, что, конечно, неправильно. Нуль не является частью того, что я печатаю на моей цифровой клавиатуре.
Нуль появился потому, что он находился на дисплее с самого начала. Нуль не должен располагаться впереди всех цифр и это очень простая проблема. Она возникла потому, что мы не научили наш Калькулятор определять, когда пользователь находится в середине ввода числа, а когда он только что начал ввод. А кроме того, на дисплее может появится результат  операции. Очевидно, что, когда мы печатаем, мы не хотим, чтобы нам что-то добавлялось со стороны — спасибо, не надо.
Поэтому мы должны научить наш “Умный Калькулятор” различать ситуацию, когда пользователь находится в середине печати, а когда — нет. И мы сделаем это с помощью еще одного свойства с именем userIsInTheMiddleOfTyping

Теперь, что касается такого длинного имени. Возможно, мы могли бы назвать переменную typing.
Вы знаете, что нужен компромисс между краткостью и понятностью. Краткость очень приветствуется, но понятность — более важна. Может быть достаточно было бы isTyping. Я все-таки предпочитаю взять сторону понятности. Так что я использовал это длинное имя. Другая причина, по которой я взял такое длинное имя заключается в том, что, напечатав это длинное имя один раз, мне больше ни разу не придется еще раз его печатать, потому что Xcode всегда будет дополнять то, что я начал печатать и помогать мне.
Теперь, когда я добавил такую прекрасную переменную var, она имеет тип Bool, а Bool, между прочим, — это то, что может true или false, я получил ошибку. Причем ошибку в строке, в которой я вообще ничего не делал:

Нам говорят, что у класса ViewController нет инициализаторов. Что за ерунда? Я ничего не делал ни с каким var?
Дело вот в чем. В Swift все свойства (properties) должны быть инициализированы. Все до одного, никаких исключений. Есть два способа инициализировать ваши свойства в классе class или структуре struct.
Один способ — с помощью инициализаторов, это такой специальный метод с именем init (I-N-I-T). У него может быть любое количество аргументов, которые требуются классу, но внутри реализации инициализатора необходимо инициализировать все неинициализированные свойства (properties). Сегодня мы не будем говорить об инициализаторах. Я расскажу о них немного в следующую Среду. Так что мы не будем использовать инициализатор, потому что есть второй способ инициализации, который состоит просто в том, чтобы дать свойству значение (value). В нашем случае это false. Очевидно, что при старте приложения, пользователь не находится в середине ввода числа, он находится в начале, поэтому false.

Это избавило нас от ошибки.
Фактически, нам не нужно указывать тип : Bool. Понимаете почему? Потому что у нас есть значение false, которое может принадлежать только Bool. Таким образом, Swift может “выводить из контекста” (infer), что это должно быть Bool.

И опять, если мы задаем свойствам значения, то их тип можно не указывать — он будет “выводится” из контекста.
А как насчет display?

У этого свойства нет инициализатора. Почему же компилятор “не жалуется”? Как же так? Ведь это свойство не получает никакого значения, следовательно должен быть инициализатор?
Все из-за того, что оно — Optional. А с Optionals мы будем обращаться особым образом. Они всегда инициализируются автоматически со значением nil,  а nil означает not set “не установлено”.

В Swift nil имеет только этот смысл — оно означает, что Optional имеет значение not set “не установлено”.
Таким образом Optional автоматически, всегда получает значение при декларировании. Поэтому имеет смысл определять свойство как Optional, потому что его не нужно устанавливать в самом начале, оно может устанавливаться только тогда, когда вы этого захотите.
Вы можете установить UILabel во что-то конкретное с самого начала, это также возможно:

Но если вы не сделаете этого, то оно получит значение not set “не установлено”. Мы выберем именно этот вариант :

Итак, у нас есть локальная переменная userIsInTheMiddleOfTyping и мы можем ее использовать в нашем коде. Если пользователь в середине печати числа, то мы используем весь этот код в методе touchDigit, а если нет, то мы просто устанавливаем display!.text равным введенной цифре digit, потому что мы только начали ввод нового числа и, естественно, мы переходим в состояние, когда мы в середине печати числа:

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

Начинаем набирать цифры “5”, “6”, “9”, “8” и т.д.. Да, проблема лидирующего нуля решена. Если же мы находимся в середине набора числа, то цифры добавляются. Так что все в порядке.
Следующим шагом в разработке Калькулятора разместим кнопки для некоторых операций. Мы уже умеем вводить числа, теперь займемся операциями.
Сейчас я сделаю очень плохую вещь — скопирую кнопку “7” и просто напечатаю там простейшую операцию “π”, смысл которой заключается в размещении значения “π” на дисплее. Через секунду вы поймете, почему я поступил плохо.

Я буду использовать CTRL -перетягивание в код, чтобы “подвязать” метод к кнопке с операцией “π”.

Это будет Action, а не Outlet. Это будет  похоже на touchDigit. Я назову наш новый метод performOperation, потому что это именно то, что он будет делать: он будет выполнять операции.

——— 65 -ая минута лекции ———

Я должен убедиться, что переключил в поле Type тип на UIButton, не забудьте это сделать в вашей Домашней работе. И я нажимаю на кнопку “Connect”.

Теперь у меня есть новый метод, который “подцеплен” к кнопке “π”, это здорово. Что я буду делать в этом методе? То же самое, что и в touchDigit: я буду спрашивать кнопку, какую операцию она выполняет. Я буду использовать локальную переменную mathematicalSymbol, потому что на этих кнопках будут символы математических операций:

Прекрасно, теперь я получил “π”, но я хочу показать вам, как и обещал, как сделать “развертку” Optional так, чтобы приложение не заканчивалось аварийно. Давайте решим, что, если у нас пустая кнопка, то есть кнопка, у которой не установлен заголовок, мы не будем делать ничего, не будем выполнять никаких операций, как будто бы на эту кнопку никто не нажимал.
Теперь посмотрите насколько важны Optional и какой мощный синтаксис имеется в Swift для этого типа: вопросительный знак ?, восклицательный знак !. Вам почти ничего не придется печатать при использовании. То, что я покажу относится к этому же разряду. Если вы хотите проверить Optional прежде, чем его “развернуть”, то вместо восклицательного ! знака в конце нужно разместить два символа вначале. Эти символы — if.

Теперь прочтите это на английском языке: “If I can let mathematical symbol equal the sender’s currentTitle, then” (Если допустить, что математический символ равен currentTitle отправителя, то …).
Таким образом, если я смогу “развернуть” Optional и получить его ассоциированное значение, то я смогу разместить какой угодно код внутри этого if, при этом mathematicalSymbol будет строкой String, ассоциированным значением, то есть “развернутым” (unwrapped) Optional.
За пределами фигурных скобок mathematicalSymbol даже не определен и не имеет значения.
Внутри фигурных скобок я мог бы написать:

То есть бесконечные if then else, if then else, if then else, что является реально плохим кодом. Поэтому я буду использовать другой подход, другое маленькое выражение. Я буду использовать оператор switch. Множество языков программирования имеют оператор switch. Я буду переключаться по математическому символу mathematicalSymbol. Не все языки могут переключаться по строке. Некоторые могут и Swift в их числе.
Я могу написать :

В случае, если mathematicalSymbol равен  “π”, тогда я хочу присвоить тексту дисплея значение, эквивалентное  “π”, например, “3.1415926”. Совершенно ужасно печатать его как строку, но я вернусь сюда через секунду. Но это именно то, что я хочу сделать — я хочу установить display в  “π”. Я получил ошибку. Что это за ошибка?

Эта ошибка сообщает нам, что оператор switch должен быть исчерпывающим (exhaustive), может быть стоит добавить предложение default. И это правда. В операторе switch вы должны представить каждый возможный случай (case). До тех пока пока мы не пройдем несколько следующих лекций, я предполагаю, что мы должны сделать что-то в следующем духе, пытаясь напечатать случаи для всевозможных строк:

Но, к счастью, у нас есть случай (case) default, который означает “все остальные случаи”. Для этого обобщенного случая я использую команду break для выхода из switch.

Заметьте, что отступы в коде для разных случаев несколько не согласованы. Должны быть одинаковые отступы для различных case, включая default. Реально крутая возможность состоит в выборе всего текста или даже целого файла и применении сочетания Ctrl + I для выравнивания отступов, это заново установит отступы абсолютно для всего. И я рекомендую это делать для всех ваших файлов с исходным кодом в вашей Домашней работе. Просто выделите все (Select All) и Ctrl + I:

Итак, мы получили операцию “π”, давайте посмотрим, как это работает. Цифры работают, затем нажимаем кнопку с операцией “π”:

Что-то странное. Я получил значение “π”, которое я напечатал непосредственно в коде, но почему добавился символ операции “π”? Это кажется странным.
Давайте попробуем новое число. О! Не может быть! Когда я набираю новое число, оно добавляется в конец старого. Может потому, что здесь “π” присутствует?
Смотрите, что будет происходить дальше. Я продолжаю печатать цифры и у меня появляется многоточие и дисплей перестает воспринимать цифры.
Полная неразбериха.

У нас с вами 3 больших проблемы. Это наличие “π” на дисплее. Второй факт, что цифры добавляются в конец, даже если печатается новое число. И третья проблема — многоточие.
Как мы будет все их исправлять?
Давайте решим сразу все три проблемы и реально быстро.
Вначале проблема с “π” на дисплее в конце.
Давайте посмотрим, где я устанавливаю display!.text? Я делаю это в трех местах, и единственное место, где происходит добавление — это метод touchDigit. Может быть кнопка с заголовком “π” подцепилась к этому методу?

——— 70 -ая минута лекции ———

Видите кнопку “π”? Она действительно “подцеплена” к методу touchDigit и выполняет его. Но она выполняет и метод performOperation:

Похоже она выполняет оба метода.
Вначале она выполняет метод performOperation и размещает 3.1415926 на экране, а затем выполняет touchDigit и добавляет digit, которым в нашем случае является символом “π”. Это очень плохо. Как мы можем это исправить?
ВОПРОС: Как вы установили порядок выполнения методов performOperation и touchDigit?
ОТВЕТ: Порядок не определен. Из моего опыта, это обычно алфавитный порядок, но официально он не определен.Вам не следует опираться на какой-то порядок. Но да, performOperation выполняется перед touchDigit. Я не знаю почему. Но у вас никогда не будет “подцеплено” сразу два метода. Здесь это очевидная ошибка. И именно она вызывает все проблемы.
Как нам ее исправить?
У нас есть другой способ узнать, что подсоединено к кнопке  “π”, отличный от использования маленького кружочка. Он заключается в том, что если вы кликните правую кнопку мыши (или удерживая CTRL, просто кликнуть на мышке) на чем-нибудь из вашего UI (например, на кнопке  “π”), то вы увидите все связи этого элемента UI.

Здесь представлены все переменные экземпляра класса, все методы и т.д.
Вы можете видеть, какие методы подсоединены к  “π” для события Touch Up Inside, которое означает касание пальцем внутренней части кнопки и подъем пальца вверх. Такое событие посылает нам два сообщения : touchDigit: и perfomOperation:, но нам определенно не нужен метод touchDigit: для  “π”.
Каким образом метод touchDigit: “привязался” к “π”? Потому что я скопировал и вставил кнопку “7”. Помните? Я говорил вам, что это плохая идея? Так вот именно поэтому она и плохая, сохраняет все “привязки”.
Давайте отсоединим метод touchDigit:“ от кнопки “π”.  Для этого достаточно нажать на маленький крестик слева вверху touchDigit: .

Теперь связь touchDigit: ушла и осталась только performOperation:.

Эту проблему, связанную с размещение символа “π”, мы решили. Но другая проблема пока осталась.
Как насчет того, что я могу печатать дополнительные числа в конце “π”?
Здесь также простое решение.
Когда мы разрешаем добавлять цифры? Когда пользователь находится в середине ввода числа. Как только я нажал “π”, я нахожусь в середине ввода числа? Нет, я просто нажал “π”, я не ввожу число. Фактически, каждый раз, когда мы вызываем метод performOperation, пользователь уходит из режима ввода числа и  локальная переменная userIsInTheMiddleOfTyping равна false. Мы больше не находимся в середине ввода числа:

Что будет показывать display? Он будет показывать результат операции. Так что мы исправили и вторую ошибку.
А как насчет многоточия и усечения чисел?
Это очень умное исправление. В Инспекторе Атрибутов для нашего дисплея display, который является меткой UILabel, есть замечательная «фишка» с именем Autoshrink, благодаря которой метка display будет автоматически «сжиматься», например, не менее, чем до 9 point.

Это решит нашу проблему:

Теперь, когда мы получаем в display слишком много цифр, наш  display сжимается вместо показа многоточия. Возможно, это не самое хорошее решение нашей проблемы, лучше было бы иметь Калькулятор, который показывает только определенное число цифр после десятичной точки. Это действительно было бы лучше, и это будет вашим дополнительным пунктом в вашем Домашнем задании. Я желаю вам удачи в этом.
Итак, мы сделали исправления по всем трем проблемам, давайте посмотрим, что у нас получилось.
Набираем число и нажимаем “π”. На display мы видим только значение “π”, самого математического символа нет, он не добавляется в конец числа. Начинаем набор нового числа после ввода “π”, оно не добавляется в конец старого набора. Еще продолжаем набирать цифры и видим, что число на display сжимается, но мы не теряем введенные цифры.

Да, возможно, не лучшее решение, но я хотел показать вам Autoshrink (автосжатие).
Что следующее?
Мы с вами займемся маленькой проблемой, связанной с заданием значения “π” в методе performOperation:

Это выглядит ужасно и я хочу показать вам действительно крутую возможность в Swift, Double.pi, что является числом двойной точности с плавающей точкой равным значению “π”.

Но, конечно, я не могу вот так вот взять и присвоить display!.text значение Double.pi, потому что:

Нам говорят, что мы не можем присваивать тип DoubleOptional строке String? Потому что единственное значение, которое мы может присвоить Optional строке помимо nil, должно иметь тип String, то есть мы таким образом устанавливаем ассоциированное значение.
Кто-нибудь может сказать мне, учитывая все сказанное на лекции, как мне преобразовать Double.pi в строку? Нет смелых?
Точно, обратный слэш и круглые скобки, это такая маленькая хитрость:

Помещаем Double.pi  внутрь круглых скобок. Doubles могут преобразовываться в String,  Победа!

На самом деле это все-таки некрасиво и выглядит уродливо, потому что представленный выше механизм означает вставку объектов в строку, это не естественный способ преобразования Doubles могут преобразовываться в String. Более естественный способ заключается в создании новой строки, вы уже видели синтаксис для создания нового объекта — новой структуры struct или нового класса class. Это имя класса или структуры и круглые скобки. Внутри этих круглых скобок помещается что-то, что класс может взять и превратить в один из своих экземпляров.

Класс может взять что-то в качестве аргументов. Помните? Я упоминал инициализаторы? Это аргументы для инициализатора, у вас может быть множество инициализаторов. У String их целая куча. Одним из них является инициализатор, который берет в качестве аргумента Double.

Так что этот способ будет естественным способом преобразования Double в String, он выглядит значительно эстетичнее в коде и более понятно, что же мы делаем.
Давайте добавим еще одну операцию. Я хочу добавить операцию взятия квадратного корня. Копирую кнопку  “<π” и заменяю символ “π” на символ “”  для квадратного корня, который я заимствую из меню Edit/Emoji & Symbols:

В полученном диалоговом окне вы можете осуществлять поиск, в нашем случае по тексту “s q u a r e    r o o t”:

——— 75 -ая минута лекции ———

Помещаем на кнопку и копируем для использования в коде:

Убедимся, что эта кнопка подсоединена к performOperation:

и не подсоединена к touchDigit:

Все прекрасно. Нам нужно добавить case для квадратного корня в performOperation:

Мы хотим, чтобы на дисплее display!.text показывалась величина корня квадратного от чего-то. От какого значения мы хотим взять корень квадратный? Мы хотим взять корень квадратный от того, что уже находится в настоящий момент на дисплее display!.text.

Это будет работать? Нет, потому что display!.text — это строка String, а мы не можем извлекать квадратный корень из строки. И не только, функция sqrt вернет нам Double и мы не сможем разместить Double в строке String, нам придется делать то, что мы делали только что — превратить Double в строку String:

Но аргументом функция sqrt все еще является String. Я вытащу выражение для аргументом функция sqrt в локальную переменную operand, которая пока все еще является String, а очень хочет быть Double:

Как мы сможем преобразовать operand в Double? Я думаю тем же путем, что и мы делали обратное преобразование:

Будет ли это работать? Ответ — ДА. ДА? А если строкой будет “Hello”? Мы не сможем преобразовать эту строку в Double. Этот инициализатор Double, который берет строку String в качестве аргумента, возвращает Optional число с двойной точностью — Double?, потому что если вы даете ему строку, которую он не может преобразовать в Double, то он возвращает состояние not set (“ не установлен”). Он вам сообщает, что не может этого сделать.

Если мы посмотрим на тип переменной operand, то она окажется Double?.

А почему, когда мы преобразовывали Double в String нам возвращалась не Optional строку? String нет необходимости возвращать Optional строку String?, потому что она всегда может преобразовать Double в String, но не всегда можно преобразовать строку String в Double.
Я собираюсь принудительно “развернуть”  Double? — в конце ставлю восклицательный “!” знак, предполагая, что на моем дисплее display никогда не появится того, что нельзя будет преобразовать в Double. Может быть это плохое предположение, но на данный момент я буду предполагать именно это.

И опять используем CTRL + I, чтобы выровнять все отступы.
Теперь все в порядке. Переменная operand имеет тип Double, и я могу рассчитать квадратный корень.

Я преобразую результат в String.
Очень беспорядочный код, я собираюсь его исправить, но концептуально, надеюсь, вы поняли, что здесь происходит.
Запускаем приложение и извлекаем квадратный корень из 81, потом еще раз квадратный корень .

Еще раз корень квадратный, теперь “π” и снова ““ теперь уже из “π”. Все работает идеально.
Но наш код очень беспорядочный. Представьте, что вам нужно добавить case для еще одной операции и еще одной операции  и т.д. И будем делать< Double (это), String (то), принудительно “разворачивать” с помощью восклицательного ! знака почти всюду?
Слишком много восклицательных ! знаков — это первое.
Второе — я устал от всех этих преобразований String в Double и Double в String. Я собираюсь исправить все это.
Давайте начнем с восклицательного ! знака. Он появляется всякий раз, как я использую display. Я использую это сочетание display! по всему коду:

Хотя я знаю, что display, как только установится iOS в самом начале, уже никогда не вернется в состояние not set (“не установлен”), он ВСЕГДА будет находится в состоянии set (“установлен”).
Давайте вернемся к вопросительному ? знаку при декларировании свойства display:

Так вот что означает восклицательный ! знак при декларировании Optional. Он означает: “Да, это Optional, то же, что и означает вопросительный ? знак, но везде, где вы будете использовать это свойство, я (восклицательный ! знак) буду автоматически его “разворачивать”.

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

Но если display находится в состоянии not set (“не установлен”), то приложение все равно закончится аварийно, потому что оно является НЕЯВНО “развернутым” (implicitly unwrapping<). Именно поэтому тип UILabel! называется “неявно развернутым Optional” (implicitly unwrapped  Optional). Уловили фразу?  Неявно развернутое Optional (implicitly unwrapped  Optional).

Это означает, что мы повсюду в коде можем изменить display! с восклицательным знаком на display без восклицательного знака:

Это сделало наш код более понятным.
Но все еще мы вынуждены “разворачивать” text. Уж извините. Мы вынуждены делать это, так как text не является “неявно развернутым Optional” (implicitly unwrapped  Optional).
А как насчет Double и String?
Представьте себе, что у вас есть переменная var с именем displayValue и она — Double. Эта переменная отслеживает все, что появляется на дисплее как Double:

Потому что все, что у нас есть на дисплее, мы получаем как String, и устанавливать можем только как String. Было бы здорово, если бы у меня была переменная var, которая возвращала бы мне то, что находится на дисплее, но только как Double. Потому что я постоянно реально нуждаюсь в содержимом дисплея в форме Double, и мне также необходимо устанавливать на дисплее значения Double.
Как я могу это делать? У Swift есть реально крутая “фишка”, которая называется вычисляемые свойства (computed properties). Все, что мне нужно сделать для этого — разместить код после декларирования свойства и тогда мы сможем вычислить значение вместо того, чтобы хранить его:

——— 80 -ая минута лекции ———

Например, свойство userIsInTheMiddleOfTyping — это хранимое свойство, у него где-то есть хранилище. А свойство displayValue — вычисляемое свойство, у которого вы можете вычислять как получение значения — {get} случай, так и установку значения — {set}случай:

У вас может быть некоторый код как для получения значения, так и для его установки. У нас нет необходимости где-то хранить значение displayValue. Мы собираемся получать его и устанавливать. Откуда мы будем брать значения для displayValue и что будем использовать для его установки?
Из текста text метки
display.
Как будет выглядеть {get}?

Мы просто возвращает Double текста text на дисплее display, вам не нужно “разворачивать” display, но вам придется “разворачивать” text и я должен принудительно “развернуть” все выражение —  Double (display.text!)!.
Повторюсь, я предполагаю, что всегда строку на дисплее display можно интерпретировать как Double. Может быть, по ходу дела я изменю это предположение, но сейчас я буду придерживаться этого предположения.
Теперь рассмотрим случай {set}?
В случае {set} я установлю текст на дисплее display, String эквивалент тому значению, которое люди пытаются установить для displayValue.
В коде это будет выглядеть так:

Допустим где-то в коде мы написали displayValue = 5. На дисплей display мы должны вывести превращенную в строку цифру “5”. Внутри String () для случая {set} вычисляемого свойства (computed property) существует специальная переменная с именем newValue, причем newValue всегда имеет тип устанавливаемого значения, потому что это правая часть равенства (например, displayValue = 5)при установке:

Здорово. Теперь, когда у нас есть переменная displayValue и она всегда отслеживает то, что находится на дисплее display, мы можем существенно улучшить наш код:

Мы избавились от беспорядочного кода, и неожиданно стал очень компактным. Это минимум того, что вы можете напечатать, чтобы сказать, что вы хотите показать значение “π” на дисплее display.
Когда я получаю displayValue, то выполняется код в {get}. Когда я устанавливаю displayValue, то выполняется код в {set}. Это называется вычисляемое свойство (computed property). Вы увидите его повсюду. Вы уже видели его. Например, currentTitle — это вычисляемое свойство (computed property), оно вычисляется классом UIButton:

Классом UIButton определяет текущий заголовок на кнопке и возвращает его. Это реализовано некоторым кодом, который и возвращает нужное значение. Как я это знаю? Потому что это read-only переменная var, которая использует вычисляемое свойство.
Давайте убедимся, что мы ничего не сломали с нашим прекрасным лаконичным кодом.

Набираю 89 — выглядит прекрасно, квадратный корень , “π”.
Все работает. Много материала для изучения, я знаю. Давайте все это проработаем.
В Среду вначале я расскажу об MVC, парадигме конструирования, а затем добавлю MVC в наш Калькулятор, кроме того, мы сделаем так, чтобы UI нашего Калькулятора работал на всех устройствах. Пока.

——— 84 -ая минута лекции ———

——- Конец лекции 1 ———-

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

  1. Прекрасная работа переводчика! Спасибо!
    Небольшая опечатка, наверное, в 1 части 1-й лекции:
    «Могут быть красные пометки, в этом случае ваше приложение даже не будет компилироваться [приложение].»

  2. Ребята, коллеги!!! Это прекрасно!!! Огромное спасибо тем кто это сделал!!! Исключительная работа!!!

  3. Большое спасибо Вам за труд! Изучаю лекции по по Вашим переводам)

Обсуждение закрыто.