Переходимо на Swift 3 з допомогою міграційного «робота» в Xcode 8.1 та 8.2



Вже відомо, що
<i>Xcode 8.2</i>
буде останнім релізом, який підтримує перехідну версію
<i>Swift 2.3</i>
. Тому потрібно терміново подумати про міграції на
<i>Swift 3</i>
.

Я хочу поділитися деяким досвідом такої міграції на прикладі додатків, пов'язаних зі стенфордським курсом «Developing iOS 9 Apps with Swift», як демонстраційних (їх 12), так і отриманих в результаті виконання Завдань навчального курсу (їх 6 варіантів). Всі вони різної складності, але там є і малювання, і багатопоточність, і показ зображень з допомогою
<font color="#0000FF">ScrollView</font>
, і робота з сервером Twitter, і база даних Core Data і робота з хмарним сервісомCloud Kit, і карти Map Kit. І все це було написано на
<i>Swift 2.2</i>
(stanford.edu), а мені було необхідно перевести всі додатки на
<i>Swift 3</i>
. Конспект лекцій стенфордського курсу російською мовою можна знайти на сайті «Про стэнфордских лекціях», а код — для Swift 2.3Github і Swift 3 Github.

Якщо ви вирішили мігрувати на
<i>Swift 3</i>
,
<i>Xcode 8</i>
вам необхідно запустити інструмент міграції (своебразного «робота») c допомогою меню EditConvertto Current Swift Syntax:


Далі вам пропонують карту відмінностей між вихідним кодом
<i>Swift 2</i>
та кодом
<i>Swift 3</i>
, який згенерував цей «робот»:



Треба сказати, що міграційний «робот»
<i>Xcode 8.1</i>
та
<i>Xcode 8.2</i>
працює чудово порівняно з початковою версією в
<i>Xcode 8.0</i>
, з якою мені довелося починати. Новий міграційний «робот» — винахідливий і дуже розумний. На прикладі цієї карти відмінностей можна чудово вивчати, які зміни зазнали ті чи інші синтаксичні конструкції в
<i>Swift 3</i>
. Міграційний «робот» робить дуже велику роботу по заміні імен, сигнатури методів і властивостей, перетворюючи, якщо це необхідно, раніше звичайні властивості Generic (наприклад,
<font color="#0000FF">NSFetchRequest</font>
, який не є Generic
<i>Swift 2</i>
, але є таким у
<i>Swift 3</i>
). Він може замінювати новим кодом цілі «паттерни», наприклад, сінглтон, якщо він був виконаний старими засобами з допомогою
<font color="#0000FF"> dispatch_once(&onceToken)</font>
.

Нижче я покажу приклади цього. Міграційний «робот» діє за принципом «не нашкодь» і де тільки можна намагається підтримати працездатність наявного коду, навіть вставляючи додатковий код. Вам слід дуже уважно подивитися ці зміни і включити в список ті місця, код яких вам незрозумілий або видається неефективним і менш читабельним. Назвемо це списком завдань для уточнення коду.

Якщо ви згодні із запропонованими «роботом» перетвореннями, то ви їх зберігаєте і працюєте далі. Але, як і очікувалося, міграційний «робот» робить тільки частину роботи для отримання компилируемого код
<i>Swift 3</i>
. У додатках залишається 2-3 помилки і 3-4 попередження. Тому вашим наступним кроком буде відкриття навігатора «помилок і попереджень» (якщо вони є) і дослідження їх все одного за іншим:



Для більшості помилок і попереджень пропонуються способи вирішення, і, в основному, це правильні рішення:



Нам потрібно зробити «кастинг типу» для змінної
<font color="#0000FF">json</font>
, яка в
<i>Swift 3</i>
представлена «роботом»
<font color="#0000FF">Any</font>
, хоча ми працюємо з нею як з посилальної (reference) змінної. В результаті отримуємо:


Але іноді доводиться виправляти помилки. Відразу після роботи міграційного «робота» ми маємо помилку при ініціалізації «паралельної» черги (більш детально цей випадок розглядається нижче):



Замість двох рядків з помилкою додаємо один рядок з правильним кодом:



Іноді вам пропонується кілька варіантів вирішення проблеми, і ви можете вибрати будь-який:


Нам повідомляється, що неявно відбудеться примусове перетворення
<font color="#0000FF">String?</font>
<font color="#0000FF">Any</font>
. Це можна виправити трьома способами і тим самим прибрати це попередження:

  1. надати значення за умовчанням
  2. примусово «розгорнути»
    <font color="#0000FF">Optional</font>
    значення
  3. здійснити явний «кастинг» у
    <font color="#0000FF">Any</font>
    код
    <font color="#0000FF">as Any</font>
    .
