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

Title iOS 8

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

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

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

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

Русскоязычный неавторизованный конспект лекции приводится ниже.

Вы можете читать к PDF-файл конспекта и использовать его offline

Лекция 3 CS193P Winter 2015 iOS 8.pdf

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

Продолжение: 2 — ая часть лекции (36 минута — конец ) — находится здесь.

Код для Лекции 3 находится на Github.

Это лекция 3. Она вся будет посвящена демонстрационному приложению Calculator.

Сегодня сделаем наш калькулятор Сalculator более мощным. Но что более важно, мы заставим его следовать парадигме конструирования  MVC. То есть я покажу, как мы разделим наше приложение на 3 лагеря. Ну, а кроме этого я буду продолжать знакомить вас с возможностями языка Swift, как обычно. Собственно это является задачей первых 3-х лекций с демонстрационным приложением Calculator.
Моя задача показать вам все это в действии, потому что вы можете сколько угодно читать о Swift, iOS, но до тех пор, пока вы не увидите это в действии, не превратите это в приложение, реально вы не будете знать Swift и iOS. Моя задача сделать для вас это реальностью.


Screen Shot 2015-02-05 at 8.56.49 AM

Сегодня

  • Новое демонстрационное приложения Calculator
    • Применяем MVC к Calculator
    • enum
    • Простой инициализатор
    • Возвращение Optional
    • Dictionary (словарь)
    • Tuples (кортежи)

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

На следующей лекции я начну рассказывать об API слоя Foundation в iOS. Это не имеет отношения к пользовательскому интерфейсу и представляет собой типа коллекции классов. И затем я остановлюсь на некоторых возможность Swift  и Foundation  в iOS. Это будет основополагающая лекция. Далее мы погрузимся в разработку сложных iOS приложений. Я понимаю, что первые две недели будут сложными — новый язык, новая система. Но я надеюсь, что после этих двух недель и после Задания 2 вы будете чувствовать себя уверенно, так как у вас появится твердая основа, от которой вы можете оттолкнуться и двигаться дальше.

Вернемся к нашему калькулятору и посмотрим на код и на UI.

Screen Shot 2015-02-05 at 9.57.21 AM

Согласно концепции MVC слева у нас лагерь V, View, состоящий из миньонов; справа — С, Controller, но выделенный код не должен принадлежать лагерю С, потому что лагерь C занимается представлением данных модели  на V, а не производством этих данных. Расчетную часть нашего калькулятора нужно поместить в лагерь M, Model.
Потому что суть калькулятора — в вычислениях, и весь код, касающийся этой основной задачи приложения, должен быть помещен в M, Model.

Сегодня мы создадим M, Model. Мы усовершенствуем наш калькулятор новой возможностью: наш калькулятор, а точнее его  Model M, должен запоминать каждый операнд или операцию, которые мы ему предоставляем для расчета. Сейчас, когда мы выполняем операцию, мы используем операнды для расчета, а затем они удаляются. В улучшенном варианте калькулятора  мы действительно будем собирать все операции и операнды в нашем стэке. А затем мы можем в любое время попросить нашу Model M рассчитать ( evaluate) наш стэк операндов и операций.

Причина, по которой я добавляю это улучшение — ваше Домашнее Задание 2, в котором вы должны добавить два функциональных усовершенствования калькулятора, требующие стэка со всеми операндами и операциями и реальной возможностью его рассчитывать. Так что Домашнее Задание 2 вы начнете с того, что я буду делать сегодня. Я скажу об этом в конце лекции, напомните мне об этом.

Создаем новый класс для нашей Model M.  Мы поместим Model в отдельный класс.
Как мы создаем новый класс в Xcode? C помощью меню File -> New -> File. 
Надо сказать, что этот путь мы будем часто использовать для самых различных вещей, в том числе для создания  чего-то нового, не только для нового класса, что мы можем добавлять в наш проект. Если мы кликнем, то появится окно со списком возможностей. Мы находимся в iOS — выбираем верхнюю часть, и мы хотим добавить Source файл. Позже в этом курсе мы будем использовать это окно для добавления   подклассов (subclasses) Cocoa Touch классов.

