Лекция 3 CS193P Winter 2015 — Применяем MVC. (часть 2)

STANFORD UNIVERSITY: Разработка iOS 8 приложений с Swift CS193P

 Лекция 3: Применяем MVC

Профессор Пол Хэгарти (Paul Hegarty)

Лекцию на английском языке и слайды можно найти на  iTunes название  “3. Applying MVC”.

Это — продолжение: 2 — ая часть лекции (36 минута — конец ), код для Swift 1.2 находится на  Github. Код для Swift 2.0 находится на Github.

Начало: 1 — ая часть лекции (0 — 36 минут) можно посмотреть здесь.

—————————— 36 минута лекции ———————————

Screen Shot 2015-02-07 at 7.58.59 AM

Давайте займемся написанием кода для нашей вспомогательной функции

func evaluate(ops: [Op])  -> (result: Double?, remainingOps: [Op])

И первое, что я сделаю в этой рекурсивной функции, — напишу строку кода для случая невозможности выполнения рекурсии с возвращением nil.

return (nil, ops)

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


Далее мне надо убедиться, что в стеке  ops что-то есть

if !ops.isEmpty {

}
Если вы дадите мне пустой стек ops , я ничего не смогу сделать и верну  nil в качестве результата.
Теперь я могу взять первый элемент из стека ( в нашем примере это «х«)

Screen Shot 2015-02-07 at 4.16.40 PMЯ использую метод массива  removeLast(). Этот метод убирает последний элемент из массива и возвращает его вам.

Мы пытаемся заставить массив работать как стек. То есть мы помещаем ( push ) что-то в стек с помощью метода append (newElement:T), который добавляет в конец массива новый элемент. Мы вытягиваем ( pop)  элемент из стека с помощью метода removeLast().

Но у нас ошибка. И очень важно понять, почему возникла эта ошибка. Я кликаю на ней и нам сообщается, что вы не можете использовать  removeLast() для ops, так как ops— immutable (неизменяемый) массив. Массив ops —  read-only, только для чтения. Вы не можете его изменять. Почему ops —  read-only? И тут две вещи, о которых нужно подумать.
Первая — это то, что при передаче параметров в функции, если эти передаваемые параметры не класс, не экземпляр класса, то в функции передается копия параметра. То есть передаваемые параметры копируются. Очень важно понимать, что такие параметры передаются по значению. Вы привыкли к этому в других языках программирования, большинство языков передают параметры по значению.
Arrays и Dictionaries в Swift не являются классами. В Swift они — структуры, ну, вы знаете, как C struct. Arrays и Dictionaries в Swift являются структурами, struct, а не классами. Структуры struct  в Swift выглядят во многом как классы: у них есть функции, есть свойства ( properties). Они во многом идентичны классам, но есть два огромных различия между структурами  structs и классами  classes. Первое состоит в том, что классы classes могут наследовать, а структуры structs — нет.  Второе в том, что структуры  structs передаются по значению, а классы classes передаются по ссылке. Очень важно понимать эти различия.
Структуры обычно используются для базовых компонентов, таких как массивы Arrays и словари Dictionaries. И даже Double и Int являются структурами в Swift. И это прекрасно, потому что это означает, что Double и Int могут иметь функции и подобные им вещи, которые мы увидим в конце нашего курса. Но все эти структуры передаются в функции по значению.
И это первая вещь, которую нужно понимать, —  массив ops при вызове  evaluate передается по значению, то есть копируется.

Вторая вещь, которую нужно понимать — это то, что неявно перед элементом, который передается как параметр в функцию,  стоит ключевое слово let.

Screen Shot 2015-02-07 at 6.01.45 PM

Другими словами все передаваемые в функцию параметры являются read-only (только для чтения). А read-only массив не может изменяться. Я не могу ничего добавить к этому массиву, я не могу из него ничего удалить, так как он read-only. В действительности, я мог бы поставить перед передаваемым параметром ключевое слово var.

Screen Shot 2015-02-07 at 6.05.25 PMТеперь ops — mutable ( изменяемый ) массив . Вы видите? Нет ошибки. Но  все равно  ops — это копия. То есть он копируется, но становится  mutable. И теперь внутри нашей функции мы имеем изменяемую копию ops, с которой можем работать. Но я не большой поклонник ставить ключевое слово  var перед передаваемым параметром. Я считаю, что это сбивает с толку того, кто читает наш код. Он не понимает точно, что происходит. Вместо этого я создам локальную переменную, которую я назову remainingOps и приравняю его к ops.