Ми віддамо перевагу перший варіант і будемо використовувати пустий рядок " ", якщо вираз дорівнює
<font color="#0000FF">nil</font>
:


Взагалі міграція — це прекрасний привід для того, щоб більш широко поглянути на свій код і, можливо, поліпшити його.

В результаті роботи зі списком помилок і попереджень вам вдалося відкомпілювати програму і запустити тестовий приклад. Це дуже важливий етап міграції.

Тепер ви можете сфокусуватися на списку завдань на уточнення коду, який ви склали при перегляді карти відмінностей між двома версіями:
<i>Swift 2</i>
та
<i>Swift 3</i>
. Весь цей код технічно коректний, але він може бути надлишковим, або неефективним, або призводити до помилок виконання. Деякі з цих ситуацій мають загальний характер, а деякі — сильно залежать від специфіки вашого додатка.

Я поділюся деякими з них, з якими мені довелося зіткнутися при міграції додатків курсу «Developing iOS 9 Apps with Swift».

1. Потрібно повернути рівень доступу
<font color="#0000FF">fileprivate</font>
назад
<font color="#0000FF">private</font>
.
В процесі міграції на
<i>Swift 3</i>
всі рівні доступу
<font color="#0000FF">private</font>
замінюються на новий рівень доступу
<font color="#0000FF">fileprivate</font>
, тому що
<font color="#0000FF">private</font>
<i>Swift 2</i>
мав сенс саме
<font color="#0000FF">fileprvate</font>
.



Міграційний «робот» діє за принципом «не нашкодь», тому він замінив всі старі
<font color="#0000FF">private</font>
на нові
<font color="#0000FF">fileprivate</font>
, тобто розширив область доступу
<font color="#0000FF">private</font>
змінних і методів.


У більшості випадків це зайва обережність, і нам зовсім не потрібний рівень доступу
<font color="#0000FF">fileprivate</font>
, але ви повинні вирішити це самостійно у своїй команді розробників і виправити в ручному режимі.

Якщо ви розробляєте framework, міграційний «робот»
<i>Swift 3</i>
замінить всі рівні доступу
<font color="#0000FF">public</font>
, які були в
<i>Swift 2</i>
, на новий рівень доступу
<font color="#0000FF">open</font>
. Це стосується тільки класів.



В
<i>Swift 3</i>
:

  • <font color="#0000FF">open</font>
    клас доступний і може мати subclasses за межами модуля, в якому він визначений. Властивості і методи
    <font color="#0000FF">open</font>
    класу доступні і можуть бути перевизначені (overridable) за межами модуля, в якому визначено клас.

  • <font color="#0000FF">public</font>
    клас доступний, але не може мати subclasses за межами модуля, в якому він визначений. Властивості і методи
    <font color="#0000FF">public</font>
    класу доступні, але не можуть бути зумовлені (overridable) за межами модуля, в якому визначено клас.
Таким чином, рівень доступу
<font color="#0000FF">open</font>
— це те, що було
<font color="#0000FF">public</font>
в попередніх версіях
<i>Swift</i>
, а рівень доступу
<font color="#0000FF">public</font>
більш обмежений. Кріс Латтнер сказав в SE-0177: Allow distinguishing between public access and public overridability,
<i>Swift 3</i>
рівень доступу
<font color="#0000FF">open</font>
просто public, чим
<font color="#0000FF">public</font>
. Ще можна подивитися SE-0025 Scoped Access Level.


При міграції frameworks
<i>Swift 3</i>
ми не будемо повертати рівень доступу
<font color="#0000FF">open</font>
назад
<font color="#0000FF">public</font>
. Тут нас все влаштовує.

Взагалі ієрархія рівнів доступу в
<i>Swift 3</i>
так розташовується в порядку убування:

<font color="#0000FF">open</font>
<font color="#0000FF">public</font>
<font color="#0000FF">internal</font>
<font color="#0000FF">fileprivate</font>
<font color="#0000FF">private</font>


2. Ви не можете порівнювати
<font color="#0000FF">Optional</font>
значення
<i>Swift 3</i>
.
При автоматичної міграції з
<i>Swift 2</i>
на
<i>Swift 3</i>
іноді перед деякими класами з'являється код:


