Лекция 2 CS193P Winter 2015 — Больше Xcode и Swift, MVC. (часть 2)

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

 Лекция 2: Больше Xcode и Swift

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

Лекцию на английском языке и слайды модно найти на  iTunes название “2. More Xcode and Swift, MVC”.

Это продолжение: 2 — ая часть лекции (30 минута — 44 минута ), код для этой части на GitHub.

Начало: 1 — ая часть лекции (0 — 30 минут) — находится здесь, код для этой части на GitHub.

продолжение: 3 — ая часть лекции  (Autolayout , MVC) (44 минута — конец ) находится здесь, код для этой части на GitHub.

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

[js]
@IBAction func operate(sender: UIButton)
[/js]

Screen Shot 2015-02-02 at 3.57.22 PM

Код для операции «x» повторим для всех остальных операций

Screen Shot 2015-02-02 at 4.07.31 PMНо это очень плохой код —  масса кода дублируется, и люди решат, что мы ужасные программисты.

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

[js]
func performOperation (operation: …)
[/js]

То есть аргументом должна быть функция. Аргументы имеют имя, в нашем случае — это operation, а тип — что-то, что пока мы не знаем, но это такая маленькая операция типа функции.

Перенесем наш код в эту функцию

Screen Shot 2015-02-02 at 5.09.58 PM

Заметим, что  все переменные, определенные внутри класса, которые мы используем в функции   performOperation,  мы разделяем их (share) с другими функциями. Это operandStack и displayValue. Поэтому при переносе  вызова этой функции в switch оператор, нам достаточно задать только операцию

Screen Shot 2015-02-02 at 5.42.24 PM

Теперь понятно, что мы должны определить аргумент в функции performOperation как функцию с двумя аргументами типа Double на входе и  возвращающую тоже  Double. Можно ли в Swift задать тип как функцию? Да, типы в Swift могут быть функциями как обычные типы. То есть в нашем случае тип — это функция. Как я должен специфицировать  тип как функцию? Как обычную функцию без названия — только типы: (Double, Double) -> Double. Это и есть тип функции.

Screen Shot 2015-02-02 at 5.39.30 PM

Давайте определим согласно этому типу функцию multiply

Screen Shot 2015-02-02 at 5.48.59 PM

Ошибок нет. Запускаем. Вводим «6» «5« «4» «х» .

Screen Shot 2015-02-02 at 6.10.53 PMРаботает.

Давайте перейдем к другим операциям. Мы могли бы написать еще функций типа divide, plus  и т.д. и скопировать и вставить код из mutiply. Но это также ужасно, как и то, от чего мы уходили ранее.   Но в Swift можно вставить непосредственно саму функцию как аргумент. То есть вместо multiply  можно вставить просто код функции и не создавать отдельную функцию. Но при вставке функции нам нужно немного изменить синтаксис. Помещаем функцию в фигурные скобки, после декларации аргументов вставляем слово in для возврата значения и убираем функцию multiply из нашего кода.

Screen Shot 2015-02-03 at 11.31.24 AM

Таким образом, вместо аргумента мы можем прямо написать функцию в фигурных скобках. Это называется, как и в других языках программирования, замыканием (closure) и оно позволяет писать функцию как аргумент. Все нормально, это работает.

Но мы можем сделать еще лучше. Вы знаете, что Swift замечательно работает с таким понятием, как «вывод типа», то есть определяет тип из контекста. И вы можете сделать здесь то же самое. Если вы посмотрите на функцию performOperation, то она знает, что ее аргументом является функция, которая берет два Doubles и возвращает Double. Поэтому реально при вызове вам не нужно опять их специфицировать. Вы можете избавиться от декларации всех типов.
Screen Shot 2015-02-03 at 11.59.07 AMИ никаких предупреждений или сообщений об ошибках.

Но можно сделать еще лучше. Функция performOperation знает, что она что-то возвращает, поэтому можно убрать ключевое слово  return, так как возвращается выражение. Далее еще интереснее: Swift не принуждает вас называть наши аргументы, если вы их не называете, то Swift использует свои имена : $0, $1, $2 и т.д.
Screen Shot 2015-02-03 at 12.13.57 PMНо есть еще одна замечательная синтаксическая возможность в Swift.  Если у вас есть функция, и она имеет функцию в качестве аргумента, как performOperation, и этот аргумент является последним в списке аргументов, вы можете вынести эту функцию за круглые скобки. Если перед этим аргументом-функцией были другие аргументы, то они останутся в скобках, но если не осталось никаких других аргументов, то круглые скобки можно вообще убрать.

Примечание. Такая синтаксическая возможность Swift называется «хвостовое» замыкание (trailing closure). В дальнейшем профессор будет пользоваться ею повсеместно.

Screen Shot 2015-02-03 at 12.24.38 PM

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

Screen Shot 2015-02-03 at 12.36.00 PMВы видите, у нас получился очень компактный код, очень читаемый и очень мощный, так как мы можем еще добавлять функции с помощью одной строчки.

Посмотрим, как это работает. Вводим  «6»  «5» ×, получаем 30, прекрасно. Как насчет  «47″ + «77″, прекрасно. «2»  ÷. Получаем 38.5.
Screen Shot 2015-02-03 at 12.58.11 PM Вы видите стэк, соответствующий этим действиям.

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

