swift_vs_objc

Всем привет!

Время от времени я натыкаюсь на статьи типа “Swift vs Objectivе-C: бла бла бла”, в которых в большинстве случаев описаны причины, почему стоит переходить на Swift с Objective-C. Но, т.к. в 90% подобных статей используются лишь общие громкие фрази типа “Swift безопаснее”, “Swift быстрее”, “меньше кода”, “управление памятью”, “динамические библиотеки” и т.д. и т.п., я решил написать статейку на эту тему и на пальцах показать разницу между Swift и Obj-C.

Статья предполагает, что читатель имеет опыт написания приложений на Objective-C и немного знаком с Swift.

Я не буду идти по принципу “сначала самое важное, потом все остальное”. Я открываю Xcode, создаю проект на Swift и, что вижу, то и анализирую.

Итак, первое, что бросается в глаза, это конечно же

В Swift для класса используется один файл (*.swift) против двух файлов заголовка (*.h) и реализации (*.m) в Objective-C

Лично у меня по этому поводу двоякое мнение, и я бы не сказал с уверенностью, что этот факт – это прямо МЕГА-преимущество. Да, с одной стороны навигация по проекту, поддержка и прочее по идее должны стать легче. Давайте посмотрим визуально на Project Navigator проекта с десятью мифическими классами на Swift и Objective-C

project_navigator_swift project_navigator_objc

Веет какой-то легкостью, не правда ли?

С другой стороны возникает логический вопрос – как в Swift посмотреть паблик интерфейс класса? В Objective-C одним кликом выбрали файл .h и вуаля – вот интерфейс и ничего лишнего. В Swift же надо сделать ряд движений, а именно 1 – включить Assistant Editor, 2 – Выбрать Counterparts

swift_public_interface

Скорее, это вопрос привычки, но в целом, один файл вместо двух, это конечно же лучше.

Едем дальше и что действительно мне с первого взгляда бросайтся в в глаза, и что действительно нравится…

Никакого #import

Предположим, в Class1 нам нужны все остальные классы. В Objective-C нам нужно их все импортировать через #import, в Swift – нет

Objective-C

Swift

Это действительно круто!

Да, фреймворки импортровать все-равно надо 😉

А дальше я заметил, что допустил ошибку в названии класса Classs5 в Objective-С и решил сделать привычный рефакторинг -> переименование, и проверить как это работает в Swift тоже. И тут упс…

Can’t refactor Swift code

no_swift_refactor

В Xcode 8.2.1 Swift 3.0 до сих пор нет такой возможности. Я думал, что кривой рефакторинг в Objective-C, который всегда приходилось допиливать ручками, наконец доведут до ума в Swift. Но увы. Тут его вообще отключили. Хочется верить, что это пока.

Где-то я видел такой заголовок

Swift более читаемый язык, чем Objective-C

Предположим. Давайте сосздадим какой-нибудь метод делегата, который должен вызываться при обновлении каких-то данных и, собственно, иметь в качестве параметров три переменные – указатель на себя (делегата в смысле), обновленные данные, и флаг, что обновление завершено.

В Objective-C такой метод выглядел бы так:

В Swift точно такой же метод будет выглядеть так:

Хорошо. Посмотрим на синтаксис на разных примерах:

Objective-C

Swift

Что можно сказать?

 В Swift нет необходимости ставить точку с запятой (;) в конце каждой строки. Раз.   В Swift нет, как многие называют “скобочного ада” [[[[]]]]. Два.   Также не надо использовать круглые скобки для условных выражений. Три. 

Синтаксис Swift, действительно, гораздо проще и выразительнее, чем Objective-C.

Ок. С тем, что видно невооруженным взглядом вроде разобрались. Открываем методичку от Apple и поехали копнем немного глубже…

Раздел The Basics нам говорит о главном, судя по всему, а именно:

Swift предлагает собственные версии фундаментальных C и Objective-C типов, включая Int, Double, Float, Bool и String. А также собственные типы коллекций Array, Set, Dictionary.

Забегая вперед скажу, что при этом, мы также можем использовать привычный NSString, NSArray, NSSet, NSDictionary.

Swift предлагает не существующий в Objective-C тип Tuple.

Swift предлагает опциональные типы, которые обрабатывают отсутствие значения. По простому – опциональные типы это как nil в Objective-C, но они могут работать с любыми типами.

Т.е. в ObjC примитивы, например int, не могут быть равны nil, а в Swift могут. Об этом чуть позже.

Ну и в завершение основ The Basics мы видим, что

Swift – типобезопасный язык со строгой типизацией.

Вот об этом и поговорим дальше.

Попробуем переменной одного типа присвоить переменную другого типа.

Objective-C

type_nosafe_objc

Что произойдет при выполнении данного кода? Во-первых, компилятор выдаст нам предупреждение “Incompatible pointer types assigning to ‘NSString *’ from ‘NSNumber *’. Но, мы спокойно соберем сборку и запустимся на выполнение, в результате чего переменная string станет типа NSNumber и будет указывать на ту же область памяти, что и number.