Но сейчас нам нужен Swift File, пустой Swift файл.

Screen Shot 2015-02-05 at 11.07.05 AM

Наша Model M является полностью независимой от UI, она  не будет подклассом (subclass ) какого-либо  IOS класса, это базовый (basic) класс, поэтому мы выбрали пустой Swift File. Кликаем Next. Далее я задаю имя файла и помещаю в то же самое место, где находятся все другие наши Swift файлы: наш  Controller и AppDelegate.
Я хочу назвать этот новый класс CalculatorBrain. Я вовсе не обязан называть Swift файл, чтобы это было имя класса, но обычно мы делаем именно так. Иногда людей смущает, что имя Swift файла отличается от основного класса. Но это совершенно законно. Так что имя Swift файла в действительности ничего не значит. Имеет значение что внутри этого файла.

CalculatorBrain — это имя нашего класса, нашей Model M. Обратите внимание на заглавную букву в названии нашего класса. В Swift мы всегда начинаем с заглавной буквы имя любого типа, мы никогда не используем символ «_» подчеркивания и что-то еще, только стиль CamelCase , когда делается заглавной начало каждого слова, включая первое. Это для типов, во всем остальном мы не пишем заглавную букву в первом слове. Это имена свойств ( property name) , имена функций  (function names). Все они начинаются с «маленькой» буквы, а в следующих словах в имени используем заглавные буквы. Это позволяет нам при чтении кода быстро определять, что — тип (type), а что — нет. Пожалуйста, используйте это в дальнейшем.

Перед вами вновь созданный Swift файл.

Screen Shot 2015-02-05 at 11.48.17 AMВы видите, что мы импортируем не UIKit, а  Foundation.
Foundation — это уровень Сore Services, о котором я говорил на первой лекции.  У меня нет ничего, связанного с UI, что правильно, потому что это Model, а она должна быть независимой от пользовательского интерфейса. Мы никогда не будем импортировать UIKit в класс Model.
Итак, мы будем создавать новый класс,  вы уже знаете, как это делается.

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

Screen Shot 2015-02-05 at 1.19.27 PM

Иногда модель Model может наследовать от класса NSObject, и я буду говорить об этом позже. У вас появятся дополнительные возможности, если вы будете наследовать от NSObject, но в нашем случае у нас будет базовый Swift класс. Базовый класс не наследует никакую функциональность.
Какая будет структура данных у нашего CalculatorBrain?
Она будет немного отличаться от той структуры данных, которую мы использовали в нашем Controller для хранения операндов. Нам нужно хранить как операнды, так и операции вместе в новой структуре данных. Я хочу, чтобы это был стэк, то есть массив, и я назову его opStack, но тип его не может быть массивом Double. Потому что операнды имеют тип Double, но операции представляют собой комбинацию математических символов (например, «+«, «х«) и даже функции. Функции, которые делают схожие вещи. Очевидно, что структура данных не может остаться той же самой. Нам нужна новая структура данных, которую я назову Op и мы ее определим через секунду. То есть Op — это просто что-то, что может быть и операндом, и операцией, но только или:  или операндом, или операцией.

Теперь пару вещей об этом: конечно, мы должны инициализировать стэк opStack, как это мы делали в Controller, то есть задать пустой массив Op.

Screen Shot 2015-02-05 at 7.26.24 PM

Хотя тот синтаксис, который я использовал, очень удобен для тех, кто впервые на  Swift, я хочу использовать немного другой синтаксис, который более предпочтителен в Swift и более краткий — квадратные скобки, а внутри тип данных.

Screen Shot 2015-02-05 at 7.28.52 PM