Screen Shot 2015-02-07 at 6.38.17 PM

Когда я пишу знак равенства «=» , то происходит копирование, если это — не класс. Классы classes — это всегда по ссылке (by reference). Но все другие конструкции enum, struct или какие-то еще всегда копируются, когда вы пишите знак равенства «=» .

Итак, у меня есть  mutable массив  remainingOps. И я могу к нему применить метод  removeLast().

ВОПРОС: А почему невидно ключевого слова let впереди всех аргументов?
ОТВЕТ: Подавляющее большинство аргументов функции имеют «невидимое» ключевое слово  let. Вы можете поставить ключевое слово  var перед любым аргументом, и превратить его в mutable ( изменяемую) копию, которую затем можете использовать локально. Но если вы только используете ее локально, то это не так уж и замечательно. Конечно, существует способ вернуть измененное значение, но я не буду сейчас об этом говорить. Вы можете об этом прочитать — это так называемые inout параметры.
Я думаю лучше использовать то, что я вам предложил — через локальную переменную, это делает код более читаемым.

ВОПРОС:  inout параметры — это типа как передача по ссылке?

ОТВЕТ: Вы передаете  inout параметры, модифицируете их, и можете вернуть их обратно в конце работы. Да, это похоже на передачу по ссылке.

Вы можете сказать: «Это ужасно, если я делаю 3 копии, это должно быть очень медленно». И ответ — нет. Swift — супер сообразительный по поводу того, чтобы не делать действительного копирование до тех пор, пока вы не делаете изменений. Когда вы пишите знак равенства, он передает копию, но не делает реального копирования, он передает что-то типа указателя, но не по ссылки, а по значению (by value). И опять, если снова копирую, реального копирования не происходит, даже если у вас массив из 10,000 элементов. Копирование начинает происходить, если вы действительно начинаете его изменять, тогда он вынужден делать копию. Но никогда не делается копия целиком массива.
Он, возможно, только отслеживает изменения. За сценой Swift — супер сообразительный. Не слишком беспокойтесь о том, что вы копируете снова и снова. Он очень мало делает реального копирования.

Итак, у меня есть локальная переменная remainingOps, которая действительно является оставшейся частью стэка ops. Я вытянул элемент с вершины стека. В нашем примере это «х«.

let op = remainingOps.removeLast()

Что я должен делать с полученным топовым элементом стека?
Я собираюсь получить его ассоциированное значение из enum. Какое-то одно из трех. Потому я буду использовать оператор switch. Я говорил вам, что это очень важный оператор, важный для вытягивания ассоциированных значений из enum. И переключаться мы будем по ассоциированным значениям enum элемента,  вытянутого нами с вершины стека
Я рассмотрю все варианты. Начнем с варианта, когда мой вытянутый элемент стека — операнд.

Screen Shot 2015-02-07 at 8.28.20 PMЗаметьте, что мы поставили маленькую точку «.» после ключевого слова case, потому что в действительности это  op.Operand.

Но если мы посмотрим на тип op, то увидим, что он имеет тип Op нашего enum

Screen Shot 2015-02-07 at 8.45.30 PMПоэтому используя «вывод типа»  (type inference), мы можем не указывать op, и  оставить только точку, то есть .Operand. В круглых скобках следом за конструкцией  .Operand ( ) вас спрашивают, что вы хотите сделать с ассоциированным значением, если вы обрабатываете этот вариант ( case ), то есть в случае, если это операнд. Я хочу присвоить это ассоциированное значение константе с именем operand.

case .Operand(let operand):

Я могу поставить ключевое слово var, которое будет делать то же самое, за исключением того, что я могу его изменять внутри контекста этого варианта (case).
Я почти никогда не буду использовать  var, почти всегда у меня будет let.

Итак, я получил операнд, он имеет тип Double, и я могу сформировать кортеж (tuple) для возврата. Я собираюсь вернуть operand — это будет result. Если вспомните наш пример, получив «4«, я верну 4 в качестве оценки этого элемента стека. И также я должен вернуть remainingOps.

Screen Shot 2015-02-07 at 9.22.20 PM

Итак, я возвращаю два значения в стеке. Заметьте, что я не обязан писать здесь result: и remainingOps: , потому что тип возвращаемого кортежа известен и все согласуется по типам.
Это все, что мне нужно сделать для операнда.
ВОПРОС: Где располагается массив remainingOps? Он в «куче»? Или он в стеке?
ОТВЕТ: Это детали реализации. Вы не знаете и не должны об этом беспокоиться.

Вариант  с операндом очень простой.
А как насчет операции? Рассмотрим унарную операцию.

Screen Shot 2015-02-07 at 9.36.14 PMПри обработке варианта унарной операции, нам не нужно первое ассоциированное значение, которое представляет собой просто символ, например, «+» для операции сложения. Для оценки значения стека важна только функция, которую нужно вызвать для расчета .  Поэтому я проигнорирую первое значение и поставлю подчеркивание «_». В Swift подчеркивание «_» означает, что я не интересуюсь этим значением, и мы увидим его в других местах, там, где я действительно не интересуюсь значением. Вам не нужно декларировать переменную с каким-то фантомным именем, вы просто ставите подчеркивание «_»  и игнорируете этот параметр.

Но я интересуюсь вторым ассоциированным значением — это операция.

Screen Shot 2015-02-07 at 10.04.57 PMЯ игнорирую символ, но беру операцию. Я хочу вернуть операцию с операндом, которого у меня пока нет, и что-то типа remainingOps, который останется после получения операнда.
Первая вещь, которую я должен сделать — получить операнд.
Я выполняю унарную операцию, допустим корень квадратный. Я должен опять вернуться к моему стеку и вытянуть следующий элемент стека, но я не могу предполагать, что это будет число, это может быть еще одна операция, так что у меня появилась рекурсия. И я покажу вам, как вызвать функцию, которая возвращает кортеж ( tuple). Есть два способа сделать это.
Я создаю локальную переменную operandEvaluation, которая получит результат вызова evaluate.

Screen Shot 2015-02-07 at 10.38.42 PM

У меня получилась рекурсия. Давайте посмотрим на тип operandEvaluation.
Screen Shot 2015-02-07 at 10.44.01 PMТип переменной operandEvaluation — это кортеж (tuple). В этом кортеже есть
result и remainingOps. Как я могу вынуть result и remainingOps из кортежа?

let operand = operandEvaluation.result

Так я получила значение из кортежа (tuple). Какого типа operand?

Screen Shot 2015-02-07 at 10.55.11 PMПрактически всегда это Optional - Double?. Нам нужно превратить ее в Double, чтобы передать в операцию. Я буду использовать if let конструкцию для превращения operand в Double.

Screen Shot 2015-02-07 at 11.16.31 PMТеперь у меня есть операнд operandи я могу его использовать в операции operation (operand). Оставшийся стек я получу с помощью выражения operandEvaluation.remainingOps.  Это стек, который мы получим после рекурсии. И это все. Мы использовали рекурсию для функции evaluate. Если что-то пойдет не так, мы вернем кортеж с nil.
Теперь сделаем бинарный вариант. Очевидно, что это очень похожий вариант на унарный, только нужно получить два операнда.

Screen Shot 2015-02-07 at 11.24.34 PMО, мы получили ошибку. Это хорошая ошибка, чтобы рассказать вам о том, что функцию evaluate нужно декларировать как private. Ошибка говорит, что метод должен декларироваться как  private, потому что он использует private параметр. Параметр ops, использует private тип Op. Следовательно функция  evaluate должна быть  private.

Заметьте, что я не использовал для switch оператора вариант

default: break

Почему мне не нужно делать это здесь?
Потому что я должен обработать каждое возможное значение op.
Всего у значения op три возможности, и я их все обработал. Поэтому мне не нужен еще и default: break. Этот вариант использования switch отличается от того, когда мы анализировали символ операции и переключались по значению с типом String. Мы обработали небольшое количество значений типа  String, поэтому нам понадобился default: break. Еще раз, мы не использовали default: break потому, что мы обработали каждую возможность. Ставить  default: break  в каждом  switch операторе — это очень плохой стиль программирования. Вы должны ставить его только тогда, когда это реально необходимо, когда вы не смогли обработать каждый возможный вариант ( case ).
И последняя вещь, которую мы должны сделать — это вызвать рекурсивную версию
evaluate из нерекурсивный версии evaluate  и получить кортеж (tuple) со значениями, но я сделаю это немного другим способом, чем прежде.

