Про переведення проекту з Objective-C на Swift

Здравствуйте, шановні читачі.

Серед найбільш актуальних тем, які піднімалися на наших видавничих радах в останні півроку, особливе місце займає мова програмування Swift. При величезний інтерес до нього з боку західних розробників і при справжньому достатку книг на цю тему мова поки здається досить сирим. Тому, промацуючи грунт щодо затребуваності нової мови, пропонуємо познайомитися з постом чудового Метта Нейбурга, автора книги «Programming iOS 8: Dive Deep into Views, View Controllers, and Frameworks». Автор докладно описує переклад програми на новий эппловский мову, переконливо доводячи: «очі бояться — руки роблять», а гібридна збірка Objective-C і Swift аж ніяк не нагадує суміш французького з нижегородським.

Приємного прочитання і плідних експериментів.

Якщо у вас вже є додаток, написаний на мові Objective-C, спробуйте переписати його на Swift. Це відмінна можливість познайомитися з мовою Swift, поекспериментувати зі Swift і вирішити для себе, чи готові ви обрати Swift в якості своєї основної технології. Я встиг виконати таку міграцію на декількох реальних додатках, в цій статті хочу поділитися деякими спостереженнями, почерпнутими на даному досвіді.

Гібридні складання

Зрозуміло, вам не доведеться переводити на Swift відразу весь ваш код; набагато ймовірніше, що ви будете переписувати клас за класом. Як тільки ви додасте файл Swift в збірку вашої програми, написаного на Objective-C, ця збірка стане гібридної. Деякі класи в ній залишаться на Objective-C, інші будуть написані на Swift. Відповідно, необхідно зробити так, щоб всі оголошення були видимі в коді на обох мовах. Перш, ніж приступити до роботи, давайте розберемося, як функціонує механізм такої видимості.
Як ви пам'ятаєте, оголошення класу на мові Objective-C зазвичай ділиться на дві частини: заголовковий файл (.h), що містить розділ interface, а також файл з кодом (.m), що містить розділ @implementation. Якщо у файлі .m потрібна інформація про класі, він імпортує файл .h цього класу.
Взаємна видимість коду Swift і Objective-C заснована на угоді: вона забезпечується на рівні файлів .h. Існує два напрямки видимості, кожне з них повинне бути розглянуто окремо.

Як Swift бачить Objective-C

