Kotlin Multiplatform
2.62K subscribers
84 photos
3 videos
407 links
Русскоязычный канал новостей о Kotlin Multiplatform
Download Telegram
Сегодня заденем тему отладки, а именно - отладки библиотеки написанной на Kotlin в iOS. В отличие от flutter, react native, xamarin и подобных - для отладки кода скомпилированного kotlin/native не требуется никакой специфичный отладчик. Достаточно Xcode и встроенного в него lldb. Например, используя следующую команду можно строчке в kotlin файле поставить breakpoint:
b -f hello.kt -l 1
И при остановке на этом месте Xcode корректно выведет стектрейс, с понятным именованием kotlin функций, даже тех что находятся внутри stdlib. Для упрощения установки breakpoint'ов в Touchlab сделали специальный плагин для Xcode, позволяющий открыть kotlin файл и поставить breakpoint так же просто как в swift файле - нажав слева от строки в Xcode. Также поддержка точек останова есть и в AppCode от JetBrains (с установленным плагином Kotlin/Native).
Единственное неудобство - локальные переменные доступные в скоупе функции, где мы остановились, не доступны для вывода информации о них. Но используя специальный python скрипт для отладчика - можно считать и их. Подробнее в разделе Variable Inspection в документации про отладку kotlin/native.

Подробнее про отладку и Variable Inspection - https://github.com/JetBrains/kotlin-native/blob/master/DEBUGGING.md
Статья с обзором плагина для Xcode - https://medium.com/hackernoon/kotlin-xcode-plugin-64f52ff8dc2a
Новость о добавлении поддержки Kotlin/Native в AppCode - https://blog.jetbrains.com/kotlin/2019/04/kotlinnative-support-for-appcode-2019-1/

#tips
Немного про подключение kotlin/native фреймворка в ios приложение. Есть несколько путей:
1. Добавить в ios проекте отдельный таргет-фреймворк, который не будет иметь какого либо исходного кода, но будет содержать специальную build phase, в которой будет происходить вызов gradle task компиляции фреймворка, после чего результат компиляции будет копироваться в директорию сборки xcode - туда, куда бы сам xcode скомпилировал фреймворк будь тут исходный код swift'а. Это самый старый вариант интеграции.
2. Добавить в ios проекте фреймворк как embed фреймворк, указывая где он будет находиться - директория должна быть всегда одна, даже для разных архитектур фреймворка. Далее добавить к проекту build phase, которая будет вызывать у gradle специальную таску. Задача таски - собрать фреймворк и положить его по тому пути, который был указан при добавлении фреймворка в проект. Таким образом нет никаких лишних таргетов. Этот подход описан в текущем туториале от JetBrains "Targeting iOS and Android with Kotlin Multiplatform".
3. Интеграция через CocoaPods используя плагин org.jetbrains.kotlin.native.cocoapods. Для айосников он будет самый удобный. В gradle потребуется добавить специальную конфигурацию плагина (по сути описание pod'а нашего) и плагин автоматически сгенерирует нам podspec. После чего в Podfile от iOS приложения мы просто ссылаемся на наш pod локальный и вся интеграция готова. Под капотом все работает примерно так же как первый и второй случай - создается специальный проект для pod'а, там нет исходного кода но есть embed framework который собирается специальной gradle таской, которая и скомпилирует и перенесет фреймворк по нужному пути. Есть у этого решения пара особенностей - во первых на момент pod install фреймворк должен быть уже скомпилирован, то есть в градле мы должны засинхать проект и запустить gradle task :podspec в нашем mpp проекте. Во вторых - из gradle проекта не видны таски, используемые в xcode build phase (syncFramework в особенности). То есть запустить перекомпиляцию с переносом фреймворка куда-надо из вне xcode нельзя. А еще плагин позволяет подключать iOSные pod'ы в kotlin/native (только те у которых есть objc биндинг).
4. Интеграция через CocoaPods но без плагина org.jetbrains.kotlin.native.cocoapods. Разница в том, что podspec файл один раз пишется и кладется рядом с проектом, а градл таски syncFramework добавляются отдельно к проекту (у нас в IceRock это делает наш mobile-multiplatform плагин) и доступны для запуска просто из IDE или gradle. Это дает больше прозрачности при разработке и полный контроль над самой build phase сборки фреймворка (мы например кеширование на проектах через shell скрипт дополнительное делаем, чтобы не запускать gradle лишний раз при сборках ios проекта - он очень долгий).

Туториал Targeting iOS and Android with Kotlin Multiplatform - https://play.kotlinlang.org/hands-on/Targeting%20iOS%20and%20Android%20with%20Kotlin%20Multiplatform/
Плагин CocoaPods - https://kotlinlang.org/docs/reference/native/cocoapods.html
Плагин mobile-multiplatform - https://github.com/icerockdev/mobile-multiplatform-gradle-plugin
Пример интеграции mobile-multiplatform плагина (пока у него нет ридми) - https://github.com/icerockdev/moko-mvvm/tree/master/sample

#tips
Обратим внимание на еще один важный для разработки момент - Continuous Integration. Уже нельзя представить современный проект без CI, который как минимум должен автоматизировать деплой билдов, а в идеале - проводить и тестирование и проверку кода на правила разработки.
Что же меняется на CI относительно чисто нативных iOS и Android проектов, если мы начинаем использовать общую библиотеку на Kotlin MultiPlatform?
Ответ - практически ничего. Для android все остается также как было. Для iOS могут потребоваться некоторые изменения. Обратим внимание на них:
1. На раннере собирающем iOS должна быть java для корректного запуска gradle и компиляции kotlin/native;
2. При интеграции общей библиотеки через cocoapods перед вызовом pod install должен быть собран фреймворк (в официальном cocoapods плагине перед pod install нужно вызвать у gradle задачу podspec, а если использовать наш mobile-multiplatform плагин как в moko-template - можно просто вызывать pod install и все сделается само);
3. При интеграции общей библиотеки через билдфазу с отдельным таргетом в самом проекте - билдфаза сама запустит градл и все что нужно сделает, без необходимости делать что-то предварительно;
4. Для запуска тестов на айос пока требуется добавлять отдельную градл таску в конфигурацию общей библиотеки. Таска соберет запускаемый файл тестов и запустит его на симуляторе iOS.

У нас, в IceRock, для CI используется GitLab CI, раннер это mac-mini на котором и xcode и android sdk установлены, сборка android проекта делается полностью средствами градла, а сборка айос полностью средствами Fastlane.

Так же по этой теме есть неплохая статья https://diamantidis.github.io/2019/09/08/continuous-integration-for-kotlin-native-projects-with-gitlab-ci, в которой разобрана вся настройка CI для GitLab под проект с общей библиотекой, включая и запуск тестов и прогон lint. Статья входит в цикл статей про MPP - там есть и про настройку окружения, и про настройку юнит тестов, и про инструменты проверки кода. Есть и проект пример с этим всем https://gitlab.com/diamantidis_io/kmp_template/tree/master

Упомянуто:
https://kotlinlang.org/docs/reference/native/cocoapods.html - официальный gradle плагин cocoapods
https://github.com/icerockdev/mobile-multiplatform-gradle-plugin - gradle плагин для упрощения настройки mobile mpp проектов
https://github.com/icerockdev/moko-template - проект шаблон с использованием mobile-multiplatform плагина (примеры фичей и ридми пока в процессе)

#posts #tips
При разговоре о мультиплатформенной разработке iOS разработчики обычно предполагают что это означает отказ от привычных им библиотек. В контексте Kotlin Multiplatform хочется развеять это заблуждение...
* Первый момент (и самый очевидный) - так как UI делается все так же в Swift, то разработчику доступны все нативные библиотеки как через CocoaPods так и Carthage;
* Второе - внутри самой общей библиотеки на Kotlin в ios-specific части тоже можно использовать зависимости из нативного мира - ограничение только в том что библиотека должна иметь objc биндинг (так как Kotlin/Native взаимодействует именно с objc). Вручную это делается через cinterop;
* Третье - для упрощения подключения iOS библиотек к kotlin ios-specific gradle плагин org.jetbrains.kotlin.native.cocoapods позволяет прям в gradle указать список pod'ов которые будут автоматически проброшены в kotlin. И в ios-specific части можно работать с подключенными библиотеками. У плагина есть пара ограничений (нет поддержки SubSpec и собирать библиотеку нужно будет обязательно через Xcode, так как для линковки все данные о подключенных Pod'ах именно у Xcode).

