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 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)
@@ -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)
+52 -1
View File
@@ -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
+1
View File
@@ -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);
+10 -1
View File
@@ -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
+2 -3
View File
@@ -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