При додаванні файлу Swift до збірки на Objective-C або файлу Objective-C складання на Swift, Xcode запропонує вам створити сполучний заголовок (bridging header). Це файл .h, що входить до складу проекту. За замовчуванням його ім'я проводиться від імені збірки, але взагалі воно довільне і може бути змінено, якщо ви аналогічним чином змініть настроювання «сполучний заголовок» і в збірці Objective-C.
Файл .h на Objective-C буде видно для Swift за умови, що ви імпортуєте його (#import) в цей сполучний заголовок.

Як Objective-C бачить Swift

Якщо у вас є сполучний заголовок, то при створенні вашої збірки відповідні оголошення верхнього рівня для всіх ваших файлів Swift автоматично переводяться на Objective-C і використовуються для створення прихованого сполучного заголовка в каталозі Intermediates даної збірки, який знаходиться глибоко в папці DerivedData. Цей прихований заголовок найпростіше переглянути за допомогою наступної команди, набираемой у вікні терміналу:

$ find ~/Library/Developer/Xcode/DerivedData-name "*Swift.h"


Так ви дізнаєтеся ім'я прихованого сполучного заголовка. В якості альтернативи спробуйте переглянути (або змінити) налаштування «Product Module Name» у вашої цільової складання; ім'я прихованого сполучного заголовка створюється на основі зазначеного тут імені продукту.

Оголошення Swift будуть видимі у ваших файлах Objective-C за умови, що ви імпортуєте (#import) даний прихований сполучний заголовок у всі ті файли Objective-C, де повинні бути видимі ці файли Swift.

Ситуація може значно змінитися від того, де саме у верхній частині файлу .m імпортується прихований сполучний заголовок. Поширений тривожний сигнал – поява помилок компіляції «Unknown type name» (Невідоме ім'я типу), де невідомий тип — це клас, оголошений на мові Objective-C. Щоб вирішити цю проблему, потрібно імпортувати файл .h, що містить оголошення невідомого типу, і в ваші файли на Objective-C, причому до імпорту прихованого сполучного заголовка. Така робота іноді дратує, особливо якщо файлу Objective-C, про який йде мова, немає необхідності знати про це класі, але таким чином ми дійсно вирішуємо проблему, після чого компіляція може бути продовжена.

Покрокові інструкції

Перш, ніж вносити які-небудь зміни, створимо нову гілку в git. Тепер переведемо наші класи з Objective-C на Swift, по одному. Я буду робити це за наступним алгоритмом:

  1. Виберіть файл .m, який потрібно перевести на Swift. Мова Objective-C не може успадковувати від класу Swift, тому вам доведеться визначити на Objective-C як підклас, так і сам клас, починаючи з підкласу. Клас делегата додатки переводиться в останню чергу.
  2. Видаліть цей файл .m із цільової складання. Для цього виділіть файл .m і скористайтеся інспектором файлів.
  3. У всіх файлах Objective-C, які імпортують відповідний файл.h, видалите затвердження #import, а на його місце імпортуйте прихований сполучний заголовок. (Якщо ви імпортуєте прихований сполучний заголовок в цей файл, то повторювати цю процедуру не потрібно.)
  4. Якщо ви імпортуєте відповідний файл .h в сполучний заголовок, видалите затвердження #import.
  5. Створіть файл .swift для даного класу. Переконайтеся, що він доданий до цільової збірці.
  6. У файлі .swift оголосіть клас і поставте оголошення-заглушки для всіх членів, які були зроблені публічними у файлі .h. Якщо цей клас повинен відповідати протоколами Cocoa, прийміть їх; можливо, вам також знадобиться надати оголошення-заглушки для всіх обов'язкових методів цього протоколу. Якщо цей файл повинен посилатися і на будь-які інші класи, які раніше оголошені у вашої цільової збірці на мові Objective-C, імпортуйте їх .h-файли в сполучний заголовок.
  7. Тепер проект має скомпилироваться! Зрозуміло, він не працює, так як у вашому файлі .swift ще немає жодного справжнього коду. Але хіба це проблема? Отже, пивка для ривка!
  8. Тепер запишемо код у нашому файлі .swift. Я віддаю перевагу переводити код з оригінального файлу Objective-C порядково, нехай результат виходить і не надто идиоматическим (свифтовским).
  9. Коли код даного файлу .m буде повністю переведений на Swift, зберіть його, запустіть і протестуйте. Якщо середовище виконання починає лаятися (тим більше, якщо вона при цьому валиться), що не може знайти цей клас, відшукайте всі посилання на нього в nib-редакторі) і введіть ім'я класу в інспекторі ідентичності (а також натисніть Tab, щоб встановити зміну). Всі збережіть і спробуйте знову.
  10. До наступного .m-файлу! Повторіть всі вищевказані кроки.
  11. Коли всі інші файли будуть переведені, переведіть клас делегата програми. На даному етапі, якщо в складанні цільової не залишилося файлів на Objective-C, то можна видалити файл main.m (замінивши його атрибутом @UIApplicationMain attribute in the app delegate class declaration) і файл .pch (попередньо скомпільовані заголовок).


Не думайте, що зобов'язані переводити весь код Swift. Цілком можливо, що деякі розділи буде краще залишити на Objective-C, це абсолютно нормально. Насправді, деякий код необхідно залишити на Objective-C, так як в Cocoa API є деталі, до яких Swift не має доступу. Наприклад, можна написати на Swift функцію C або вказівник на функцію, тому ви не зможете викликати CGPatternCreate або AudioServicesAddSystemSoundCompletion без допоміжного методу Objective-C. Метод appearanceWhenContainedIn: також можна викликати з Swift.

З іншого боку, код, що використовує один з методів performSelector:, які також доступні на Swift, можна відразу залишити на Objective-C, але рано чи пізно вам доведеться придумати який-небудь обхідний маневр, щоб ці методи можна було замінити кодом на Swift.

Поекспериментуємо з Swift

Вітаємо! Тепер ваш додаток цілком або частково написано на Swift. Але якщо ви виконували всі мої рекомендації, то на даному етапі додаток ще не надто «свіфтівська». Адже ми в першу чергу прагнули запустити наш код. Тепер, коли все працює, можна повернутися до самого коду і постаратися зробити його більш идиоматическим. Можливо, ви виявите, що якісь завдання, які вирішувалися на Objective-C хитромудро або незграбно, набагато чистіше і симпатичніше реалізуються на Swift

Зрозуміло, скрізь, де тільки можна, слід перейти на нативні типи Swift. Пари незмінних / змінюваних типів, наприклад, NSString і NSMutableString, NSArray і NSMutableArray, а також NSDictionary і NSMutableDictionary можна замінити власними типами Swift: String, Array і Dictionary. Ви також знайдете, що більше не потребуєте в деяких складних прийомах, які були пов'язані із цими типами. Так, масив володіє методами примірника map, filter і reduce; вони вам дуже знадобляться.

Наприклад, в одному моєму додатку був табличний вигляд, де виводилися дані, розділені по секціях. На внутрішньосистемному рівні ці дані записані в масиві масивів, де кожен подмассив складається з рядків, що представляють собою табличні ряди в тій чи іншій секції. В таблиці можна виконувати пошук, і тепер я хочу знайти та видалити ті рядки, в яких відсутній рядок, уведений користувачем в поле для пошуку. Самі секції я чіпати не збираюся, але якщо при видаленні рядків якась секція повністю спорожніє, то я хочу повністю видалити весь масив даної секції. Ось як це робилося в Objective-C (sb — це UISearchBar):

NSPredicate* p = [NSPredicate predicateWithBlock:

^BOOL(id obj, NSDictionary *d) {

NSString* s = obj;

NSStringCompareOptions options = NSCaseInsensitiveSearch;

return ([s rangeOfString:sb.text

options:options].location != NSNotFound);

}];

NSMutableArray* filteredData = [NSMutableArray new];

for (NSArray* arr in self.sectionData) {

NSArray* filteredArr = [arr filteredArrayUsingPredicate:p];

if (filteredArr.count)

[filteredData addObject: filteredArr];

}

self.filteredSectionData = filteredData;


Спочатку формуємо NSPredicate, щоб відфільтрувати масив. Потім циклічно перебираємо наш масив масивів, «роздаючи» компоненти кожного відфільтрованого подмассива в порожній NSMutableArray, один за іншим; впевнений, ви знайомі з цією ідіомою. В Swift ж нам не потрібно ні NSPredicate, ні ідіоми «роздачі» — так само як і двох проміжних масивів! У нас є map та filter, вся робота вкладається в єдине твердження:

self.filteredSectionData = self.sectionData.map {

$0.filter {

let options = NSStringCompareOptions.CaseInsensitiveSearch

let found = $0.rangeOfString(sb.text, options: options)

return (found != nil)

}

}.filter {$0.count > 0}


Ви також помітите, що тих місцях, де ваш код спирався на динаміку Objective-C, пов'язану з пересиланням повідомлень, ви зможете обійтися без цього механізму, так як в Swift функція є об'єктом першого класу.

Розглянемо приклад.
В моєму додатку зі словниковими картками у мене є клас Term, що представляє латинське слово. Він оголошує безліч властивостей. Кожній картці відповідає один термін, а кожна з його властивостей відображається в окремому текстовому полі. Коли користувач натискає на будь-який з текстових полів на екрані, я хочу, щоб актуальний термін в інтерфейсі змінився на наступний – відрізняється від попереднього тим властивістю, яке вибрав користувач. Відповідно, код для всіх трьох текстових полів буде однаковим; вся різниця полягає в тому, за яким саме властивості ми будемо підбирати наступний термін для виводу на екран.

В Objective-C найпростіший спосіб вираження такого паралелізму полягав у використанні пар ключ-значення (g — це розпізнавач жестів натискання):

NSInteger tag = g.view.tag; // the tag tells us which text field was tapped

NSString* key = nil;

switch (tag) {

case 1: key = @"lesson"; break; 

case 2: key = @"lessonSection"; break;

case 3: key = @"lessonSectionPartFirstWord"; break;

}

// отримуємо актуальне значення відповідної змінної примірника

NSString* curValue = [[self currentCardController].term valueForKey: key];


Тепер я можу продовжувати використовувати пари ключ-значення в Swift; але для цього необхідно, щоб мій клас Term успадковував від NSObject, а він залежить від динаміки Objective-C / Cocoa — переведення рядків імена властивостей — чужої духом Swift. Виявляється, в Swift можна з легкістю реалізувати таку ж динаміку — перетворивши мітки у виклики методів — за допомогою простого масиву анонімних функцій:

let tag = g.view!.tag

let arr : [(Term) -> String] = [

{$0.lesson}, {$0.lessonSection}, {$0.lessonSectionPartFirstWord}

]

let curValue = arr[tag-1](self.currentCardController.term)


Висновок

Багато властивості Swift націлені саме на те, щоб ваш код з самого початку виходив більш надійним.

Поширена помилка при програмуванні на Objective-C — оголосити властивість екземпляра, але забути встановити його у вихідне значення; при відправці повідомлення до nil нічого не станеться, тому ви можете досить довго працювати, не помічаючи проблеми. Swift вимагає ініціалізувати всі властивості екземплярів. Далі ми підходимо до горезвісної суворої типізації Swift; в Objective-C цілком можна гадати, з елементів якого типу складається масив, але в Swift необхідно вказувати для масиву строго певний тип елемента. Не намагайтеся битися з цими рисами Swift, будьте вдячні за них! Якщо вам просто вдасться змусити ваш код на Swift скомпилироваться, ви вже чомусь навчитеся; швидше за все, ваш код буде правильним з тієї простої причини, що така коректність закладена в самій природі мови Swift.

Якщо ви сумнівалися, чи варто експериментувати зі Swift – не сумнівайтеся! Зараз саме час вивчати Swift і смакувати цю мову. У бета-версії середовища Xcode 6.3 вже доступна версія Swift 1.2, вона дозволяє переконатися, що ця мова уже сягнув достатньої зрілості. Swift – цікавий і простий мову, ви зможете досить швидко перевести на нього будь-який додаток, яке було написано на Objective-C. Можливо, ви навіть здивуєтеся, наскільки ясніше і простіше виявиться отриманий код.
Про затребуваність мови Swift

/>
/>


<input type=«radio» id=«vv66859»
class=«radio js-field-data»
name=«variant[]»
value=«66859» />
Потрібен переклад вищезгаданої книги Метта Нейбурга, бажано без скорочень і скоріше
<input type=«radio» id=«vv66861»
class=«radio js-field-data»
name=«variant[]»
value=«66861» />
Потрібен переклад іншої книги по Swift (посилання в коментарях)
<input type=«radio» id=«vv66863»
class=«radio js-field-data»
name=«variant[]»
value=«66863» />
Видавати російською мовою книгу по Swift поки передчасно

Проголосувало 32 людини. Утримався 21 людина.


Тільки зареєстровані користувачі можуть брати участь в опитуванні. Увійдіть, будь ласка.


Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.