Справа в тому, що в
<i>Swift 2</i>
можна було порівнювати
<font color="#0000FF">Optional</font>
значення, наприклад, таким чином:


або так:


В
<i>Swift 3</i>
таку можливість прибралиSE-0121 – Remove Optional Comparison Operators) і для збереження працездатності такого коду
<i>Swift 3</i>
міграційний «робот» додає вищенаведений код, що, звичайно, зручно на початковому етапі переходу на
<i>Swift 3</i>
, але негарно, так як якщо у вас зустрічається порівняння
<font color="#0000FF">Optional</font>
значень в декількох розташованих в окремих файлах класах, то вищенаведений код додатися багаторазово. Цей код потрібно видалити, відразу виникне проблема, і вирішувати проблему треба на місці. Спочатку позбавляємося від
<font color="#0000FF">Optional</font>
з допомогою синтаксичної конструкції
<font color="#0000FF">if let</font>
, а потім проводимо необхідне порівняння. Наприклад, так:


або так:


3.
<i>Swift 3</i>
не забезпечує автоматичної сумісності (bridging) чисел з
<font color="#0000FF">NSNumber</font>
.
В
<i>Swift 2</i>
багато типів при необхідності автоматично поєднувалися («bridging«) з примірниками деяких subclasses
<font color="#0000FF">NSObject</font>
, наприклад,
<font color="#0000FF">String</font>
<font color="#0000FF">NSString</font>
або
<font color="#0000FF">Int</font>
,
<font color="#0000FF">Float</font>
,…
<font color="#0000FF">NSNumber</font>
. В
<i>Swift 3</i>
вам доведеться робити це перетворення явноSE -0072 Fully eliminate implicit bridging conversions from Swift). Наприклад,
<i>Swift 2</i>
ми мали код для перетворення числа в рядок:


В
<i>Swift 3</i>
після міграційного «робота» ми отримаємо помилку:


Від нас вимагають явного перетворення
<font color="#0000FF">Double</font>
<font color="#0000FF">NSNumber</font>
, і ми можемо використовувати два способи перетворення — з допомогою оператора
<font color="#0000FF">as</font>
:


або з допомогою ініціалізатор
<font color="#0000FF">NSNumber</font>
:


4. «Робот» не вміє перетворювати паралельні черзі, але прекрасно працює з
<font color="#0000FF">dispatch_once</font>

Наприклад, звичайний «патерн» асинхронного виконання коду на паралельній черзі
<font color="#0000FF">QOS_CLASS_USER_INITIATED</font>
з подальшим переходом на main queue для відображення даних UI
<i>Swift 2</i>
виглядає наступним чином:


Міграційний робот" перетворює цей код в код з помилкою і пропонує функцію
<font color="#0000FF">global(priority: qos)</font>
, яка буде скасована
<i>iOS 10</i>
:


Для того, щоб прибрати цю помилку, нам потрібно використовувати іншу функцію —
<font color="#0000FF">global (qos: .userInitiated)</font>
:


Зате міграційний «робот» чудово справляється з
<font color="#0000FF">dispatch_once</font>
, яка скасована в
<i>Swift 3</i>
, і її слід замінити або глобальної, або статичної змінної або константи.

Ось як виглядає код для одноразової ініціалізації фонової черги при вибірці даних з сервера Flickr.com
<i>Swift 2</i>
:



А це код
<i>Swift 3</i>
після роботи міграційного «робота»:



Ви бачите, що «робот» вийняв внутрішність сінглтона і оформив її у вигляді
<font color="#0000FF">lazy</font>
змінної
<font color="#0000FF">__once</font>
, яка представлена як виконується замикання, за це нас попереджають, що змінна
<font color="#0000FF">onceToken</font>
не використовується. Вона дійсно більше не потрібна, і ми прибираємо цю рядок:



5. Будьте дуже уважні з заміною методів типу
<font color="#0000FF">...оперативне</font>
, при переході на
<i>Swift 3</i>
.
<i>Swift 3</i>
повертається до угоди про найменування методів та функцій, яке було в
<i>Swift 1</i>
, тобто функції і методи іменуються в залежності від того, чи створюють вони «побічний ефект». І це чудово. Давайте наведемо кілька прикладів.

Спочатку розглянемо методи не мають «побічного ефекту», вони, як правило, іменуються Іменниками. Наприклад,

<font color="#0000FF">x.distance (to y) 
 
x = y.union(z)</font>


