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:
interface I
class C : I
fun I.foo(i: Int): Int = i
Without SKIE, this extension has to be called from Swift like this:
FileKt.foo(C(), i: 1)
With SKIE, the same extension can be called using the same syntax as class extensions:
C().foo(i: 1)
Properties
The situation with properties is similar. Given the above example, let's add an interface extension property:
var I.bar: Int
get() = 1
set(value) {}
Without SKIE, this property has to be called from Swift like this:
let c = C()
let getValue = FileKt.bar(c)
FileKt.setBar(c, value: 1)
With SKIE, the syntax is much simpler:
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.
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.