Cocoa, Swift, Objective-C: Взаимодействие swift с Objective-C

Подразделы:

Как использовать совместно Objective-C и swift из одного App Target

Если мы пишем обычное приложение и хотим сделать импорт Objective-C для работы в swift необходимо создать Objective-C bridging header (связующий заголовок). Как только вы пытаетесь добавить Objective-C файл, XCode сам предложит это сделать.

bridge header

Если мы согласимся — будет создан файл <Имя модуля>-Bridging-Header.h
В моем случае программу для проверки работы swift я назвал SwiftTest, соответственно файл был автоматически назван. Если в имени модуля присутствуют пробелы и прочие нецифробуквенные символы — их надо заменить на символ подчеркивания _
SwiftTest-Bridging-Header.h
Но можно создать такой файл и вручную File > New > File > (iOS or OS X) > Source > Header File

Чтобы проверить работ создадим тестовый Objective-C класс ObjcTestClass.
Создадим файл заголовка ObjcTestClass.h и поместим туда код

И файл с исходным кодом ObjcTestClass.m

В SwiftTest-Bridging-Header.h же вносим строку импорта данного класса

После этого мы можем из swift делать вызов стандартным

Если пишет что не может найти данный класс — нужно проверить прописан ли путь к Objective-C связующему заголовку в Build Settings, в моем случае это SwiftTest/SwiftTest-Bridging-Header.h

path - bridge header

Если же нужно использовать ваши swift классы в Objective-C — эта задача возлагается на Xcode-generated header — автоматически генерируемый Objective-C заголовок имеющий имя <Имя модуля>-Swift.h Скажу сразу он генерируется во временной папке, и в проекте его не видно и не надо его создавать руками.
В общем в итоге раздел Swift Compiler — Code generation должен выглядеть как то так

Swift Compiler - Code Generation

Причем лучше все опции проставить до того как начнете добавлять swift файлы.

По умолчанию генерируемый заголовок содержит все swift public declarations (объявления). Если присутствует Bridging-Header то и internal declarations. Private declarations присутствуют только если они помечены с помощью @IBAction, @IBOutlet или @objc.


Начиная со swift 2.0 — если в проекте включена поддержка юнит тестов — unit test target получает доступ ко всем internal определениям словно они определены как public, с помощью @testable атрибута при импорте нашего модуля


Стоит так же отметить — если наш swift класс использует Objective-C классы — то необходимо делать import заголовков этих классов в .m файле который будет использовать наш swift класс. Запутано, поэтому на примере.

Класс SwiftClass — использует ObjcClass — который соответственно написан на Objective-C
Если мы создадим Objective-C класс ContainerObjcClass в котором будем использовать наш SwiftClass — мы должны будем добавить в нем импорт ObjcClass

Для проверки я создал класс SwiftTestClass

Для того чтобы swift класс стало видно в Objective-C можно пометить этот класс атрибутом @objc, но тогда будет недоступен alloc, так что если нет причин делать по другому — проще всего унаследовать наш Swift класс от NSObject (это автоматически помечает класс атрибутом @objc, плюс предоставляет доступ к стандартной инфраструктуре создания объекта).

Содержимое ObjcTestClass.h

Проверить можно в тестовых целях напрямую из ViewController

Подытожим для случая когда и swift код и Objective-C находятся в одном target‘е

В swift коде использовать swift классы можно без всяких импортов
В swift коде использовать Objective-C классы можно без импортов, но должен быть сформирован с нужными import‘ами Bridging-Header
В Objective-C коде использовать swift классы можно после добавления импорта #import «ProductModuleName-Swift.h» (не забываем добавить import для всех Objective-C заголовков если они были использованы в swift файле)
В Objective-C коде использовать Objective-C классы с помощью стандартного импорта
#import «ObjcClassHeader.h»

Как использовать совместно Objective-C и swift из одного Framework Target

При написании фреймворка так же бывает необходимость смешивать swift и Objective-C

