Kotlin/Native v0.7 released: smoother interop, frozen objects, optimisations and more.

Posted on by

We’re happy to announce the release of Kotlin/Native v0.7, May Day edition! This release provides even smoother interoperability with Objective-C and Swift, memory management improvements, global program analysis and performance optimisations. Also numerous bugfixes and optimizations were implemented in this release.

AppCode and CLion Kotlin/Native plugins were updated to work with v0.7, along with minor performance and usability improvements.

Binaries can be downloaded from the following links: macOS, Linux, Windows

Also Linux Snap package is available.

GitHub release page is here.

Smoother interop.

Imagine the following code on Kotlin side, compiled to the framework and later used from Swift.

fun isItTimeForKotlin(date: NSDate): String { val kotlin10Release = NSISO8601DateFormatter().dateFromString( "2016-02-15T00:00:00Z")!! return if (date.timeIntervalSinceDate(kotlin10Release) >= 0) "sure!" else "yes" }

Before, calling Objective-C APIs like timeIntervalSinceDate() with an object coming from the Swift world was not possible, now one could call it like this:

let now = Date() print(Common.isItTimeForKotlin(date: now))

Other popular request was ability to convert objects between Swift and Kotlin world transparently, i.e. now one could do

val string = "Kotlin" as NSString val list = listOf(1, "Konan") as NSArray val map = mapOf(1 to "one", 2 to "two") as NSDictionary

And in reverse direction, i.e. convert Objective-C NSDictionary to Kotlin like this:

val data = ("{ \"foo\": 1} " as NSString).dataUsingEncoding(NSUTF8StringEncoding)!! val jsonObject = NSJSONSerialization.JSONObjectWithData( data, 0, null) as? Map<Any?, *> ?: throw Error("JSON parsing error") println(jsonObject["foo"])

and then it could be used as a regular Kotlin map.

When creating UI, it is typical to use initializers override, while Kotlin’s constructors are not virtual. Following snippet would help:

@ExportObjCClass class ViewController : UIViewController { @OverrideInit constructor(coder: NSCoder) : super(coder) @ObjCOutlet lateinit var button: UIButton @ObjCAction fun buttonPressed() { println("Hello!") } }

Note that constructor accepting NSCoder is marked with @OverrideInit and is called virtually. Other nice features are ability to interoperate with checked exceptions thrown by Kotlin on Objective-C/Swift side (if Kotlin method is marked with a @Throws annotation).

Memory model improvements: frozen objects, weak references.

Traditionally, Kotlin/Native was trying to avoid shared data as much as possible. However, in some scenarios there is need for some shared immutable data. Typical example is a configuration data, read from the file on startup and afterwards made available everywhere. To achieve such a goal, concept of object freezing is implemented. Object is either in the mutable state and is owned by a single thread or worker, or is immutable, and could be shared amongst multiple threads or worker, and could be freed once all consumers no longer need it.

data class Config(val root: String, val version: Int) fun init() { val config = Config("/path", 1).freeze() val workers = Array(10, { _ -> startWorker()}) // Pass all workers a config. workers.forEach { it -> it.schedule(TransferMode.CHECKED, { config } ) { config -> println("got $config hash is ${config.hashCode()}") }} }

This code creates a frozen instance and passes it to 10 workers, and they all share the same instance of the configuration. Frozen objects has nice property of concurrent cycle-safe reference counter-based memory management, as during freezing process object graph is condensed to a DAG, and so could be collected basing on reference counter only. If one tries to mutate a frozen object, a runtime exception is thrown. So freezing allows for safer sharing of objects across multiple threads or workers. See this document for more details on concurrency approaches in Kotlin/Native.

Another requested feature is weak references, including weak reference to Objective-C objects. Weak reference allows to know when a certain object is no longer needed, and was deallocated by the memory manager. This allows to implement data structures like caches, or break reference cycles when interoperating with the Objective-C world.

class Ref : NSObject() { var ref: WeakReference<NSObject> = WeakReference(this) } fun update(ref: Ref) = autoreleasepool { ref.ref = WeakReference(Ref()) } fun test() { val x = Ref() println(x.ref.get()) // Gives reference to x. update(x) println(x.ref.get()) // Gives null. }

Performance optimisations: devirtualization, box caching.

Kotlin/Native approach to performance is to do as much ahead of time optimisations as possible (contrary to the JVM approach, which does most optimisations just in time). Major enabler of such optimisations is building global call graph with the resolved polymorphism (that is, computing actual runtime type of the variable, not one explicitly stated in the signature or inferred by the frontend). This is possible in the closed world compilation, where all the pieces are known during the final code generation. Kotlin/Native performs devirtualization analysis based on a call graph and cross-module type propagation. This would allow even more optimisations, such as escape analysis for arena-based memory allocator in the future.

Box values for popular primitive types are now cached during compilation, and so are not allocated in the runtime, thus saving memory management overhead.