Якщо функції і методи мають «побічний ефект», то вони, як правило, іменуються імперативним Дієсловом в наказовому способі. Якщо я хочу, щоб масив
<font color="#0000FF">X</font>
був відсортований, то я скажу: «
<font color="#0000FF">X</font>
відсортуй (
<font color="#0000FF">sort</font>
) сам себе або
<font color="#0000FF">X</font>
додай (
<font color="#0000FF">append</font>
) до себе
<font color="#0000FF">Y</font>
»:

<font color="#0000FF">x.sort () 
 
x.append(y)
 
y.formUnion(z)</font>


Таким чином
<i>Swift 3</i>
групує методи за двома категоріями: методи, які виробляють дію за місцем — думайте про них як про Дієсловах — і методи, які повертають результат виконання певної дії, не зачіпаючи вихідний об'єкт — думайте про них як про Іменників.
Якщо НЕМАЄ закінчення «
<font color="#0000FF">ed</font>
», то все відбувається «по місцю»:
<font color="#0000FF">sort ()</font>
,
<font color="#0000FF">reverse ()</font>
,
<font color="#0000FF">enumerate ()</font>
. ЦеДієслова. Кожен раз, коли
<i>Swift 3</i>
модифікує метод додаванням закінчення «
<font color="#0000FF">ed</font>
» або «
<font color="#0000FF">ing</font>
»:
<font color="#0000FF">sorted ()</font>
,
<font color="#0000FF">reversed ()</font>
,
<font color="#0000FF">enumerated ()</font>
, то ми маємо обчислене значення. Це Іменники.

Ці досить безневинні правила викликають плутанину, якщо мова заходить про зміну методів сортування при переході від
<i>Swift 2</i>
на
<i>Swift 3</i>
. Справа в тому, що в
<i>Swift 2</i>
всі функції і методи, які працюють «по місцю», містять у своїй назві слово «
<font color="#0000FF">Оперативне</font>
», тому для сортування за місцем використовується функція
<font color="#0000FF">sortInPlace ()</font>
, а функція
<font color="#0000FF">sort ()</font>
в Swift 2 повертає відсортований масив. В Swift 3, як видно з вищенаведених прикладів,
<font color="#0000FF">sort ()</font>
перейменовано в
<font color="#0000FF">sorted ()</font>
, а
<font color="#0000FF">sortInPlace ()</font>
<font color="#0000FF">sort ()</font>
.

В результаті метод
<font color="#0000FF">sort ()</font>
має різну семантику
<i>Swift 2</i>
та
<i>Swift 3</i>
. Але це нестрашно, бо якщо і
<i>Swift 2</i>
і
<i>Swift 3</i>
є пара функцій ( як з побічним ефектом, так і без нього), то міграційний «робот» блискуче здійснить заміну одного імені іншим:



А що, якщо в
<i>Swift 2</i>
були дві функції, а
<i>Swift 3</i>
залишилася одна? Наприклад,
<i>Swift 2</i>
були функції
<font color="#0000FF">insetInPlace</font>
та
<font color="#0000FF">insetBy</font>
, а
<i>Swift 3</i>
залишилася, з якоїсь причини, одна —
<font color="#0000FF">insetBy</font>
? Міграційний «робот» нам в цьому випадку не допоможе — він залишить стара назва функції —
<font color="#0000FF">insetInPlace</font>
— яке, звичайно, дасть помилку, і нам доведеться виправляти її вручну.



Всі методи в
<i>Swift 2</i>
з присутністю «
<font color="#0000FF">оперативне</font>
» в імені вимагають особливої уваги при переході на
<i>Swift 3</i>
.

Я сама попалася на цьому начебто невинному зміну. Розглянемо найпростіший метод
<font color="#0000FF">one()</font>
, який збільшує розмір прямокутника
<font color="#0000FF">bbox</font>
до тих пір, поки не «поглине» якийсь інший прямокутник
<font color="#0000FF">rect</font>
. Цей сильно спрощений приклад має реальний прототип, а саме клас
<font color="#0000FF">AxesDrawer</font>
, який був наданий в стенфордському курсі для малювання осей графіка в Завданні 3. Саме там зустрічається випадок, представлений нижче, і з ним довелося мати справу при перекладі класу
<font color="#0000FF">AxesDrawer</font>
<i>Swift 2.3</i>
<i>Swift 3</i>
.


В
<i>Swift 2</i>
я можу використовувати метод
<font color="#0000FF"> insetInPlace</font>
для прямокутників
<font color="#0000FF">CGRect</font>
, який буде збільшувати розмір прямокутника на
<font color="#0000FF">dx</font>
по осі X та
<font color="#0000FF">dy</font>
по осі Y:


Тут не потрібно використовувати значення, що повертається методу
<font color="#0000FF">insetInPlace</font>
, тому що прямокутник змінюється «за місцем».

Якщо ми використовуємо міграційний «робот» для переходу на
<i>Swift 3</i>
, то він залишить метод
<font color="#0000FF">insetInPlace</font>
незмінним, так як аналога йому в
<i>Swift 3</i>
немає, і ми отримаємо помилку:


В
<i>Swift 3</i>
є тільки метод
<font color="#0000FF">insetBy</font>
, застосовуємо його, помилка зникає, і нам пропонують змінити змінну
<font color="#0000FF">var bbox</font>
на константу
<font color="#0000FF">let bbox</font>
:


що ми і робимо:


Ви бачите, що немає ніяких попереджень, ніяких помилок, а ми ж створили «вічний» цикл, тому що новий метод
<font color="#0000FF"> insetBy</font>
не змінює прямокутник «за місцем», а повертає змінене значення, яке ми не використовуємо в циклі
<font color="#0000FF">while</font>
, але про це також чомусь немає повідомлення, так що склалася ДУЖЕ НЕБЕЗПЕЧНА ситуація, коли ми «зациклили» назавжди наш код.

Ми повинні знову присвоїти
<font color="#0000FF">bbox</font>
, що повертається методом
<font color="#0000FF">insetBy</font>
значення:


Природно, нам пропонують повернутися назад від константи
<font color="#0000FF">let bbox</font>
до змінної
<font color="#0000FF">var bbox</font>
, і ми це робимо:


Тепер код працює правильно. Так що будьте дуже уважні з заміною методів
<font color="#0000FF">...оперативне</font>
при переході на
<i>Swift 3</i>
.

6. В
<i>Swift 3</i>
запит
<font color="#0000FF">NSFetchRequest <NSFetchRequestResult></font>
до бази даних
<i>Core Data</i>
став
<i>Generic</i>

Але працездатність класу
<font color="#0000FF">CoreDataTableViewController</font>
, наданого стенфордським університетом для роботи з даними Core Data у таблиці, забезпечується автоматично при використанні міграційного інструменту. Давайте розглянемо, як це виходить.

Якщо ви працюєте з фреймворком
<i>Core Data</i>
, то слід звернути увагу на те, що запит до бази даних, який в
<i>Swift 2</i>
<font color="#0000FF">NSFetchRequest</font>
, в
<i>Swift 3</i>
став
<i>Generic</i>
<font color="#0000FF">NSFetchRequest <NSFetchRequestResult></font>
, а отже, став
<i>Generic</i>
і клас
<font color="#0000FF">NSFetchResultsController<NSFetchRequestResult></font>
. В
<i>Swift 3</i>
вони стали залежати від обраного результату, який повинен реалізувати протокол
<font color="#0000FF">NSFetchRequestResult</font>
:


На щастя, об'єкти
<font color="#0000FF">NSManagedObject</font>
бази даних
<i>Core Data</i>
автоматично виконують протокол
<font color="#0000FF">NSFetchRequestResult</font>
та ми «законно» можемо розглядати їх в якості результату запиту.

В
<i>Swift 2</i>
запит та його виконання виглядають так:


В
<i>Swift 3</i>
ми можемо вказати в запиті тип одержуваного результату (в нашому випадку
<font color="#0000FF">Photo</font>
), і тим самим уникнути додаткового «кастингу типу»:


Дійсно, якщо ми подивимося на тип результату вибірки
<font color="#0000FF">results</font>
<i>Swift 3</i>
, то це буде
<font color="#0000FF">[Photo]</font>
, що нам дозволить отримати атрибут
<font color="#0000FF">unique</font>
об'єкта бази даних
<font color="#0000FF">Photo</font>
:


Однак, якщо б ми використовували міграційний «робот» для переходу на
<i>Swift 3</i>
, то ми отримали б код, в якому результат вибірки
<font color="#0000FF">results</font>
визначається тільки тим, що він повинен виконувати протокол
<font color="#0000FF">NSFetchRequestResult</font>
:


Тому «роботу» довелося застосувати «кастинг типу»
<font color="#0000FF">as ? [Photo]</font>
для вилучення атрибута
<font color="#0000FF">unique</font>
об'єкта бази даних
<font color="#0000FF">Photo</font>
. Ми бачимо, що міграційний «робот» знову намагається нам «підсунути» більш узагальнене рішення, цілком працездатний, але менш ефективне та менш «читабельне», ніж наведений вище «ручний» варіант. Тому після роботи міграційного «робота» нам доведеться правити код вручну.