Screen Shot 2015-02-08 at 8.26.56 AMВ левой части я определяю кортеж (tuple) cо всеми его составляющими, и вызываю рекурсивный вариант evaluate, на вход которого подаю мой полный стэк opStack.
Это другой способ вызова функции, которая возвращает кортеж (tuple). Имена в нашем кортеже (tuple) в левой части не должны быть обязательно теме же самыми:
result — то же самое, но  remainder — вместо слова remainingOps. Они не обязаны быть теми же самыми. И далее мы просто возвращаем  result.

Итак, мы многое узнали. Узнали о кортежах (tuple), как возвращать кортежи (tuples) различными способами, как игнорировать какие-то элементы в enum, и как получать ассоциированные значения.

Это полная реализация нашего CalculatorBrain. И он не так уж плох? Очень простая структура данных. Мы должны только определить известные операции. У нас есть рекурсия, очень простой, но мощный код.
Давайте возьмем наш CalculatorBrain и используем его в нашем старом Controller.

ВОПРОС: Можно ли поставить знак подчеркивания «_» в  кортеже (tuple) в левой части?

ОТВЕТ: Да, я могу поставить знак подчеркивания  «_«, так как мне из возвращаемого кортежа  (tuple) нужен только result. Я просто использовал имя remainder, чтобы показать, что можно использовать разные имена, но я не использую  remainder, мне нужен только  result.
Screen Shot 2015-02-08 at 9.30.37 AMВОПРОС: Cодержит ли opStack то же самое, что и перед вызовом evaluate?

ОТВЕТ: На opStack не влияет вызов evaluate, потому что opStack — массив , а массивы передаются по значению, поэтому внутри рекурсивной evaluate работает копия. Оригинал остается неприкосновенным, а это то, что нам нужно. Я не хочу, чтобы evaluate «съедала» мой стэк. Я хочу вызывать  evaluate снова и снова, и получать результат.

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

Screen Shot 2015-02-08 at 9.13.53 PM

Поговорим о печати через секунду.
Но вначале вернемся в наш Controller и будем использовать CalculatorBrain, эту прекрасную Модель M, которую мы только что изобрели. 

Но вначале я хочу вам показать, как вы можно получить две вещи на экране одновременно. Перед этим у нас была storyboard и я кликал на Ассистенте Редактора ( Assistant Editor) — кнопка в правом верхнем углу — и он показывал мне соответствующий код для Controller, потому что такова логика работы Ассистента Редактора: если у вас есть storyboard, то , возможно , вы захотите выбрать код соответствующего Controller  для выбранного scene (эпизода) на storyboard. Но если у нас на экране код нашего CalculatorBrain, то нет соответствующего scene (эпизода) на storyboard. Тогда Ассистент Редактор скажет, что не знает что показывать.

Screen Shot 2015-02-08 at 10.13.23 AM

Есть и другой путь, каким вы сможете  вызвать на экран две различные вещи. Кликаете на  верхней панели и можете выбрать из множества возможностей: подклассы (subclasses) этого класса (что слева), суперклассы  (superclasses) и другие.

Screen Shot 2015-02-08 at 10.20.56 AM

Вы можете использовать «ручной» режим (  Manual ) для того, чтобы выбрать, например, наш ViewController.swift файл и подгрузить его в это окно. Но вы можете выбрать и что-то другое, используя навигатор.

Но я покажу другой способ вывода нужного файла в дополнительное окно справа. Достаточно, удерживая нажатой клавишу option кликнуть на нужном файле, например, ViewController, и он появится справа в окне Ассистента Редактора. Так что option + клик — это очень удобный способ поместить файл в окно справа.
Screen Shot 2015-02-08 at 10.30.59 AM

Теперь у нас калькулятор слева, а Controller —  справа.

Screen Shot 2015-02-08 at 10.57.27 AM

Мы можем убрать лишний код из Controller  и заменить его работой   CalculatorBrain. В результате наш Controller станет очень простым, в нем ничего не останется. Нам не нужен стэк, потому что это будет делать CalculatorBrain.

Но я создам новую переменную экземпляра класса CalculatorBrain, которая будет нашим калькулятором. Это очень общий прием, это соответствует зеленой стрелке, которая идет от Сontroller к  Model  на схемах MVC, то есть  Сontroller может разговаривать с Model и может говорить что хочет и когда хочет. Именно поэтому стрелка зеленая. Эту переменную для CalculatorBrain я назову brain.
Screen Shot 2015-02-08 at 11.07.25 AMЕстественно зеленая стрелка может соответствовать гораздо более сложной Модели, например, при получении данных из сети или из базы данных. Но в нашем случае это просто Модель нашего калькулятора.