Это абсолютно тоже самое, просто другой способ представления массива. Кроме того, может быть он немного более предпочтителен, так как используется при инициализации массива и мы увидим это позже.
Теперь поговорим о том, что представляет собой  Op.
Мы могли бы сделать его классом, в котором одно свойство имело бы значение операнда, другое свойство было бы математическим символом для операции, и, наконец, еще одно свойство для функции. И соответственно, если одно свойство, например, операнд, было бы установлено, то другие два будут неустановленными. И наоборот. Но иметь класс, в котором имеются свойства, которые иногда могут быть не установлены,  когда другие свойства установлены, кажется несколько запутанным. Нам нужно не это.
То что нам нужно — это enum (перечисления). Возможно вы использовали  enum (перечисления) в других языках. Я создаю enum, и это очень похоже на создание класса, но только вместо ключевого слова class, я использую enum, и, очевидно, нет наследования.

Screen Shot 2015-02-05 at 7.31.57 PM

Но  enums могут иметь функции, как и классы, они также могут иметь свойства (properties ), но только вычисляемые свойства ( computed properties). enum — это базовый тип, который реально применяется там, где что-то может быть только какой-то определенной вещью в каждый отдельный момент времени, и другой вещью — в другой момент времени, и никогда двумя вещами одновременно. Оно перечисляет различные возможности. Для нашего случая мы можем написать, что одна возможность — операнд, другая — операция.

Screen Shot 2015-02-05 at 7.34.15 PM

В других языках это было бы бесполезно, потому что у нас есть массив, и определять, является ли элемент массива операндом или операцией — совершенно бесполезно. Нам нужен сам операнд, и нам нужна сама операция. Мне нужна операция: математический символ и его функция. Они должны быть в моем enum.
И у Swift есть замечательная возможность, когда мы можем ассоциировать данные с любым вариантом в enum.  Если значение моего enum — это операнд, я буду ассоциировать данные типа Double, это будет значение операнда.   Похожая вещь со значением моего enum, если это операция. Но вначале я сделаю унарную операцию UnaryOperation с одним аргументом. У нее в качестве ассоциированного значения будет строка, содержащая символ математической операции и функция.

Screen Shot 2015-02-05 at 7.35.51 PM

В Swift функция может быть типом. И Double -> Double — это не просто строка, это тип функции. В большинстве случаев мы будем создавать в фигурных скобках какую-то функциональность — замыкания. То есть использовать замыкания для создания функций и эти замыкания могут быть ассоциированными значениями для операций.

Создадим бинарную операцию. У нее два аргумента и у функции два аргумента

Screen Shot 2015-02-05 at 7.38.52 PM

Теперь у нас есть стэк. Это массив [Op].  Op является либо оператором, либо одной из двух операций. Как мы сможем что-то положить в стэк?

Давайте поговорим об API.  За аббревиатурой стоит Application Programming Interface ( программный интерфейс приложения). API — это методы  и свойства (properties), которые составляют ваш класс, его описание. Дальше я буду часто использовать слово API.

Мне реально нужно иметь возможность поместить операнд в стек. Я создаю функция с именем pushOperand, которая берет operand: Double  и  ничего не возвращает.

Screen Shot 2015-02-05 at 7.42.38 PMФункция  append это функция Array, которая добавляет в массив элемент того типа <T>, который указан при определении массива.

Screen Shot 2015-02-05 at 7.49.38 PM
В нашем случае типом  T является Op.
Как мы создадим enum элемент? Через точку с указанием ассоциированного значения в круглых скобках: Op.Operand( operand ).
Теперь перейдем к операциям. Вместо  push, назовем метод performOperation.
Но прежде, чем я буду писать этот метод, мой CalculatorBrain должен иметь ряд встроенных в него известных операций, он должен знать как их выполнять. И когда вы будете выполнять метод performOperation вы должны определить математический символ какой-то из уже известных нашему CalculatorBrain операций. Следовательно аргументом будет строка.

 func performOperation(symbol: String) {... }

Мне нужно создать «известные операции». Это будет еще одна переменная экземпляра класса, назовем ее   knownOps. И это будет словарь — вы читали о словарях на прошлой недели, он значился в обязательном списке Reading assignment.

Screen Shot 2015-02-05 at 9.27.18 PM

