Support weak references to Objective-C objects (#1531)

This commit is contained in:
SvyatoslavScherbina
2018-04-24 12:32:43 +03:00
committed by GitHub
parent 6dc6ba5137
commit 5f661b20aa
9 changed files with 132 additions and 17 deletions
@@ -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)
@@ -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)
+52 -1
View File
@@ -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
+1
View File
@@ -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);
+10 -1
View File
@@ -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
+2 -3
View File
@@ -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