diff --git a/OBJC_INTEROP.md b/OBJC_INTEROP.md index 25f600c5bf3..b9892013932 100644 --- a/OBJC_INTEROP.md +++ b/OBJC_INTEROP.md @@ -239,9 +239,6 @@ val nsNumber = 42 as NSNumber Kotlin classes and interfaces can be subclassed by Swift/Objective-C classes and protocols. -Currently a class that adopts the Kotlin protocol should inherit `NSObject` -(either directly or indirectly). Note that all Kotlin classes do inherit `NSObject`, -so a Swift/Objective-C subclass of Kotlin class can adopt the Kotlin protocol. ### Subclassing Swift/Objective-C classes and protocols from Kotlin diff --git a/backend.native/tests/framework/values/values.kt b/backend.native/tests/framework/values/values.kt index 466a5c0a1bc..1af9f7d2fe5 100644 --- a/backend.native/tests/framework/values/values.kt +++ b/backend.native/tests/framework/values/values.kt @@ -145,6 +145,8 @@ interface I { fun iFun(): String = "I::iFun" } +fun I.iFunExt() = iFun() + private interface PI { fun piFun(): Any fun iFun(): String = "PI::iFun" @@ -261,4 +263,6 @@ class Bridge : BridgeBase() { override fun foo2() = throw MyException() override fun foo3() = throw MyException() override fun foo4() = throw MyException() -} \ No newline at end of file +} + +fun Any.same() = this diff --git a/backend.native/tests/framework/values/values.swift b/backend.native/tests/framework/values/values.swift index a98f8a2b213..11d86ec5d7c 100644 --- a/backend.native/tests/framework/values/values.swift +++ b/backend.native/tests/framework/values/values.swift @@ -470,6 +470,26 @@ func testShared() throws { try assertNotFrozen(FinalClassExtOpen()) } +class PureSwiftClass { +} + +struct PureSwiftStruct { + var x: Int +} +class PureSwiftKotlinInterfaceImpl : I { + func iFun() -> String { + return "pure" + } +} + +func testPureSwiftClasses() throws { + let pureSwiftClass = PureSwiftClass() + try assertTrue(ValuesKt.same(pureSwiftClass) as? AnyObject === pureSwiftClass) + + try assertEquals(actual: 123, expected: (ValuesKt.same(PureSwiftStruct(x: 123)) as? PureSwiftStruct)?.x) + try assertEquals(actual: "pure", expected: ValuesKt.iFunExt(PureSwiftKotlinInterfaceImpl())) +} + // -------- Execution of the test -------- class ValuesTests : TestProvider { @@ -505,6 +525,7 @@ class ValuesTests : TestProvider { TestCase(name: "TestCompanionObj", method: withAutorelease(testCompanionObj)), TestCase(name: "TestInlineClasses", method: withAutorelease(testInlineClasses)), TestCase(name: "TestShared", method: withAutorelease(testShared)), + TestCase(name: "TestPureSwiftClasses", method: withAutorelease(testPureSwiftClasses)), ] } } diff --git a/runtime/src/main/cpp/ObjCExport.mm b/runtime/src/main/cpp/ObjCExport.mm index 273511fb2a3..debcea57057 100644 --- a/runtime/src/main/cpp/ObjCExport.mm +++ b/runtime/src/main/cpp/ObjCExport.mm @@ -135,6 +135,8 @@ static inline id AtomicSetAssociatedObject(ObjHeader* obj, id associatedObject) @interface KotlinBase : NSObject @end; +static void initializeObjCExport(); + @implementation KotlinBase { KRefSharedHolder refHolder; } @@ -144,6 +146,9 @@ static inline id AtomicSetAssociatedObject(ObjHeader* obj, id associatedObject) } +(void)initialize { + if (self == [KotlinBase class]) { + initializeObjCExport(); + } initializeClass(self); } @@ -376,16 +381,22 @@ static ALWAYS_INLINE OBJ_GETTER(convertUnmappedObjCObject, id obj) { static OBJ_GETTER(blockToKotlinImp, id self, SEL cmd); static OBJ_GETTER(boxedBooleanToKotlinImp, NSNumber* self, SEL cmd); +static OBJ_GETTER(SwiftObject_toKotlinImp, id self, SEL cmd); +static void SwiftObject_releaseAsAssociatedObjectImp(id self, SEL cmd); + static void checkLoadedOnce(); static void initializeObjCExport() { - checkLoadedOnce(); - SEL toKotlinSelector = @selector(toKotlin:); Method toKotlinMethod = class_getClassMethod([NSObject class], toKotlinSelector); RuntimeAssert(toKotlinMethod != nullptr, ""); const char* toKotlinTypeEncoding = method_getTypeEncoding(toKotlinMethod); + SEL releaseAsAssociatedObjectSelector = @selector(releaseAsAssociatedObject); + Method releaseAsAssociatedObjectMethod = class_getClassMethod([NSObject class], releaseAsAssociatedObjectSelector); + RuntimeAssert(releaseAsAssociatedObjectMethod != nullptr, ""); + const char* releaseAsAssociatedObjectTypeEncoding = method_getTypeEncoding(releaseAsAssociatedObjectMethod); + Class nsBlockClass = objc_getClass("NSBlock"); RuntimeAssert(nsBlockClass != nullptr, "NSBlock class not found"); @@ -399,6 +410,20 @@ static void initializeObjCExport() { added = class_addMethod(booleanClass, toKotlinSelector, (IMP)boxedBooleanToKotlinImp, toKotlinTypeEncoding); RuntimeAssert(added, "Unable to add 'toKotlin:' method to __NSCFBoolean class"); + + for (const char* swiftRootClassName : { "SwiftObject", "_TtCs12_SwiftObject" }) { + Class swiftRootClass = objc_getClass(swiftRootClassName); + if (swiftRootClass != nullptr) { + added = class_addMethod(swiftRootClass, toKotlinSelector, (IMP)SwiftObject_toKotlinImp, toKotlinTypeEncoding); + RuntimeAssert(added, "Unable to add 'toKotlin:' method to SwiftObject class"); + + added = class_addMethod( + swiftRootClass, releaseAsAssociatedObjectSelector, + (IMP)SwiftObject_releaseAsAssociatedObjectImp, releaseAsAssociatedObjectTypeEncoding + ); + RuntimeAssert(added, "Unable to add 'releaseAsAssociatedObject' method to SwiftObject class"); + } + } } @interface NSObject (NSObjectToKotlin) @@ -416,11 +441,19 @@ static void initializeObjCExport() { +(void)load { static dispatch_once_t onceToken = 0; dispatch_once(&onceToken, ^{ - initializeObjCExport(); + checkLoadedOnce(); }); } @end; +static OBJ_GETTER(SwiftObject_toKotlinImp, id self, SEL cmd) { + RETURN_RESULT_OF(convertUnmappedObjCObject, self); +} + +static void SwiftObject_releaseAsAssociatedObjectImp(id self, SEL cmd) { + objc_release(self); +} + @interface NSString (NSStringToKotlin) @end;