Я создаю словарь с ключевым словом Dictionary и с треугольными скобками <>, точно также, как мы поступали вначале при создании массива, но внутри мы должны указать тип ключей и тип значений. В нашем случае ключи имеют тип String, значения — тип Op. И я создам пустой словарь. В этот словарь я помещу все известные мне операции.

Когда кто-то говорит функции performOperation :»Выполни эту операцию !», и задает символ операции, то performOperation использует этот символ как ключ, по которому ищет  в knownOps  актуальную операцию. И если я ее нахожу, то помещаю операцию в стэк. 
Мы должны чем-то загрузить knownOps. Но прежде я хочу остановиться на синтаксисе определения словаря с помощью квадратных скобок. В квадратных скобках мы указываем двоеточие между типами ключа и значения. Очень похоже на массив Array. 

Screen Shot 2015-02-05 at 10.13.15 PM

Как я буду инициализировать knownOps? И сейчас самое время увидеть как работают инициализаторы. До сих пор мы создавали классы прямо при декларировании, просто указывая круглые скобки. И когда мы это делали для  Array<T> (), вызывался инициализатор, для Dictionary<T> () — тоже вызывался инициализатор.

А теперь мы создадим свой собственный инициализатор, инициализатор класса CalculatorBrain, и мы сделаем это просто набирая init (   ) . И если кто-то в коде напишет

let brain  =  CalculatorBrain ()

то он попадет в наш  init (   ) и вызовется наш инициализатор с подходящими аргументами.  Если аргументов нет,  то вызовется init (   ) 
Что мы будем делать в init (   ) ? Мы начнем заполнять наш словарь  knownOps.
Screen Shot 2015-02-05 at 10.53.59 PMКак нам создать операцию? Очень похоже на то, как мы создавали enum :
Op, затем точка «.» и знак «×» в бинарной операции. И вы видите, что код заполняется автоматически.  Мы просто нажимаем Tab, чтобы произошло заполнение кода автоматически. Мы напечатали символ операции дважды в одной строке. Если я случайно использую неверный символ, например, другой символ с этой доски с символами, то можно получить ошибку, так как эти символы не будут совпадать.
В конце этого демонстрационного примера я покажу, как мы можем избавиться от лишнего символа  «×«, чтобы не печатать его дважды.
Но сейчас нам нужна функция. Вы знаете как можно задать функцию умножения.
Просто  задаем замыкание {$0 * $1} .
Screen Shot 2015-02-05 at 11.08.19 PM

Я могу поместить замыкание за пределами круглых скобок, так как это последний параметр функции. Это законно, и мы это сделаем. Добавим еще 3 бинарных операции и одну унарную.

Screen Shot 2015-02-06 at 7.12.05 PM

Вы видите, их API действительно очень прост. Пока я здесь, хотел показать вам две вещи относительно замыканий (closures).

В enum, в варианте унарной операции

case UnaryOperation(String, Double -> Double)

в качестве аргумента используется функция с одним  Double аргументом, которая возвращает одно Double значение. И вы знаете, что это — Doubles, при создании унарной операции. Мы можем пользоваться здесь «выводом типа» (inferring type).
Тогда выражение { sqrt($0) }  представляет собой функцию типа Double -> Double внутри другой функции Double -> Double. Поэтому нам не нужно замыкание и аргументы внутри него, мы передаем функцию sqrt как второй параметр в унарную операцию и она может иметь имя или использовать фигурные скобки { }.

Screen Shot 2015-02-06 at 7.14.09 PM

Дальше еще интереснее, потому что оператор умножения * в Swift  представляет собой функцию типа (Double ,  Double )-> Double, и мы можем оставить только этот символ. То же самое с оператором сложения + — это тоже функция типа (Double ,  Double )-> Double. Все операторы * , + , — , /  в Swift определены таким образом, что они являются инфиксными правыми операторами

infix operator <*> { associativity right }

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

Screen Shot 2015-02-06 at 7.18.47 PMМы не можем также поступить с делением / и вычитанием  из-за обратного порядка операндов.

Итак, у нас есть knownOps. Он загружается при создании CalculatorBrain.
Используем его в performOperation.