Але є одне місце в додатках, пов'язаних з
<i>Core Data</i>
, де міграційний «робот», працюючи так, як показано вище, пропонує геніальний код
<i>Swift 3</i>
. Це клас
<font color="#0000FF">NSFetchResultsController</font>
, який у
<i>Swift 3</i>
також, як і запит
<font color="#0000FF">NSFetchRequest</font>
став
<i>Generic</i>
, тобто
<font color="#0000FF">NSFetchResultsController<NSFetchRequestResult></font>
. В результаті виникли деякі труднощі при використанні в
<i>Swift 3</i>
фантастично зручного класу
<font color="#0000FF">CoreDataTableViewController</font>
, який розроблений в Стенфорді.

Спочатку дуже коротко нагадаю про те, звідки з'явився клас
<font color="#0000FF">CoreDataTableViewController</font>
. Коли у вас величезна кількість інформації в базі даних, то прекрасним засобом показу цієї інформації є
<i>Table View</i>
. У 99% випадків або
<i>Table View</i>
або
<i>Collection View</i>
використовуються для показу вмісту великих баз даних. І це настільки поширене, що Apple забезпечила нас в iOS прекрасним класом
<font color="#0000FF">NSFetchedResultsController</font>
, який «підв'язує» запит
<font color="#0000FF">NSFetchRequest</font>
до таблиці
<font color="#0000FF">UITableView</font>
.

І не тільки «підв'язує» лише одного разу, а ця «підв'язка» діє постійно і, якщо в базі даних якимось чином відбуваються зміни,
<font color="#0000FF">NSFetchRequest</font>
повертає нові результати і таблиця оновлюється. Так що база даних може змінюватися «за сценою», але таблиця
<font color="#0000FF">UITableView</font>
завжди залишається в синхронізованому з нею стані.

<font color="#0000FF">NSFetchResultsController</font>
забезпечує нас методами протоколів
<font color="#0000FF">UITableViewDataSource</font>
та
<font color="#0000FF">UITableViewDelegate</font>
, такими як
<font color="#0000FF">numberOfSectionsInTableView</font>
,
<font color="#0000FF">numberOfRowsInSections</font>
і т. д. Єдиний метод, який він не реалізує, — це
<font color="#0000FF">cellForRowAt</font>
. Вам самим доведеться реалізувати його, тому що для реалізації методу
<font color="#0000FF">cellForRowAt</font>
потрібно знати користувальницький UI для клітинки таблиці, а ви — єдиний, хто знає, які дані і як вони розміщуються на екрані. Але що стосується інших методів протоколу
<font color="#0000FF">UITableViewDataSource</font>
, навіть таких, як
<font color="#0000FF">sectionHeaders</font>
і всього іншого,
<font color="#0000FF">NSFetchedResultsController</font>
бере все на себе.

Як працювати з
<font color="#0000FF">NSFetchResultsController</font>
?

Від вас буде потрібно тільки створити запит
<font color="#0000FF">request</font>
, налаштувати його предикат і сортування, а виведенням даних у таблицю займеться
<font color="#0000FF">NSFetchResultsController</font>
.

<font color="#0000FF">NSFetchResultsController</font>
також спостерігає за всіма змінами, що відбуваються у базі даних, і синхронізує їх з
<i>Table View</i>
.

Спосіб, яким вона це робить, пов'язаний з делегатом
<font color="#0000FF">NSFetchResultsControllerDelegate</font>
, методи якого вам пропонується без зміни скопіювати з документації у ваш клас.

«Ну от, я думав, що налаштувати
<font color="#0000FF">NSFetchResultsController</font>
— це просто, а тут з'ясовується, що я повинен реалізувати методи делегата
<font color="#0000FF">NSFetchResultsControllerDelegate</font>
?» — подумаєте ви.
Але вам пощастило, всю цю роботу зробили за вас і надали у ваше розпорядження чудовий клас з ім'ям
<font color="#0000FF">CoreDataTableViewController</font>
.

При цьому був не тільки скопіював весь необхідний код з документації
<font color="#0000FF">NSFetchResultsController</font>
, але і переписаний з
<i>Objective-C</i>
на
<i>Swift</i>
.

