Skip to main content

Interface Extensions

Overview

Objective-C doesn't support extensions for protocols (interfaces in Kotlin). Due to this limitation, Kotlin has to expose the interface extensions differently. The chosen solution is to treat them as if they were global functions with a special receiver parameter.

The drawback of this solution is that the syntax is far from ideal and introduces many problems, for example:

  • The user must know/find the name of the Kotlin file in which the extension is defined, and Xcode doesn't help with that much.
  • The user has to distinguish whether the extension is declared on an interface or a class - as those behave differently.
  • The receiver is passed as a parameter, so extension properties must be exposed as separate getter and setter functions.

SKIE solves these problems by generating wrappers for these extensions in Swift instead of Objective-C. This is possible because Swift does allow to define extensions for Objective-C protocols.

Examples

Functions

Consider the following interface extension:

Kotlin (File.kt)
interface I

class C : I

fun I.foo(i: Int): Int = i

Without SKIE, this extension has to be called from Swift like this:

Swift without SKIE
FileKt.foo(C(), i: 1)

With SKIE, the same extension can be called using the same syntax as class extensions:

Swift with SKIE
C().foo(i: 1)

Properties

The situation with properties is similar. Given the above example, let's add an interface extension property:

Kotlin
var I.bar: Int
get() = 1
set(value) {}

Without SKIE, this property has to be called from Swift like this:

Swift without SKIE
let c = C()

let getValue = FileKt.bar(c)

FileKt.setBar(c, value: 1)

With SKIE, the syntax is much simpler:

Swift with SKIE
let c = C()

let getValue = c.bar

c.bar = 1

Limitations

This feature has two minor limitations:

  • Only interface extensions are currently supported (for example optional extensions are not yet supported).
  • In some rare situations, it can introduce new name collisions.

The most common occurrence of a name collision is if some other interface (or a class) implements another interface and both have conflicting extensions. Another similar situation is if a single interface has two conflicting extensions, but they are located in different files.

Both situations would be a problem for class extensions, but they previously weren't a problem for interface extensions. This is because the interface extensions were exposed as global functions in separate namespaces. So, this was one of the few cases where the namespaces have some advantage.

If such a name collision occurs, the generated wrapper will be renamed using the standard "_" suffix.

caution

We do not recommend using any declarations that are renamed using the "_" suffix in Swift. Read more about the associated risks here.

Migration and Compatibility

This feature shouldn't introduce any breaking changes. The original extensions are unmodified and still available under their original names. Therefore, incremental adoption of this feature is possible without any manual configuration.