Кто-то передает нам символ операции и мы пытаемся ее выполнить. Надеюсь, эта операция нам известна. Мы будем искать ее в нашем словаре «известных» операций.

Используя  subscript нотацию с квадратными скобками для словарей, мы найдем значение в словаре, соответствующее ключу = символу.

let operation = knownOps[symbol]

Какой тип будет иметь operation?

Screen Shot 2015-02-06 at 7.31.55 PMВы можете подумать, что это Op , потому что вы ищите в словаре, где тип значений —  Op, а тип ключей  String.  Но это Optional тип — Op? . Почему мы получили  Optional тип? Потому что возможен случай, когда мы ищем в словаре значение по ключу и можем не найти его из-за отсутствия этого ключа. Поэтому возвращается Op? 

Это общий случай со словарем: когда мы ищем что-то в словаре Dictionary <T>, всегда возвращается Optional<T>. Либо nil, либо значение типа T. Поэтому я использую конструкцию if let. И помещаю полученную операцию в мой стек opStack.

Screen Shot 2015-02-06 at 7.57.12 PMЗдорово.
Я хочу сделать паузу и поговорить немного об управлении доступом ( access
control). Управление доступом — это разговор о public и private. Если методы в этом классе являются public, то другие люди могут их использовать, другим классам в моем коде разрешено их вызывать. Если я делаю что-то private, то это мои внутренние структуры данных, и я не хочу, чтобы люди в них копались.

Способ, каким работают public и private в Swift, очень интересен. Вы просто определяете те вещи, которые хотите сделать private,  путем размещения ключевого слова private перед ним. Если вы не поставите перед методом или переменной никакого слова, то этот метод или переменная будут public внутри вашей программы. Ключевое слово public используется только, если вы поставляете что-то типа фреймворка  объектов другим людям и хотите, чтобы что-то использовалось за пределами фреймворка. Например, я сделал некоторый фреймворк (framework) ряда объектов, который я хочу продать другим людям, я должен дать людям возможность использовать что-то или делать что-то с этими объектами.  Это и будет public. Но, конечно, я бы хотел иметь некоторые вещи, некоторые объекты, которые бы находились внутри моего фреймворка, и которые бы «разговаривали» бы друг с другом. Тогда нам не нужно никакое слово — не надо ничего указывать перед методом или переменной. Это  внутренний public — public внутри вашей программы.. Но если у меня есть какие-то вещи внутри определенных объектов , и которые я бы хотел использовать только внутри этих объектов, то тогда используется ключевое слово private. А другие методы и свойства в этих объектах могут быть  public , их может использовать тот, кто купил мой фреймворк. На этом курсе мы не будем делать фреймворки, у нас нет на это времени, и мы не будем использовать ключевое слово public. Но мы будем использовать ключевое слово private. И начиная с Домашнего Задания 2, это будет одним из критериев успешного выполнения вашей работы. Сделать private вещи private. Это важно. Кто-то может подумать :»А не сделать ли мне все public?» Это плохо. Если вы сделаете внутренний объект  public, и некоторые другие объекты в вашей программе начинают портить и ломать ваш объект, а затем вы приходите с новой версией вашей программы и что-то изменяете, то каждый может это сломать. Это плохо, это хаос.

Единственные вещи, которую вы не захотите сделать private, связаны с тем, что вы подписываетесь на использование этого объекта. На нашем курсе, единственная вещь, которая является публичной — это инициализатор. Очевидно, что вы захотите позволить людям создавать CalculatorBrain. Это очевидно public внутри программы (то есть не нужно никакого ключевого слова). А также методы pushOperand и performOperation — это  public  внутри программы. Все остальное я сделаю  private.

Я сделаю enum private. И тут же получу ошибку.

Screen Shot 2015-02-06 at 9.21.59 PM

Не может быть не  private то, что использует  private. Свойство  opStack должно быть задекларировано как  private, потому что оно  private  в любом случае, как наша внутренняя структура.

