Swift for Android: Our Experience and Tools
Jun 19, 2018 · 6 min read
Early last week, we launched Spark for Teams — a revolutionary email client that’s changing the way teams use email. Spark is still an phenomenal email for personal use because it shows what’s important in your inbox.
Spark is one of the most popular email apps available for Free o n iPhone, iPad, Mac, as well as Apple Watch. For the millions of users on the Android platform, the time when Spark helps you love your email isn’t very far. In the meantime, we want to share with you an incredible new toolchain we’ve created that takes the pain and hassles out of working with Swift for Android. This toolchain helps dev teams utilize the code they’ve written for the iOS or Mac platforms to create great Android applications.
The Apple Swift compiler has had the ability to compile code for the Android platform for a few years now, but it hasn’t made many friends in the developer community owing to its complexity. Our toolchain was designed to solve this problem by taking the complexity and headaches out of the process, so you can focus on building great apps for your users.
When we started working on the development of the Android version of Spark, we were looking for a cross-platform language that could be used for both Android & iOS development and would also be easy to migrate from our existing Objective-C codebase.
It was around this time that Swift became an open-source project and after a short while, it also got the ability to compile code for the Android platform. We explored our options and it was pretty obvious to us that we should move in that direction.
Swift Android Compiler
How does Swift Android Compiler work?
The answer to that is simple: Low-Level Virtual Machine.
The LLVM is a compiler infrastructure that is based on the concepts of three-phase design with re-targetability. In simple terms, this means that any language that has a front-end LLVM compiler can be compiled for any target that has a LLVM backend compiler.
Google already makes use of LLVM compilers for native Android development with C/C++. That’s why, from an Android device perspective, there is no difference in libraries compiled from C/C++ or Swift code. Moreover, it’s actually better for developers, because they can re-use most of the instruments that were created for C/C++ development — Android profiling tools or an Android low-level debugger (with a limitation on an evaluation of swift code).
What frameworks are available?
Well…there aren’t many. We only have access to SwiftCore, Dispatch, and SwiftFoundation in our toolchain. SwiftCore and Dispatch are largely identical to iOS and Mac versions. But SwiftFoundation on the other hand is not the same Foundation that Apple uses for its own platforms; it’s a re-implementation of all classes from the original library. That’s why SwiftFoundation is still missing some features, but it covers all basic needs such as performing network requests, parsing JSON/XML, storing data on disk, etc.
Obviously UIKit or any high level frameworks are not available, so your Swift app for iOS cannot magically run on Android — you can use Swift for business logic code, but you will have to re-write all user interface and OS dependent parts specifically for Android.
What are the limitations of the Swift Android Compiler?
- At the moment, the Swift compiler only supports the ARM-v7A Android platform.
- Minimum version of Android supported is v5.0 (Lollipop).
- And one of the most significant limitations is the generation 32MB+ APK files, because build should include SwiftCode, SwiftFoundation, and libDispatch.
Introducing the Swift Android Toolchain
One of the biggest hurdles in using Swift compiler for Android is the lack of official support from Apple, which means no continuous integrations or no official builds. We realized that we could optimize the many of the hassles present in developing in Swift for Android and at the same time, improve the experience for our developers in this area. We wanted to avoid all the headaches that Android developers typically have to suffer when working with these tools.
That’s why we have the Swift Android Toolchain — a collection of tools that gives Android developers the ability to use Swift in their projects comfortably and without any hassles. It is an open-source project based on Apple Swift compiler and SwiftJava and SwiftAndroid projects. You can download the toolchain directly from Bintray (Mac only) or compile on your own from GitHub.
We’re currently using forked version of Swift. Our fork contains option to disable @objc and dynamic features in compiler because it doesn’t work properly on non-Darwin platforms. It also contains some minor fixes. We’re working on being able to use official Swift repository in future.
Furthermore, we use Swift Package Manager (SPM) as the build system for Swift. It is the build system that Apple uses in the compiler and others Swift libraries. This tool provides the ability to connect dependencies, compile code, link artifacts (dynamic libraries or executables) and run tests. Along with our toolchain, we provide scripts for SPM that will automate all processes of development and testing Android apps.
Gradle plugin
Of course, it’s great to build Swift code from the command line, but it’s simply more natural for Android developers to use the Android Studio IDE. Fortunately, the Android IDE uses a very flexible build system called ‘Gradle’.
We created a Gradle plugin for adding a Swift compilation build step. It gives us the ability to build mixed Swift/Java/Kotlin projects with just one button.
JVM interoperability
All Android applications run in the Java Virtual Machine. In case any developer prefers native code (C/C++), they must load the library inside an app and interact with it via the Java Native Interface (JNI). Swift is no exception. Thus, for using Swift code in JVM, we should load the libraries and write a JNI bridge.
It’s entirely up to you what JVM language (Java or Kotlin) you use and how you write the bridging code, but we recommend you take a look at our Swift Annotation Processor that generates all JNI code from the Java classes and interfaces.
Blueprint project
The Android team at Readdle has created a sample Swift application for trying out the Swift Android Toolchain in action. If you want to try Swift for Android, this is an excellent project to start with. The Blueprint project is a simple to-do app with business logic written in Swift, while the UI was done in Java.
Testing
The Swift project already includes a testing framework called XCTest and it can already be used for testing Swift code on Android platform. The Swift Package Manager allows compiling tests in one fat binary executable; after that, developers can upload binary executable on Android devices and run it via the Android shell.
BONUS: Our toolchain automates all stages of this testing process. And the Blueprint project includes few simple tests for you to try.
What’s next?
We still have a lot to do for Swift toolchain:
- ARM64 compiler (tentative deadline: August 2019)
- X86, X86_64 compiler (Chromebooks support)
- LLDB with Swift support: client and server
We hope that our Swift Android Toolchain will help you in making your development process easy and hassle-free. The Swift language has been evolving greatly over the last couple of years and developers who already code for the iOS and Mac platforms using Swift can now easily have Android on their radar too.
Источник
Как сделать Swift-friendly API с Kotlin Multiplatform Mobile
Kotlin Multiplatform Mobile позволяет компилировать Kotlin код в нативные библиотеки для Android и iOS. И если в случае с Android полученная из Kotlin библиотека будет интегрироваться с приложением написанным на Kotlin, то для iOS интеграция будет с Swift и на стыке Kotlin и Swift, из-за разницы языков, происходит потеря удобства использования. В основном это связано с тем, что компилятор Kotlin/Native (который компилирует Kotlin в iOS framework и является частью Kotlin Multiplatform) генерирует публичное API фреймворка на ObjectiveC, а из Swift мы обращаемся к Kotlin за счет этого сгенерированного ObjectiveC API, так как Swift имеет интероп с ObjectiveC. Далее я покажу примеры ухудшения API на стыке Kotlin-Swift и покажу инструмент, который позволяет получить более удобное API для использования из Swift.
Рассмотрим пример использования sealed interface в Kotlin:
Это удобная конструкция для описания состояний, которая активно используется в Kotlin коде. Теперь посмотрим как она выглядит со стороны Swift?
Удобный для использования в Kotlin sealed interface со стороны Swift выглядит просто набором классов, которые имеют общий интерфейс. Разумеется в таком случае нельзя надеяться на проверку полноты реализации switch , так как это не enum . Для разработчиков знакомых с Swift более правильным аналогом sealed interface считается enum , например:
Мы можем написать со стороны Swift такой enum и преобразовывать полученный из Kotlin UIState в наш Swift enum, но что если таких sealed interface будет много? Достаточно распространен подход MVI в котором состояние экрана и события описываются именно sealed class/interface. Писать под каждый такой случай аналог в swift — трудоемко. И в дополнение у нас появляется риск рассинхронизации класса в Kotlin и enum в Swift.
Решая эту проблему мы в IceRock сделали специальный gradle plugin — MOKO KSwift. Это gradle plugin, который читает все klib, используемые при компиляции iOS framework. klib это формат библиотек, в который Kotlin/Native компилирует всё, перед тем как собирать финальные бинарники под конкретный таргет. Внутри klib доступно множество метаданных, которые дают полную информацию о всем публичном kotlin api, без каких либо потерь информации. Наш плагин анализирует все klib, которые указаны в export для iOS framework (то есть те, API которых будет включено в header фреймворка), и на основе полного представления о kotlin коде генерирует Swift код, в дополнение к тому что есть в Kotlin. Для нашего примера с UIState плагин автоматически генерирует следующую конструкцию:
Мы автоматически получаем Swift enum, который гарантированно соответствует sealed interface из Kotlin. Этот enum можно создать передав в него объект UIState , который мы получаем из Kotlin. И в этом enum есть доступ к классам из Kotlin, чтобы получить всю необходимую информацию. Так как данный код полностью генерируется автоматически при каждой компиляции, то мы избегаем рисков связанных с человеческим фактором — машина не может забыть обновить код в Swift после изменения в Kotlin.
Перейдем к следующему примеру. В MOKO mvvm (наш порт android architecture components с android в Kotlin Multiplatform Mobile) для привязки LiveData к UI элементам мы реализовали для iOS набор extension функций, например:
Но после компиляции в iOS framework нас ждало разочарование, ведь Kotlin/Native не умеет добавлять extension’ы к платформенным классам:
В использовании вместо удобного API label.bindText(myLiveData) требуется UILabelBindingKt.bindText(label, myLiveData) .
Данную проблему также позволяет решить MOKO KSwift, так как обладает полными знаниями о всем публичном интерфейсе Kotlin библиотек. В результате генерируется следующая функция:
На данный момент в плагине KSwift доступно «из коробки» два генератора — SealedToSwiftEnumFeature (для генерации swift enum) и PlatformExtensionFunctionsFeature (для генерации extension к платформенным классам), но сам плагин имеет расширяемую API, вы можете реализовать генерацию нужного вам Swift кода в дополнение к вашему Kotlin коду без внесения изменений непосредственно в плагин — просто в своем gradle проекте. Подключив плагин как зависимость к buildSrc можно будет написать свой генератор, например:
В приведенном примере мы включаем анализ Kotlin классов ( ClassContext ) и генерируем для каждого из Kotlin классов extension в Swift. В классах Context доступна вся информация из метаданных klib, а в метаданных есть вся информация о классах, методах, пакетах и прочем, в том же объеме что и у компиляторных плагинов, но доступно только для чтения (в то время как компиляторные плагины позволяют менять код на этапе компиляции).
На данный момент плагин является новым решением и может работать некорректно в некоторых случаях, о которых стоит обязательно сообщать в issue на GitHub. Для сохранения возможности использовать плагин и в случаях, когда генерируется некорректный код, добавлена возможность фильтрации подвергаемых генерации сущностей. Например для исключения из генерации класса UIState нужно прописать в gradle:
А также доступна фильтрация по обрабатываемым библиотекам и возможность включать режим includeFilter (чтобы генерация происходила только для указанных сущностей).
Если вы используете у себя технологию Kotlin Multiplatform Mobile, рекомендую вам попробовать плагин на своем проекте (и дать обратную связь на github) — работа iOS разработчиков станет лучше, когда они получат Swift-friendly API для работы с Kotlin модулем. А также, по возможности, делитесь своими вариантами генераторов также на GitHub — чем больше улучшения API будет поддерживаться плагином «из коробки» — тем проще будет всем.
Отдельное спасибо Святославу Щербине из JetBrains, за подсказку про возможность использования klib metadata.
Источник
Компиляция swift под андроид
Copy raw contents
Copy raw contents
Getting Started with Swift on Android
The Swift stdlib can be compiled for Android armv7, x86_64, and aarch64 targets, which makes it possible to execute Swift code on a mobile device running Android or an emulator. This guide explains:
- How to run a simple «Hello, world» program on your Android device.
- How to run the Swift test suite on an Android device.
If you encounter any problems following the instructions below, please file a bug using https://bugs.swift.org/.
Let’s answer a few frequently asked questions right off the bat:
Does this mean I can write Android applications in Swift?
No. Although the Swift compiler is capable of compiling Swift code that runs on an Android device, it takes a lot more than just the Swift stdlib to write an app. You’d need some sort of framework to build a user interface for your application, which the Swift stdlib does not provide.
Alternatively, one could theoretically call into Java interfaces from Swift, but unlike as with Objective-C, the Swift compiler does nothing to facilitate Swift-to-Java bridging.
To follow along with this guide, you’ll need:
- A Linux environment capable of building Swift from source, preferably Ubuntu 18.04 or Ubuntu 16.04. Before attempting to build for Android, please make sure you are able to build for Linux by following the instructions in the Swift project README.
- The latest version of the Android NDK (r23b at the time of this writing), available to download here: https://developer.android.com/ndk/downloads/index.html.
- An Android device with remote debugging enabled or the emulator. We require remote debugging in order to deploy built stdlib products to the device. You may turn on remote debugging by following the official instructions: https://developer.chrome.com/devtools/docs/remote-debugging.
«Hello, world» on Android
1. Downloading (or building) the Swift Android stdlib dependencies
You may have noticed that, in order to build the Swift stdlib for Linux, you needed to apt-get install libicu-dev icu-devtools . Similarly, building the Swift stdlib for Android requires the libiconv and libicu libraries. However, you’ll need versions of these libraries that work on Android devices.
The steps are as follows:
- Ensure you have curl , autoconf , automake , libtool , and git installed.
- Clone the SwiftAndroid/libiconv-libicu-android project. From the command-line, run the following command: git clone https://github.com/SwiftAndroid/libiconv-libicu-android.git .
- From the command-line, run which ndk-build . Confirm that the path to the ndk-build executable in the Android NDK you downloaded is displayed. If not, you may need to add the Android NDK directory to your PATH .
- Change directories into libiconv-libicu-android : cd libiconv-libicu-android
- Run the Swift build script: ./build-swift.sh
- Confirm that the various libicuXYZswift.so libraries are located in the armeabi-v7a directory.
2. Building the Swift stdlib for Android
Enter your Swift directory, then run the build script, passing paths to the Android NDK, as well as the directories that contain the libicuucswift.so and libicui18nswift.so you downloaded or built in step one:
3. Compiling hello.swift to run on an Android device
Create a simple Swift file named hello.swift :
Then use the built Swift compiler from the previous step to compile a Swift source file, targeting Android:
This should produce a hello executable in the directory you executed the command. If you attempt to run this executable using your Linux environment, you’ll see the following error:
This is exactly the error we want: the executable is built to run on an Android device—it does not run on Linux. Next, let’s deploy it to an Android device in order to execute it.
4. Deploying the build products to the device
You can use the adb push command to copy build products from your Linux environment to your Android device. If you haven’t already installed adb , you may do so via apt-get :
Once you have adb installed, verify your device is connected and is listed when you run the adb devices command — currently this example works only in devices / emulators with at least Android 7.0, API 24 — then run the following commands to copy the Swift Android stdlib:
You will also need to push the icu libraries:
In addition, you’ll also need to copy the Android NDK’s libc++:
Finally, you’ll need to copy the hello executable you built in the previous step:
5. Running «Hello, world» on your Android device
You can use the adb shell command to execute the hello executable on the Android device:
You should see the following output:
Congratulations! You’ve just run your first Swift program on Android.
Running the Swift test suite hosted on an Android device
When running the test suite, build products are automatically pushed to your device. As in part four, you’ll need to connect your Android device via USB:
Источник