Skip to content

Commit a978a7f

Browse files
authored
update: conversion notes (#4838)
1 parent b8b298e commit a978a7f

File tree

1 file changed

+68
-24
lines changed

1 file changed

+68
-24
lines changed

docs/topics/native/native-objc-interop.md

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
>
1010
{style="warning"}
1111

12-
This document covers some aspects of Kotlin/Native interoperability with Swift/Objective-C: how you can use Kotlin
12+
Kotlin/Native provides indirect interoperability with Swift through Objective-C. This document covers how you can use Kotlin
1313
declarations in Swift/Objective-C code and Objective-C declarations in Kotlin code.
1414

1515
Some other resources you might find useful:
@@ -43,12 +43,12 @@ Kotlin modules can be used in Swift/Objective-C code if compiled into a framewor
4343
>
4444
{style="warning"}
4545

46-
To make your Kotlin code more Objective-C/Swift-friendly, you can hide a Kotlin declaration from Objective-C and Swift
47-
with `@HiddenFromObjC`. The annotation disables a function or property export to Objective-C.
46+
To make your Kotlin code more Swift/Objective-C-friendly, use the `@HiddenFromObjC` annotation to hide a Kotlin declaration
47+
from Objective-C and Swift. It disables the function or property export to Objective-C.
4848

4949
Alternatively, you can mark Kotlin declarations with the `internal` modifier to restrict their visibility in the
50-
compilation module. Choose `@HiddenFromObjC` if you only want to hide the Kotlin declaration from Objective-C and Swift,
51-
but still keep it visible from other Kotlin modules.
50+
compilation module. Use `@HiddenFromObjC` if you want to hide the Kotlin declaration from Objective-C and Swift
51+
while keeping it visible to other Kotlin modules.
5252

5353
[See an example in the Kotlin-Swift interopedia](https://github.com/kotlin-hands-on/kotlin-swift-interopedia/blob/main/docs/overview/HiddenFromObjC.md).
5454

@@ -186,9 +186,9 @@ The table below shows how Kotlin concepts are mapped to Swift/Objective-C and vi
186186
| `companion` member <- | Class method or property | Class method or property | |
187187
| `null` | `nil` | `nil` | |
188188
| `Singleton` | `shared` or `companion` property | `shared` or `companion` property | [note](#kotlin-singletons) |
189-
| Primitive type | Primitive type / `NSNumber` | | [note](#nsnumber) |
189+
| Primitive type | Primitive type / `NSNumber` | | [note](#primitive-types) |
190190
| `Unit` return type | `Void` | `void` | |
191-
| `String` | `String` | `NSString` | |
191+
| `String` | `String` | `NSString` | [note](#strings) |
192192
| `String` | `NSMutableString` | `NSMutableString` | [note](#nsmutablestring) |
193193
| `List` | `Array` | `NSArray` | |
194194
| `MutableList` | `NSMutableArray` | `NSMutableArray` | |
@@ -240,7 +240,7 @@ Kotlin constructors are imported as initializers to Swift/Objective-C.
240240
241241
### Setters
242242
243-
Writeable Objective-C properties overriding read-only properties of the superclass are represented as `setFoo()` method
243+
Writeable Objective-C properties overriding read-only properties of the superclass are represented as the `setFoo()` method
244244
for the property `foo`. The same goes for a protocol's read-only properties that are implemented as mutable.
245245
246246
### Top-level functions and properties
@@ -304,12 +304,12 @@ All Kotlin exceptions are unchecked, meaning that errors are caught at runtime.
304304
that are handled at compile time. So, if Swift or Objective-C code calls a Kotlin method that throws an exception,
305305
the Kotlin method should be marked with the `@Throws` annotation, specifying a list of "expected" exception classes.
306306

307-
When compiling to the Objective-C/Swift framework, non-`suspend` functions that have or inherit the `@Throws` annotation
307+
When compiling to the Swift/Objective-C framework, non-`suspend` functions that have or inherit the `@Throws` annotation
308308
are represented as `NSError*`-producing methods in Objective-C and as `throws` methods in Swift.
309-
Representations for `suspend` functions always have `NSError*`/`Error` parameter in completion handler.
309+
Representations for `suspend` functions always have an `NSError*`/`Error` parameter in the completion handler.
310310

311-
When Kotlin function called from Swift/Objective-C code throws an exception which is an instance of one of
312-
the `@Throws`-specified classes or their subclasses, it is propagated as `NSError`.
311+
When a Kotlin function called from Swift/Objective-C code throws an exception which is an instance of one of
312+
the classes specified with `@Throws` or their subclasses, the exception is propagated as an `NSError`.
313313
Other Kotlin exceptions reaching Swift/Objective-C are considered unhandled and cause program termination.
314314

315315
`suspend` functions without `@Throws` propagate only `CancellationException` (as `NSError`).
@@ -438,28 +438,72 @@ See more examples in the Kotlin-Swift interopedia:
438438
* [How to access Kotlin objects using `shared`](https://github.com/kotlin-hands-on/kotlin-swift-interopedia/blob/main/docs/classesandinterfaces/Objects.md)
439439
* [How to access members of Kotlin companion objects from Swift](https://github.com/kotlin-hands-on/kotlin-swift-interopedia/blob/main/docs/classesandinterfaces/Companion%20objects.md).
440440

441-
### NSNumber
441+
### Primitive types
442442

443443
Kotlin primitive type boxes are mapped to special Swift/Objective-C classes. For example, the `kotlin.Int` box is represented
444-
as `KotlinInt` class instance in Swift (or `${prefix}Int` instance in Objective-C, where `prefix` is the framework names prefix).
444+
as the `KotlinInt` class instance in Swift (or the `${prefix}Int` instance in Objective-C, where `prefix` is the framework's name prefix).
445445
These classes are derived from `NSNumber`, so the instances are proper `NSNumber`s supporting all corresponding operations.
446446

447-
`NSNumber` type is not automatically translated to Kotlin primitive types when used as a Swift/Objective-C parameter type
448-
or return value. The reason is that `NSNumber` type doesn't provide enough information about a wrapped primitive value
447+
The `NSNumber` type is not automatically translated to Kotlin primitive types when used as a Swift/Objective-C parameter type
448+
or return value. The reason is that the `NSNumber` type doesn't provide enough information about a wrapped primitive value
449449
type, for example, `NSNumber` is statically not known to be `Byte`, `Boolean`, or `Double`. So Kotlin primitive values
450450
should be [cast to and from `NSNumber` manually](#casting-between-mapped-types).
451451

452-
### NSMutableString
452+
### Strings
453+
454+
When a Kotlin `String` is passed to Swift, it's first exported as an Objective-C object, and then the Swift compiler
455+
copies it one more time for a Swift conversion. This results in additional runtime overhead.
456+
457+
To avoid that, access Kotlin strings in Swift directly as an Objective-C `NSString` instead.
458+
[See the conversion example](#see-the-conversion-example).
459+
460+
#### NSMutableString
453461

454462
`NSMutableString` Objective-C class is not available from Kotlin.
455463
All instances of `NSMutableString` are copied when passed to Kotlin.
456464

457465
### Collections
458466

459-
Kotlin collections are converted to Swift/Objective-C collections as described in the [table above](#mappings).
460-
Swift/Objective-C collections are mapped to Kotlin in the same way, except for `NSMutableSet` and `NSMutableDictionary`.
467+
#### Kotlin -> Objective-C -> Swift
468+
469+
When a Kotlin collection is passed to Swift, it's first converted to an Objective-C equivalent, and then the Swift compiler
470+
copies the entire collection and converts it into a Swift-native collection as described in the [mappings table](#mappings).
471+
472+
This last conversion leads to performance costs. To prevent this, when using Kotlin collections in Swift,
473+
explicitly cast them to their Objective-C counterparts: `NSDictionary`, `NSArray`, or `NSSet`.
474+
475+
##### See the conversion example {initial-collapse-state="collapsed" collapsible="true"}
476+
477+
For example, the following Kotlin declaration:
478+
479+
```kotlin
480+
val map: Map<String, String>
481+
```
482+
483+
In Swift, might look like this:
484+
485+
```Swift
486+
map[key]?.count ?? 0
487+
```
488+
489+
Here, the `map` is implicitly converted to Swift's `Dictionary`, and its string values are mapped to Swift's `String`.
490+
This results in a performance cost.
491+
492+
To avoid the conversion, explicitly cast `map` to Objective-C's `NSDictionary` and access values as `NSString` instead:
493+
494+
```Swift
495+
let nsMap: NSDictionary = map as NSDictionary
496+
(nsMap[key] as? NSString)?.length ?? 0
497+
```
498+
499+
This ensures that the Swift compiler doesn't perform an additional conversion step.
500+
501+
#### Swift -> Objective-C -> Kotlin
502+
503+
Swift/Objective-C collections are mapped to Kotlin as described in the [mappings table](#mappings),
504+
except for `NSMutableSet` and `NSMutableDictionary`.
461505

462-
`NSMutableSet` isn't converted to a Kotlin `MutableSet`. To pass an object to Kotlin `MutableSet`, explicitly create this
506+
`NSMutableSet` isn't converted to a Kotlin's `MutableSet`. To pass an object to Kotlin `MutableSet`, explicitly create this
463507
kind of Kotlin collection. To do this, use, for example, the `mutableSetOf()` function in Kotlin or the
464508
`KotlinMutableSet` class in Swift and `${prefix}MutableSet` in Objective-C (`prefix` is the framework names prefix).
465509
The same is true for `MutableMap`.
@@ -499,7 +543,7 @@ foo {
499543

500544
### Generics
501545

502-
Objective-C supports "lightweight generics" defined on classes, with a relatively limited feature set. Swift can import
546+
Objective-C supports "lightweight generics" defined in classes, with a relatively limited feature set. Swift can import
503547
generics defined on classes to help provide additional type information to the compiler.
504548

505549
Generic feature support for Objective-C and Swift differ from Kotlin, so the translation will inevitably lose some
@@ -564,7 +608,7 @@ let variOutAny : GenVarOut<BaseData> = variOut as! GenVarOut<BaseData>
564608
#### Constraints
565609

566610
In Kotlin, you can provide upper bounds for a generic type. Objective-C also supports this, but that support is unavailable
567-
in more complex cases, and is currently not supported in the Kotlin - Objective-C interop. The exception here being a non-nullable
611+
in more complex cases and is currently not supported in the Kotlin - Objective-C interop. The exception here being a non-nullable
568612
upper bound will make Objective-C methods/properties non-nullable.
569613

570614
#### To disable
@@ -670,7 +714,7 @@ The annotation instructs the Kotlin compiler to ignore conflicting overloads, in
670714
argument types, but different argument names, are inherited from the Objective-C class.
671715

672716
By default, the Kotlin/Native compiler doesn't allow calling a non-designated Objective-C initializer as a `super()`
673-
constructor. This behaviour can be inconvenient if the designated initializers aren't marked properly in the Objective-C
717+
constructor. This behavior can be inconvenient if the designated initializers aren't marked properly in the Objective-C
674718
library. To disable these compiler checks, add the `disableDesignatedInitializerChecks = true` to the library's [`.def` file](native-definition-file.md).
675719

676720
## C features
@@ -680,7 +724,7 @@ such as unsafe pointers, structs, and so on.
680724

681725
## Unsupported
682726

683-
Some features of Kotlin programming language are not yet mapped into the respective features of Objective-C or Swift.
727+
Some features of the Kotlin programming language are not yet mapped into the respective features of Objective-C or Swift.
684728
Currently, the following features are not properly exposed in generated framework headers:
685729

686730
* Inline classes (arguments are mapped as either underlying primitive type or `id`)

0 commit comments

Comments
 (0)