Screen Shot 2015-02-03 at 1.40.12 PMТеперь я возвращаюсь к коду и добавляя еще одну позицию в наш оператор switch,  я буду использовать тот же механизм, что и прежде.
Screen Shot 2015-02-03 at 1.48.27 PM
И получаю ошибку, которая говорит о том, что мы вызываем  performOperation с аргументом, который является функцией и она берет два аргумента, а у нас всего один , $0. Ошибка говорит об этом несовпадении. Мы сможем ее исправить.
Создаем еще одну функцию performOperation с одним аргументом. И, верите мне или нет, но ошибки исчезнут.Screen Shot 2015-02-03 at 2.29.20 PM Swift  автоматически определяет, какую функцию вызвать в зависимости от количества аргументов, типа аргументов и т.д.. В Swift можно иметь функции с одинаковыми именами, но если они имеют разные аргументы. Тогда Swift выбирает подходящую, если сможет. Если не сможет подобрать нужную, то будет жаловаться.


Замечание. Лектор работал с Swift 1.1 и iOS 8.2. И ошибки действительно исчезали. Но в Swift 1.2 и Swift 2 этого не происходит. Наоборот, появляется ошибка

 Method 'perform' with Objective-C selector 'perform: ' conflicts with previous declaration with the same Objective-C selector

Screen Shot 2015-05-17 at 2.23.09 PM
Не вдаваясь на этом этапе в подробности, для исправления ситуации в Swift 1.2 и Swift 2 достаточно сделать одну из этих функций (или обе) private и ошибка исчезает.
Screen Shot 2015-05-17 at 2.46.04 PM
А для Swift 2 (Xcode 7) более логично использовать новый атрибут @nonobjc

Screen Shot 2015-10-14 at 8.22.33 AM

Объяснение дается в специальном посте Дополнение к Лекция 2 — особенности кода Calculator в Swift 1.2 или как не «застрять» на этом месте.


Запускаем и проверяем. «81»  √. Получили 9. Водим «9» х  √. Работает.
Screen Shot 2015-02-03 at 2.40.05 PMПоследняя вещь, о которой я хочу рассказать, это Autolayout (Разметка)

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

 Код для этой части на GitHub. (нужно добавить private для одной из двух функций performOperation (…))

Продолжение следует…

Лекция 2 CS193P Winter 2015 — Больше Xcode и Swift, MVC. (часть 2): 12 комментариев

  1. Спасибо большое за эти переводы! Только начал изучать, до этого написал сам калькулятор, но с использованием возможностей этого языка всё гораздо понятнее и короче!

  2. Запускаем и проверяем. «36» √. Получили 9. Водим «9» х √. Работает.
    замените 36 на 81 плиз, потому что 9 в квадрате — 81, и сбивает это 36

  3. > Создаем еще одну функцию performOperation с одним аргументом. И, верите мне или нет, но ошибки исчезнут.

    С выходом нового Swift компилятор теперь жалуется на две функции с одним именем:
    Method ‘perform’ with Objective-C selector ‘perform: ‘ conflicts with previous declaration with the same Objective-C selector
    Причина — Swift 1.2 is strict about checking type-based overloading of @objc methods and initializers, something not supported by Objective-C.
    Как выход — либо назвать вторую функцию с одним аргументом иначе, либо поставить
    private func performOperation
    (перед одной из этих функций)

    • Я не понимаю откуда у Вас две функции performOperation? Она одна func performOperation(symbol: String) -> Double?
      Не очень понятно о чем вы говорите.Причем здесь Objective-C? Фрагмент вашего кода с ошибкой можете привести?

      • У вас на предпоследней картинке они выделены красным — функция с одним аргументом и с двумя, performOperation.
        В следующей лекции, когда модель выделят в отдельный файл, функция будет одна, верно. А во второй лекции их еще две. Легко зависнуть, не понимая, почему у лектора все красиво получилось, а у меня ругается компилятор.

        • Да, это действительно связано со Swift 1.2.. Достаточно сделать один (или оба) из этих методов private
          private func performOperation (operation: (Double, Double) -> Double ){...
          и проблема «уйдет».
          Проблема в том, что UIViewController — это @objc класс, и имена в нем должны следовать правилам Objective-C.
          Рекомендуют использовать внутри класса типы, которые не поддерживаются Objective-C, тогда это предотвратит передачу класса компилятору Objective-C на стадии runtime, а заставит функционировать компилятор Swift, в котором overloading (перегрузка с одинаковыми именами) функций разрешена.
          В следующей лекции в классе CalculatorBrain тоже будут функции с одинаковыми именами
          func pushOperand(operand: Double) -> Double? {…
          func pushOperand(symbol: String) -> Double? { …
          , но компилятор ругаться не будет, так как класс CalculatorBrain — базовый (base) и ни от кого не наследует.
          Ответ найден на http://stackoverflow.com/questions/29457720/compiler-error-method-with-objective-c-selector-conflicts-with-previous-declara/29670644#29670644
          Огромное спасибо, что заметили это — сделаю комментарий в посте.

    • Достаточно сделать один (или оба) из этих методов private
      private func performOperation (operation: (Double, Double) -> Double ){…
      и проблема «уйдет».
      Будем заставлять работать Swift.

  4. Спасибо!
    Я запомню навсегда: если xcode чето гонит про objective-C, пишу: @nonobjc )))

    • Я бы так не делала. Как нужно делать написано в ответе на предыдущий ваш вопрос.

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