В отношении свойства knownOps ситуация более интересная. Возможно, когда-нибудь, я захочу сделать его public, потому что я захочу, чтобы люди учили   CalculatorBrain использовать новые операции. Это было бы разумно. Но сейчас я не готов поддерживать эту функциональность в моем CalculatorBrain. Пока я не уверен в отношении обучения новым операциям. Тогда я начинаю с private. Это общее правило при конструировании  APIs: начинать лучше с того, что сделать что-то private, а затем открывать это по мере того, как вы собираетесь расширять функциональность.

ВОПРОС : Является ли общей практикой в Swift делать большую часть переменных экземпляра класса private, а иметь  getters и setters, которые сделать public?

ОТВЕТ: В Swift это не имеет значения. Это больше подходит для  элементов Objective-C. В Objective-C у вас есть переменные экземпляра класса (instance variables), и соответствующие им свойства (properties), которые могут получать и устанавливать значения переменным экземпляра класса. В Swift все это более плотно «сплетено» друг с другом, все это в «объединенном» (merged) виде так, что в действительности является  public или private getter и setters. Так что, если у вас есть свойство ( property), вам нужно только решить, действительно ли вы хотите дать людям доступ к этому свойству, или хотите сделать его приватным. Если вы решили предоставить доступ — делайте это public. Или делайте не private, то есть доступным внутри программы — ну, вы знаете. И это все, о чем вам надо подумать. Я бы не стал думать в Swift в терминах переменной экземпляра класса ( instance variable) против свойств ( property). Да это вряд ли возможно в Swift, потому что у вас есть вычисляемые свойства (computed properties). Кроме того, могли бы быть  setters и getters, которые устанавливают private свойства. Вы увидите это в Среду, когда я буду говорить немного о способе, который, когда вы получаете или устанавливаете свойства, позволяет вам вмешиваться в этот процесс и вы можете сделать необходимые проверки правильности значения при установки значения свойства. Это позволяет иметь некоторую защиту. Ну, вы увидите.

Вот как выглядит теперь наш класс.

Screen Shot 2015-02-06 at 10.30.57 PM

Но есть еще одна вещь, которую мы должны сделать внутренним  public (без какого-либо ключевого слова) в нашем CalculatorBrain. Мы должны позволить людям оценивать этот стэк. Итак, люди помещают операнды в стек, выполняют операции, но они хотят знать результат.
Они помещают операнд «5«, затем «4«, они выполняют операцию + , но они хотят видеть результат 4 + 5 = 9.   Это «9«. Им нужен результат.
Следовательно нам нужен еще один public метод, и я назову его  evaluate.

