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
|
||||
|
||||
@ExportTypeInfo("theForeignObjCObjectTypeInfo")
|
||||
internal open class ForeignObjCObject
|
||||
internal open class ForeignObjCObject : konan.internal.ObjCObjectWrapper
|
||||
|
||||
abstract class ObjCObjectBase protected constructor() : ObjCObject {
|
||||
@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 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:
|
||||
|
||||
@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]!!
|
||||
CLASS, CONSTRUCTOR, FUNCTION, ACCESSOR ->
|
||||
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")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import kotlinx.cinterop.*
|
||||
import objcSmoke.*
|
||||
import konan.ref.*
|
||||
import kotlin.test.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
@@ -11,6 +12,7 @@ fun main(args: Array<String>) {
|
||||
fun run() {
|
||||
testTypeOps()
|
||||
testConversions()
|
||||
testWeakRefs()
|
||||
|
||||
println(
|
||||
getSupplier(
|
||||
@@ -145,6 +147,35 @@ fun testMethodsOfAny(kotlinObject: Any, equalNsObject: NSObject, otherObject: An
|
||||
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 {
|
||||
elements.forEach {
|
||||
this.addObject(it as ObjCObject)
|
||||
|
||||
@@ -165,6 +165,48 @@ KBoolean Kotlin_Interop_IsObjectKindOfClass(id obj, void* 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"
|
||||
|
||||
#else // KONAN_OBJC_INTEROP
|
||||
@@ -205,7 +247,16 @@ KRef Kotlin_Interop_unwrapKotlinObjectHolder(void* holder) {
|
||||
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||
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"
|
||||
|
||||
#endif // KONAN_OBJC_INTEROP
|
||||
#endif // KONAN_OBJC_INTEROP
|
||||
|
||||
@@ -84,6 +84,7 @@ extern const TypeInfo* theStringTypeInfo;
|
||||
extern const TypeInfo* theThrowableTypeInfo;
|
||||
extern const TypeInfo* theUnitTypeInfo;
|
||||
extern const TypeInfo* theForeignObjCObjectTypeInfo;
|
||||
extern const TypeInfo* theObjCObjectWrapperTypeInfo;
|
||||
|
||||
KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE;
|
||||
void CheckCast(const ObjHeader* obj, const TypeInfo* type_info);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "Memory.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -39,11 +40,19 @@ inline void unlock(int32_t* address) {
|
||||
extern "C" {
|
||||
|
||||
OBJ_GETTER(makeWeakReferenceCounter, void*);
|
||||
OBJ_GETTER(makeObjCWeakReferenceImpl, void*);
|
||||
|
||||
// See Weak.kt for implementation details.
|
||||
// Retrieve link on the counter object.
|
||||
OBJ_GETTER(Konan_getWeakReferenceCounter, ObjHeader* referred) {
|
||||
OBJ_GETTER(Konan_getWeakReferenceImpl, ObjHeader* referred) {
|
||||
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) {
|
||||
ObjHolder counterHolder;
|
||||
// Cast unneeded, just to emphasize we store an object reference as void*.
|
||||
|
||||
@@ -18,7 +18,14 @@ package konan.internal
|
||||
|
||||
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()
|
||||
|
||||
@@ -29,7 +36,7 @@ internal class NSArrayAsKList : AbstractList<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()
|
||||
|
||||
@@ -49,7 +56,7 @@ internal class NSMutableArrayAsKMutableList : AbstractMutableList<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()
|
||||
|
||||
@@ -66,7 +73,7 @@ internal class NSSetAsKSet : AbstractSet<Any?>(), konan.internal.KonanSet<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 {
|
||||
if (this === other) return true
|
||||
|
||||
@@ -27,14 +27,14 @@ class WeakReference<T : Any> {
|
||||
* removing object, and is nullified once object is collected.
|
||||
*/
|
||||
constructor(referred: T) {
|
||||
pointer = getWeakReferenceCounter(referred)
|
||||
pointer = getWeakReferenceImpl(referred)
|
||||
}
|
||||
|
||||
/**
|
||||
* Backing store for the object pointer, inaccessible directly.
|
||||
*/
|
||||
@PublishedApi
|
||||
internal var pointer: WeakReferenceCounter?
|
||||
internal var pointer: WeakReferenceImpl?
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
|
||||
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 konan.internal.ExportForCppRuntime
|
||||
import konan.internal.ExportForCompiler
|
||||
|
||||
/**
|
||||
* Theory of operations:
|
||||
@@ -43,18 +42,22 @@ import konan.internal.ExportForCompiler
|
||||
*/
|
||||
|
||||
// Clear holding the counter object, which refers to the actual object.
|
||||
@ExportForCompiler
|
||||
internal class WeakReferenceCounter(var referred: COpaquePointer?) {
|
||||
internal class WeakReferenceCounter(var referred: COpaquePointer?) : WeakReferenceImpl() {
|
||||
// Spinlock, potentially taken when materializing or removing 'referred' object.
|
||||
var lock: Int = 0
|
||||
|
||||
@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.
|
||||
@SymbolName("Konan_getWeakReferenceCounter")
|
||||
external internal fun getWeakReferenceCounter(referent: Any): WeakReferenceCounter
|
||||
@SymbolName("Konan_getWeakReferenceImpl")
|
||||
external internal fun getWeakReferenceImpl(referent: Any): WeakReferenceImpl
|
||||
|
||||
// Create a counter object.
|
||||
@ExportForCppRuntime
|
||||
|
||||
Reference in New Issue
Block a user