Если вы приступаете к изучению Лекции 2 (1-ая часть, 2-ая часть и 3-я часть) с загруженными Swift 1.2 и Xcode 6.3, а не Swift 1.1 и Xcode 6.2, как у профессора Пола Хэгерти, то ближе к концу лекции (конец 2-ой части) вы получите ошибку, которой у профессора нет. Это может сильно «притормозить» ваш процесс обучения. Для того, чтобы этого не произошло, я расскажу, чем отличается ситуация, которую демонстрирует профессор, в Swift 1.1 и в Swift 1.2. Профессор Пола Хэгерти хочет показать такую возможность Swift как перегрузка (overloading) функций с одинаковыми именами на примере функций performOperation для операций с одним и двумя аргументами
Его «задумка» в Swift 1.1 сработает, и он не получит никакой ошибки, а вы в Swift 1.2 — получите ошибку
Method 'perform' with Objective-C selector 'perform: ' conflicts with previous declaration with the same Objective-C selector
которая говорит о том, что «Метод perform c Objective-C селектором perform: конфликтует с предыдущим определением того же самого Objective-C селектора».
Позвольте, ну причем здесь Objective-C? Я пишу на Swift! А в Swift разрешена перегрузка функций. Разъяснение дается в Xcode Release Notes для Xcode 6.3 и Swift 1.2.
«Теперь Swift обнаруживает несоответствие между перегрузкой (overloading) и переопределением (overriding) в системе типов Swift и эффективным поведением, рассматриваемым на этапе Objective—C runtime.
Например, диагностируется следующий конфликт между Objective—C setter для “property” в этом классе и методом “setProperty” в его расширении»:
[objc]
class A : NSObject {
var property: String = "Hello" //Objective-C метод ‘setProperty:’
// уже декларирован setter для свойства’property’
}
extension A {
func setProperty(str: String) { } // ошибка: метод ‘setProperty’
// заново декларирует Objective-C метод ‘setProperty:’
}
[/objc]
И все-таки непонятно, откуда взялся Objective-C?
Все дело в том, что наш класс ViewController, который мы создали для интерфейса калькулятора, наследует от Cocoa класса UIViewCiontroller
Классам, которые является совместимыми с Objective—C объектами (такие классы используют @objc тэг или наследуют от существующих Cocoa классов, таких как NSObject или UIView), запрещено использовать перегрузку ( overloading) функций, так как они передаются на этапе runtime компилятору Objective—C, который вообще не поддерживает перегрузку ( overloading) функций.
Для устранения этой ошибки рекомендуют использовать внутри класса типы, которые не поддерживаются Objective—C, тогда это предотвратит передачу класса компилятору Objective—C на стадии runtime, а заставит функционировать компилятор Swift, в котором overloading (перегрузка с одинаковыми именами) функций разрешена.
Наиболее простой способ этого добиться — сделать хотя бы один из методов performOperation private.
Надо сказать, что в Лекции 3 мы перенесем все «вычислительные» методы в специальный класс CalculatorBrain, который является базовым и ни от чего не наследует. Там нет ограничений ни на перегрузку (overloading), ни на переопределение (overriding) функций, так что дальше мы с этой проблемой не встретимся.
Однако проблема не исчезла, она есть и сохранится в Swift 2.0.
На WWDC 2015 на сессия 401 «Swift and Objective-С Interoperability» (Взаимодействие Swift и Objective-C) на 6-ой минуте рассматривается именно наш пример с калькулятором и двумя методами с одинаковыми именами, но разными типами входных параметров.
Эта проблема связа с вопросом: «Когда Swift выставляет мой класс для использования в Objective-C?» То есть, когда Swift неявно используется атрибут @objc для всех методов и свойств моего класса? Компилятор Swift делает это автоматически вместо вас. В качестве ответа на этот вопрос я перевожу отрывок из WWDC 2015 сессии 401, ссылка на которую указана выше.
. . . . . . . . . . .
Давайте пройдемся по правилам представления Swift кода Objective-С, которые выполняются по умолчанию.
Если у вас есть класс , который является subclass NSObject, то по умолчанию, все его методы и свойства неявно выставляются компилятором Swift для использования в Objective-C.
Если вы пометите метод как private, что означает видимость этого метода только в пределах текущего файла, то Swift не будет выставлять этот метод Objective-C по умолчанию.
Если вы используете что-то очень специфическое только для Swift, например, необычный возвращаемый тип, который нельзя представить в Objective-C, то Swift даже не будет пытаться представить этот метод Objective-C.
Наконец, если вы не subclass NSObject, но просто подтверждаете Objective-C протокол, то вы должны очень внимательно отнестись к тому, чтобы ваши методы удовлетворяли всем требованиям протокола. И в Xcode 7 вы получите предупреждение о том, что этот метод является non-objc и не удовлетворяет требованиям @objc протокола.
Сказанное до сих пор относится к случаю по умолчанию.
Что произойдет, если вы захотите изменить поведение выставления Swift кода Objective-С по умолчанию?
Если вы хотите выставить какой-то особенный метод или свойство Objective-C, вам нужно использовать старый добрый атрибут @objc без какой-либо дополнительной семантики.
Все эти правила вы можете прочитать в руководстве «Using Swift with Cocoa and Objective-C», которое доступно на developer.apple.com/Swift.
Итак, мы разобрали, как ведет себя Swift по умолчанию при выставлении классов, свойств и методов Objective-C, мы разобрали, что нужно сделать, чтобы переопределить поведение по умолчанию и сделать некоторые вещи более ObjC.
А как нам быть, если нужно решить противоположную проблему? Давайте посмотрим на класс СalculatorСontroller.
У него два метода с именем performOperation. Один из этих методов берет один тип замыкания, другой метод — другой тип замыкания. Это нормально. Swift в состоянии различить эти методы, просто основываясь на различных типах аргумента. Но Objective-C так не работает. В Objective-C методы различаются только по именам, не по типам.
В нашем случае Swift знает, что это может быть ошибка и он дает вам сообщение об ошибке.
Теперь мы вынуждены всегда исправлять эту ошибку, используя, например, @objc атрибут в форме селектора.
Написав этот код, я переименовал нижний метод в performBinaryOperation, если я буду использовать его в Objective-C. Верхний метод все еще называется performOperation.
Примечание. Мы знаем, что эту проблему можно решить проще, сделав один или оба метода private, и тем самым предотвратив неявное появление атрибута @objc у соответствующего метода.
В Xcode 7 мы добавили другую опцию для разрешения этой проблемы, которая представляет собой атрибут @nonobjc .
Как можно ожидать из названия, берется то, что в нормальном режиме выставляется Objective-C и предотвращается это выставление Objective-C.
Вы можете применять атрибут @nonobjc к любому методу, свойству, инициализатору или подписке (subscript).
Ну раньше то работало, у профессора, например.
Без всяких этих костылей.
В чем же соль, зачем было менять в худшую сторону?
Не было такого вопроса на WWDC 2015 на сессия 401 ?
Во-первых, сессия 401 WWDC 2015 5-ая минута и далее — рассматривается именно наш калькулятор. Если вы посмотрите внимательно на класс ViewController, то увидите, что он наследует от UIViewController, а в конечном итоге от NSObject, что резко повышает вероятность использования его в Objective-C, поэтому в Swift 1.2 (а профессор читал лекции для Swift 1.0, а сейчас Swift 2.2 и 3.0 на подходе) было решено, что по умолчанию все методы в классе, который в конечном итоге наследует от NSObject, рассматривать как представимые в Objective-C без каких-либо дополнительных «пометок». Если же в методе присутствует синтаксис, специфический для Swift, то этот метод не представим в Objective-C. Однако каждый метод (performOperation) в отдельности может быть представим в Objective-C, но наличие нескольких методов с одинаковыми имена, но разной сигнатурой допустимо только в Swift. Именно об этом компилятор вас и предупреждает и просит указать явно, что эти методы не будут экспонироваться в Objective-C.
Но все приведенное выше, еще раз повторяю, относится только к классам, наследуемым от Objective-C.
Для любого чистого Swift класса этой проблемы нет вообще. Если вы посмотрите Лекцию 3 и Задание 2, когда мы имеем дело с чистым Swift классом CalculatorBrain, то там множество функций с одинаковыми именами и проблема не возникает.
Так что просто будьте более внимательны с классами, наследуемыми от Objective-C.
Хорошо, большое спасибо!
Эта проблема связа с вопросом: «Когда Swift выставляется мой класс для использования в Objective-C?»
Исправьте! Эта проблема связана с вопросом: «Когда Swift выставляет мой класс для использования в Objective-C?»
Спасибо.