func evaluate() -> Double? {

Мы заставим его вернуть, например,  Double. Он бы просто оценивал наш opStack и возвращал значение. Но я не могу сделать его просто Double. В действительности он должен быть Optional , то есть  Double?. Потому что кто-то может взять наш  CalculatorBrain, и сделать сразу же такую вещь : выполнить операцию + . Хорошо, но у меня нет операндов, а вы хотите оценить + . Я не могу. Я должен вернуть что-то, что скажет вам,что я не могу оценить то, что вы меня просите. Я собираюсь вернуть nil в этом случае. Следовательно, функция evaluate должна возвращать Optional. Вы должны всегда думать, нужно ли возвращать Optional или нет. У нас классический случай, когда вы получаете что-то, вы должны вернуть это, а если нет, то вы возвращаете  nil.

Как мы будем реализовывать evaluate? Поднимите руки, кто знаком с такой концепцией информатики, как рекурсия? Прекрасно, 90%. Но я все же очень быстро это объясню.

Как мы собираемся использовать рекурсия для оценки стека?
Допустим, что наш стек выглядит так

х
4
+
5
6

И я начинаю его оценивать, используя рекурсию. Мне нужен некоторый вспомогательный метод (helper method), который я тоже назову  evaluate. Он берет стек, как аргумент, стек вещей для оценки, но он оценивает только вершину стека. Но вершина стека может вовлекать другие элементы стека.

Мы нашли «х» — ставим символ  «х» в результат оценки, эта операция  требует двух операнд ов и evaluate рекурсивно будет вызывать сам себя дважды. Сначала для получения 1-го операнда умножения. Стираем из стека  «х».

Нашли  «4» — ставим на место 1-го операнда умножения в результирующем выражении. Стираем «4» из стека. Нужен второй операнд для умножения —  evaluate рекурсивно вызывает сам себя  для получения 2-го операнда умножения.

Нашли «+» — ставим скобки на место 2-го операнда  умножения и внутри новая операция — сложение, которая тоже требует двух операндов. Нужен первый операнд для сложения —  evaluate рекурсивно вызывает сам себя для получения 1-го операнда сложения. Стираем из стека  «+».

Нашли «5» — ставим на место 1-го операнда  сложения . Но нужен 2-ой операнд  сложения —  evaluate рекурсивно вызывает сам себя для получения 2-го операнда сложения. . Стираем из стека  «5». 

Нашли «6» — ставим на место 2-го операнда  сложения . Стираем из стека  «6».

Стек пустой. Оценка произведена.

Screen Shot 2015-02-07 at 6.43.48 AM

Вы видите — я должен поддерживать рекурсивный вызов для оценке моего стека. Если в стеке — число, то я могу остановиться, так как я получил число. Но если это операция, то она заставляет меня опять искать новые операнды и рекурсивно вызывать evaluate до тех пор, пока не получу все мои операнды.
В действительности это очень простая рекурсия.

Итак, у меня отдельный  evaluate, и он использует стек [Op] в качестве аргумента. Потому что как только мы вызываем его рекурсивно, он будет использовать «оставшийся» стек. Это не тот большой полный стек, который мы подаем на вход CalculatorBrain. Это стек, который мы укорачиваем при каждом рекурсивном вызове, как бы «потребляем» его часть. При каждом повторном  рекурсивном вызове в нем становится все меньше и меньше элементов. Но, конечно, при первом вызове  моего вспомогательного evaluate  на вход подается полный стек.

Screen Shot 2015-02-07 at 7.27.27 AM

Но что возвращает наш дополнительный evaluate?

Есть одна очень интересная вещь, о которой я попросил вас почитать — это кортежи ( tuples). И в Swift вы можете комбинировать множество разных вещей в одну, и создавать такую мини структуру данных — кортеж ( tuple). Все эти многочисленные вещи нужно поместить в круглые скобки — и все. Вы можете использовать кортеж (tuple) для возврата значениий. И в нашем дополнительном evaluate мне нужно вернуть две вещи.
Одна — это результат оценки, а другая — это остаток стека, который я еще не использовал. Потому что я работаю вниз по стеку. Я вынимаю из стека «х», «4», «5» и их использую, но у меня еще остается часть стека. И я должен вернуть, что осталось, чтобы поддерживать его дальнейшее «потребление».
Я буду возвращать эти две вещи ввиде кортежа ( tuple) и синтаксис очень простой — просто круглые скобки. Я буду возвращать  Double? —  это мой результат, и оставшийся стек  [Op]. И кортеж  ( tuple)  выглядит так: ( Double? ,  [Op] ).
Это не именованный кортеж  ( tuple).
Но я поставлю имена ( result: Double? ,  remainingOps: [Op] ).

Я очень рекомендую ставить имена в кортеже  ( tuple), и вы поймете это через секунду. Потому что есть два способа вызвать функцию, которая возвращает   кортеж  ( tuple) и получить результат  result.  Кроме того, именование — это хороший способ  документирования вашей функции. Кто-то посмотрит на result: Double? и будет точно знать, что это результат оценки стека, а remainingOps: [Op] — оставшаяся его часть.

Screen Shot 2015-02-07 at 7.58.59 AM——— 36-  я минута лекции ———

Продолжение находится здесь.

 

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

  1. Спасибо за материал! Особенно за разъяснения ситуации с перегрузкой из урока 2.

  2. Коллеги, я, конечно, все понимаю, по слово «СТЕК» пишется через «е». Исправьте, пожалуйста. Учим язык программирования, а родной язык не знаем.

    • Спасибо. Действительно, пишется «стек», хотя читается «стЭк».

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