Данная статья получилась из отчета программиста после «опыта» разработки небольшого приложения на новом языке Swift.
Дима занимается программированием почти два года и изучает языки самостоятельно. Документация, чужой код. На звание профессионала не претендует, но его опыт может быть полезен многим, кто подумывает начать кодить на Swift. Опыты, наблюдения и выводы. Приглашаем к дискуссии! Далее текст автора.
Когда я начал писать для IOS (а было это не так уж и давно), Objective-C меня немало смутил. Не то чтобы его было сложно понять, просто очень уж непривычно выглядел синтаксис. Со временем привык, конечно, но когда узнал о появлении Swift’а, при первой возможности, новый проект начал писать на нем.
Кроме того, есть моменты, о которые можно обжечься. Обо всем этом я и хотел порассуждать. Спорные моменты, замеченные мной в процессе работы, я попытался описать и обобщить.
К концу разработки и к концу написания статьи, я обновил Xcode, а с ним и Swift до версии 1.2. Выяснилось, что некоторые отмеченные мною проблемы потеряли актуальность. Тем не менее, я оставил их в тексте, поскольку они иллюстрируют выводы, которые актуальности не потеряли.
Первое существенно-слабое место — совместимость с pure C. Как мы все знаем, Swift полностью совместим с ObjC. ObjC полностью совместим с С. Совместим ли Swift с С? Тоже да. Специальные мостики создатели свифта сделали не только к ObjC, но и к C. А вот здесь раздел документации о том, как взаимодействовать с C API. Проблема у меня возникла при попытке использования Core Foundation (который как раз С-эшный API). CFNotificationCenterAddObserver принимает в качестве параметра указатель на С-функцию (с подобным, вероятно, есть и другие полезные функции из этой библиотеки). В Swift’е можно создать указатель на С-функцию (предусмотрен CFunctionPointer), но в самом swift-файле написать её нельзя, синтаксис-то у них разный! То есть для ObjC совместимость с С заключается в том, что вы в любом месте можете просто начать писать на С, соответственно, и использовать С-код из других мест (библиотеки), а для Swift’а возможно лишь использование библиотек или сторонних С, или ObjC файлов. Немного разная совместимость. Казалось бы, мелочь, но вот вам конкретная ситуация: если пишете только на Swift’е, Darwin notifications использовать не сможете.
Перейдем к более общим вещам. Говоря о Swift «полностью совместим» [c ObjC], следует держать в уме, что все вещи из ObjC доступны в Swift не напрямую, а через мостики. Простейшие примеры: NSArray -> [AnyObject], NSDictionary -> [NSObject:AnyObject], а в строках свифта (String) вроде как есть функции из NSString, но опять же, не все (например, length), куча глобальных функций, начинающихся с objc_. И в большинстве случаев при переводе чего-либо из ObjC в Swift, мы видим AnyObject. Причины я знаю, но сейчас не о них, а о последствиях. Безусловно, AnyObject нужен, как возможность, ситуации разные бывают. Но его использование немного противоречит идеологии языка, ведь Swift характеризуется строгой типизацией (что должно повышать надежность кода). А при использовании любых фреймворков от Apple, мы везде в функциях видим AnyObject, ведь все они на ObjC. В результате, мы должны результат каждой второй функции из стандартной библиотеки кАстить к нужному типу. И не дай боже вам не угадать или перепутать какой должен быть тип! [Знаю, что можно сделать проверку с is, но вдруг куда-то улетучивается вся краткость и простота кода].
Итак, нам дают новый более надежный язык, за счет того, что мы обязаны указывать тип переменных. Но функции стандартных библиотек частенько возвращают не типизированные объекты. Конечно, условие «полной совместимости» не нарушено, но на этом моменте меня уже немного коробит.
Похожая ситуация, хотя и в более легкой форме, с опциональными переменными. Optional переменные вполне можно назвать инструментами языка, но если писать в том стиле, в котором предполагает свифт, то их приходится использовать очень нечасто. Функции же NS классов возвращают optional через одну. И это тоже заставляет менять стиль программирования в угоду совместимости.
[deprecated in 1.2]
Вернемся снова к частностям. Есть вещи в Foundation, которые пока совершенно недоступны в Swift. Например, класс NSDecimal. Хотя в документации было описание на свифте, использовать его было невозможно. Таким образом, если в функции ObjC был NSDecimal в качестве входного или выходного параметра, в свифте вы её просто не видели. А если писали — она не компилировалась из-за ошибки. Впрочем, если верить гуглу, кроме пользователей библиотеки Core Plot (из-за которой и я столкнулся с этой проблемой), этот класс не особо-то и был кому-то нужен. Правда, в 1.2 его теперь можно использовать. Но это то, с чем только я столкнулся. И наверняка, даже в комментариях найдутся и другие примеры недоступных классов.
Смотрите сами, но я, учитывая все вышесказанное, оценивая совместимость Swift и ObjC, скорее бы использовал слова «почти полная».
Мне не очень нравится Objective-C. И мне почти очень нравится Swift. Как утверждает заглавная страничка свифта от Apple, этот язык и инновационный, и современный. С этим я спорить не буду, но по моим ощущениям, он пока ещё не дорос до того состояния, когда им можно пользоваться безболезненно. Некоторые недостатки бросаются в глаза почти сразу при знакомстве с языком, но некоторые — достаточно неочевидны. Я же просто перечислю все то, что мешает сейчас, но я надеюсь, будет исправлено в будущем.
[deprecated in 1.2]
Первое, это затруднения при дебаге. В то время как словари и массивы в ObjC мы могли раскрывать и просматривать прямо в панели дебаггера, стандартные классы свифта раскрыть и посмотреть нельзя. Дебаггер только показывает их тип. Как я сказал, это касается стандартных классов, если вы используете NSDictionary или NSArray, они раскрываются, как и прежде (оно и понятно, Foundation же на ObjC). Как вариант, можно воспользоваться NSLog, но это очевидно менее удобно.
Аналогичная предыдущей ситуация с optional переменными. Какой бы ни был тип, в дебаггер вы увидите только nil эта переменная или some, и больше ничего от неё не добьётесь.
Fixed в 1.2. И some, и классы свифта теперь можно смотреть.
Не знаю, как назвать эту ситуацию, но происходит следующее: при анализе свифт-кода в Xcode что-то ломается, он выдает ошибку об этом и пропадает вся подсветка синтаксиса. Затем это что-то, видимо, перезапускается, подсветка опять появляется, и даже autocomplete снова работает. Но вот эти вот сбои происходят в тот момент, когда код некомпилируем (например, скобка открыта и пр.), а иногда, когда я ещё недописывал имя переменной или функции. Хоть Xcode не вылетает, и то хорошо, но тем не менее, все это не смотрится очень проработанно.
String — стандартный класс языка свифт. Имеет автоматический bridge к NSString. Однако функции length не имеет. Знаю, что есть тому причина, но факт остается фактом: такая простейшая функция как длина строки делается в Swift сложнее.
Совсем частный случай. objc_setAssociatedObject последним параметром принимает параметр типа — typealias objc_AssociationPolicy = UInt. Рядом с типом определены переменные, определяющие эту политику OBJC_ASSOCIATION_ASSIGNб OBJC_ASSOCIATION_RETAINб OBJC_ASSOCIATION_COPY и пр. и они имеют тип Int. Почему? Зачем я должен явно приводить и писать в 2 раза длиннее: писать objc_AssociationPolicy(OBJC_ASSOCIATION_COPY)? Надеюсь, кто-нибудь объяснит (комментарии?!)
Swift — язык новый, даже с момента анонса ещё и года не прошло. Он активно развивается, что не может не радовать, и меняется, что немного заставляет задуматься. И порой, изменения могут существенно изменить стиль программирования, например, раньше константы необходимо было присваивать сразу при объявлении, а с версии 1.2 — можно после. Другие — это просто изменение синтаксиса (countElements -> count). Буквально во время написания этого текста обновил Xcode (напомню, версия 6.3 вышла 8.04.15 и содержит новую версию свифта 1.2), и вот проект некомпилируем. Почти все as просит заменить на as!, ну это из описания изменений новой версии видно. А вот неочевидное изменение: NSDictionary теперь почему-то не конвертируется автоматически в [NSObject:AnyObject], как и NSString в String, и NSSet -> Set, и NSArray -> [AnyObject] тоже больше нет, ну, вы поняли, как это работает. А раньше было. А теперь — нет. Есть наверняка тому веские причины, но я сейчас не о них говорю. Обратное присваивание, кстати, все ещё работает без явного приведения.
В Xcode есть функция для миграции на свежую версию свифта. По моему опыту обновления, увы, не работает как должно. Все несоответствия новой версии убрались только после третьего проведения этой операции (предыдущие разы тоже срабатывали, но убрались не всё), но и в результате код компилируемым так и не стал. В одном месте осталась неисправленной функция utf16Count, хотя до первого конвертирования Xcode предлагал автозамену, которую теперь пришлось заменять вручную. То есть мне пришлось 3 раза делать конвертирование, поскольку каждый раз он делал лишь часть замен, а потом ещё и править код в одном месте вручную. Боюсь представить, какие неудобства испытывают в больших проектах. Опасаясь подобной ситуации, я и не хотел обновлять Xcode до завершения проекта, хотя обновление уже вышло официально. Вот такая показательная ситуация с обновлениями.
И ещё совсем кратенько упомяну не задокументированные глобальные функции, которые, на мой взгляд, тоже являются признаком молодости языка: countElements, dropFirst, dropLast, first, last, prefix, suffix, reverse. Так что берите playground и пробуйте.
Дебаг уже доработали, NSDecimal появился. Это из исправленных недостатков, в списке изменений 1.2 есть, конечно же, и другие улучшения. Видно, что работа над языком продолжает вестись активно. И я нисколько не сомневаюсь, что со временем и прочие недостатки исправят, и фишек добавят. Но есть «но». Foundation никто же не будет переписывать: ObjC же никуда не денется. А пока все базовые библиотеки написаны на ObjC, на Swift нельзя будет писать в том стиле, для какого он был спроектирован, поскольку всегда нужно будет взаимодействовать с ObjC. Что ж, надеюсь когда-нибудь увидеть новый фреймворк, созданный специально для свифта. Звучит фантастично, но если в планах Apple полный переход на свифт, то, полагаю, рано или поздно это случится. Ведь есть же Core Foundation, и есть Foundation.
Приглашаю к дискуссии.
Дима занимается программированием почти два года и изучает языки самостоятельно. Документация, чужой код. На звание профессионала не претендует, но его опыт может быть полезен многим, кто подумывает начать кодить на Swift. Опыты, наблюдения и выводы. Приглашаем к дискуссии! Далее текст автора.
Введение
Когда я начал писать для IOS (а было это не так уж и давно), Objective-C меня немало смутил. Не то чтобы его было сложно понять, просто очень уж непривычно выглядел синтаксис. Со временем привык, конечно, но когда узнал о появлении Swift’а, при первой возможности, новый проект начал писать на нем.
Кроме того, есть моменты, о которые можно обжечься. Обо всем этом я и хотел порассуждать. Спорные моменты, замеченные мной в процессе работы, я попытался описать и обобщить.
К концу разработки и к концу написания статьи, я обновил Xcode, а с ним и Swift до версии 1.2. Выяснилось, что некоторые отмеченные мною проблемы потеряли актуальность. Тем не менее, я оставил их в тексте, поскольку они иллюстрируют выводы, которые актуальности не потеряли.
О совместимости
Первое существенно-слабое место — совместимость с pure C. Как мы все знаем, Swift полностью совместим с ObjC. ObjC полностью совместим с С. Совместим ли Swift с С? Тоже да. Специальные мостики создатели свифта сделали не только к ObjC, но и к C. А вот здесь раздел документации о том, как взаимодействовать с C API. Проблема у меня возникла при попытке использования Core Foundation (который как раз С-эшный API). CFNotificationCenterAddObserver принимает в качестве параметра указатель на С-функцию (с подобным, вероятно, есть и другие полезные функции из этой библиотеки). В Swift’е можно создать указатель на С-функцию (предусмотрен CFunctionPointer), но в самом swift-файле написать её нельзя, синтаксис-то у них разный! То есть для ObjC совместимость с С заключается в том, что вы в любом месте можете просто начать писать на С, соответственно, и использовать С-код из других мест (библиотеки), а для Swift’а возможно лишь использование библиотек или сторонних С, или ObjC файлов. Немного разная совместимость. Казалось бы, мелочь, но вот вам конкретная ситуация: если пишете только на Swift’е, Darwin notifications использовать не сможете.
Решение из интернета
Указатель на С-функцию в свифте — это CFunctionPointer. Теоретически, его можно создать из указателя на свифт-функцию, правда, мне не понятно как все это работает. Описание из интернета, как создать указатель на С-функцию в свифте, выглядит так:
The pointer to this function in C would have int (*)(void) type, while in Swift it will have CFunctionPointer<() -> Int32> type. To create CFunctionPointer, COpaquePointer is needed, e.g.:
let pointer = UnsafeMutablePointer<() -> Int32>.alloc(1)
pointer.initialize(getNextRandomValue)
let cPointer = COpaquePointer(pointer)
let functionPointer = CFunctionPointer<() -> Int32>(cPointer)
To call a function via a pointer to it, do the following:
let newCPointer = COpaquePointer(functionPointer)
let newPointer = UnsafeMutablePointer<() -> Int32>(newCPointer)
let rNumber = newPointer.memory()
Пробовал в песочнице, подсунув изначально функцию, написанную на свифте. В последней строке функция выполнилась. Казалось бы, вин, но в программе почему-то не сработало. Программа компилится, указатель на функцию создается, объект на уведомления подписывается и получает их, но при получении приложение падает без ошибки. Какой-то указатель не вполне качественный получается, но в чем, моих знаний не хватает разобраться.
The pointer to this function in C would have int (*)(void) type, while in Swift it will have CFunctionPointer<() -> Int32> type. To create CFunctionPointer, COpaquePointer is needed, e.g.:
let pointer = UnsafeMutablePointer<() -> Int32>.alloc(1)
pointer.initialize(getNextRandomValue)
let cPointer = COpaquePointer(pointer)
let functionPointer = CFunctionPointer<() -> Int32>(cPointer)
To call a function via a pointer to it, do the following:
let newCPointer = COpaquePointer(functionPointer)
let newPointer = UnsafeMutablePointer<() -> Int32>(newCPointer)
let rNumber = newPointer.memory()
Пробовал в песочнице, подсунув изначально функцию, написанную на свифте. В последней строке функция выполнилась. Казалось бы, вин, но в программе почему-то не сработало. Программа компилится, указатель на функцию создается, объект на уведомления подписывается и получает их, но при получении приложение падает без ошибки. Какой-то указатель не вполне качественный получается, но в чем, моих знаний не хватает разобраться.
Мое решение
Написал класс на ObjC. А когда понадобилось наследовать — наследников на свифте.
Перейдем к более общим вещам. Говоря о Swift «полностью совместим» [c ObjC], следует держать в уме, что все вещи из ObjC доступны в Swift не напрямую, а через мостики. Простейшие примеры: NSArray -> [AnyObject], NSDictionary -> [NSObject:AnyObject], а в строках свифта (String) вроде как есть функции из NSString, но опять же, не все (например, length), куча глобальных функций, начинающихся с objc_. И в большинстве случаев при переводе чего-либо из ObjC в Swift, мы видим AnyObject. Причины я знаю, но сейчас не о них, а о последствиях. Безусловно, AnyObject нужен, как возможность, ситуации разные бывают. Но его использование немного противоречит идеологии языка, ведь Swift характеризуется строгой типизацией (что должно повышать надежность кода). А при использовании любых фреймворков от Apple, мы везде в функциях видим AnyObject, ведь все они на ObjC. В результате, мы должны результат каждой второй функции из стандартной библиотеки кАстить к нужному типу. И не дай боже вам не угадать или перепутать какой должен быть тип! [Знаю, что можно сделать проверку с is, но вдруг куда-то улетучивается вся краткость и простота кода].
Итак, нам дают новый более надежный язык, за счет того, что мы обязаны указывать тип переменных. Но функции стандартных библиотек частенько возвращают не типизированные объекты. Конечно, условие «полной совместимости» не нарушено, но на этом моменте меня уже немного коробит.
Похожая ситуация, хотя и в более легкой форме, с опциональными переменными. Optional переменные вполне можно назвать инструментами языка, но если писать в том стиле, в котором предполагает свифт, то их приходится использовать очень нечасто. Функции же NS классов возвращают optional через одну. И это тоже заставляет менять стиль программирования в угоду совместимости.
[deprecated in 1.2]
Вернемся снова к частностям. Есть вещи в Foundation, которые пока совершенно недоступны в Swift. Например, класс NSDecimal. Хотя в документации было описание на свифте, использовать его было невозможно. Таким образом, если в функции ObjC был NSDecimal в качестве входного или выходного параметра, в свифте вы её просто не видели. А если писали — она не компилировалась из-за ошибки. Впрочем, если верить гуглу, кроме пользователей библиотеки Core Plot (из-за которой и я столкнулся с этой проблемой), этот класс не особо-то и был кому-то нужен. Правда, в 1.2 его теперь можно использовать. Но это то, с чем только я столкнулся. И наверняка, даже в комментариях найдутся и другие примеры недоступных классов.
Смотрите сами, но я, учитывая все вышесказанное, оценивая совместимость Swift и ObjC, скорее бы использовал слова «почти полная».
Об удобности
Мне не очень нравится Objective-C. И мне почти очень нравится Swift. Как утверждает заглавная страничка свифта от Apple, этот язык и инновационный, и современный. С этим я спорить не буду, но по моим ощущениям, он пока ещё не дорос до того состояния, когда им можно пользоваться безболезненно. Некоторые недостатки бросаются в глаза почти сразу при знакомстве с языком, но некоторые — достаточно неочевидны. Я же просто перечислю все то, что мешает сейчас, но я надеюсь, будет исправлено в будущем.
[deprecated in 1.2]
Первое, это затруднения при дебаге. В то время как словари и массивы в ObjC мы могли раскрывать и просматривать прямо в панели дебаггера, стандартные классы свифта раскрыть и посмотреть нельзя. Дебаггер только показывает их тип. Как я сказал, это касается стандартных классов, если вы используете NSDictionary или NSArray, они раскрываются, как и прежде (оно и понятно, Foundation же на ObjC). Как вариант, можно воспользоваться NSLog, но это очевидно менее удобно.
Аналогичная предыдущей ситуация с optional переменными. Какой бы ни был тип, в дебаггер вы увидите только nil эта переменная или some, и больше ничего от неё не добьётесь.
Fixed в 1.2. И some, и классы свифта теперь можно смотреть.
Не знаю, как назвать эту ситуацию, но происходит следующее: при анализе свифт-кода в Xcode что-то ломается, он выдает ошибку об этом и пропадает вся подсветка синтаксиса. Затем это что-то, видимо, перезапускается, подсветка опять появляется, и даже autocomplete снова работает. Но вот эти вот сбои происходят в тот момент, когда код некомпилируем (например, скобка открыта и пр.), а иногда, когда я ещё недописывал имя переменной или функции. Хоть Xcode не вылетает, и то хорошо, но тем не менее, все это не смотрится очень проработанно.
String — стандартный класс языка свифт. Имеет автоматический bridge к NSString. Однако функции length не имеет. Знаю, что есть тому причина, но факт остается фактом: такая простейшая функция как длина строки делается в Swift сложнее.
Варианты
Глобальная функция count (до совсем недавнего времени countElements). Только её нет в онлайн-документации (о чем позже), но вам может о ней кто-нибудь сказать. count(string)
Функция .utf16Count. Делает как раз то, что вы хотели бы сделать функцией .length, название выглядит почти также интуитивно. Пример: string.utf16Count
Убрали в 1.2. Xcode предлагает заменить на count(string.utf16).
endIndex — индекс последнего символа. Но является типом String.Index, чтобы получить Int придется помучаться. string.endIndex
каст в NSString и использовать .length: (string as NSString).length
Функция .utf16Count. Делает как раз то, что вы хотели бы сделать функцией .length, название выглядит почти также интуитивно. Пример: string.utf16Count
Убрали в 1.2. Xcode предлагает заменить на count(string.utf16).
endIndex — индекс последнего символа. Но является типом String.Index, чтобы получить Int придется помучаться. string.endIndex
каст в NSString и использовать .length: (string as NSString).length
Совсем частный случай. objc_setAssociatedObject последним параметром принимает параметр типа — typealias objc_AssociationPolicy = UInt. Рядом с типом определены переменные, определяющие эту политику OBJC_ASSOCIATION_ASSIGNб OBJC_ASSOCIATION_RETAINб OBJC_ASSOCIATION_COPY и пр. и они имеют тип Int. Почему? Зачем я должен явно приводить и писать в 2 раза длиннее: писать objc_AssociationPolicy(OBJC_ASSOCIATION_COPY)? Надеюсь, кто-нибудь объяснит (комментарии?!)
Об изменчивости
Swift — язык новый, даже с момента анонса ещё и года не прошло. Он активно развивается, что не может не радовать, и меняется, что немного заставляет задуматься. И порой, изменения могут существенно изменить стиль программирования, например, раньше константы необходимо было присваивать сразу при объявлении, а с версии 1.2 — можно после. Другие — это просто изменение синтаксиса (countElements -> count). Буквально во время написания этого текста обновил Xcode (напомню, версия 6.3 вышла 8.04.15 и содержит новую версию свифта 1.2), и вот проект некомпилируем. Почти все as просит заменить на as!, ну это из описания изменений новой версии видно. А вот неочевидное изменение: NSDictionary теперь почему-то не конвертируется автоматически в [NSObject:AnyObject], как и NSString в String, и NSSet -> Set, и NSArray -> [AnyObject] тоже больше нет, ну, вы поняли, как это работает. А раньше было. А теперь — нет. Есть наверняка тому веские причины, но я сейчас не о них говорю. Обратное присваивание, кстати, все ещё работает без явного приведения.
В Xcode есть функция для миграции на свежую версию свифта. По моему опыту обновления, увы, не работает как должно. Все несоответствия новой версии убрались только после третьего проведения этой операции (предыдущие разы тоже срабатывали, но убрались не всё), но и в результате код компилируемым так и не стал. В одном месте осталась неисправленной функция utf16Count, хотя до первого конвертирования Xcode предлагал автозамену, которую теперь пришлось заменять вручную. То есть мне пришлось 3 раза делать конвертирование, поскольку каждый раз он делал лишь часть замен, а потом ещё и править код в одном месте вручную. Боюсь представить, какие неудобства испытывают в больших проектах. Опасаясь подобной ситуации, я и не хотел обновлять Xcode до завершения проекта, хотя обновление уже вышло официально. Вот такая показательная ситуация с обновлениями.
И ещё совсем кратенько упомяну не задокументированные глобальные функции, которые, на мой взгляд, тоже являются признаком молодости языка: countElements, dropFirst, dropLast, first, last, prefix, suffix, reverse. Так что берите playground и пробуйте.
Заключение
Дебаг уже доработали, NSDecimal появился. Это из исправленных недостатков, в списке изменений 1.2 есть, конечно же, и другие улучшения. Видно, что работа над языком продолжает вестись активно. И я нисколько не сомневаюсь, что со временем и прочие недостатки исправят, и фишек добавят. Но есть «но». Foundation никто же не будет переписывать: ObjC же никуда не денется. А пока все базовые библиотеки написаны на ObjC, на Swift нельзя будет писать в том стиле, для какого он был спроектирован, поскольку всегда нужно будет взаимодействовать с ObjC. Что ж, надеюсь когда-нибудь увидеть новый фреймворк, созданный специально для свифта. Звучит фантастично, но если в планах Apple полный переход на свифт, то, полагаю, рано или поздно это случится. Ведь есть же Core Foundation, и есть Foundation.
Приглашаю к дискуссии.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Заменит ли Swift в ближайшем будущем ObjC
50% Да. Apple прилагает к этому максимум усилий и они оправдаются.334
40.57% Не в ближайшем, но заменит. Инертность программистов, привыкших к ObjC, преодолеть получится не скоро.271
13.47% Нет. Swift не заменит.90
2.1% Не согласен с предложенными вариантами, пишу в комментариях свое мнение.14
Проголосовали 668 пользователей. Воздержались 260 пользователей.