В итоге, наше приложение не упало, все прекрасно работает, но на самом деле, это жуткий баг, который может привести к непредсказуемому поведению, и затрате кучи времени на поиски и отладку.

Если мы попытаемся сделать тоже самое в Swift,

type_safe_swift

мы просто не соберемся. Компилятор выдаст ошибку “Cannot assign value of type ‘NSNumber’ to ‘NSString'”.

Swift – язык со строгой типизацией. Язык со строгой типизацией призывает вас иметь четкое представление о типах значений с которыми может работать ваш код. Если часть вашего кода ожидает String, вы не сможете передать ему Int по ошибке.

Поскольку Swift имеет строгую типизацию, он выполняет проверку типов при компиляции кода и отмечает любые несоответствующие типы как ошибки. Это позволяет в процессе разработки ловить, и как можно раньше, исправлять ошибки.

Продолжая говорить о безопасности, стоит поговорить про

Опциональный Тип (Optional)

Что такое Опциональный Тип и зачем он нужен на конкретном примере.

Предположим у нас есть следущий код в Objective-C

У нас есть строка, и нам нужно знать ее длину. Все просто. ln = 10.

Теперь представим, что эта строка нам приходит с сервера, и в какой-то момент она не пришла, т.е. она nil

В данном случае ln будет равен нулю. Т.е. мы можем подумать, что пришла строка, и ее длина ноль символов, хотя на самом деле, строки нет. Это две принципиальные разницы. И в этом вся суть опционального типа – показать, имеет ли переменная или константа значение или не имеет.

Как этот кейс будет выглядеть в Swift?

Во-первых если мы объявляем пременную и она может принимать nil, она будет автоматически конвертирована в опциональный тип NSString? (знак ? говорит о том, что string типа Optional NSString)

Во-вторых string?.length вернет Optional Int.

И в данном случае ln будет равен nil, говоря нам о том, что мы не получили значение длины строки.

Ну и дальше нужно понимать, что такое Forced Unwrapping,  Optional Binding и Implicitly Unwrapped.

Про переменные и константы

Отдельно стоит сказать про переменные и константы.

В Swift для объявления переменной используется ключевое слово var, для объявления константы – let.

Например:

let name = “James”

var age = 25

В данном примере мы объявили константу name, которая далее нигде в коде не сможет быть изменена, и переменную age, которая может быть измененна.

Вспомним следующие классы из Objective-C: NSString и NSMutableString, NSArray и NSMutableArray, NSDictionary и NSMutableDictionary и т.д.

Понимаете к чему я?

В Swift изменяемость определяется ключевыми словами let и var.

Например:

let array = Array() –  неизменяемый массив

var array = Array() – изменяемый массив, и т.д.

Проверка доступности API

В Swift есть встроенная поддержка для проверки доступности API

Дженерики (Generics)

Тут я просто приведу пример из методички Apple

“Дженерики одна из самых мощных особенностей Swift, и большая часть всех библиотек Swift построена на основе дженериков. На самом деле вы используете дженерики все время, даже если вы этого не осознаете. Например, коллекции Swift Array или Dictionary являются универсальными. Вы можете создать массив, который содержит значения типа Int или массив, который содержит значения String, или на самом деле любой другой массив, который может содержать любой другой тип. Аналогично вы создаете словарь, который может содержать значения разных типов, и нет никакого ограничения по типу хранящихся значений.”

Приведем обычную, стандартную, неуниверсальную функцию swapTwoInts(_:_:), которая меняет два Int местами:

Функция swapTwoInts(_:_:) полезная, но она применима только для значений типа Int. Если вы хотите поменять местами два значения типа String или два значения Double, то вам придется написать больше функций, к примеру, swapTwoStrings(_:_:) или swapTwoDoubles(_:_:), которые показаны ниже:

Дженерик функции могут работать с любыми типами. Ниже приведена дженерик версия функции swapTwoInts(_:_:), которая теперь называется swapTwoValues(_:_:):

Дженерик версия использует заполнитель имени типа (называется T в нашем случае) вместо текущего имени типа (Int, String, Double…). Заполнитель имени типа ничего не говорит о том, чем должно являться T, но он говорит о том, что и a и b должны быть одного типа T, не зависимо от того, что такое T. Текущий тип T будет определяться каждый раз, как вызывается функция swapTwoValues(_:_:).

Другое отличие в том, что за именем дженерик функции (swapTwoValues(_:_:)) идет заполнитель имени типа (Т) в угловых скобках (<T>). Угловые скобки говорят Swift, что T является заполнителем имени типа внутри определения функции swapTwoValues(_:_:). Так как T является заполнителем, то Swift не смотрит на текущее значение T.

Функция swapTwoValues(_:_:) теперь может быть вызвана точно так же как и функция swapTwoInts, за исключением того, что в нее можно передавать значения любого типа, до тех пор пока они одного типа. Каждый раз при вызове функции swapTwoValues(_:_:), тип Т выводится из типов, которые передаются в эту функцию.

продолжение следует…