Тепер, для того, щоб ваш
<font color="#0000FF">UITableViewController</font>
успадкував всю функціональність
<font color="#0000FF">NSFetchResultsController</font>
, вам достатньо зробити
<font color="#0000FF">CoreDataTableViewController</font>
вашим superclass і визначити
<font color="#0000FF">public var</font>
з назвою
<font color="#0000FF">fetchedResultsController</font>
. Ви встановлюєте цю змінну, і
<font color="#0000FF">CoreDataTableViewController</font>
буде використовувати її для відповіді на всі питання
<font color="#0000FF">UITableViewDataSource</font>
, а також делегата
<font color="#0000FF">NSFetchedResultsController</font>
, який будуть відслідковувати зміну бази даних.

У підсумку вам всього лише потрібно:

  1. встановити змінну
    <font color="#0000FF">var fetchedResultsController</font>
    і
  2. реалізувати метод
    <font color="#0000FF">cellForRowAt</font>
    .
У класі, які є нащадками
<font color="#0000FF">CoreDataTableViewController</font>
, створюємо
<font color="#0000FF">NSFetchResultsController</font>
з допомогою ініціалізатор, що включає в якості аргументу запит
<font color="#0000FF">request</font>
, а потім присвоюємо її змінну
<font color="#0000FF">var</font>
з назвою
<font color="#0000FF">fetchedResultsController</font>
. Як тільки ви це зробите, таблиця зі списком фотографій почне автоматично оновлюватися (
<i>Swift 2</i>
):


Звичайно, реалізуємо метод
<font color="#0000FF">cellForRowAtIndexPath</font>
(
<i>Swift 2</i>
):


Отримуємо список фотографій з сервера Flickr.com:


Все дуже гарно і просто в
<i>Swift 2</i>
, але в
<i>Swift 3</i>
запит
<font color="#0000FF">NSFetchRequest<NSFetchRequestResult></font>
став
<i>Generic</i>
, а отже, став
<i>Generic</i>
і клас
<font color="#0000FF">NSFetchResultsController<NSFetchRequestResult></font>
.

Змінна
<font color="#0000FF">public var</font>
з назвою
<font color="#0000FF">fetchedResultsController</font>
, з якою ми працюємо в
<font color="#0000FF">CoreDataTableViewController</font>
, теж стала
<i>Generic</i>
<i>Swift 3</i>
після застосування міграційного «робота»:


По ідеї і клас
<font color="#0000FF">CoreDataTableViewController</font>
потрібно зробити
<i>Generic</i>
, але ми цього робити не будемо, тому що його subclasses, наприклад, такі, як наведений вище
<font color="#0000FF">PhotosCDTVC</font>
, испольуются на
<i>storyboard</i>
, а
<i>storyboard</i>
не працюють
<i>Generic</i>
класи.

Як же нам бути? Клас
<font color="#0000FF">CoreDataTableViewController</font>
надзвичайно зручний і дозволяє уникнути дублювання коду у всіх
<i>Table View</i>
, що працюють c
<i>Core Data</i>
?

Тут нам на допомогу приходить міграційний «робот». Подивіться, як він перетворив клас
<font color="#0000FF">PhotosCDTVC</font>
в частині визначення змінної з ім'ям
<font color="#0000FF">fetchedResultsController</font>
, в якій результат вибірки в запиті визначається тільки тим, що він повинен виконувати протокол
<font color="#0000FF">NSFetchRequestResult</font>
(
<i>Swift 3</i>
):


А це як раз те, що вимагає змінна з ім'ям
<font color="#0000FF">fetchedResultsController</font>
в нашому суперклассе
<font color="#0000FF">CoreDataTableViewController</font>
, тобто фактично «робот» виконав «кастинг типу» ВГОРУ (upcast) нашого результату вибірки об'єкта бази даних
<font color="#0000FF">Photo</font>
<font color="#0000FF">NSFetchRequestResult</font>
. Зрозуміло, що ми отримаємо результат вибірки типу
<font color="#0000FF">NSFetchRequestResult</font>
, тому коли приходить час працювати з реальним об'єктом
<font color="#0000FF">Photo</font>
в методі
<font color="#0000FF">cellForRowAt</font>
міграційний «робот» виконує зворотну операцію — «кастинг типу» ВНИЗ (downcast) — з допомогою оператора
<font color="#0000FF">as?</font>
(
<i>Swift 3</i>
):


Так що у випадку з класом
<font color="#0000FF">CoreDataTableViewController</font>
міграційний «робот» спрацював ідеально. Вам нічого не потрібно змінювати або доповнювати.

