Swift/Objective-C와의 상호 운용성
Swift/Objective-C와의 상호 운용성
Kotlin/Native는 Objective-C를 거쳐 Swift와의 간접적인 상호 운용성을 제공해요. 이 문서는 코틀린 선언을 Swift/Objective-C 코드에서 어떻게 쓰고, Objective-C 선언을 코틀린 코드에서 어떻게 쓰는지 다뤄요.
본문
도움이 될 만한 다른 자료들도 있어요.
- Kotlin-Swift interopedia — Swift 코드에서 코틀린 선언을 쓰는 방법에 관한 예시 모음이에요.
- Swift/Objective-C ARC와의 통합 문서 — 코틀린의 추적 GC와 Objective-C의 ARC 사이의 통합 세부 사항을 다뤄요.
Swift/Objective-C 라이브러리를 코틀린으로 가져오기
Objective-C 프레임워크와 라이브러리는 빌드에 제대로 임포트되면 코틀린 코드에서 사용할 수 있어요(시스템 프레임워크는 기본으로 임포트돼요). 자세한 내용은 다음을 참고하세요.
- 라이브러리 정의 파일 만들고 구성하기
- 네이티브 라이브러리 컴파일 구성하기
Swift 라이브러리는 그 API가 @objc로 Objective-C에 export되면 코틀린 코드에서 쓸 수 있어요. 순수 Swift 모듈은 아직 지원되지 않아요.
Swift/Objective-C에서 코틀린 사용하기
코틀린 모듈은 프레임워크로 컴파일되면 Swift/Objective-C 코드에서 사용할 수 있어요.
- 바이너리 선언 방법은 "최종 네이티브 바이너리 빌드하기" 문서를 참고하세요.
- 예시는 Kotlin Multiplatform 샘플 프로젝트를 확인하세요.
코틀린 선언을 Objective-C와 Swift에서 숨기기
코틀린 코드를 Swift/Objective-C 친화적으로 만들려면 @HiddenFromObjC 애너테이션을 써서 코틀린 선언을 Objective-C와 Swift에서 숨길 수 있어요. 이 애너테이션은 함수나 프로퍼티의 Objective-C export를 비활성화해요.
다른 방법으로, 코틀린 선언에 internal 수정자를 붙여 컴파일 모듈 안에서의 가시성을 제한할 수도 있어요. 코틀린 선언을 Objective-C와 Swift에서는 숨기되 다른 코틀린 모듈에는 보이게 하고 싶다면 @HiddenFromObjC를 사용하세요.
예시는 Kotlin-Swift interopedia에 있어요.
Swift에서 리파이닝(refining) 사용하기
@ShouldRefineInSwift는 코틀린 선언을 Swift로 작성된 래퍼로 대체하는 데 도움을 줘요. 이 애너테이션은 함수나 프로퍼티를 생성된 Objective-C API에서 swift_private로 표시해요. 그런 선언들은 __ 접두사를 얻게 되어 Swift에서는 보이지 않게 돼요.
이 선언들을 Swift 코드에서 Swift 친화적인 API를 만들기 위해 여전히 사용할 수 있지만, Xcode 자동완성에는 제안되지 않아요.
- Swift에서 Objective-C 선언을 리파이닝하는 방법에 대한 자세한 내용은 공식 Apple 문서를 참고하세요.
@ShouldRefineInSwift애너테이션 사용 예시는 Kotlin-Swift interopedia에서 확인하세요.
선언 이름 바꾸기
코틀린 선언 이름을 바꾸지 않으려면 @ObjCName 애너테이션을 사용해요. 이 애너테이션은 코틀린 컴파일러에게 애너테이션이 붙은 클래스, 인터페이스, 그 외 코틀린 개체에 대해 커스텀 Objective-C·Swift 이름을 쓰라고 지시해요.
@ObjCName(swiftName = "MySwiftArray")
class MyKotlinArray {
@ObjCName("index")
fun indexOf(@ObjCName("of") element: String): Int = TODO()
}
// Usage with the ObjCName annotations
let array = MySwiftArray()
let index = array.index(of: "element")
또 다른 예시는 Kotlin-Swift interopedia에서 확인하세요.
KDoc 주석으로 문서 제공하기
문서는 어떤 API를 이해하는 데 없어서는 안 될 요소예요. 공유 코틀린 API에 문서를 제공하면 사용 방법, 해야 할 것과 하지 말아야 할 것 등을 사용자와 소통할 수 있어요.
Objective-C 헤더를 생성할 때 코틀린 코드의 KDoc 주석은 대응하는 Objective-C 주석으로 번역돼요. 예를 들어 KDoc이 있는 다음 코틀린 코드는
/**
* Prints the sum of the arguments.
* Properly handles the case when the sum doesn't fit in 32-bit integer.
*/
fun printSum(a: Int, b: Int) = println(a.toLong() + b)
해당 주석이 붙은 Objective-C 헤더를 만들어 내요.
/**
* Prints the sum of the arguments.
* Properly handles the case when the sum doesn't fit in 32-bit integer.
*/
+ (void)printSumA:(int32_t)a b:(int32_t)b __attribute__((swift_name("printSum(a:b:)")));
KDoc 주석은 klib에 내장되고, klib에서 생성된 Apple 프레임워크로 추출돼요. 그 결과 클래스와 메서드에 대한 주석이 예를 들어 Xcode의 자동완성에 나타나요. .h 파일에서 함수 정의로 이동하면 @param, @return 같은 태그에 대한 주석을 볼 수 있어요.
알려진 제한 사항은 다음과 같아요.
- 의존성 문서는
-Xexport-kdoc옵션으로 컴파일되지 않으면 export되지 않아요. 이 컴파일러 옵션으로 컴파일된 라이브러리는 다른 컴파일러 버전과 호환되지 않을 수 있어요. - KDoc 주석은 대부분 그대로 export되지만,
@property같은 많은 KDoc 블록 태그는 지원되지 않아요.
필요하다면 Gradle 빌드 파일의 binaries {} 블록에서 klib에서 생성된 Apple 프레임워크로 KDoc 주석이 export되는 것을 끌 수 있어요.
매핑(Mappings)
아래 표는 코틀린 개념이 Swift/Objective-C로, 그리고 그 반대로 어떻게 매핑되는지 보여 줘요.
->와 <-는 매핑이 한 방향으로만 간다는 뜻이에요.
| Kotlin | Swift | Objective-C | Notes |
|---|---|---|---|
| class | class | @interface | note |
| interface | protocol | @protocol | |
| constructor/create | Initializer | Initializer | note |
| Property | Property | Property | note 1, note 2 |
| Method | Method | Method | note 1, note 2 |
| enum class | class | @interface | note |
| suspend-> | completionHandler:/async | completionHandler: | note 1, note 2 |
| @Throws fun | throws | error:(NSError**)error | note |
| Extension | Extension | Category member | note |
| companion member <- | Class method or property | Class method or property | |
| null | nil | nil | |
| Singleton | shared or companion property | shared or companion property | note |
| Primitive type | Primitive type / NSNumber | note | |
| Unit return type | Void | void | |
| String | String | NSString | note |
| String | NSMutableString | NSMutableString | note |
| List | Array | NSArray | |
| MutableList | NSMutableArray | NSMutableArray | |
| Set | Set | NSSet | |
| MutableSet | NSMutableSet | NSMutableSet | note |
| Map | Dictionary | NSDictionary | |
| MutableMap | NSMutableDictionary | NSMutableDictionary | note |
| Function type | Function type | Block pointer type | note |
| Inline classes | Unsupported | Unsupported | note |
클래스
이름 변환
Objective-C 클래스는 원래 이름 그대로 코틀린으로 임포트돼요. 프로토콜은 Protocol 이름 접미사를 붙인 인터페이스로 임포트돼요. 예를 들어 @protocol Foo는 interface FooProtocol가 되죠. 이런 클래스와 인터페이스는 빌드 구성에 지정된 패키지에 놓여요(사전 구성된 시스템 프레임워크는 platform.* 패키지).
코틀린 클래스와 인터페이스의 이름은 Objective-C로 임포트될 때 접두사가 붙어요. 그 접두사는 프레임워크 이름에서 유래해요.
Objective-C는 프레임워크에서 패키지를 지원하지 않아요. 코틀린 컴파일러가 같은 프레임워크 안에서 이름은 같지만 패키지가 다른 코틀린 클래스를 찾으면 이름을 바꿔요. 이 알고리즘은 아직 안정적이지 않아서 코틀린 릴리스 사이에 바뀔 수 있어요. 이 문제를 우회하려면 프레임워크 안에서 충돌하는 코틀린 클래스 이름을 직접 바꿔 주면 돼요.
강한 링킹(strong linking)
코틀린 소스에서 Objective-C 클래스를 사용할 때마다 그 클래스는 강하게 링크된 심볼로 표시돼요. 결과 빌드 아티팩트는 관련 심볼을 강한 외부 참조로 언급해요.
즉 앱이 실행되는 동안 심볼을 링크하려 시도하고, 심볼이 없으면 앱이 크래시돼요. 그 심볼을 실제로 쓴 적이 없어도 크래시가 나요. 심볼이 특정 기기나 OS 버전에 없을 수 있거든요.
이 문제("Symbol not found" 오류)를 우회하려면, 클래스가 실제로 사용 가능한지 확인하는 Swift나 Objective-C 래퍼를 사용하세요. 이 우회 방법이 Compose Multiplatform 프레임워크에서 어떻게 구현됐는지 확인해 보세요.
초기화자(Initializers)
Swift/Objective-C 초기화자는 코틀린에서 생성자로, 또는 이름이 create인 팩토리 메서드로 임포트돼요. 후자는 Objective-C 카테고리나 Swift 확장으로 선언된 초기화자에서 일어나요. 코틀린에는 확장 생성자(extension constructor)라는 개념이 없기 때문이죠.
코틀린 생성자는 Swift/Objective-C의 초기화자로 임포트돼요.
세터(Setters)
슈퍼클래스의 읽기 전용 프로퍼티를 오버라이드하는 쓰기 가능한 Objective-C 프로퍼티는 foo 프로퍼티에 대해 setFoo() 메서드로 표현돼요. 프로토콜의 읽기 전용 프로퍼티를 mutable로 구현한 경우도 마찬가지예요.
최상위 함수와 프로퍼티
최상위 코틀린 함수와 프로퍼티는 특별한 클래스의 멤버로 접근돼요. 각 코틀린 파일이 그런 클래스 하나로 변환돼요. 예를 들어:
// MyLibraryUtils.kt
package my.library
fun foo() {}
그러면 Swift에서 foo() 함수를 이렇게 호출할 수 있어요.
MyLibraryUtilsKt.foo()
Kotlin-Swift interopedia에서 최상위 코틀린 선언에 접근하는 예시 모음을 확인하세요.
- 최상위 함수
- 최상위 읽기 전용 프로퍼티
- 최상위 수정 가능한 프로퍼티
메서드 이름 변환
일반적으로 Swift 인자 라벨과 Objective-C 셀렉터 조각은 코틀린 파라미터 이름으로 매핑돼요. 이 두 개념은 의미가 달라서, Swift/Objective-C 메서드가 충돌하는 코틀린 시그니처로 임포트될 때가 있어요. 이 경우 충돌하는 메서드들은 코틀린에서 명명된 인자(named argument)로 호출할 수 있어요. 예를 들어:
[player moveTo:LEFT byMeters:17]
[player moveTo:UP byInches:42]
코틀린에서는 이렇게 돼요.
player.moveTo(LEFT, byMeters = 17)
player.moveTo(UP, byInches = 42)
kotlin.Any 함수들이 Swift/Objective-C로 매핑되는 방식은 다음과 같아요.
| Kotlin | Swift | Objective-C |
|---|---|---|
| equals() | isEquals(_:) | isEquals: |
| hashCode() | hash | hash |
| toString() | description | description |
데이터 클래스가 포함된 예시는 Kotlin-Swift interopedia에서 확인하세요.
코틀린 선언을 @ObjCName 애너테이션으로 바꾸는 대신, Swift나 Objective-C에서 더 관용적인 이름을 지정할 수도 있어요.
오류와 예외
모든 코틀린 예외는 unchecked예요. 즉 오류는 런타임에 잡혀요. 반면 Swift에는 컴파일 타임에 처리되는 checked 오류만 있어요. 그래서 Swift나 Objective-C 코드가 예외를 던지는 코틀린 메서드를 호출한다면, 그 코틀린 메서드에 "예상되는(expected)" 예외 클래스 목록을 지정하는 @Throws 애너테이션을 붙여야 해요.
Swift/Objective-C 프레임워크로 컴파일할 때, @Throws 애너테이션을 갖거나 물려받은 non-suspend 함수는 Objective-C에서는 NSError*를 만드는 메서드로, Swift에서는 throws 메서드로 표현돼요. suspend 함수의 표현은 completion handler에 항상 NSError*/Error 파라미터가 있어요.
Swift/Objective-C 코드에서 호출된 코틀린 함수가, @Throws로 지정된 클래스 중 하나 또는 그 하위 클래스의 인스턴스인 예외를 던지면, 그 예외는 NSError로 전파돼요. Swift/Objective-C에 도달하는 그 외 코틀린 예외는 처리되지 않은 것으로 간주되어 프로그램을 종료시켜요.
@Throws가 없는 suspend 함수는 CancellationException만(그것도 NSError로) 전파해요. @Throws가 없는 non-suspend 함수는 코틀린 예외를 전혀 전파하지 않아요.
반대 방향의 변환은 아직 구현되지 않았어요. 즉 Swift/Objective-C의 오류를 던지는 메서드는 코틀린에 예외를 던지는 방식으로 임포트되지 않아요.
예시는 Kotlin-Swift interopedia에서 확인하세요.
열거형(Enums)
코틀린 열거형은 Objective-C로는 @interface, Swift로는 class로 임포트돼요. 이 데이터 구조는 각 열거값에 대응하는 프로퍼티를 가져요. 다음 코틀린 코드를 보세요.
// Kotlin
enum class Colors {
RED, GREEN, BLUE
}
Swift에서 이 열거형 클래스의 프로퍼티에 이렇게 접근할 수 있어요.
// Swift
Colors.red
Colors.green
Colors.blue
코틀린 열거형 변수는 Swift switch 문에서 쓸 때, 컴파일 오류를 막기 위해 default 문을 제공하세요.
switch color {
case .red: print("It's red")
case .green: print("It's green")
case .blue: print("It's blue")
default: fatalError("No such color")
}
또 다른 예시는 Kotlin-Swift interopedia에서 확인하세요.
일시 중단 함수(Suspending functions)
코틀린의 일시 중단 함수(suspend)는 생성된 Objective-C 헤더에서 Swift/Objective-C 용어의 completion handler, 즉 콜백이 있는 함수로 표현돼요.
Swift 5.5부터 코틀린의 suspend 함수는 completion handler를 쓰지 않고 async 함수로도 Swift에서 호출할 수 있어요. 현재 이 기능은 매우 실험적이고 특정 제한 사항이 있어요. 자세한 내용은 이 YouTrack 이슈를 참고하세요.
- async/await 메커니즘에 대한 자세한 내용은 Swift 문서에서 확인하세요.
- 같은 기능을 구현한 서드파티 라이브러리에 대한 예시와 권장 사항은 Kotlin-Swift interopedia에서 확인하세요.
확장과 카테고리 멤버
Objective-C 카테고리와 Swift 확장의 멤버는 일반적으로 코틀린의 **확장(extension)**으로 임포트돼요. 그래서 이런 선언들은 코틀린에서 오버라이드할 수 없고, 확장 초기화자도 코틀린 생성자로는 쓸 수 없어요.
"일반적인" 코틀린 클래스에 대한 코틀린 확장은 Swift로는 확장으로, Objective-C로는 카테고리 멤버로 임포트돼요. 다른 타입에 대한 코틀린 확장은 리시버 파라미터가 하나 더 붙은 최상위 선언으로 취급돼요. 그런 타입에는 다음이 포함돼요.
- 코틀린
String타입 - 코틀린 컬렉션 타입과 하위 타입
- 코틀린 인터페이스 타입
- 코틀린 원시 타입
- 코틀린 인라인 클래스
- 코틀린
Any타입 - 코틀린 함수 타입과 하위 타입
- Objective-C 클래스와 프로토콜
예시 모음은 Kotlin-Swift interopedia에서 확인하세요.
코틀린 싱글턴
코틀린 싱글턴(object 선언, companion object 포함)은 Swift/Objective-C에서 단일 인스턴스를 가진 클래스로 임포트돼요.
그 인스턴스는 shared와 companion 프로퍼티로 접근할 수 있어요.
다음 코틀린 코드에 대해
object MyObject {
val x = "Some value"
}
class MyClass {
companion object {
val x = "Some value"
}
}
이렇게 접근하세요.
MyObject.shared
MyObject.shared.x
MyClass.companion
MyClass.Companion.shared
자세한 예시는 Kotlin-Swift interopedia에서 확인하세요.
shared로 코틀린 object에 접근하는 방법- Swift에서 코틀린 companion object의 멤버에 접근하는 방법
원시 타입
코틀린 원시 타입의 박스는 특별한 Swift/Objective-C 클래스로 매핑돼요. 예를 들어 kotlin.Int 박스는 Swift에서 KotlinInt 클래스 인스턴스로 표현돼요(Objective-C에서는 ${prefix}Int 인스턴스. prefix는 프레임워크의 이름 접두사예요). 이 클래스들은 NSNumber에서 파생되어서, 인스턴스는 해당 연산을 모두 지원하는 온전한 NSNumber예요.
NSNumber 타입은 Swift/Objective-C 파라미터 타입이나 반환값으로 쓰일 때 코틀린 원시 타입으로 자동 변환되지 않아요. 이유는 NSNumber 타입이 감싸진 원시 값 타입에 대한 충분한 정보를 주지 않기 때문이에요. 예를 들어 NSNumber가 정적으로 Byte인지 Boolean인지 Double인지 알 수 없죠. 그래서 코틀린 원시 값은 NSNumber로, 그리고 NSNumber에서 직접 캐스팅해야 해요.
문자열
코틀린 String을 Swift에 전달하면 먼저 Objective-C 객체로 export되고, 그다음 Swift 컴파일러가 Swift 변환을 위해 한 번 더 복사해요. 그 결과 런타임 오버헤드가 추가돼요.
이를 피하려면 코틀린 문자열을 Swift에서 Objective-C NSString로 직접 접근하세요. 변환 예시를 확인하세요.
NSMutableString
NSMutableString Objective-C 클래스는 코틀린에서 사용할 수 없어요. NSMutableString의 모든 인스턴스는 코틀린에 전달될 때 복사돼요.
컬렉션
Kotlin -> Objective-C -> Swift
코틀린 컬렉션이 Swift에 전달되면 먼저 Objective-C 상당물로 변환되고, 그다음 Swift 컴파일러가 전체 컬렉션을 복사해 매핑 표에 설명된 대로 Swift 네이티브 컬렉션으로 변환해요.
이 마지막 변환이 성능 비용을 일으켜요. 이를 막으려면 Swift에서 코틀린 컬렉션을 쓸 때 명시적으로 Objective-C 대응물인 NSDictionary, NSArray, NSSet으로 캐스팅하세요.
변환 예시를 보세요. 예를 들어 다음 코틀린 선언은
val map: Map<String, String>
Swift에서는 이렇게 보여요.
map[key]?.count ?? 0
여기서 map은 묵시적으로 Swift의 Dictionary로 변환되고, 문자열 값은 Swift의 String으로 매핑돼요. 그 결과 성능 비용이 발생해요.
변환을 피하려면 map을 명시적으로 Objective-C의 NSDictionary로 캐스팅하고 값을 NSString로 접근하세요.
let nsMap: NSDictionary = map as NSDictionary
(nsMap[key] as? NSString)?.length ?? 0
이렇게 하면 Swift 컴파일러가 추가 변환 단계를 수행하지 않아요.
Swift -> Objective-C -> Kotlin
Swift/Objective-C 컬렉션은 매핑 표에 설명된 대로 코틀린으로 매핑되는데, NSMutableSet과 NSMutableDictionary는 예외예요.
NSMutableSet은 코틀린의 MutableSet으로 변환되지 않아요. 코틀린 MutableSet에 객체를 전달하려면 이런 코틀린 컬렉션을 명시적으로 만들어야 해요. 이를 위해 코틀린에서는 예를 들어 mutableSetOf() 함수를, Swift에서는 KotlinMutableSet 클래스를, Objective-C에서는 ${prefix}MutableSet을 사용하면 돼요(prefix는 프레임워크 이름 접두사). MutableMap도 마찬가지예요.
예시는 Kotlin-Swift interopedia에서 확인하세요.
함수 타입
코틀린 함수 타입 객체(예: 람다)는 Swift에서는 클로저로, Objective-C에서는 블록으로 변환돼요. 람다가 있는 코틀린 함수 예시는 Kotlin-Swift interopedia에서 확인하세요.
다만 함수 자체와 함수 타입을 번역할 때 파라미터와 반환값 타입이 매핑되는 방식에 차이가 있어요. 함수 타입의 경우 원시 타입은 박스된 표현으로 매핑돼요. 코틀린 Unit 반환값은 Swift/Objective-C에서 대응하는 Unit 싱글턴으로 표현돼요. 이 싱글턴의 값은 다른 코틀린 object와 똑같은 방식으로 가져올 수 있어요. 위 표의 싱글턴 부분을 보세요.
다음 코틀린 함수를 보면
fun foo(block: (Int) -> Unit) { ... }
Swift에서는 이렇게 표현돼요.
func foo(block: (KotlinInt) -> KotlinUnit)
그리고 이렇게 호출할 수 있어요.
Objective-C 블록 타입의 명시적 파라미터 이름
export되는 Objective-C 헤더의 코틀린 함수 타입에 명시적 파라미터 이름을 추가할 수 있어요. 그러면 Xcode 자동완성이 Objective-C 블록에서 Objective-C 함수를 호출할 때 그 이름을 제안해 줘요. 생성된 블록에서 Clang 경고를 피하는 데 도움이 돼요.
명시적 파라미터 이름을 활성화하려면 gradle.properties 파일에 다음 바이너리 옵션을 추가하세요.
kotlin.native.binary.objcExportBlockExplicitParameterNames=true
예를 들어 다음 코틀린 코드에 대해
// Kotlin:
fun greetUser(block: (name: String) -> Unit) = block("John")
코틀린은 코틀린 함수 타입의 파라미터 이름을 Objective-C 블록 타입으로 전달해서, Xcode가 제안에 그 이름을 쓸 수 있게 해 줘요.
제네릭
Objective-C는 클래스에 정의된 "경량 제네릭(lightweight generics)"을 지원하는데, 기능 세트가 비교적 제한적이에요. Swift는 클래스에 정의된 제네릭을 임포트해 컴파일러에 추가 타입 정보를 제공할 수 있어요.
Objective-C와 Swift의 제네릭 기능 지원은 코틀린과 다르므로 번역 과정에서 정보가 일부 손실될 수밖에 없어요. 다만 지원되는 기능은 의미 있는 정보를 유지해요.
Swift에서 코틀린 제네릭을 쓰는 구체적인 예시는 Kotlin-Swift interopedia에서 확인하세요.
제한 사항
Objective-C 제네릭은 코틀린이나 Swift의 모든 기능을 지원하지 않으므로 번역에서 정보가 일부 손실돼요.
제네릭은 인터페이스(Objective-C와 Swift의 프로토콜)나 함수가 아니라 클래스에만 정의할 수 있어요.
널 가능성(Nullability)
코틀린과 Swift는 둘 다 타입 지정의 일부로 널 가능성을 정의해요. 반면 Objective-C는 타입의 메서드와 프로퍼티에 널 가능성을 정의해요. 그래서 다음 코틀린 코드는
class Sample<T>() {
fun myVal(): T
}
Swift에서는 이렇게 보여요.
class Sample<T>() {
fun myVal(): T?
}
잠재적으로 널이 될 수 있는 타입을 지원하려면 Objective-C 헤더가 myVal을 널 가능한 반환값으로 정의해야 해요.
이를 완화하려면 제네릭 클래스를 정의할 때, 제네릭 타입이 절대 null이면 안 된다면 널이 아닌 타입 제약을 제공하세요.
class Sample<T : Any>() {
fun myVal(): T
}
그러면 Objective-C 헤더가 myVal을 널이 아닌 것으로 표시하도록 강제돼요.
분산(Variance)
Objective-C는 제네릭을 공변(covariant) 또는 반변(contravariant)으로 선언할 수 있어요. Swift는 분산을 지원하지 않아요. Objective-C에서 온 제네릭 클래스는 필요에 따라 force-cast할 수 있어요.
제약(Constraints)
코틀린에서는 제네릭 타입에 상한을 제공할 수 있어요. Objective-C도 이를 지원하지만, 더 복잡한 경우에는 지원이 없고 현재 Kotlin-Objective-C interop에서도 지원되지 않아요. 예외는 널이 아닌 상한이 Objective-C 메서드/프로퍼티를 널이 아닌 것으로 만드는 경우예요.
비활성화하기
프레임워크 헤더를 제네릭 없이 작성하려면 빌드 파일에 다음 컴파일러 옵션을 추가하세요.
전방 선언(Forward declarations)
전방 선언을 임포트하려면 objcnames.classes와 objcnames.protocols 패키지를 사용하세요. 예를 들어 library.package를 가진 Objective-C 라이브러리에 선언된 objcprotocolName 전방 선언을 임포트하려면 특수 전방 선언 패키지를 사용하세요: import objcnames.protocols.objcprotocolName.
objcnames.protocols.ForwardDeclaredProtocolProtocol을 쓰는 objcinterop 라이브러리 하나와, 다른 패키지에 실제 구현이 있는 라이브러리 하나를 생각해 보세요.
// First objcinterop library
#import <Foundation/Foundation.h>
@protocol ForwardDeclaredProtocol;
NSString* consumeProtocol(id<ForwardDeclaredProtocol> s) {
return [NSString stringWithUTF8String:"Protocol"];
}
// Second objcinterop library
// Header:
#import <Foundation/Foundation.h>
@protocol ForwardDeclaredProtocol
@end
// Implementation:
@interface ForwardDeclaredProtocolImpl : NSObject <ForwardDeclaredProtocol>
@end
id<ForwardDeclaredProtocol> produceProtocol() {
return [ForwardDeclaredProtocolImpl new];
}
두 라이브러리 사이에서 객체를 전달하려면 코틀린 코드에서 명시적 as 캐스팅을 사용하세요.
매핑된 타입 사이의 캐스팅
코틀린 코드를 작성할 때 객체를 코틀린 타입에서 이에 상응하는 Swift/Objective-C 타입으로, 또는 그 반대로 변환해야 할 때가 있어요. 이때 as 캐스팅을 사용할 수 있어요. 예를 들어:
@file:Suppress("CAST_NEVER_SUCCEEDS")
import platform.Foundation.*
val nsNumber = 42 as NSNumber
val nsArray = listOf(1, 2, 3) as NSArray
val nsString = "Hello" as NSString
val string = nsString as String
IDE가 "This cast can never succeed" 경고를 잘못 내보낼 수 있어요. 그런 경우 @Suppress("CAST_NEVER_SUCCEEDS") 애너테이션을 사용하세요.
서브클래싱
Swift/Objective-C에서 코틀린 클래스와 인터페이스 서브클래싱하기
코틀린 클래스와 인터페이스는 Swift/Objective-C 클래스와 프로토콜로 서브클래싱될 수 있어요.
코틀린에서 Swift/Objective-C 클래스와 프로토콜 서브클래싱하기
Swift/Objective-C 클래스와 프로토콜은 코틀린 final 클래스로 서브클래싱될 수 있어요. Swift/Objective-C 타입을 상속하는 non-final 코틀린 클래스는 아직 지원되지 않아서, Swift/Objective-C 타입을 상속하는 복잡한 클래스 계층을 선언하는 건 불가능해요.
일반 메서드는 코틀린 override 키워드로 오버라이드할 수 있어요. 이 경우 오버라이드하는 메서드는 오버라이드되는 메서드와 같은 파라미터 이름을 가져야 해요.
초기화자를 오버라이드해야 할 때가 있어요. 예를 들어 UIViewController를 서브클래싱할 때죠. 코틀린 생성자로 임포트된 초기화자는 @OverrideInit 애너테이션이 붙은 코틀린 생성자로 오버라이드할 수 있어요.
class ViewController : UIViewController {
@OverrideInit constructor(coder: NSCoder) : super(coder)
...
}
오버라이드하는 생성자는 오버라이드되는 생성자와 같은 파라미터 이름과 타입을 가져야 해요.
충돌하는 코틀린 시그니처를 가진 여러 메서드를 오버라이드하려면 클래스에 @ObjCSignatureOverride 애너테이션을 추가할 수 있어요. 이 애너테이션은 같은 인자 타입이지만 다른 인자 이름을 가진 여러 함수가 Objective-C 클래스에서 상속될 때, 충돌하는 오버로드를 무시하라고 코틀린 컴파일러에 지시해요.
기본적으로 Kotlin/Native 컴파일러는 지정되지 않은(non-designated) Objective-C 초기화자를 super() 생성자로 호출하는 것을 허용하지 않아요. designated 초기화자가 Objective-C 라이브러리에서 제대로 표시되지 않으면 이 동작이 불편할 수 있어요. 이런 컴파일러 검사를 끄려면 라이브러리의 .def 파일에 disableDesignatedInitializerChecks = true를 추가하세요.
C 기능
라이브러리가 안전하지 않은 포인터, 구조체 등 일반 C 기능을 사용하는 예시는 "C와의 상호 운용성" 문서를 확인하세요.
지원되지 않는 것
코틀린 프로그래밍 언어의 일부 기능은 아직 Objective-C나 Swift의 해당 기능으로 매핑되지 않았어요. 현재 다음 기능들은 생성된 프레임워크 헤더에서 제대로 노출되지 않아요.
- 인라인 클래스 (인자는 기본 원시 타입 또는
id로 매핑돼요) - 표준 코틀린 컬렉션 인터페이스(
List,Map,Set)를 구현하는 커스텀 클래스와 그 외 특수 클래스 - Objective-C 클래스의 코틀린 하위 클래스
더 알아보기
- C와의 상호 운용성
- Swift export를 통한 Swift 상호 운용성
- Swift/Objective-C ARC 연동
- Kotlin-Swift interopedia