No último post, aprendemos a criar um XCFramework
a partir de código Kotlin e exploramos algumas características dos tipos de build gerados.
Com isso, podemos avançar e aprender como o código Kotlin compilado para Objective-C funciona e como consumi-lo no iOS.
- Exportando um 'Olá mundo' em Kotlin para iOS
- Compreendendo o código gerado pelo Kotlin/Native
- Melhorando a interoperabilidade com Swift
- Outras maneiras de melhorar a interoperabilidade
- Conclusões finais
Exportando um 'Olá mundo' em Kotlin para iOS
Para começar, vamos entender alguns pontos importantes sobre como o código Kotlin é convertido para Objective-C e, consequentemente, como utilizá-lo no iOS.
Vamos criar um simples HelloWorld
em Kotlin:
//HelloWorld.kt commonMain
expect fun helloWorld(): String
//HelloWorld.apple.kt appleMain
actual fun helloWorld(): String = "Olá mundo Apple Main"
Agora precisamos compilar um XCFramework
e integra-lo no Xcode. Existem diversos tutoriais na internet sobre como realizar essa tarefa; para esta demonstração, segui o guia "How to Integrate Kotlin Multiplatform (KMP) into Your iOS Project".
Os passos básicos são:
- Compilar o
XCFramework
com./gradlew assembleKotlinSharedXCFramework
. NOTA: substitua "KotlinShared" pelo nome do seuXCFramework
. Explicamos isso nos artigos anteriores. - Configurar o projeto Xcode para consumir o
XCFramework
gerado. - Utilizar o código Kotlin no iOS.
Depois que toda a configuração for realizada, conseguimos avançar e criar uma tela bem simples em SwiftUI para consumir o código Kotlin:
import SwiftUI
import KotlinShared
struct ContentView: View {
@State private var showText = false
var body: some View {
Button("Show Text") { showText.toggle() }
if showText { Text(HelloWorld_appleKt.helloWorld()) }
}
}
Como resultado, teremos:
O que está acontecendo aqui?
Vamos entender o que está acontecendo nos bastidores:
- O código Kotlin é compilado para Objective-C e empacotado em um
XCFramework
. - O
XCFramework
é integrado no projeto Xcode. - Com o
XCFramework
integrado, podemos importar o código Kotlin no iOS usandoimport KotlinShared
. - Dentro de
KotlinShared
(o nome doXCFramework
), temos acesso ao código Kotlin compilado para Objective-C. - A classe
HelloWorld_appleKt
é gerada automaticamente pelo Kotlin/Native, permitindo o acesso ao métodohelloWorld()
. - Assim, podemos utilizar o código Kotlin no iOS!
import KotlinShared
let helloWorld = HelloWorld_appleKt.helloWorld()
Mas se notarmos, a sintaxe para acessar o código Kotlin no iOS é bem estranha. HelloWorld_appleKt.helloWorld()
é uma sintaxe nada idiomática para o Swift.
Vamos entender melhor esse ponto.
Compreendendo o código gerado pelo Kotlin/Native
A maior limitação hoje no Kotlin/Native é a interoperabilidade com Objective-C. O Kotlin/Native não consegue gerar um código que seja 100% compatível com o Swift.
Isso porque o Kotlin/Native é um compilador que gera código Objective-C, e não Swift. O código gerado é compatível com Objective-C, e não Swift.
Ou seja, temos várias funcionalidades em Kotlin traduzidas diretamente para Swift (como high order functions, enums, etc), mas não temos uma tradução direta de Kotlin --> Objective-c.
Para investigar como o código Kotlin é traduzido para Objective-C, podemos acessar o código gerado pelo Kotlin/Native. Para isso, basta dar um cmd + click
na nossa classe HelloWorld_appleKt
:
Para melhorar a experiência de uso do código Kotlin no iOS, podemos codificar nosso código Kotlin de uma forma diferente, para ser mais idiomático ao Swift.
Melhorando a interoperabilidade com Swift
Observamos que não podemos simplesmente escrever código Kotlin e esperar que ele seja idiomático ao Swift devida a característica do Kotlin/Native somente gerar código Objective-C.
Para isso, temos que escrever nosso código Kotlin de uma forma que seja mais amigável ao Swift. Vamos refatorar o código HelloWorld
para ser mais idiomático ao Swift:
// HelloWorld.apple.kt appleMain
package br.com.rsicarelli.example
@HiddenFromObjC
actual fun helloWorld(): String = "Olá mundo Apple Main"
object HelloWorld
fun HelloWorld.get(): String = helloWorld()
Agora, realizamos o mesmo passo a passo para utilizar no Xcode:
- Compilar o XCFramework com
./gradlew assembleKotlinSharedXCFramework
. - No Xcode,
Products
>Build for ...
>Running
, ou simplesmentecmd + shift + r
Logo após o build, notamos que a nossa classe anterior HelloWorld_appleKt
não está mais disponível.
Antes de entender o porquê, vamos integrar nosso código KMP utilizando a nova abordagem:
import KotlinShared
struct ContentView: View {
@State private var showText = false
var body: some View {
Button("Show Text") { showText.toggle() }
if showText { Text(HelloWorld.shared.get()) }
}
}
Sucesso! Esse código é mais idiomático ao Swift, e conseguimos utilizar o código Kotlin no iOS de uma forma mais amigável.
Se abrirmos o código Objective-C gerado pelo Kotlin/Native, notamos algumas diferenças:
Interessante observar que, agora, nossa classe HelloWorld
é gerada como um Singleton, e o método get
é gerado como uma extensão!
E a anotação @HiddenFromObjC
?
A anotação @HiddenFromObjC
é uma anotação do Kotlin/Native que indica que o método não deve ser exposto para Objective-C. Isso é útil para métodos que não devem ser acessados diretamente pelo Objective-C, como funções de extensão.
A lógica do uso dessa anotação nesse contexto é a seguinte: temos duas formas de acessar o método helloWorld()
:
- Através da função de alto nível (high order function no Kotlin)
- Através da extensão do objeto
HelloWorld
Nesse caso, expormos as duas maneiras para o Objective-C não faz sentido, pois a função de alto nível apenas delegar para a extensão do objeto HelloWorld
. Isso pode ser confuso para quem está consumindo o código Kotlin no iOS.
Para isso, utilizamos a anotação @HiddenFromObjC
para esconder a função de alto nível do Objective-C, e expor apenas a extensão do objeto HelloWorld
!
Notas importantes:
- A anotação
@HiddenFromObjC
é uma anotação do Kotlin/Native, ou seja, não podemos utilizar em nenhum outro source set do KMP. - A anotação
@HiddenFromObjC
pode ser utilizada para funções, classes, atributos, etc.
Uma documentação completa entre a interoperabilidade entre Kotlin e Objective-C pode ser encontrada aqui Interoperability with Swift/Objective-C.
Outras maneiras de melhorar a interoperabilidade
Essa abordagem já funciona muito bem, porém, pode ser bem tedioso ter que criar uma extensão para cada função que queremos expor para o iOS.
No final, o que queremos é ter um código Kotlin que seja idiomático ao Swift, mas, ao mesmo tempo, codando Kotlin com todo seu potencial.
Para isso, temos três opções:
- Utilizar o plugin SKIE (Swift Kotlin Interface Enhancer)
- Atualizar para o Kotlin 2.1 e utilizar o novo sistema de interoperabilidade entre Kotlin --> Swift.
- Manualmente exportar extensions para cada acesso que queremos utilizar para o iOS, utilizando Swift.
A primeira opção é a mais robusta e a mais recomendada, já que o SKIE possuí uma série de funcionalidades que facilitam a interoperabilidade entre Kotlin e Swift.
A segunda opção, exportar código Swift utilizando Kotlin 2.1, continua em fase experimental, e não é recomendada para produção.
A terceira forma é bem manual e pode ser bem tediosa, mas é uma opção válida para quem não quer utilizar o SKIE. Como DEVs KMP, queremos escrever menos código possível, então é uma abordagem custosa de se escalar.
Para esse artigo, vamos utilizar o SKIE para melhorar a interoperabilidade entre Kotlin e Swift!
Utilizando o SKIE para melhorar a interoperabilidade
Integrar o SKIE em um módulo KMP é bem tranquilo e o projeto fornece uma documentação detalhada sobre a integração, SKIE > Installation
Mas de forma resumida:
- Aplicar o plugin
co.touchlab.skie
nobuild.gradle.kts
do projeto KMP - O plugin deve ser aplicado apenas no módulo que gera o XCFramework.
Basicamente é isso, aplicar o plugin e sincronizar.
Agora, vamos retornar a nossa abordagem anterior e apenas exportar a função helloWorld()
(sem a anotação @HiddenFromObjC
):
// HelloWorld.apple.kt appleMain
actual fun helloWorld(): String = "Olá mundo Apple Main"
Seguimos o passo a passo para utilizar no Xcode:
- Compilar o XCFramework com
./gradlew assembleKotlinSharedXCFramework
. - Aqui na minha máquina eu precisei de um clean build no Xcode, então
Products
>Clean Build Folder
- No Xcode,
Products
>Build for ...
>Running
, ou simplesmentecmd + shift + r
Agora, podemos utilizar o código Kotlin no iOS de uma forma mais idiomática ao Swift:
import SwiftUI
import KotlinShared
struct ContentView: View {
@State private var showText = false
var body: some View {
Button("Show Text") { showText.toggle() }
if showText { Text(helloWorld()) }
}
}
Analisando a função helloWorld()
, observamos que o SKIE gera uma função global que é acessível diretamente no Swift. Essa função global acessa a função helloWorld()
do Kotlin (na forma "feia"), e a expõe para o Swift.
Muito melhor hein? Agora, conseguimos utilizar o código Kotlin no iOS de uma forma idiomática ao Swift!
Considerações sobre o SKIE
O SKIE é extremamente poderoso e facilita muito a interoperabilidade entre Kotlin e Swift.
Porém, é importante lembrar que o SKIE é um plugin experimental, e está sujeito a mudança e depreciações.
Além disso, como é adicionado uma camada extra de conversão, a construção do XCFramework é deteriorada, e o tempo de build pode aumentar consideravelmente.
Isso porque o SKIE percorre todo o código Kotlin e cria seu par em Swift, o que pode ser um processo bem custoso. O SKIE fará isso não só com seu código Kotlin, mas também com todas as dependências que você exporta como "api" para o KotlinShared
.
Reduzindo o tempo de build do SKIE utilizando anotações
Uma funcionalidade muito legal do SKIE é possibilidade de escolher quais funcionalidades do SKIE você quer utilizar.
Para isso, o SKIE fornece uma série de anotações que permitem customizar a exportação de código Kotlin para Swift. Isso nos possibilita escolher a dedo qual código queremos exportar para o Swift, e reduzir o tempo de build do SKIE.
Conclusões finais
Com esse artigo, conseguimos entender como utilizar código Kotlin no Swift, suas características e limitações, e como melhorar a interoperabilidade entre Kotlin e Swift com uma escrita alternativa de código Kotlin ou utilizando o SKIE.
O KMP é craque em exportar código Objective-C, mas estamos atualmente limitados na exportação de código Swift. Com o SKIE, conseguimos melhorar essa limitação e exportar código Kotlin de uma forma mais idiomática ao Swift. E, as próximas versões do Kotlin, a interoperabilidade entre Kotlin e Swift será ainda mais robusta e nativa.
Espero que tenham gostado do artigo! 🚀
Até a próxima 🤙
Top comments (0)