Чтобы использовать Objective-C в swift необходимо
1) Defines Module выставить в YES
2) В umbrella header file (он создается автоматом для framework типа проекта) добавляем все нужные swift классы

По сути то же самое что и в случае App Target‘а, но добавляем не в Bridging-Header а в umbrella header

Чтобы использовать swift в Objective-C необходимо
1) Defines Module выставить в YES
2) В .m файл добавить импорт заголовочного файла сгенерированного XCode
#import

Как и в App Target, в заголовочный файл сгенерированного XCode’ом добавляются public объявления. Internal только если swift класс был унаследован от Objective-C

Подведем итоги

В swift коде использовать swift классы можно без всяких импортов
В swift коде использовать Objective-C классы можно без импортов, но должен быть сформирован с нужными import‘ами umbrella header
В Objective-C коде использовать swift классы можно после добавления импорта #import «ProductName/ProductModuleName-Swift.h» (не забываем добавить import для всех Objective-C заголовков если они были использованы в swift файле)
В Objective-C коде использовать Objective-C классы с помощью стандартного импорта
#import «ObjcClassHeader.h»

Использование внешних библиотек
В swift командой

В Objective-C командой

При использовании swift из Objective-C мы не можем пользоваться специфическими фишками языка swift

Generics (Универсальные шаблоны) (но вроде скоро появятся)
Tuples (Кортежи)
— Перечисления которые объявлены без Int raw value type (Int в качестве сырого значимого типа, подробнее Enumerations (Перечисления))
— структуры созданные в swift
— функции верхнего уровня созданные в swift
— глобальные переменные созданные в Swift
typealias созданные в Swift
— параметры с переменным числом возможных значений (swift-style variadic)
— вложенные типы
— curried function (каррированые функции)
Так же нельзя наследовать swift класс в Objective-C

Чтобы избежать циклических зависимостей — не стоит импортировать swift код в Objective-C заголовочные файлы (.h). Достаточно просто их обьявить

Классы и протоколы объявленные таким образом могут быть использованы лишь для определения методов и свойств.

Objective-C класс вполне может реализовать swift протокол


В swift 2.0 ввели макрос NS_SWIFT_NAME позволяющий исправить ситуации некорректного импорта Objective-C кода, когда не распознается фабричный метод класса

или наоборот распознается как таковой, но им не является.

Так же этот макрос помогает избежать обрезания значений перечисления

К сожалению в XCode 7 beta этот код выдает ошибку
parameter of ‘swift_name’ attribute must be an ASCII identifier string
Думаю к релизу исправят

Naming Your Product Module (именование модуля продукта)

В этой главе часто встречалось ProductModuleName, есть один нюанс. По умолчанию — имя модуля совпадает с именем продукта. Но если в имени продукта есть пробелы или любые не цифробуквенные символы 0 их надо заменить на символ подчеркивания _
Так же можно изменить Product Module Name указав его в настройках проекта изменив Product Module Name

Подводные камни

— Не забываем о возможных конфликтах имен между swift и Objective-C частями кода
— если работаем с framework‘ами, не забываем выставить опцию Defined Module в YES
— Если нужен Bridging-Header, убедитесь что он существует и путь к нему проставлен в настройках (Objective-C Bridging Header)
— XCode использует product module name а не target name
— чтобы использовать swift класс в Objective-C он должен быть помечен атрибутом @objc или унаследован от NSObject (что автоматически его так помечает)
— при использовании swift кода в Objective-C не забываем что не все возможности доступны
— если ваш swift класс использует Objective-C классы — сделайте их импорт в Objective-C класс использующий ваш Swift класс
Swift private объявления недоступны в Objective-C, если только не помечены атрибутами @IBAction, @IBOutlet, или @objc
— для app targetinternal объявления доступны в Objective-C если в app target присутствует Bridging-Header
— для framework target появляется возможность использовать internal свойства и методы только если они определены в классе унаследованного от Objective-C класса.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *