Support weak references to Objective-C objects (#1531)
This commit is contained in:
committed by
GitHub
parent
6dc6ba5137
commit
5f661b20aa
@@ -24,7 +24,7 @@ interface ObjCClassOf<T : ObjCObject> : ObjCClass // TODO: T should be added to
|
|||||||
typealias ObjCObjectMeta = ObjCClass
|
typealias ObjCObjectMeta = ObjCClass
|
||||||
|
|
||||||
@ExportTypeInfo("theForeignObjCObjectTypeInfo")
|
@ExportTypeInfo("theForeignObjCObjectTypeInfo")
|
||||||
internal open class ForeignObjCObject
|
internal open class ForeignObjCObject : konan.internal.ObjCObjectWrapper
|
||||||
|
|
||||||
abstract class ObjCObjectBase protected constructor() : ObjCObject {
|
abstract class ObjCObjectBase protected constructor() : ObjCObject {
|
||||||
@Target(AnnotationTarget.CONSTRUCTOR)
|
@Target(AnnotationTarget.CONSTRUCTOR)
|
||||||
@@ -150,6 +150,20 @@ private external fun <T : ObjCObject> getObjCClass(): NativePtr
|
|||||||
@konan.internal.Intrinsic external fun getMessenger(superClass: NativePtr): COpaquePointer?
|
@konan.internal.Intrinsic external fun getMessenger(superClass: NativePtr): COpaquePointer?
|
||||||
@konan.internal.Intrinsic external fun getMessengerLU(superClass: NativePtr): COpaquePointer?
|
@konan.internal.Intrinsic external fun getMessengerLU(superClass: NativePtr): COpaquePointer?
|
||||||
|
|
||||||
|
internal class ObjCWeakReferenceImpl : konan.ref.WeakReferenceImpl() {
|
||||||
|
@SymbolName("Konan_ObjCInterop_getWeakReference")
|
||||||
|
external override fun get(): Any?
|
||||||
|
}
|
||||||
|
|
||||||
|
@SymbolName("Konan_ObjCInterop_initWeakReference")
|
||||||
|
private external fun ObjCWeakReferenceImpl.init(objcPtr: NativePtr)
|
||||||
|
|
||||||
|
@konan.internal.ExportForCppRuntime internal fun makeObjCWeakReferenceImpl(objcPtr: NativePtr): ObjCWeakReferenceImpl {
|
||||||
|
val result = ObjCWeakReferenceImpl()
|
||||||
|
result.init(objcPtr)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// Konan runtme:
|
// Konan runtme:
|
||||||
|
|
||||||
@Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.WARNING)
|
@Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.WARNING)
|
||||||
|
|||||||
+1
-1
@@ -108,7 +108,7 @@ internal class IrDescriptorDeserializer(val context: Context,
|
|||||||
descriptorIndex[index]!!
|
descriptorIndex[index]!!
|
||||||
CLASS, CONSTRUCTOR, FUNCTION, ACCESSOR ->
|
CLASS, CONSTRUCTOR, FUNCTION, ACCESSOR ->
|
||||||
descriptorIndex[index] ?:
|
descriptorIndex[index] ?:
|
||||||
findInTheDescriptorTree(proto)!!
|
findInTheDescriptorTree(proto)!! // Note: can be null when using `@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")`
|
||||||
else -> TODO("Unexpected descriptor kind: $kind")
|
else -> TODO("Unexpected descriptor kind: $kind")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import kotlinx.cinterop.*
|
import kotlinx.cinterop.*
|
||||||
import objcSmoke.*
|
import objcSmoke.*
|
||||||
|
import konan.ref.*
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
@@ -11,6 +12,7 @@ fun main(args: Array<String>) {
|
|||||||
fun run() {
|
fun run() {
|
||||||
testTypeOps()
|
testTypeOps()
|
||||||
testConversions()
|
testConversions()
|
||||||
|
testWeakRefs()
|
||||||
|
|
||||||
println(
|
println(
|
||||||
getSupplier(
|
getSupplier(
|
||||||
@@ -145,6 +147,35 @@ fun testMethodsOfAny(kotlinObject: Any, equalNsObject: NSObject, otherObject: An
|
|||||||
assertNotEquals(equalNsObject, otherObject)
|
assertNotEquals(equalNsObject, otherObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun testWeakRefs() {
|
||||||
|
testWeakReference({ NSObject.new()!! })
|
||||||
|
|
||||||
|
createAndAbandonWeakRef(NSObject())
|
||||||
|
|
||||||
|
testWeakReference({ NSArray.arrayWithArray(listOf(42)) as NSArray })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testWeakReference(block: () -> NSObject) {
|
||||||
|
val ref = autoreleasepool {
|
||||||
|
createAndTestWeakReference(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNull(ref.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createAndTestWeakReference(block: () -> NSObject): WeakReference<NSObject> {
|
||||||
|
val ref = createWeakReference(block)
|
||||||
|
assertNotNull(ref.get())
|
||||||
|
assertEquals(ref.get()!!.hash(), ref.get()!!.hash())
|
||||||
|
return ref
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createWeakReference(block: () -> NSObject) = WeakReference(block())
|
||||||
|
|
||||||
|
fun createAndAbandonWeakRef(obj: NSObject) {
|
||||||
|
WeakReference(obj)
|
||||||
|
}
|
||||||
|
|
||||||
fun nsArrayOf(vararg elements: Any): NSArray = NSMutableArray().apply {
|
fun nsArrayOf(vararg elements: Any): NSArray = NSMutableArray().apply {
|
||||||
elements.forEach {
|
elements.forEach {
|
||||||
this.addObject(it as ObjCObject)
|
this.addObject(it as ObjCObject)
|
||||||
|
|||||||
@@ -165,6 +165,48 @@ KBoolean Kotlin_Interop_IsObjectKindOfClass(id obj, void* cls) {
|
|||||||
return [((id<NSObject>)obj) isKindOfClass:(Class)cls];
|
return [((id<NSObject>)obj) isKindOfClass:(Class)cls];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used as an associated object for ObjCWeakReferenceImpl.
|
||||||
|
@interface KotlinObjCWeakReference : NSObject
|
||||||
|
@end;
|
||||||
|
|
||||||
|
// libobjc:
|
||||||
|
id objc_loadWeakRetained(id *location);
|
||||||
|
id objc_storeWeak(id *location, id newObj);
|
||||||
|
void objc_destroyWeak(id *location);
|
||||||
|
void objc_release(id obj);
|
||||||
|
|
||||||
|
@implementation KotlinObjCWeakReference {
|
||||||
|
@public id referred;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when removing Kotlin object.
|
||||||
|
-(void)releaseAsAssociatedObject {
|
||||||
|
objc_destroyWeak(&referred);
|
||||||
|
objc_release(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end;
|
||||||
|
|
||||||
|
OBJ_GETTER(Kotlin_Interop_refFromObjC, id obj);
|
||||||
|
|
||||||
|
OBJ_GETTER(Konan_ObjCInterop_getWeakReference, KRef ref) {
|
||||||
|
MetaObjHeader* meta = ref->meta_object();
|
||||||
|
KotlinObjCWeakReference* objcRef = (KotlinObjCWeakReference*)meta->associatedObject_;
|
||||||
|
|
||||||
|
id objcReferred = objc_loadWeakRetained(&objcRef->referred);
|
||||||
|
KRef result = Kotlin_Interop_refFromObjC(objcReferred, OBJ_RESULT);
|
||||||
|
objc_release(objcReferred);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Konan_ObjCInterop_initWeakReference(KRef ref, id objcPtr) {
|
||||||
|
MetaObjHeader* meta = ref->meta_object();
|
||||||
|
KotlinObjCWeakReference* objcRef = [KotlinObjCWeakReference new];
|
||||||
|
objc_storeWeak(&objcRef->referred, objcPtr);
|
||||||
|
meta->associatedObject_ = objcRef;
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
||||||
#else // KONAN_OBJC_INTEROP
|
#else // KONAN_OBJC_INTEROP
|
||||||
@@ -205,7 +247,16 @@ KRef Kotlin_Interop_unwrapKotlinObjectHolder(void* holder) {
|
|||||||
RuntimeAssert(false, "Objective-C interop is disabled");
|
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OBJ_GETTER(Konan_ObjCInterop_getWeakReference, KRef ref) {
|
||||||
|
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||||
|
RETURN_OBJ(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Konan_ObjCInterop_initWeakReference(KRef ref, void* objcPtr) {
|
||||||
|
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
||||||
#endif // KONAN_OBJC_INTEROP
|
#endif // KONAN_OBJC_INTEROP
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ extern const TypeInfo* theStringTypeInfo;
|
|||||||
extern const TypeInfo* theThrowableTypeInfo;
|
extern const TypeInfo* theThrowableTypeInfo;
|
||||||
extern const TypeInfo* theUnitTypeInfo;
|
extern const TypeInfo* theUnitTypeInfo;
|
||||||
extern const TypeInfo* theForeignObjCObjectTypeInfo;
|
extern const TypeInfo* theForeignObjCObjectTypeInfo;
|
||||||
|
extern const TypeInfo* theObjCObjectWrapperTypeInfo;
|
||||||
|
|
||||||
KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE;
|
KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE;
|
||||||
void CheckCast(const ObjHeader* obj, const TypeInfo* type_info);
|
void CheckCast(const ObjHeader* obj, const TypeInfo* type_info);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -39,11 +40,19 @@ inline void unlock(int32_t* address) {
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
OBJ_GETTER(makeWeakReferenceCounter, void*);
|
OBJ_GETTER(makeWeakReferenceCounter, void*);
|
||||||
|
OBJ_GETTER(makeObjCWeakReferenceImpl, void*);
|
||||||
|
|
||||||
// See Weak.kt for implementation details.
|
// See Weak.kt for implementation details.
|
||||||
// Retrieve link on the counter object.
|
// Retrieve link on the counter object.
|
||||||
OBJ_GETTER(Konan_getWeakReferenceCounter, ObjHeader* referred) {
|
OBJ_GETTER(Konan_getWeakReferenceImpl, ObjHeader* referred) {
|
||||||
MetaObjHeader* meta = referred->meta_object();
|
MetaObjHeader* meta = referred->meta_object();
|
||||||
|
|
||||||
|
#if KONAN_OBJC_INTEROP
|
||||||
|
if (IsInstance(referred, theObjCObjectWrapperTypeInfo)) {
|
||||||
|
RETURN_RESULT_OF(makeObjCWeakReferenceImpl, meta->associatedObject_);
|
||||||
|
}
|
||||||
|
#endif // KONAN_OBJC_INTEROP
|
||||||
|
|
||||||
if (meta->counter_ == nullptr) {
|
if (meta->counter_ == nullptr) {
|
||||||
ObjHolder counterHolder;
|
ObjHolder counterHolder;
|
||||||
// Cast unneeded, just to emphasize we store an object reference as void*.
|
// Cast unneeded, just to emphasize we store an object reference as void*.
|
||||||
|
|||||||
@@ -18,7 +18,14 @@ package konan.internal
|
|||||||
|
|
||||||
import konan.internal.ExportForCppRuntime
|
import konan.internal.ExportForCppRuntime
|
||||||
|
|
||||||
internal class NSArrayAsKList : AbstractList<Any?>() {
|
/**
|
||||||
|
* This interface denotes the object to be a wrapper for the Objective-C object,
|
||||||
|
* so the latter should be used to observe object lifetime.
|
||||||
|
*/
|
||||||
|
@ExportTypeInfo("theObjCObjectWrapperTypeInfo")
|
||||||
|
internal interface ObjCObjectWrapper
|
||||||
|
|
||||||
|
internal class NSArrayAsKList : AbstractList<Any?>(), ObjCObjectWrapper {
|
||||||
|
|
||||||
override val size: Int get() = getSize()
|
override val size: Int get() = getSize()
|
||||||
|
|
||||||
@@ -29,7 +36,7 @@ internal class NSArrayAsKList : AbstractList<Any?>() {
|
|||||||
external override fun get(index: Int): Any?
|
external override fun get(index: Int): Any?
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class NSMutableArrayAsKMutableList : AbstractMutableList<Any?>() {
|
internal class NSMutableArrayAsKMutableList : AbstractMutableList<Any?>(), ObjCObjectWrapper {
|
||||||
|
|
||||||
override val size: Int get() = getSize()
|
override val size: Int get() = getSize()
|
||||||
|
|
||||||
@@ -49,7 +56,7 @@ internal class NSMutableArrayAsKMutableList : AbstractMutableList<Any?>() {
|
|||||||
external override fun set(index: Int, element: Any?): Any?
|
external override fun set(index: Int, element: Any?): Any?
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class NSSetAsKSet : AbstractSet<Any?>(), konan.internal.KonanSet<Any?> {
|
internal class NSSetAsKSet : AbstractSet<Any?>(), konan.internal.KonanSet<Any?>, ObjCObjectWrapper {
|
||||||
|
|
||||||
override val size: Int get() = getSize()
|
override val size: Int get() = getSize()
|
||||||
|
|
||||||
@@ -66,7 +73,7 @@ internal class NSSetAsKSet : AbstractSet<Any?>(), konan.internal.KonanSet<Any?>
|
|||||||
external override fun iterator(): Iterator<Any?>
|
external override fun iterator(): Iterator<Any?>
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class NSDictionaryAsKMap : Map<Any?, Any?> {
|
internal class NSDictionaryAsKMap : Map<Any?, Any?>, ObjCObjectWrapper {
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ class WeakReference<T : Any> {
|
|||||||
* removing object, and is nullified once object is collected.
|
* removing object, and is nullified once object is collected.
|
||||||
*/
|
*/
|
||||||
constructor(referred: T) {
|
constructor(referred: T) {
|
||||||
pointer = getWeakReferenceCounter(referred)
|
pointer = getWeakReferenceImpl(referred)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backing store for the object pointer, inaccessible directly.
|
* Backing store for the object pointer, inaccessible directly.
|
||||||
*/
|
*/
|
||||||
@PublishedApi
|
@PublishedApi
|
||||||
internal var pointer: WeakReferenceCounter?
|
internal var pointer: WeakReferenceImpl?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears reference to an object.
|
* Clears reference to an object.
|
||||||
@@ -47,5 +47,4 @@ class WeakReference<T : Any> {
|
|||||||
/**
|
/**
|
||||||
* Returns either reference to an object or null, if it was collected.
|
* Returns either reference to an object or null, if it was collected.
|
||||||
*/
|
*/
|
||||||
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
|
|
||||||
public inline fun <reified T : Any> WeakReference<T>.get() = pointer?.get() as T?
|
public inline fun <reified T : Any> WeakReference<T>.get() = pointer?.get() as T?
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package konan.ref
|
|||||||
|
|
||||||
import kotlinx.cinterop.COpaquePointer
|
import kotlinx.cinterop.COpaquePointer
|
||||||
import konan.internal.ExportForCppRuntime
|
import konan.internal.ExportForCppRuntime
|
||||||
import konan.internal.ExportForCompiler
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theory of operations:
|
* Theory of operations:
|
||||||
@@ -43,18 +42,22 @@ import konan.internal.ExportForCompiler
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Clear holding the counter object, which refers to the actual object.
|
// Clear holding the counter object, which refers to the actual object.
|
||||||
@ExportForCompiler
|
internal class WeakReferenceCounter(var referred: COpaquePointer?) : WeakReferenceImpl() {
|
||||||
internal class WeakReferenceCounter(var referred: COpaquePointer?) {
|
|
||||||
// Spinlock, potentially taken when materializing or removing 'referred' object.
|
// Spinlock, potentially taken when materializing or removing 'referred' object.
|
||||||
var lock: Int = 0
|
var lock: Int = 0
|
||||||
|
|
||||||
@SymbolName("Konan_WeakReferenceCounter_get")
|
@SymbolName("Konan_WeakReferenceCounter_get")
|
||||||
internal external fun get(): Any?
|
external override fun get(): Any?
|
||||||
|
}
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal abstract class WeakReferenceImpl {
|
||||||
|
abstract fun get(): Any?
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a counter from non-null object.
|
// Get a counter from non-null object.
|
||||||
@SymbolName("Konan_getWeakReferenceCounter")
|
@SymbolName("Konan_getWeakReferenceImpl")
|
||||||
external internal fun getWeakReferenceCounter(referent: Any): WeakReferenceCounter
|
external internal fun getWeakReferenceImpl(referent: Any): WeakReferenceImpl
|
||||||
|
|
||||||
// Create a counter object.
|
// Create a counter object.
|
||||||
@ExportForCppRuntime
|
@ExportForCppRuntime
|
||||||
|
|||||||
Reference in New Issue
Block a user