![](https://habrastorage.org/files/d0b/2ee/a1c/d0b2eea1caf14101a34636bd02cf105c.png)
Уже известно, что
Xcode 8.2
будет последним релизом, который поддерживает переходную версию Swift 2.3
. Поэтому нужно срочно подумать о миграции на Swift 3
. Я хочу поделиться некоторым опытом такой миграции на примере приложений, связанных со стэнфордским курсом «Developing iOS 9 Apps with Swift», как демонстрационных (их 12), так и полученных в результате выполнения Заданий этого обучающего курса (их 6 с вариантами). Они все разной сложности, но там есть и рисование, и многопоточность, и показ изображений с помощью
ScrollView
, и работа с сервером Twitter, и база данных Core Data, и работа с облачным сервисом Cloud Kit, и карты Map Kit. И все это было написано на Swift 2.2
(stanford.edu), а мне было необходимо перевести все приложения на Swift 3
. Конспект лекций стэнфордского курса на русском языке можно найти на сайте «О стэнфордских лекциях», а код — для Swift 2.3 на Github и для Swift 3 на Github.Если вы решили мигрировать на
Swift 3
, то в Xcode 8
вам нужно запустить инструмент миграции (своебразного «робота») c помощью меню Edit → Convert → to Current Swift Syntax:![](https://habrastorage.org/files/03c/3d9/440/03c3d9440f6d40dfbca169a69a4459d9.png)
Далее вам предлагают карту различий между исходным кодом
Swift 2
и кодом Swift 3
, который сгенерировал этот «робот»:![](https://habrastorage.org/files/341/70a/bfb/34170abfb4924c1594281bcd981941bf.png)
Надо сказать, что миграционный «робот» в
Xcode 8.1
и Xcode 8.2
работает превосходно по сравнению с начальной версией в Xcode 8.0
, с которой мне пришлось начинать. Новый миграционный «робот» — изобретательный и очень разумный. На примере этой карты различий можно прекрасно изучать, какие изменения претерпели те или иные синтаксические конструкции в Swift 3
. Миграционный «робот» делает очень большую работу по замене имен, сигнатуры методов и свойств, превращая, если это необходимо, ранее обычные свойства в Generic (например, NSFetchRequest
, который не является Generic в Swift 2
, но является таковым в Swift 3
). Он может заменять новым кодом целые «паттерны», например, синглтон, если он был выполнен старыми средствами с помощью dispatch_once(&onceToken)
. Ниже я покажу примеры этого. Миграционный «робот» действует по принципу «не навреди» и где только можно старается поддержать работоспособность имеющегося кода, даже вставляя дополнительный код. Вам следует очень внимательно посмотреть эти изменения и включить в список те места, код которых вам непонятен или кажется неэффективным и менее читаемым. Назовем это списком заданий для уточнения кода.
Если вы согласны с предложенными «роботом» преобразованиями, то вы их сохраняете и работаете дальше. Но, как и ожидалось, миграционный «робот» делает только часть работы для получения компилируемого кода в
Swift 3
. В приложениях остается 2-3 ошибки и 3-4 предупреждения. Поэтому вашим следующим шагом будет открытие навигатора «ошибок и предупреждений» (если они есть) и исследование их все одного за другим:![](https://habrastorage.org/files/cce/737/c23/cce737c233114ba08857afff70814500.png)
Для большинства ошибок и предупреждений предлагаются способы решения, и, в основном, это правильные решения:
![](https://habrastorage.org/files/97b/ddf/643/97bddf643fbe4c00ba40ec37c2e02907.png)
Нам нужно сделать «кастинг типа» для переменной
json
, которая в Swift 3
представлена «роботом» как Any
, хотя мы работаем с ней как с ссылочной (reference) переменной. В результате получаем:![](https://habrastorage.org/files/07c/d89/81c/07cd8981c06242c78ddbc6028d8083fc.png)
Но иногда приходится исправлять ошибки. Сразу после работы миграционного «робота» мы имеем ошибку при инициализации «параллельной» очереди (более подробно этот случай рассматривается ниже):
![](https://habrastorage.org/files/010/0dc/d90/0100dcd9006144b69838bf61615484b8.png)
Вместо двух строк с ошибкой добавляем одну строку с правильным кодом:
![](https://habrastorage.org/files/276/54f/408/27654f4089c44c1ca7a64605dfad6614.png)
Иногда вам предлагается несколько вариантов решения проблемы, и вы можете выбрать любой:
![](https://habrastorage.org/files/985/233/66e/98523366e4d742ab961514d2d82181dd.png)
Нам сообщается, что неявно произойдет принудительное преобразование
String?
в Any
. Это можно исправить тремя способами и тем самым убрать это предупреждение:- предоставить значение по умолчанию,
- принудительно «развернуть»
Optional
значение, - осуществить явный «кастинг» в
Any
с помощью кодаas Any
.
Мы предпочтем первый вариант и будем использовать пустую строку " ", если выражение равно
nil
:![](https://habrastorage.org/files/1cc/c21/9a5/1ccc219a5957486fafe7926517bc9f5f.png)
Вообще миграция — это прекрасный повод для того, чтобы более широко взглянуть на свой код и, возможно, улучшить его.
В результате работы со списком ошибок и предупреждений вам удалось откомпилировать приложение и запустить тестовый пример. Это очень важный этап миграции.
Теперь вы можете сфокусироваться на списке заданий на уточнение кода, который вы составили при просмотре карты различий между двумя версиями:
Swift 2
и Swift 3
. Весь этот код технически корректен, но он может быть либо избыточным, либо неэффективным, либо приводить к ошибкам выполнения. Некоторые из этих ситуаций имеют общий характер, а некоторые — сильно зависят от специфики вашего приложения. Я поделюсь некоторыми из них, с которыми мне пришлось столкнутся при миграции приложений курса «Developing iOS 9 Apps with Swift».
1. Нужно вернуть уровень доступа fileprivate
обратно в private
.
В процессе миграции на
Swift 3
все уровни доступа private
заменяются на новый уровень доступа fileprivate
, потому что private
в Swift 2
имел смысл именно fileprvate
.![](https://habrastorage.org/files/94d/aca/066/94daca06614143148505610bf21993e0.png)
Миграционный «робот» действует по принципу «не навреди», поэтому он заменил все старые
private
на новые fileprivate
, то есть расширил область доступа private
переменных и методов.![](https://habrastorage.org/files/b9a/dfc/6a9/b9adfc6a97374c8398d9f7c0a906b505.png)
В большинстве случаев это излишняя предосторожность, и нам вовсе не нужен уровень доступа
fileprivate
, но вы должны решить это самостоятельно в своей команде разработчиков и исправить в ручном режиме.Если вы разрабатываете framework, то миграционный «робот» в
Swift 3
заменит все уровни доступа public
, которые были в Swift 2
, на новый уровень доступа open
. Это касается только классов.![](https://habrastorage.org/files/ab5/475/c4a/ab5475c4a5c34003be0d9f6d7b042710.png)
В
Swift 3
:-
open
класс доступен и может иметь subclasses за пределами модуля, в котором он определен. Свойства и методыopen
класса доступны и могут быть переопределены (overridable) за пределами модуля, в котором определен класс.
public
класс доступен, но не может иметь subclasses за пределами модуля, в котором он определен. Свойства и методыpublic
класса доступны, но не могут быть переопределены (overridable) за пределами модуля, в котором определен класс.
Таким образом, уровень доступа
open
— это то, что было public
в предыдущих версиях Swift
, а уровень доступа public
более ограничен. Крис Латтнер сказал в SE-0177: Allow distinguishing between public access and public overridability, что в Swift 3
уровень доступа open
просто более public, чем public
. Еще можно посмотреть SE-0025 Scoped Access Level.![](https://habrastorage.org/files/472/f02/9a2/472f029a293541e3a10b07e2716e3a06.png)
При миграции frameworks в
Swift 3
мы не будем возвращать уровень доступа open
назад в public
. Здесь нас все устраивает.Вообще иерархия уровней доступа в
Swift 3
так располагается в порядке убывания:open
→ public
→ internal
→ fileprivate
→ private
2. Вы не можете сравнивать Optional
значения в Swift 3
.
При автоматической миграции с
Swift 2
на Swift 3
иногда перед некоторыми классами появляется код:![](https://habrastorage.org/files/019/af0/49d/019af049d312491fa078b7a68ee25ec9.png)
Дело в том, что в
Swift 2
можно было сравнивать Optional
значения, например, таким образом:![](https://habrastorage.org/files/029/7c1/3ba/0297c13ba4064256ba9195bcddb40f49.png)
или так:
![](https://habrastorage.org/files/db4/e51/25a/db4e5125a736436eb0907432c19617e3.png)
В
Swift 3
такую возможность убрали (SE-0121 – Remove Optional Comparison Operators) и для сохранения работоспособности такого кода в Swift 3
миграционный «робот» добавляет вышеприведенный код, что, конечно, удобно на начальном этапе перехода на Swift 3
, но некрасиво, так как если у вас встречается сравнение Optional
значений в нескольких расположенных в отдельных файлах классах, то вышеприведенный код добавиться многократно. Этот код нужно удалить, сразу обозначится проблема, и решать проблему нужно на месте. Вначале избавляемся от Optional
с помощью синтаксической конструкции if let
, а затем проводим необходимое сравнение. Например, так:![](https://habrastorage.org/files/4ac/e01/79f/4ace0179f3f14ddcb05eb52f6f4dc84d.png)
или так:
![](https://habrastorage.org/files/f8d/83b/678/f8d83b6784e8488f882def6feae690bd.png)
3. Swift 3
не обеспечивает автоматической совместимости (bridging) чисел с NSNumber
.
В
Swift 2
многие типы при необходимости автоматически совмещались («bridging«) с экземплярами некоторых subclasses NSObject
, например, String
в NSString
, или Int
, Float
, … в NSNumber
. В Swift 3
вам придется делать это преобразование явно (SE -0072 Fully eliminate implicit bridging conversions from Swift). Например, в Swift 2
мы имели код для преобразования числа в строку:![](https://habrastorage.org/files/6a4/874/ba3/6a4874ba3bee425c900abe8d8239879c.png)
В
Swift 3
после миграционного «робота» мы получим ошибку:![](https://habrastorage.org/files/354/cee/12b/354cee12b7ce4a309ac62080054649f1.png)
От нас требуют явного преобразования
Double
в NSNumber
, и мы можем использовать два способа преобразования — с помощью оператора as
:![](https://habrastorage.org/files/1d6/e00/701/1d6e00701b5b48fd9397c85e0807704a.png)
или с помощью инициализатора
NSNumber
:![](https://habrastorage.org/files/324/937/cda/324937cda5ba43ab867e80a9931b81ad.png)
4. «Робот» не умеет преобразовывать параллельные очереди, но прекрасно работает с dispatch_once
Например, обычный «паттерн» асинхронного выполнения кода на параллельной очереди
QOS_CLASS_USER_INITIATED
с последующим переходом на main queue для отображения данных на UI на Swift 2
выглядит следующим образом:![](https://habrastorage.org/files/0f8/a67/c83/0f8a67c830c543848ea91222f78cb5eb.png)
Миграционный робот" преобразует этот код в код с ошибкой и предлагает функцию
global(priority: qos)
, которая будет упразднена в iOS 10
:![](https://habrastorage.org/files/4bd/36c/839/4bd36c8396554e6ab0387e0fef31fbd7.png)
Для того, чтобы убрать эту ошибку, нам нужно использовать другую функцию —
global (qos: .userInitiated)
:![](https://habrastorage.org/files/bdb/cc8/9b2/bdbcc89b26a34c0c9a0424540c6809bf.png)
Зато миграционный «робот» прекрасно справляется с
dispatch_once
, которая упразднена в Swift 3
, и ее следует заменить либо глобальной, либо статической переменной или константой.Вот как выглядит код для однократной инициализации фоновой очереди при выборке данных с сервера Flickr.com в
Swift 2
:![](https://habrastorage.org/files/b30/704/fd6/b30704fd6e05489ab91d8958d9ee9969.png)
А это код в
Swift 3
после работы миграционного «робота»:![](https://habrastorage.org/files/780/15d/2d7/78015d2d7b0849eea169dfb671480f85.png)
Вы видите, что «робот» вынул внутренность синглтона и оформил ее в виде
lazy
переменной __once
, которая представлена как выполняемое замыкание, при это нас предупреждают, что переменная onceToken
не используется. Она действительно больше не нужна, и мы убираем эту строку:![](https://habrastorage.org/files/485/95d/a18/48595da186be43b9bd6227215f9e99d3.png)
5. Будьте очень внимательны с заменой методов типа …inPlace
, при переходе на Swift 3
.
Swift 3
возвращается к соглашению о наименовании методов и функций, которое было в Swift 1
, то есть функции и методы именуются в зависимости от того, создают ли они «побочный эффект». И это замечательно. Давайте приведем пару примеров.Вначале рассмотрим методы не имеющие «побочного эффекта», они, как правило, именуются Существительными. Например,
x.distance (to: y)
x = y.union(z)
Если функции и методы имеют «побочный эффект», то они, как правило, именуются императивным Глаголом в повелительном наклонении. Если я хочу, чтобы массив
X
был отсортирован, то я скажу: «X
отсортируй (sort
) сам себя или X
добавь (append
) к себе Y
»:x.sort ()
x.append(y)
y.formUnion(z)
Таким образом
Swift 3
группирует методы по двум категориям: методы, которые производят действие по месту — думайте о них как о Глаголах — и методы, которые возвращают результат выполнения определенного действия, не затрагивая исходный объект — думайте о них как о Существительных.Если НЕТ окончания «
ed
», то все происходит «по месту»: sort ()
, reverse ()
, enumerate ()
. Это Глаголы. Каждый раз, когда Swift 3
модифицирует метод добавлением окончания «ed
» или «ing
»: sorted ()
, reversed ()
, enumerated ()
, то мы имеем возвращаемое значение. Это Существительные.Эти довольно невинные правила вызывают путаницу, если речь заходит об изменении методов сортировки при переходе от
Swift 2
к Swift 3
. Дело в том, что в Swift 2
все функции и методы, которые работают «по месту», содержат в своем названии слово «InPlace
», поэтому для сортировки по месту используется функция sortInPlace ()
, а функция sort ()
в Swift 2 возвращает отсортированный массив. В Swift 3, как видно из вышеприведенных примеров, sort ()
переименован в sorted ()
, а sortInPlace ()
в sort ()
.В результате метод
sort ()
имеет разную семантику в Swift 2
и в Swift 3
. Но это нестрашно, потому что если и в Swift 2
, и в Swift 3
имеется пара функций ( как с побочным эффектом, так и без него), то миграционный «робот» блестяще осуществит замену одного имени другим:![](https://habrastorage.org/files/fb0/3ab/0c4/fb03ab0c41db4b6ba4b35c87c916eb97.png)
А что, если в
Swift 2
были две функции, а в Swift 3
осталась одна? Например, в Swift 2
были функции insetInPlace
и insetBy
, а в Swift 3
осталась, по какой-то причине, одна — insetBy
? Миграционный «робот» нам в этом случае не поможет — он оставит старое название функции — insetInPlace
— которое, конечно, даст ошибку, и нам придется исправлять ее вручную.![](https://habrastorage.org/files/fd6/123/c16/fd6123c1686f41519b66ddf7b08d39d8.png)
Все методы в
Swift 2
с присутствием «inPlace
» в имени требуют особого внимания при переходе на Swift 3
.Я сама попалась на этом вроде бы невинном изменении. Рассмотрим простейший метод
one()
, который увеличивает размер прямоугольника bbox
до тех пор, пока не «поглотит» некий другой прямоугольник rect
. Этот сильно упрощенный пример имеет реальный прототип, а именно класс AxesDrawer
, который был предоставлен в стэнфордском курсе для рисования осей графика в Задании 3. Именно там встречается случай, представленный ниже и с ним пришлось иметь дело при переводе класса AxesDrawer
из Swift 2.3
в Swift 3
.![](https://habrastorage.org/files/971/9f3/f91/9719f3f919614b90b350347d0b2b8cba.png)
В
Swift 2
я могу использовать метод insetInPlace
для прямоугольников CGRect
, который будет увеличивать размер прямоугольника на dx
по оси X и на dy
по оси Y:![](https://habrastorage.org/files/de4/b1c/2a0/de4b1c2a0d424cacb8673b48ea2b9376.png)
Здесь не требуется использовать возвращаемое значение метода
insetInPlace
, потому что прямоугольник изменяется «по месту».Если мы используем миграционный «робот» для перехода на
Swift 3
, то он оставит метод insetInPlace
неизменным, так как аналога ему в Swift 3
нет, и мы получим ошибку:![](https://habrastorage.org/files/edd/52f/b83/edd52fb83d034cdc95a51f612634a134.png)
В
Swift 3
есть только метод insetBy
, применяем его, ошибка исчезает, и нам предлагают изменить переменную var bbox
на константу let bbox
: ![](https://habrastorage.org/files/cef/9ad/af8/cef9adaf8e904e74941d9c876a9a1315.png)
что мы и делаем:
![](https://habrastorage.org/files/b72/0ae/fb2/b720aefb28924c068dc23955b0ed6e1e.png)
Вы видите, что нет никаких предупреждений, никаких ошибок, а мы ведь создали «вечный» цикл, потому что новый метод
insetBy
не изменяет прямоугольник «по месту», а возвращает измененное значение, которое мы не используем в цикле while
, но об этом тоже почему-то нет сообщения, так что создалась ОЧЕНЬ ОПАСНАЯ ситуация, когда мы «зациклили» навсегда наш код. Мы должны снова присвоить
bbox
, возвращаемое методом insetBy
значение:![](https://habrastorage.org/files/1c8/dbd/c49/1c8dbdc496f04c4ca083026222d59698.png)
Естественно, нам предлагают обратно вернуться от константы
let bbox
к переменной var bbox
, и мы это делаем:![](https://habrastorage.org/files/9b8/97c/85e/9b897c85eed44ade912e320157120435.png)
Теперь код работает правильно. Так что будьте очень внимательны с заменой методов
…inPlace
при переходе на Swift 3
.6. В Swift 3
запрос NSFetchRequest <NSFetchRequestResult>
к базе данных Core Data
стал Generic
Но работоспособность класса
CoreDataTableViewController
, предоставленного стэнфордским университетом для работы с данными Core Data в таблице, обеспечивается автоматически при использовании миграционного инструмента. Давайте рассмотрим, как это получается.Если вы работаете с фреймворком
Core Data
, то следует обратить внимание на то, что запрос к базе данных, который в Swift 2
был NSFetchRequest
, в Swift 3
стал Generic
NSFetchRequest <NSFetchRequestResult>
, а следовательно, стал Generic
и класс NSFetchResultsController<NSFetchRequestResult>
. В Swift 3
они стали зависеть от выбираемого результата, который должен реализовать протокол NSFetchRequestResult
:![](https://habrastorage.org/files/345/05b/db5/34505bdb533e415a93a2e5b9b95d77cd.png)
К счастью, объекты
NSManagedObject
базы данных Core Data
автоматически выполняют протокол NSFetchRequestResult
и мы «законно» можем рассматривать их в качестве результата запроса.В
Swift 2
запрос и его выполнение выглядят так:![](https://habrastorage.org/files/b36/f0f/502/b36f0f50270448d4a2f97dae12ae342a.png)
В
Swift 3
мы можем указать в запросе тип получаемого результата (в нашем случае Photo
), и тем самым избежать дополнительного «кастинга типа»:![](https://habrastorage.org/files/456/805/6ea/4568056ead3d4436bd0470a6eab1113e.png)
Действительно, если мы посмотрим на тип результата выборки
results
в Swift 3
, то это будет [Photo]
, что нам позволит извлечь атрибут unique
объекта базы данных Photo
:![](https://habrastorage.org/files/031/1e3/26e/0311e326e9e14cbcaf4df4168177729e.png)
Однако, если бы мы использовали миграционный «робот» для перехода на
Swift 3
, то мы получили бы код, в котором результат выборки results
определяется только тем, что он должен выполнять протокол NSFetchRequestResult
:![](https://habrastorage.org/files/967/f61/7e2/967f617e219246529457b8266a060f58.png)
Поэтому «роботу» пришлось применить «кастинг типа»
as ? [Photo]
для извлечения атрибута unique
объекта базы данных Photo
. Мы видим, что миграционный «робот» опять пытается нам «подсунуть» более обобщенное решение, вполне работоспособное, но менее эффективное и менее «читабельное», чем приведенный выше «ручной» вариант. Поэтому после работы миграционного «робота» нам придется править код вручную.Но есть одно место в приложениях, связанных с
Core Data
, где миграционный «робот», работая так, как показано выше, предлагает гениальный код в Swift 3
. Это класс NSFetchResultsController
, который в Swift 3
также, как и запрос NSFetchRequest
стал Generic
, то есть NSFetchResultsController<NSFetchRequestResult>
. В результате возникли некоторые трудности при использовании в Swift 3
фантастически удобного класса CoreDataTableViewController
, который разработан в Стэнфорде.Вначале очень кратко напомню о том, откуда появился класс
CoreDataTableViewController
. Когда у вас огромное количество информации в базе данных, то прекрасным средством показа этой информации является Table View
. В 99% случаев либо Table View
, либо Collection View
используются для показа содержимого больших баз данных. И это настолько распространено, что Apple обеспечила нас в iOS прекрасным классом NSFetchedResultsController
, который “подвязывает” запрос NSFetchRequest
к таблице UITableView
. И не только “подвязывает” лишь однажды, а эта “подвязка” действует постоянно и, если в базе данных каким-то образом происходят изменения,
NSFetchRequest
возвращает новые результаты и таблица обновляется. Так что база данных может меняться “за сценой”, но таблица UITableView
всегда остается в синхронизированном с ней состоянии. NSFetchResultsController
обеспечивает нас методами протоколов UITableViewDataSource
и UITableViewDelegate
, такими, как numberOfSectionsInTableView
, numberOfRowsInSections
и т.д. Единственный метод, который он не реализует, — это cellForRowAt
. Вам самим придется реализовать его, потому что для реализации метода cellForRowAt
нужно знать пользовательский UI для ячейки таблицы, а вы — единственный, кто знает, какие данные и как они размещаются на экране. Но что касается других методов протокола UITableViewDataSource
, даже таких, как sectionHeaders
и всего остального, NSFetchedResultsController
берет все на себя.Как работать с
NSFetchResultsController
?От вас потребуется только создать запрос
request
, настроить его предикат и сортировку, а выводом данных в таблицу займется NSFetchResultsController
.NSFetchResultsController
также наблюдает за всеми изменениями, происходящими в базе данных, и синхронизирует их с Table View
.Способ, каким она это делает, связан с делегатом
NSFetchResultsControllerDelegate
, методы которого вам предлагается без изменения скопировать из документации в ваш класс.«Ну вот, я думал, что настроить
NSFetchResultsController
— это просто, а тут выясняется, что я должен реализовать методы делегата NSFetchResultsControllerDelegate
?» — подумаете вы.Но вам повезло, всю эту работу проделали за вас и предоставили в ваше распоряжение замечательный класс с именем
CoreDataTableViewController
.При этом был не только скопировал весь необходимый код из документации по
NSFetchResultsController
, но и переписан с Objective-C
на Swift
. Теперь, для того, чтобы ваш
UITableViewController
унаследовал всю функциональность NSFetchResultsController
, вам достаточно сделать CoreDataTableViewController
вашим superclass и определить public var
с именем fetchedResultsController
. Вы устанавливаете эту переменную, и CoreDataTableViewController
будет использовать ее для ответа на все вопросы UITableViewDataSource
, а также делегата NSFetchedResultsController
, который будут отслеживать изменение базы данных.В итоге вам всего лишь нужно:
- установить переменную
var fetchedResultsController
и - реализовать метод
cellForRowAt
.
В классе, наследующим от
CoreDataTableViewController
, cоздаем NSFetchResultsController
с помощью инициализатора, включающего в качестве аргумента запрос request
, а затем присваиваем его переменной var
с именем fetchedResultsController
. Как только вы это сделаете, таблица cо списком фотографий начнет автоматически обновляться (Swift 2
):![](https://habrastorage.org/files/234/5d1/901/2345d190102c443ca5dee9a0c7b0a403.png)
Конечно, реализуем метод
cellForRowAtIndexPath
(Swift 2
):![](https://habrastorage.org/files/f39/d7c/c9b/f39d7cc9b4fe449ca2595453c0ea5aad.png)
Получаем список фотографий с сервера Flickr.com:
![](https://habrastorage.org/files/445/4da/35e/4454da35e76940dea24e2adbbf5513d8.png)
Все очень здорово и просто в
Swift 2
, но в Swift 3
запрос NSFetchRequest<NSFetchRequestResult>
стал Generic
, а следовательно, стал Generic
и класс NSFetchResultsController<NSFetchRequestResult>
.Переменная
public var
с именем fetchedResultsController
, с которой мы работаем в CoreDataTableViewController
, тоже стала Generic
в Swift 3
после применения миграционного «робота»:![](https://habrastorage.org/files/733/bc2/526/733bc2526bdc464695a24bfdfdb97559.png)
По идее и класс
CoreDataTableViewController
нужно сделать Generic
, но мы этого делать не будем, потому что его subclasses, например, такие, как приведенный выше PhotosCDTVC
, испольуются на storyboard
, а на storyboard
не работают Generic
классы. Как же нам быть? Класс
CoreDataTableViewController
чрезвычайно удобный и позволяет избежать дублирования кода во всех Table View
, работающих c Core Data
?Тут нам на помощь приходит миграционный «робот». Посмотрите, как он преобразовал класс
PhotosCDTVC
в части определения переменной с именем fetchedResultsController
, в которой результат выборки в запросе определяется только тем, что он должен выполнять протокол NSFetchRequestResult
(Swift 3
):![](https://habrastorage.org/files/37a/3c3/354/37a3c3354a724f8189c962903182386e.png)
А это как раз то, что требует переменная с именем
fetchedResultsController
в нашем суперклассе CoreDataTableViewController
, то есть фактически «робот» выполнил «кастинг типа» ВВЕРХ (upcast) нашего результата выборки объекта базы данных Photo
до NSFetchRequestResult
. Понятно, что мы получим результат выборки типа NSFetchRequestResult
, поэтому когда приходит время работать с реальным объектом Photo
в методе cellForRowAt
миграционный «робот» выполняет обратную операцию — «кастинг типа» ВНИЗ (downcast) — с помощью оператора as?
(Swift 3
):![](https://habrastorage.org/files/178/38c/d26/17838cd2642f42098872cf7a9f045b00.png)
Так что в случае с классом
CoreDataTableViewController
миграционный «робот» сработал идеально. Вам ничего не нужно изменять или дополнять.7. Swift 3 расширил использование синтаксиса #selector
, аргументами могут быть getter:
и setter:
для Objective-C свойств.
Когда вы определяете в
Swift 3
селектор #selector
, относящийся к Objective-C
свойствам, то необходимо указать, имеете ли вы ввиду setter
или getter
.Так получилось, что в одном из своих приложений на
Swift
, работающих с Core Data
, я использовала в качестве public API переменную var coreDataStack
:![](https://habrastorage.org/files/43d/d9f/a66/43dd9fa66eb34151a5e2cc29b63accfa.png)
Эту переменную я устанавливаю в
AppDelegate
не совсем обычным образом — через Objective-C setter
setCoreDataStack
для Swift
свойства с именем coreDataStack
. Этот способ я подсмотрела на одном из видео на сайте raywenderlich.com:![](https://habrastorage.org/files/1bd/64e/552/1bd64e5529634f66a96eb5d8ee9d25cb.png)
Мне было любопытно, как можно установить селектор на метод
setCoreDataStack
, которого явно нет в приложении. Этот код так и остался, пока я не решила перейти на Swift 3
. Какого же было мое удивление, когда я обнаружила, как деликатно обошелся с этим кодом миграционный «робот» — он использовал синтаксическую конструкцию #selector
с незнакомым для меня аргументом setter
:![](https://habrastorage.org/files/990/896/6c8/9908966c89a74e44b7e4c93cc371d61c.png)
Мне захотелось больше узнать о
#selector
и я нашла замечательную статью «Hannibal #selector».8. В Swift 3
вы получите предупреждение, если не будете использовать возвращаемое функцией не Void
значение.
В
Swift 2
при вызове функции необязательно было использовать возвращаемое функцией значение, даже если это значение не Void
. Никакого предупреждения от компилятора в этом случае не поступало. Если вы хотите, чтобы пользователь получал такое предупреждение от компилятора, то вам нужно было специально разместить предложение @warn_unused_result
перед декларированием этой функции. Это касалось, в основном, методов, которые меняют структуру данных. Например, sortInPlace
.В
Swift 3
ситуация поменялась на противоположную. Теперь всегда, когда вы не используете любую функцию с возвращаемым значением, вы будете получать предупреждение. Для того, чтобы отменить в Swift 3
появление такого предупреждения достаточно разместить предложение @discardableResult
перед декларацией функции.Например, в
Swift 2
мы могли использовать метод без получения возвращаемого значения:![](https://habrastorage.org/files/c39/78d/59d/c3978d59d4f74bde9dc635309700a653.png)
Но после применения миграционного «робота» вы получите в этом коде предупреждение:
![](https://habrastorage.org/files/f5c/f0c/6fe/f5cf0c6fe5ac4e5e9d0d478bd6cd8889.png)
Которое сообщает вам, что возвращаемое значение
[UIViewController]?
не используется. Если вы хотите убрать это предупреждение, то нужно дать понять компилятору ЯВНО, что вы не интересуетесь возвращаемым значение, с помощью символа _
(подчеркивания):![](https://habrastorage.org/files/b8a/1a7/bce/b8a1a7bcef854ad093085c8054aab9e2.png)
ВЫВОДЫ
Перевод кода из
Swift 2
на Swift 3
— очень увлекательное занятие. Можно в качестве исходных файлов использовать те, которые указаны в начале поста, а можно и более ранние, написанные. например, на Swift 2.0
. Так что используйте миграционный «робот» в Xcode 8.1
и 8.2
для расширения своих знаний о Swift 3
. Если вы хотите использовать в вашем приложении, написанном на Swift 3
, какие-то куски кода, написанного на Swift 2
, то также удобно использовать миграционный «робот». Надеюсь, он вас не подведет.Ссылки: Yammer iOS App ported to Swift 3