Документация по плагину - https://github.com/JetBrains/kotlin-native/blob/master/COCOAPODS.md
Ручное подключение библиотеки, без плагина - https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#cinterop-support

#tips
Плагин для Xcode, позволяющий ставить breakpoint'ы в kotlin коде, получил обновление до Xcode 11.
Для установки нужно просто скачать актуальное состояние с master и запустить скрипт setup.

Помимо установки точек останова можно выводить использовать специальны скрипт для вывода данных из локальных переменных – konan_lldb.py для lldb (консоль отладчика в Xcode). Для его использования согласно документации по отладке Kotlin/Native нужно:
вызвать command script import <путь до konan_lldb.py, а после этого, используя p <имя переменной>, можно получать информацию о kotlin объектах.

#tips
Опрос показал, что половина участников пока только знакомится с информацией и статьями, но не пробовали kotlin multiplatform в разработке мобильных приложений.

На грядущие выходные есть полезные материалы для этой половины:
1. На github доступен шаблон проекта moko-template, который позволяет быстро начать разработку мобильного приложения с mpp внутри, имеет подключенные moko-библиотеки и пример из пары фич (архитектура проекта многомодульная, что положительно сказывается на времени пересборки ios-таргета);
2. В дополнение к moko-template доступны codelab'ы, в которых разобрано создание приложения просмотра списка GIF'ок на основе moko-template;
3. На kmp.icerock.dev можно найти множество ссылок на полезные статьи, важные блоки документации kotlin про mpp, полезные библиотеки как официальные так и от сообщества, проекты примеры.

Надеюсь эти материалы помогут вам попробовать технологию и упростят изучение.
Ошибки/предложения по можно писать в issues на github (moko-template, codelabs, kmp).

#tips
Советы из опыта

suspend-функции в публичном интерфейсе библиотеки, которая компилируется в iOS framework, автоматически стираются. Это происходит потому что в objective-c (и swift) нет аналога suspend-функций. По этой причине рекомендуется держать всю работу с kotlin-coroutines внутри kotlin.

Если же требуется вызывать suspend-функцию из swift/objective-c, то можно в ios-specific коде сделать экстеншен-функцию принимающую callback, а внутри функции запускать корутину например на GlobalScope. При чем это можно делать не вручную, а использовать плагин, генерирующий такую функцию автоматически, например - Recast, kotlin-native-suspend-function-callback.

#tips
Советы из опыта

При компиляции Kotlin в iOS-framework нужно учитывать, что abstract class'ы станут в Objective-C (и Swift) обычными классами. Это происходит из-за того, что в Objective-C и Swift нет абстрактных классов. Поэтому Xcode при компиляции никак не проверит реализовал ли разработчик абстрактный метод или нет. Если абстрактный метод останется не реализованным то в рантайме будет выброшено исключение.

Рекомендуется просто не выносить абстрактные классы в публичный интерфейс. Их можно использовать внутри kotlin, а наружу можно выдать интерфейс, который требуется с нативной стороны реализовать.

#tips