7. Swift 3 розширив використання синтаксису
<font color="#0000FF">#selector</font>
, аргументами можуть бути
<font color="#0000FF">getter:</font>
та
<font color="#0000FF">setter:</font>
для Objective-C властивостей.
Коли ви визначаєте
<i>Swift 3</i>
селектор
<font color="#0000FF">#selector</font>
, що відноситься до
<i>Objective-C</i>
властивостями, то необхідно вказати, чи ви маєте на увазі
<i>setter</i>
або
<i>getter</i>
.

Так вийшло, що в одному зі своїх додатків на
<i>Swift</i>
, що працюють з
<i>Core Data</i>
, я використовувала як public API змінну
<font color="#0000FF">var coreDataStack</font>
:


Цю змінну я встановлюю в
<font color="#0000FF">AppDelegate</font>
не зовсім звичайним способом — через
<i>Objective-C setter</i>
<font color="#0000FF">setCoreDataStack</font>
<i>Swift</i>
властивості з ім'ям
<font color="#0000FF">coreDataStack</font>
. Цей спосіб я підгледіла на одному з відео на сайтіraywenderlich.com:

<img src=«habrastorage.org/files/1bd/64e/552/1bd64e5529634f66a96eb5d8ee9d25cb.png» width=«700» висота=«75» />
Мені було цікаво, як можна встановити селектор на метод
<font color="#0000FF">setCoreDataStack</font>
, якого явно немає в додатку. Цей код так і залишився, поки я не вирішила перейти на
<i>Swift 3</i>
. Якого ж було моє здивування, коли я виявила, як делікатно обійшовся з цим кодом міграційний «робот» — він використовував синтаксичну конструкцію
<font color="#0000FF">#selector</font>
з незнайомим для мене аргументом
<i>setter</i>
:

<img src=«habrastorage.org/files/990/896/6c8/9908966c89a74e44b7e4c93cc371d61c.png» width=«700» висота=«60» />
Мені захотілося більше дізнатися про
<font color="#0000FF">#selector</font>
та я знайшла чудову статтю «Hannibal #selector».

8. В
<i>Swift 3</i>
ви отримаєте попередження, якщо не будете використовувати повертається функцією не
<font color="#0000FF">Void</font>
значення.
В
<i>Swift 2</i>
при виклику функції необов'язково було використовувати повертається функцією значення, навіть якщо це значення не
<font color="#0000FF">Void</font>
. Ніякого попередження від компілятора в цьому випадку не надходило. Якщо ви хочете, щоб користувач отримував таке попередження від компілятора, то вам потрібно було спеціально розмістити пропозицію
<font color="#0000FF">@warn_unused_result</font>
перед декларуванням цієї функції. Це стосувалося, в основному, методів, які змінюють структуру даних. Наприклад,
<font color="#0000FF">sortInPlace</font>
.

В
<i>Swift 3</i>
ситуація змінилася на протилежну. Тепер завжди, коли ви не використовуєте будь-яку функцію з її обчислене значення, ви будете одержувати попередження. Для того, щоб скасувати
<i>Swift 3</i>
поява такого попередження досить розмістити пропозицію
<font color="#0000FF">@discardableResult</font>
перед декларацією функції.

Наприклад,
<i>Swift 2</i>
ми могли використовувати метод без отримання повернутого значення:


Але після застосування міграційного «робота» ви отримаєте в цьому коді попередження:


Яке повідомляє вам, що повертає значення
<font color="#0000FF">[UIViewController]?</font>
не використовується. Якщо ви хочете прибрати це попередження, то потрібно дати зрозуміти компілятору ЯВНО, що ви не цікавитеся її обчислене значення, з допомогою символу
<font color="#0000FF">_</font>
(підкреслення):



ВИСНОВКИ
Переклад коду
<i>Swift 2</i>
на
<i>Swift 3</i>
— дуже захоплююче заняття. Можна в якості вихідних файлів використовувати ті, які вказані на початку посту, а можна і більш ранні, написані. наприклад, на
<i>Swift 2.0</i>
. Так що використовуйте міграційний «робот»
<i>Xcode 8.1</i>
та
<i>8.2</i>
для розширення своїх знань про
<i>Swift 3</i>
. Якщо ви хочете використовувати у вашому додатку, написаному на
<i>Swift 3</i>
, якісь шматки коду, написаного на
<i>Swift 2</i>
, то також зручно використовувати міграційний «робот». Сподіваюся, він вас не підведе.

Посилання:Yammer iOS App ported Swift to 3
Джерело: Хабрахабр

0 коментарів

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