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]
Код для операции «x» повторим для всех остальных операций
Но это очень плохой код — масса кода дублируется, и люди решат, что мы ужасные программисты.
Было бы неплохо иметь такую прекрасную функцию, аргументом которой была бы операция, выполняемая над операндами из стэка.
[js]
func performOperation (operation: …)
[/js]
То есть аргументом должна быть функция. Аргументы имеют имя, в нашем случае — это operation, а тип — что-то, что пока мы не знаем, но это такая маленькая операция типа функции.
Перенесем наш код в эту функцию
Заметим, что все переменные, определенные внутри класса, которые мы используем в функции performOperation, мы разделяем их (share) с другими функциями. Это operandStack и displayValue. Поэтому при переносе вызова этой функции в switch оператор, нам достаточно задать только операцию
Теперь понятно, что мы должны определить аргумент в функции performOperation как функцию с двумя аргументами типа Double на входе и возвращающую тоже Double. Можно ли в Swift задать тип как функцию? Да, типы в Swift могут быть функциями как обычные типы. То есть в нашем случае тип — это функция. Как я должен специфицировать тип как функцию? Как обычную функцию без названия — только типы: (Double, Double) -> Double. Это и есть тип функции.
Давайте определим согласно этому типу функцию multiply
Ошибок нет. Запускаем. Вводим «6» ⏎ «5« «4» «х» .
Давайте перейдем к другим операциям. Мы могли бы написать еще функций типа divide, plus и т.д. и скопировать и вставить код из mutiply. Но это также ужасно, как и то, от чего мы уходили ранее. Но в Swift можно вставить непосредственно саму функцию как аргумент. То есть вместо multiply можно вставить просто код функции и не создавать отдельную функцию. Но при вставке функции нам нужно немного изменить синтаксис. Помещаем функцию в фигурные скобки, после декларации аргументов вставляем слово in для возврата значения и убираем функцию multiply из нашего кода.
Таким образом, вместо аргумента мы можем прямо написать функцию в фигурных скобках. Это называется, как и в других языках программирования, замыканием (closure) и оно позволяет писать функцию как аргумент. Все нормально, это работает.
Но мы можем сделать еще лучше. Вы знаете, что Swift замечательно работает с таким понятием, как «вывод типа», то есть определяет тип из контекста. И вы можете сделать здесь то же самое. Если вы посмотрите на функцию performOperation, то она знает, что ее аргументом является функция, которая берет два Doubles и возвращает Double. Поэтому реально при вызове вам не нужно опять их специфицировать. Вы можете избавиться от декларации всех типов.
И никаких предупреждений или сообщений об ошибках.
Но можно сделать еще лучше. Функция performOperation знает, что она что-то возвращает, поэтому можно убрать ключевое слово return, так как возвращается выражение. Далее еще интереснее: Swift не принуждает вас называть наши аргументы, если вы их не называете, то Swift использует свои имена : $0, $1, $2 и т.д.
Но есть еще одна замечательная синтаксическая возможность в Swift. Если у вас есть функция, и она имеет функцию в качестве аргумента, как performOperation, и этот аргумент является последним в списке аргументов, вы можете вынести эту функцию за круглые скобки. Если перед этим аргументом-функцией были другие аргументы, то они останутся в скобках, но если не осталось никаких других аргументов, то круглые скобки можно вообще убрать.
Примечание. Такая синтаксическая возможность Swift называется «хвостовое» замыкание (trailing closure). В дальнейшем профессор будет пользоваться ею повсеместно.
Мы сделали такой краткий код благодаря замечательным возможностям Swift, но то же самое можно сделать с другими операциями. Но нужно быть внимательными с порядком операндов, потому что при делении то, на что мы делим мы достаем из стэка последним. То же самое касается вычитания.
Вы видите, у нас получился очень компактный код, очень читаемый и очень мощный, так как мы можем еще добавлять функции с помощью одной строчки.
Посмотрим, как это работает. Вводим «6» ⏎ «5» ×, получаем 30, прекрасно. Как насчет «47″ + «77″, прекрасно. «2» ÷. Получаем 38.5.
Вы видите стэк, соответствующий этим действиям.
Но я собираюсь добавить еще одну операцию, которая немного отличается от предыдущих. Поэтому я добавлю еще правый ряд кнопок — они понадобятся для домашней работы, а я изменю только нижнюю на другой оператор — корень квадратный √.
Теперь я возвращаюсь к коду и добавляя еще одну позицию в наш оператор switch, я буду использовать тот же механизм, что и прежде.
И получаю ошибку, которая говорит о том, что мы вызываем performOperation с аргументом, который является функцией и она берет два аргумента, а у нас всего один , $0. Ошибка говорит об этом несовпадении. Мы сможем ее исправить.
Создаем еще одну функцию performOperation с одним аргументом. И, верите мне или нет, но ошибки исчезнут. 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
Не вдаваясь на этом этапе в подробности, для исправления ситуации в Swift 1.2 и Swift 2 достаточно сделать одну из этих функций (или обе) private и ошибка исчезает.
А для Swift 2 (Xcode 7) более логично использовать новый атрибут @nonobjc
Объяснение дается в специальном посте : Дополнение к Лекция 2 — особенности кода Calculator в Swift 1.2 или как не «застрять» на этом месте.
Запускаем и проверяем. «81» √. Получили 9. Водим «9» х √. Работает.
Последняя вещь, о которой я хочу рассказать, это Autolayout (Разметка)
———— 44 минута лекции —-
Код для этой части на GitHub. (нужно добавить private для одной из двух функций performOperation (…))
Продолжение следует…
Thanks a lot!
Спасибо большое за эти переводы! Только начал изучать, до этого написал сам калькулятор, но с использованием возможностей этого языка всё гораздо понятнее и короче!
Запускаем и проверяем. «36» √. Получили 9. Водим «9» х √. Работает.
замените 36 на 81 плиз, потому что 9 в квадрате — 81, и сбивает это 36
Спасибо.
> Создаем еще одну функцию 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
Огромное спасибо, что заметили это — сделаю комментарий в посте.
На предпоследней картинке 2 функции.
Достаточно сделать один (или оба) из этих методов private
private func performOperation (operation: (Double, Double) -> Double ){…
и проблема «уйдет».
Будем заставлять работать Swift.
Спасибо!
Я запомню навсегда: если xcode чето гонит про objective-C, пишу: @nonobjc )))
Я бы так не делала. Как нужно делать написано в ответе на предыдущий ваш вопрос.