В методе

@IBAction func enter()

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

Screen Shot 2015-02-08 at 1.36.30 PM

Но есть одна вещь, которую я также должен сделать — это обновить мой дисплей.  Потому что при помещении (push) операнда displayValue в  brain, изменяется внутренний стэк и меняется результат оценки (результат evaluate) этого нового стэка. Мы должны отразить это на дисплее. Следовательно, метод pushOperand  должен вернуть нам результат оценки обновленного стэка. Для этого внесем изменения в метод pushOperand в нашем CalculatorBrain.чтобы он возвращал результат evaluate

Screen Shot 2015-02-08 at 2.06.50 PM

Каждый раз, когда я буду применять метод  pushOperand, я  получу  назад результат  evaluate типа Double?  Кто-то может возразить, а является ли это лучшим местом, куда следует поместить код получения результата? По семантике этого метода здесь это не нужно. Но мне кажется, что здесь хорошее место, потому что мы не собираемся иметь 1000 операндов и операций в стеке, хотя вы конечно, можете их иметь. И возможно, это не вызовет таких уж больших затрат производительности. Тоже самое сделаем с методом performOperation, хотя он не обязан возвращать результат оценки, но я это сделаю.

Вернемся к нашему Controller и методу  enter (). Так как возвращается Optional,то я применяю if let

Screen Shot 2015-02-08 at 2.17.07 PM

Если pushOperand возвращает не nil, я обновляю дисплей с помощью evaluate.
А что делать, если вернется nil? Похоже, я застрял.
Вот почему в вашей домашней работе я подумал, а почему бы не сделать displayValue Optional? Действительно, если displayValue берет и возвращает Optional, вы могли бы вывести на дисплей что-то значимое, если вы не можете оценить (evaluate) стек. В домашней работе сделать displayValue Optional это обязательный пункт задания. Вы можете сделать это и для домашнего задания 1, но для домашнего задания 2 вам придется это сделать, так как это обязательный пункт.

Чтобы иметь больше времени, я не собираюсь здесь учить вас вещам, связанным с  NumberFormatter и другим вещам для домашнего задания 1. Поэтому лучшее, что я могу сделать сейчас — это написать displayValue = 0. Это, конечно, неуклюже, но это все, что я могу на данный момент. Я действительно хотел бы написать displayValue = nil, то есть просто displayValue =  result, и все, а значение nil очистило бы дисплей или, как в домашнем задании 2 в дополнительном задании (extra credit) разместило бы сообщение на моем дисплее. Но это дополнительное задание, а сейчас — 0. 
Аналогичные изменения сделаем с performOperation.

Screen Shot 2015-02-08 at 3.24.35 PMИ это все. Посмотрите, каким простым стал наш Сontroller. В нем практически нет кода. Именно это замечательно при разработке  iOS приложений. Вы можете построить пользовательский интерфейс практически полностью графически и очень небольшое количество кода требуется для управления тем, что на нем происходит.

И даже наша Модель, которая является очень мощным калькулятором и в которую вы могли бы добавить сколько угодно функций, представленных одной строкой кода, способна оценивать любой набор таких функций. В вашей домашней работе  вы должны добавить переменные и их помешать в стек.  Другая часть вашей домашней работы состоит в том, чтобы представить стек в удобной для человека читабельной форме с использованием инфиксной нотации, круглых скобок, когда это необходимо. Это будет сделать существенно легче с помощью рекурсии, то есть используйте рекурсию для представления стека в удобной инфиксной форме, Например,так: 4 х ( 5 + 6 ) .
Это два очень важных задания в вашей домашней работе.

Запускаем приложение и смотрим, все ли работает. Заметьте, что мы даже не трогали наш UI. Он не изменился, так как мы не открывали storyboard. Все изменения касались только Controller  и Model, взаимодействию между Controller  и Model, а View не изменялось.

Набираем «8» «5« «4» «1» «+» «√«. Все работает.
Что если мы наберем что-то, что нельзя оценить, например «х» «х» «х» ? На дисплее 0.0 , что не очень хорошо.

В вашей домашней работе вы должны напечатать в удобной форме  что происходит со стеком, чтобы видеть многочисленный набор «х» «х» «х«. Дисплей в ошибочном случае должен быть пустым, но никак не 0.0.

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

Screen Shot 2015-02-08 at 4.28.53 PM

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

Screen Shot 2015-02-08 at 4.40.22 PM

Мы видим, что печатается все в непонятной форме. Что происходит? Когда мы при печати помещаем что-то в круглые скобки,  Swift пытается конвертировать это в String.  Массив opStack знает как конвертировать себя в String. Он открывает квадратную скобку и далее просит каждый элемент массива конвертировать себя в String, разделяет элементы массива запятыми и ставит закрывающуюся квадратную скобку. Наш массив состоит из элементов типа Op, которые не умеют конвертировать себя в String.  Система тоже не знает как конвертировать Op в String, поэтому и пишет (Enum, Value ). Я покажу вам, как научить тип превращать себя в строку. Потому что такая строка необходима для отладки и для вашего домашнего задания. Что для этого надо сделать ? Вам нужно добавить вычисляемое свойство к своему типу. 

Сomputed properties (вычисляемые свойства) предоставляются классами, структурами и перечислениями. Stored properties предоставляются только классами и структурами.
Поэтому только computed properties существуют в enums. structs и classes могут иметь stored properties.
Для преобразования самого себя в String, мы должны добавить в наш тип Op вычисляемое свойство (computed property) description. Такое  название выбрано потому, что это будет описывающая этот тип строка. Тип этого свойства — String. Добавим свойство  в наш enum Op.
Screen Shot 2015-02-08 at 5.43.18 PM

Мы можем задавать у свойства get { }  и set { } , это можно задать как у computed properties, так и у  stored properties. Но свойство description, является read-only, поэтому у него нет set{ }, только get { } . Внутри  get { } мы должны вернуть  Op как String. Как мы превратим  Op в String? Для операций (унарной и бинарной) мы просто вернем символ операции, например для операции +, вернем «+«. Для операнда я должен преобразовать Double в String. Будем использовать оператор switch  и переключать его по self. Внутри каждого варианта достаем ассоциированные значения. Для операнда я достаю сам операнд, для операций (унарной  и бинарной) меня  интересует только символ операции и совсем не интересует функция. В случае с операндом мы возвращаем  «\(operand)» , а случае с операциями просто symbol

Screen Shot 2015-02-08 at 6.58.23 PM

Мы не только обработали все варианты switch, но и вернули соответствующие значения. У нас есть прекрасное computed read-only свойство  description.
Но этого недостаточно. Вам нужно написать в заголовке нашего перечисления : Printable

Screen Shot 2015-02-08 at 7.01.03 PM

Я говорил, что перечисления enum и структуры struct не имеют наследования (inheritance). А это что? Наследование от Printable? И ответ :  нет, не наследуют. Это перечисление enum и у него нет наследования, только классы имеют наследование.
То, что мы использовали enum Op: Printable — это называется протокол protocol.

Мы сказали Swift, что этот enum реализовал все, что в этом протоколе. Этот протокол требует, чтобы было одно вычисляемое свойство read-only c именем  description, которое возвращает String.
Мы будем говорить о протоколах более глубоко, потому что протоколы очень важны для объяснения того, как все работает в iOS. Но сегодня мы больше говорить о них не будем.

Запускаем приложение. Вводим «6»  «5»  «4« «х» .

Screen Shot 2015-02-08 at 7.30.47 PM

Нам бы хотелось иметь кнопку для сброса калькулятора в начальное состояние, но это вы будете делать в Задании 1.

И последняя вещь, о которой я бы хотел поговорить, и которая использует description для устранения проблемы, о которой я говорил в начале лекции. Проблема состоит в том, что в «известных» операциях нам приходится дважды набирать символы операций, что может вызывать дополнительные ошибки. Помните?   Я создам  функция с именем learnOp, которая позволит использовать символ операции однократно

Screen Shot 2015-02-08 at 7.48.00 PM

Вы видите, что имея функцию  learnOp, мы задаем символ операции один раз. Другая вещь, которая здесь интересна : функция находится внутри init(). Вам разрешено помещать функции внутри других функций для внутреннего использования.  Если вы разместите  learnOp за пределами init(), то у нас возникнут проблемы, так как она должна быть private — она использует private тип Op.

Но я не хочу делать ее private и не хочу выносить ее за пределы  init().

Лекция закончена.
Код для Swift 1.2 находится на Github. Код для Swift 2.0 находится на Github.