一、前言
转载请注明出处
本文主要是在iOS端的角度来看Kotlin-Native是如何进行内存管理的,需要捋清楚OC实例和kotlin实例的对应关系,以及在两个不同的Runtime中是适配引用计数和GC的内存回收的。
1.1 kotlin-native编译
我们基于kotlin 2.0.21进行编译和调试(需要使用Xcode15编译),可能会遇到编译报错:
* What went wrong:Could not determine the dependencies of task ':kotlin-stdlib:compileJava9KotlinJvm'.> No matching toolchains found for requested specification: {languageVersion=9, vendor=any, implementation=vendor-specific} for MAC_OS on aarch64. > No locally installed toolchains match and the configured toolchain download repositories aren't able to provide a match either.
我们需要按照官方文档提示执行如下两步操作(新版本已经集成到一个sh脚本中):
$ sed -i '' -e '/<components>/,/<\/components>/d' gradle/verification-metadata.xml$ ./gradlew -i --write-verification-metadata sha256,md5 -Pkotlin.native.enabled=true resolveDependencies
依然会报错,可以看到是因为没有java9的工具链,我们可以在stdlib这个module的build.gradle.kts里面修改对应Java9的配置即可。
具体的编译指令按照官方文档里面./gradlew对应的指令即可(我编译的完整版的bundle版本),如果要编译带debug信息需要加上debug编译参数:
./gradlew -Pshims=true :kotlin-native:bundle -Pkotlin.native.debug=true
当我们编译成功之后,可以在demo工程的gradle.properties里面配置我们的kotlin-native的路径:
kotlin.native.home=/Users/wandawwang/workspace/kmm/kotlin/kotlin-native/dist
1.2 基于CLion代码编辑
这部分官方的在kotlin-native/HACKING.md中说明了如何进行编译的预设置,生成编译数据库:
./gradlew :kotlin-native:compdb
然后使用Clion打开kotlin-native打开项目并配置一下compile-command.json的路径(我们编译完成之后会在kotlin-native路径下生成)。使用Clion的目的只是为了能够在看代码时更好跳转(编译和运行还是用上面的gradlew)。
1.3 kotlin-native调试
我们成功编译kotlin-native之后,可以手动建一个Demo并把kn的路径设置为我们自己编译的路径(上一节中已经提到)。
AS中我们编译自己需要导出的代码:
interface InteroperatAble {
}
/**
* export
*/
object Interoperator {
private var bridge: InteroperatAble? = null
// oc to kotlin
fun inject(bridge: InteroperatAble?) {
println("inject")
this.bridge = bridge
}
}
并在Xcode中写调用代码:
- (void)viewDidLoad {
[super viewDidLoad];
[SharedInteroperator.shared injectBridge:self];
}
这时候我们就可以运行自己的代码,并查看对应的执行情况。
如果使用Android Studio创建的kmp工程,需要在toml里面把kotlin的版本号改成2.0.21版本。这样就能和我们自己编译的kotlin-native版本保持一致:
kotlin = "2.0.21"
二、Runtime能力注入
从kn的源码可以看到一个与objc相关的很基础的类KotlinBase:
struct ObjHeader;
@interface KotlinBase : NSObject <NSCopying>
+ (instancetype)createRetainedWrapper:(struct ObjHeader *)obj;
- (instancetype)initWithExternalRCRef:(uintptr_t)ref NS_REFINED_FOR_SWIFT;
// Return kotlin.native.ref.ExternalRCRef stored in this class
- (uintptr_t)externalRCRef NS_REFINED_FOR_SWIFT;
@end
在该类中重写了两个比较关键的方法load和initialize,我们分别来看看分别都做了什么。
2.1 load
load方法会在应用程序启动期间由系统自动进行调用,因此kn在load方法内部调用了injectToRuntimeImpl函数(使用dispatch_once保证只调用一次)。
static void injectToRuntimeImpl() {
Kotlin_ObjCExport_toKotlinSelector = @selector(toKotlin:);
Kotlin_ObjCExport_releaseAsAssociatedObjectSelector = @selector(releaseAsAssociatedObject);
}
- 1、将toKotlin方法绑定到Kotlin_ObjCExport_toKotlinSelector全局变量中(类型在iOS中为SEL);
- 2、将releaseAsAssociatedObject方法绑定到Kotlin_ObjCExport_releaseAsAssociatedObjectSelector全局变量中。
2.2 initialize
这部分会做三个事情:
- 1.尝试性再一次调用injectToRuntime方法去绑定toKolitn和release的selector;
- 2.为Block、Bool以及swift类添加toKotlin和release方法;
- 3.将Kotlin中类型定义的方法和接口绑定到对应的原生类中;
首先在initialize方法的内部如果当前类型是KotlinBase也会去调用上面提到injectToRuntime方法。
我们在编译Kotlin代码时会为类添加@ObjCName注解或者iosmain中定义的类/接口,编译器会将这部分内容进行导出(这部分源码在ObjCExportCodeGenerator.kt中),并将类/接口保存在如下的弱符号变量中(这是编译属性,也就是):
__attribute__((weak)) const ObjCTypeAdapter** Kotlin_ObjCExport_sortedClassAdapters = nullptr;
__attribute__((weak)) int Kotlin_ObjCExport_sortedClassAdaptersNum = 0;
__attribute__((weak)) const ObjCTypeAdapter** Kotlin_ObjCExport_sortedProtocolAdapters = nullptr;
__attribute__((weak)) int Kotlin_ObjCExport_sortedProtocolAdaptersNum = 0;
这里的 attribute((weak)) 表示这是一个弱符号,初始值为 0。弱符号的特点是如果在其他地方有同名的强符号定义,则会使用强符号的值;如果没有,则使用这里的默认值。导出的ObjCTypeAdapter类型。
因此initTypeAdapters会读取由编译器在编译时写入的Kotlin_ObjCExport_sortedClassAdapters(class适配器)和Kotlin_ObjCExport_sortedProtocolAdapters(protocol适配器)写入到TypeInfo中缓存起来,以便在按需转换OC实例和Kotlin实例时能找到对应的适配器:
typeInfo->writableInfo_->objCExport.typeAdapter = adapter;
然后为 NSBlock、布尔类和 Swift 对象添加了 toKotlin: 方法,使这些对象可以转换为 Kotlin 对象:
BOOL added = class_addMethod(nsBlockClass, toKotlinSelector, (IMP)blockToKotlinImp, toKotlinTypeEncoding);
added = class_addMethod(booleanClass, toKotlinSelector, (IMP)boxedBooleanToKotlinImp, toKotlinTypeEncoding);
added = class_addMethod(swiftRootClass, toKotlinSelector, (IMP)SwiftObject_toKotlinImp, toKotlinTypeEncoding);
以及为 Swift 对象添加了 releaseAsAssociatedObject 方法,用于管理对象的生命周期:
added = class_addMethod(
swiftRootClass, releaseAsAssociatedObjectSelector,
(IMP)SwiftObject_releaseAsAssociatedObjectImp, releaseAsAssociatedObjectTypeEncoding
)
最后就是第3点提到的内容,这部分在Kotlin_ObjCExport_initializeClass函数中实现。先将ObjCTypeAdapter的kotlinTypeInfo和Class类型关联起来:
objc_setAssociatedObject(clazz, &associatedTypeInfoKey, value, OBJC_ASSOCIATION_ASSIGN);

在第3步的最后将kotlin定义的方法实现指针绑定到该class中,以便于后续所有针对该Class或者Class实例的方法调用都能找到方法的实现:
// 给class添加实例方法
class_addMethod(clazz, selector, adapter->imp, adapter->encoding);
// 给class添加类方法
class_addMethod(object_getClass(clazz), selector, adapter->imp, adapter->encoding);
// 给class添加协议
addProtocolForInterface(clazz, typeInfo->implementedInterfaces_[i]);
这都是一些准备工作,下面我们来详细看一下是如何进行跨Runtime内存内管里。
三、跨Runtime的内存管理
我们从上面提到的例子入手来看一下跨Runtime的内存管理是如何进行的。
这里shared的framework命名是通过在build.gradle.kts中配置的:
cocoapods {
name = "shared"
podfile = project.file("../iosApp/Podfile")
framework {
baseName = "shared"
isStatic = true
}
}
为了搞清楚跨Runtime的内存管理,我们需要明确如下目标:
目标:观察objc实例时候如何传递kotlin侧使用,并且查看objc实例和对应的kotlin实例是关联的?
把目标拆分一下:
- 1.纯OC的实例传递到Kotlin侧;
- 2.Kotlin导出到OC的类,在OC侧基于该类生成的实例并回传给Kotlin;
- 3.Kotlin侧生成类的实例并导出到OC;
3.1 纯OC的实例传递到Kotlin侧
我们在objc侧调用的injectBridge方法实参self指针是如何传递到kotlin的呢?kotlin-native透露的内容很少,那如何查看其内部的真相是个问题:
- 我们可以正向的去阅读konan的源码看看编译器是如何插入的,但那内容太多了。
- 通过编译出来的MachO文件来看汇编吗?没问题,但那读起来晦涩难懂。
- 那再往上一层,我们去看经过LLVM编译之后IR代码。那我们尝试一下。
3.1.1 LLVM IR
我在文章最开头部分有列出当前的kotlin版本是2.0.21,我们要能够看到编译过程中的IR,需要配置一下build.gradle.kts的编译选项:
iosSimulatorArm64() {
compilations.all {
kotlinOptions {
// 打印gc的信息
freeCompilerArgs += "-Xruntime-logs=gc=info"
freeCompilerArgs += "-Xbinary=stripDebugInfoFromNativeLibs=false"
freeCompilerArgs += "-g"
freeCompilerArgs += "-verbose"
}
}
}
添加了verbose选项之后,我们在编译的时候能够在输出栏看到如下内容输出:
/Users/wandawwang/.konan/dependencies/llvm-12.0.1-macos-aarch64-20250512/bin/clang++ -cc1 -emit-obj -disable-llvm-passes -x ir -triple arm64-apple-ios14.0-simulator -O0 -mllvm -fast-isel=false -mllvm -global-isel=false /var/folders/0g/29bl_1hj56ddl7fw5_l8ylvh0000gn/T/konan_temp3482722465042693681/out.bc -o
会将中间的临时IR输出到形如"/var/folders/0g/xxxxxx/T/konan_temp3...1/out.bc"的路径中。然后使用llvm-dis将bc转换为ll文件即可。
然后我们在kotlin侧的inject方法内加一个断点,运行起来能看到类似这样的调用栈:
#0 0x0000000102454144 in kfun:com.example.kotlinnativedebugdemo.Interoperator#inject(com.example.kotlinnativedebugdemo.InteroperatAble?){}
#1 0x0000000102455104 in objc2kotlin_kfun:com.example.kotlinnativedebugdemo.Interoperator#inject(com.example.kotlinnativedebugdemo.InteroperatAble?){} at /<compiler-generated>:1
#2 0x0000000102451908 in -[MainViewController viewDidLoad]
很明显我们要搞清楚objc的指针如何传递到kotlin侧,objc2kotlin_kfun:com.example.kotlinnativedebugdemo.Interoperator#inject方法是我们必须要去弄清楚的。我们去上面得到的ll文件中进行搜索,可以看到类似的方法定义:
define internal void @"objc2kotlin_kfun:com.example.kotlinnativedebugdemo.Interoperator#inject(com.example.kotlinnativedebugdemo.InteroperatAble?){}"(i8* %0, i8* %1, i8* %2) #9 personality i32 (...)* @__gxx_personality_v0 !dbg !2292 {
...
}
有三个参数:
- %0(对应的SharedInteroperator.shared);
- %1(对应injectBridge这个selector)
- %2(对应self指针),类型在 LLVM IR 中被类型擦除为 i8*
关于SharedInteroperator.shared和self的变量处理主要是这部分代码:
entry: ; preds = %stack_locals_init
%9 = invoke %struct.ObjHeader* @Kotlin_ObjCExport_refFromObjC(i8* %0, %struct.ObjHeader** %6)
to label %call_success unwind label %fatal_landingpad, !dbg !2293
call_success: ; preds = %entry
%10 = invoke %struct.ObjHeader* @Kotlin_ObjCExport_refFromObjC(i8* %2, %struct.ObjHeader** %7)
to label %call_success1 unwind label %fatal_landingpad, !dbg !2293
可以看到entry对应的内容主要是处理SharedInteroperator.shared的,它的第一个实参是%0。而我们关心的self是call_success对应的内容,它的第一个实参是%2。很明显它调用了一个Kotlin_ObjCExport_refFromObjC函数!
call_success之后接着是调用call_success1:
call_success1: ; preds = %call_success
invoke void @"kfun:com.example.kotlinnativedebugdemo.Interoperator#inject(com.example.kotlinnativedebugdemo.InteroperatAble?){}"(%struct.ObjHeader* %9, %struct.ObjHeader* %10)
to label %call_success3 unwind label %kotlinExceptionHandler, !dbg !2293
注意这个函数和我们上面提到的inject方法有点类似,但并不一样!它是kfun的前缀,也就是它是纯kotlin侧的inject方法;而上面的是objc2kotlin_kfun,它是由编译器生成由objc到kotlin的桥接方法。
从这里我们能知道从Native调用到Kotlin侧,首先会调用编译器生成的objc2kotlin_kfun的方法,然后在该方法内部去调用kotlin侧,也就是以kfun为前缀的方法。
我们继续看看主流程,也就是oc的实例是如何转换为kotlin侧使用的实例的。
3.1.2 Kotlin_ObjCExport_refFromObjC函数
从函数的命名我们能够大概猜到它是生成一个基于objc实例的引用,其源代码如下:
extern "C" ObjHeader* Kotlin_ObjCExport_refFromObjC(id obj, ObjHeader** __result__) {
kotlin::AssertThreadState(kotlin::ThreadState::kRunnable);
if (obj == nullptr) {
ObjHeader* __obj = nullptr;
UpdateReturnRef(__result__, __obj);
return __obj;
};
auto msgSend = reinterpret_cast<ObjHeader* (*)(id self, SEL cmd, ObjHeader** slot)>(&objc_msgSend);
ObjHeader* __result = msgSend(obj, Kotlin_Kotlin_ObjCExport_toKotlinSelector_toKotlinSelector, __result__);
return __result;
}
我们在Xcode中下一个符号断点验证一下是否会执行到对应的函数。通过输出的结果来看我们前面的分析是正确的:
(lldb) register read x0
x0 = 0x000000010640e200
(lldb) po 0x000000010640e200
<MainViewController: 0x10640e200>
回到源码,obj明显不是nullptr。因此会执行到Kotlin_ObjCExport_toKotlinSelector,我们在上一节的load有提到该selector会绑定到toKotlin方法。那我们下一个符号断点"-[KotlinBase toKotlin:]"来看看,但是发现该断点并不会生效!
那怎么办呢?只能在Kotlin_ObjCExport_refFromObjC进行单步调试,停留在objc_msgSend之前:
0x1061fa9d4 <+76>: ldur x2, [x29, #-0x10]
0x1061fa9d8 <+80>: ldur x0, [x29, #-0x8]
0x1061fa9dc <+84>: adrp x8, 147
0x1061fa9e0 <+88>: ldr x1, [x8, #0x7b0]
-> 0x1061fa9e4 <+92>: bl 0x10620d654 ; symbol stub for: objc_msgSend
对于objc_msgSend来说,其第二个参数x1对应到selector。我们打印看看情况:
(lldb) register read x1
x1 = 0x000000010620ef58 "Shared_toKotlin:"
通过搜索源码可以看到拼接规则:
fun addPrivateSelector(name: String) {
literalPatches += LiteralPatch(ObjCDataGenerator.selectorGenerator, name, "${privatePrefix}_$name")
}
从ll文件中也能搜到Shared_toKotlin关键字样:
@OBJC_METH_VAR_NAME_.24 = private unnamed_addr constant [17 x i8] c"Shared_toKotlin:\00", section "__TEXT,__objc_methname,cstring_literals", align 1
理论上来说,我们应该能够在ll文件中看到三个基于Shared_toKotlin的变量,他们分别用于:
- 1.Kotlin中基础数据类型。比如Int、Long、Boolean、Double、Byte等等添加的toKotlin方法;
- 2.为Kotlin中的集合类型:Map、List、Set等等。添加toKotlin方法;
- 3.OC中的封装类型:NSObject、NSString、NSNumber等等添加toKotlin方法,还有就是KotlinBase的toKotlin方法;
我们这里要看的self的对应的toKotlin方法,也就是上面的第三种。那他实现是什么呢?来看一下其内容:
@"_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_NSObjectToKotlin" = internal global
{ i32, i32, [2 x %_objc_method] }
{ i32 24, i32 2, [2 x %_objc_method]
[
%_objc_method {
i8* getelementptr inbounds ([17 x i8], [17 x i8]* @OBJC_METH_VAR_NAME_.24, i32 0, i32 0),
i8* getelementptr inbounds ([46 x i8], [46 x i8]* @OBJC_METH_VAR_TYPE_.56, i32 0, i32 0),
i8* bitcast (%struct.ObjHeader* (%27*, i8*, %struct.ObjHeader**)* @"\01-[NSObject(NSObjectToKotlin) toKotlin:]" to i8*)
},
...
]
}, section "__DATA, __objc_const", align 8
这里定义了toKotlin方法:
%_objc_method {
// 方法名称
i8* getelementptr inbounds ([17 x i8], [17 x i8]* @OBJC_METH_VAR_NAME_.24, i32 0, i32 0),
// 方法签名,也就是方法的类型
i8* getelementptr inbounds ([46 x i8], [46 x i8]* @OBJC_METH_VAR_TYPE_.56, i32 0, i32 0),
// 方法实现
i8* bitcast (%struct.ObjHeader* (%27*, i8*, %struct.ObjHeader**)* @"\01-[NSObject(NSObjectToKotlin) toKotlin:]" to i8*)
}
变量“@OBJC_METH_VAR_NAME_.24”(往前第二个代码块,可以搜索关键字)就是我们上面已经贴出来的内容,他表示的这个objc_method的方法名称是“Shared_toKotlin:”。
方法签名@OBJC_METH_VAR_TYPE_.56(在上一个代码框的第5行,方法签名中)的定义:
@OBJC_METH_VAR_TYPE_.56 = private unnamed_addr constant [46 x i8] c"^{ObjHeader=^{TypeInfo}}24@0:8^^{ObjHeader}16\00", section "__TEXT,__objc_methtype,cstring_literals", align 1
其中"^{ObjHeader=^{TypeInfo}}"表示返回的返回值是ObjHeader结构体,在该结构体内部包含有TypeInfo类型的成员变量。
24表示进入这个方法需要申请24字节的栈大小。
- @ 符号表示的对象类型,
- :符号表示的selector类型;
- ^^{ObjHeader}表示的是指针的指针;
他们后面数字表示偏移量,分别是0,8,16;
对应的方法签名类似于:
- (ObjHeader *)xxxxx:(ObjHeader **)param;
最后我们可以看到具体的方法实现,指向了"-[NSObject(NSObjectToKotlin) toKotlin:]"。从源码中能够看到其定义如下:
@implementation NSObject (NSObjectToKotlin)
-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT {
ObjHeader* __result = Kotlin_ObjCExport_convertUnmappedObjCObject(self, OBJ_RESULT);
return __result;
}
@end
可以看到其内部调用了Kotlin_ObjCExport_convertUnmappedObjCObject函数。我们在Xcode添加该函数的符号断点,看到其参数的确是MainViewController的实例:

总结一下这个函数的作用:
调用对应的类型基于NSObject的扩展toKotlin方法,生成与OC对象对应的ObjHeader作为Kotlin使用的实例
3.1.3 Kotlin_ObjCExport_convertUnmappedObjCObject函数
源码如下:
extern "C" ObjHeader* Kotlin_ObjCExport_convertUnmappedObjCObject(id obj, ObjHeader** OBJ_RESULT) {
const TypeInfo* typeInfo = getOrCreateTypeInfo(object_getClass(obj));
ObjHeader* __result = AllocInstanceWithAssociatedObject(typeInfo, objc_retain(obj), OBJ_RESULT);
return __result;
}
static const TypeInfo* getOrCreateTypeInfo(Class clazz) {
const TypeInfo* result = Kotlin_ObjCExport_getAssociatedTypeInfo(clazz);
if (result != nullptr) {
return result;
}
Class superClass = class_getSuperclass(clazz);
const TypeInfo* superType = superClass == nullptr ?
theForeignObjCObjectTypeInfo :
getOrCreateTypeInfo(superClass);
std::lock_guard lockGuard(typeInfoCreationMutex);
result = Kotlin_ObjCExport_getAssociatedTypeInfo(clazz); // double-checking.
if (result == nullptr) {
result = createTypeInfo(clazz, superType, nullptr);
setAssociatedTypeInfo(clazz, result);
}
return result;
}
首先获取当前class对应的kotlin的TypeInfo。
由于我们传入的是MainViewController的实例,是无法获取到已有的TypeInfo的,所以他会尝试去查找其父类的TypeInfo(调试看依次查找了UIViewController、UIResponder和NSObject)。
可以看到MainViewController自身是没有TypeInfo的:
const TypeInfo* result = Kotlin_ObjCExport_getAssociatedTypeInfo(clazz);

最后在运行期间会通过createTypeInfo给OC的实例创建与之对应的TypeInfo:
result = createTypeInfo(clazz, superType, nullptr);
并将typeInfo添加到类的关联对象中,以便于下一次class可以直接找到对应的TypeInfo而不用去重复创建TypeInfo:
static void setAssociatedTypeInfo(Class clazz, const TypeInfo* typeInfo) {
kotlin::NativeOrUnregisteredThreadGuard threadStateGuard(/* reentrant = */ true);
NSValue* value = [[NSValue alloc] initWithBytes:&typeInfo objCType:@encode(void*)];
objc_setAssociatedObject(clazz, &associatedTypeInfoKey, value, OBJC_ASSOCIATION_ASSIGN);
}
然后基于上面得到的TypeInfo创建对应的实例:
inline static ObjHeader* AllocInstanceWithAssociatedObject(const TypeInfo* typeInfo, id associatedObject, ObjHeader** OBJ_RESULT) {
ObjHeader* result = AllocInstance(typeInfo, OBJ_RESULT);
SetAssociatedObject(result, associatedObject);
return result;
}
在调用AllocInstanceWithAssociatedObject传入的参数,是调用objc_retain(obj)的返回值。也就是说这里会对OC的实例引用计数+1:
ObjHeader* __result = AllocInstanceWithAssociatedObject(typeInfo, objc_retain(obj), OBJ_RESULT);
我们可以自行验证一下:
object KotlinInnerOperator {
internal var bridge: InteroperatAble? = null
}
object Interoperator {
private var bridge: InteroperatAble? = null
private var bridge_bk: InteroperatAble? = null
fun inject(bridge: InteroperatAble?) {
this.bridge = bridge
this.bridge_bk = bridge
KotlinInnerOperator.bridge = bridge
}
}

生成逻辑:纯OC的实例会调用NSObject的toKolitn扩展方法,在该方法内部调用Kotlin_ObjCExport_convertUnmappedObjCObject生成Kotlin侧的ObjHeader!
引用计数:纯OC的实例传递到kotlin侧会使其引用计数+1,在kotlin侧无论增加了多少次引用,均不会继续增加该实例的引用计数。

可以简单一下TypeInfo的内存分布情况:
(lldb) x/16gx 0x000000010631adb0
0x10631adb0: 0x000000010631adb0 0x0000000106327330
0x10631adc0: 0x0000000c00000000 0x0000000106310f10
0x10631add0: 0x0000000000000000 0x0000000000000000
0x10631ade0: 0x00000001063272f8 0x0000000000000001
0x10631adf0: 0x0000000106327310 0x00000001063270f0
0x10631ae00: 0x0000000106327360 0x0000000000000302
0x10631ae10: 0x00000001063445b0 0x0000000000000000
0x10631ae20: 0x00000001062ccd8c 0x0000000000000008
3.2 OC侧生成Kotlin类的实例并传递到Kotlin侧
我们上面观察到了纯OC类的实例传递给Kotlin侧的执行流程。现在我们将一个kotlin类导出到OC侧并在OC侧生成该类的实例,最终将该实例回传给kotlin。
修改一下我们的demo:
class Kotlin2Objc:InteroperatAble {
}
OC的代码修改如下:
SharedKotlin2Objc *k2o = [[SharedKotlin2Objc alloc] init];
[SharedInteroperator.shared injectBridge:k2o];
我们debug能够看到alloc会走到KotlinBase的allocWithZone方法。我们把着重看一下其内部关于内存管理部分的代码:
KotlinBase* result = [super allocWithZone:zone];
ObjHolder holder;
AllocInstanceWithAssociatedObject(typeInfo, result, holder.slot());
result->refHolder.initAndAddRef(holder.obj());
result是native侧调用生成的OC对象(我们在MainViewController中调用了alloc生成的),holder.slot()是调用AllocInstanceWithAssociatedObject返回的结果,我们可以把它理解为Kotlin侧的实例。最后result会被添加kotlin侧的实例的AssociatedObject内部保留起来。
这里AllocInstanceWithAssociatedObject调用传入的参数明显和3.1.3节调用时传递的不一样,这里没有执行objc_retain来增加引用计数。
上面提到的的refHolder是KotlinBase内部的一个成员变量:
@implementation KotlinBase {
BackRefFromAssociatedObject refHolder;
}
所以我们先来看BackRefFromAssociatedObject的内容。
3.2.1 BackRefFromAssociatedObject
它主要作用是维护从OC对象到 Kotlin 对象的反向引用(back reference),提供了引用计数机制,确保只要关联对象存在,Kotlin 对象就不会被垃圾回收。
这样做的目的是保证了只要OC对象没有被销毁,那么该Kotlin对象就不会被回收;反过来只要Kotlin对象不销毁,OC对象也能够一直存在。这保证了生命周期的同步。
BackRefFromAssociatedObject定义如下(移除了部分我们此时不关注的内容):
class BackRefFromAssociatedObject {
private:
union {
struct {
// ....
volatile int refCount;
}; // Legacy MM
struct {
kotlin::mm::RawSpecialRef* ref_;
// ...
}; // New MM. Regular object.
};
};
调用的initAndAddRef和createObjCBackRef都是针对ref_成员变量的操作,其实现为:
void BackRefFromAssociatedObject::initAndAddRef(ObjHeader* obj) {
ref_ = static_cast<mm::RawSpecialRef*>(mm::ObjCBackRef::create(obj));
deallocMutex_.construct();
}
mm::ObjCBackRef mm::SpecialRefRegistry::ThreadQueue::createObjCBackRef(ObjHeader* object) noexcept {
// 默认的引用计数为1(非OC侧的引用计数)
return mm::ObjCBackRef(registerNode(object, 1, false).asRaw());
}
Node& registerNode(ObjHeader* obj, Node::Rc rc, bool allowFastDeletion) noexcept {
RuntimeAssert(obj != nullptr, "Creating node for null object");
queue_.emplace_back(owner_, obj, rc);
auto& node = queue_.back();
return node;
}
这里生成了Node实例,并将其放入到queue_中。注意这里传入了ObjHeader并被Node实例所持有,后面在toKotlin中返回的实例就是该obj!
3.2.2 ObjCBackRef和Node
上面提到了ObjCBackRef的类型,它主要用于桥接 Objective-C 和 Kotlin/Native 的对象生命周期管理。让 ObjC 的对象可以安全地持有 Kotlin 对象的引用(作为“反向引用”/back reference),通过引用计数(refcount)机制,配合 Kotlin 的 GC,确保Kotlin的对象不会被过早回收。
GC 会自动跟踪引用计数大于 0 的对象作为根,并在 Kotlin 对象被收集时使引用计数为 0 的引用失效。
实现了retain/release/tryRetain 等接口,模拟 ObjC 的引用计数内存管理。
class ObjCBackRef : private MoveOnly {
private:
raw_ptr<SpecialRefRegistry::Node> node_;
}
node_成员的赋值是其在显示构造方法中:
explicit ObjCBackRef(RawSpecialRef* raw) noexcept : node_(SpecialRefRegistry::Node::fromRaw(raw)) {}
Node的类型定义如下:
class Node : private Pinned {
private:
ObjHeader* obj_; // kotlin侧实例的指针
std::atomic<Rc> rc_; // 引用计数
std::atomic<Node*> nextRoot_ = nullptr;
}
因此后续的retain和release都是基于Node的rc_成员进行操作的:
void retain() noexcept {
if (!node_) return;
node_->retainRef();
}
void release() noexcept {
if (!node_) return;
node_->releaseRef();
}
3.2.3 获取Kotlin实例
这里需要特别说明一下:并不是说Kotlin侧定义并导出到OC侧的类都是KotlinBase的子类,依次查看superclass可以看到其父类链路中并不包含KotlinBase类,编译器会基于自己的Framework前缀生成一个XXXBase!
比如咱们例子中的SharedBase,看一下SharedBase在llvm中间码中的定义如下:
@"OBJC_CLASS_$_SharedBase" = global %_class_t {
%_class_t* @"OBJC_METACLASS_$_SharedBase", // isa指针
%_class_t* @"OBJC_CLASS_$_NSObject", // superclass指针
%_objc_cache* @_objc_empty_cache, // cache指针
i8* (i8*, i8*)** null, // vtable指针
%_class_ro_t* @"_OBJC_CLASS_RO_$_KotlinBase" // class_ro_t的data指针
}, section "__DATA, __objc_data", align 8
每一个属性的含义如下:
- isa指针:指向该类的元类(metaclass)。元类用于存储类方法。
- superclass指针:指向父类 NSObject,表明 SharedBase 继承自 NSObject。
- cache指针:指向方法缓存,初始为空缓存。
- vtable指针:这里设为 null。
- class_ro_t数据指针:指向类的只读数据,注意这里指向的是 KotlinBase 而不是 SharedBase。
说明编译器帮忙生成的SharedBase包括方法、成员变量、属性和协议等等信息都是指向了KotlinBase类。
我们在3.2.1知道在Native侧调用alloc_init会走到KotlinBase的allocWithZone生成一个Native实例,但后面我们需要将该实例传递到Native侧。
这里的转换用到了KotlinBase的toKotlin方法,这里可以从生成ll代码来看(用llvm-dis从bc转换过来的,我们3.1节有详细讲):
define internal void @"objc2kotlin_kfun:com.example.kn_runtime_debug_demo.Interoperator#inject(com.example.kn_runtime_debug_demo.InteroperatAble?){}"(i8* %0, i8* %1, i8* %2) #5 personality i32 (...)* @__gxx_personality_v0 !dbg !17589 {
// ...
%9 = invoke %struct.ObjHeader* @Kotlin_ObjCExport_refFromObjC(i8* %0, %struct.ObjHeader** %6)
to label %call_success unwind label %fatal_landingpad, !dbg !17590
// ...
}
这个和我们3.1看到的情况一样,都是调用Kotlin_ObjCExport_refFromObjC函数,那我们添加对应函数的符号断点,并在调用objc_msgSend之前查看x1的情况:

我们使用类似3.1.2节拆出KotlinBase的实例方法定义列表:
@"_OBJC_$_INSTANCE_METHODS_KotlinBase" = internal global { i32, i32, [9 x %_objc_method] }
{ i32 24, i32 9, [9 x %_objc_method]
[
%_objc_method {
i8* getelementptr inbounds ([17 x i8], [17 x i8]* @OBJC_METH_VAR_NAME_.49.916, i32 0, i32 0),
i8* getelementptr inbounds ([46 x i8], [46 x i8]* @OBJC_METH_VAR_TYPE_.84, i32 0, i32 0),
i8* bitcast (%struct.ObjHeader* (%31*, i8*, %struct.ObjHeader**)* @"\01-[KotlinBase toKotlin:]" to i8*)
},
// ....
]
}, section "__DATA, __objc_const", align 8
在第7行能看到其实现是调用"-[KotlinBase toKotlin:]"。来到kotlin-native的runtime源码,由于我们生成的非持久性的实例,因此在toKotlin中执行代码如下:
-(ObjHeader *)toKotlin:(ObjHeader **)__result__ {
// ...
ObjHeader* __obj = refHolder.ref<ErrorPolicy::kTerminate>();
UpdateReturnRef(__result__, __obj);
return __obj;
}
ObjHeader* BackRefFromAssociatedObject::ref() const {
return *mm::ObjCBackRef::reinterpret(ref_);
}
可以看到这里的ref函数是调用了ObjCBackRef的解引用操作符:
ObjHeader* operator*() const noexcept {
if (!node_) return nullptr;
return node_->ref();
}
这里node_的ref函数最终返回的是Node中所持有的obj_成员,所以在这里我们就得到了Kotlin侧的ObjHeader实例。
再回头看一下内存管理,由于KotlinBase重写了retain和release方法,因此所有针对KotlinBase实例的引用计数操作均时下面的实现:
-(instancetype)retain {
if (permanent) {
[super retain];
} else {
refHolder.addRef<ErrorPolicy::kTerminate>();
}
return self;
}
template <ErrorPolicy errorPolicy>
void BackRefFromAssociatedObject::addRef() {
mm::ObjCBackRef::reinterpret(ref_).retain();
}
看到非permanent对象在retain和release的时候是直接调用的refHolder对应的函数。这是kn-runtime自己管理的引用计数(非iOS原生的引用计数,该引用计数并不会影响到OC的对象释放)。

无论如何增加其引用关系,我们通过
️️️CFGetRetainCount得到的引用计数一直为1(并未增加引用计数),这和我们在3.1.3节观察到纯OC实例的引用计数表现完全不一致。
这里我们看一下OC实例是如何反向引用Kotlin的ObjHeader的(ObjHeader正向引用OC实例,我们在3.3节再详细看看)

其中nextRoot_管理所有需要作为 GC 根的特殊引用指针,这是单向链表。基本上所有Kotlin类型的实例在OC侧创建时所生成Node均会被添加到这个单向链表中:
Node(SpecialRefRegistry& registry, ObjHeader* obj, Rc rc) noexcept : obj_(obj), rc_(rc) {
// 在3.2.1节中调用registerNode(object, 1, false).asRaw()
// 传入的rc都为1
if (rc > 0) {
registry.insertIntoRootsHead(*this);
}
}
3.3 Kotlin侧生成类的实例导出到OC并回传到Kotlin侧
同样的我们改造一下我们的Demo,新增一个Kt2OC2Kt的类(实际上我们使用的Interoperator单例也能作为这个case的例子,但我们要观察release的情况)。
class Kt2OC2Kt: InteroperatAble {
}
object Interoperator {
private var k2o2k: Kt2OC2Kt? = Kt2OC2Kt()
fun getKotlinInstance(): Kt2OC2Kt? {
return k2o2k
}
fun inject(bridge: InteroperatAble?) {}
}
这个case和上面两种case最大的差异在于:上面两种case都是在OC侧生成的实例,该case是在Kotlin侧生成的实例,并传递到OC侧。在3.1和3.2我们基本弄清楚了OC的实例是如何转换为Kotlin侧能够使用ObjHeader指针的。
本节我们需要弄清楚的问题是:
Kotlin侧生成的实例是如何转换为OC侧可以使用的实例呢?
用llvm-dis翻译一下 ir代码"objc2kotlin_kfun:com.example.kn_runtime_debug_demo.Interoperator#getKotlinInstance()
",注意这里是objc2kotlin_kfun!!!表明当前还处于oc侧的一个函数。
我摘出了比较关键的代码:
entry: ; preds = %stack_locals_init
%8 = invoke %struct.ObjHeader* @Kotlin_ObjCExport_refFromObjC(i8* %0, %struct.ObjHeader** %5)
to label %call_success unwind label %fatal_landingpad, !dbg !17588
call_success: ; preds = %entry
%9 = invoke %struct.ObjHeader* @"kfun:com.example.kn_runtime_debug_demo.Interoperator#getKotlinInstance(){}com.example.kn_runtime_debug_demo.Kt2OC2Kt?"(%struct.ObjHeader* %8, %struct.ObjHeader** %7)
to label %call_success2 unwind label %kotlinExceptionHandler, !dbg !17588
call_success2: ; preds = %call_success
%10 = invoke i8* @Kotlin_ObjCExport_refToRetainedObjC(%struct.ObjHeader* %9)
to label %call_success3 unwind label %fatal_landingpad, !dbg !17588
call_success3: ; preds = %call_success2
// ...
%11 = tail call i8* @llvm.objc.autoreleaseReturnValue(i8* %10) #9, !dbg !17588
ret i8* %11, !dbg !17588
首先是entry块(preds表示当前块的前驱,这里stack_locals_init块我忽略了,可以理解为会先执行本地栈初始化工作)调用Kotlin_ObjCExport_refFromObjC(我们3.1.2节已经详细说了)获取oc对象对应的kotlin实例(ObjHeader);
接着call_success去调用对应kfun,其返回值保存在%9变量中。最后在call_success2中调用Kotlin_ObjCExport_refToRetainedObjC函数的返回值保存在%10变量中。最后的尾调用autoreleaseReturnValue将OC的实例放入自动释放池中。
3.3.1 Kotlin_ObjCExport_refToRetainedObjC
其函数定义:
extern "C" id Kotlin_ObjCExport_refToRetainedObjC(ObjHeader* obj) {
return Kotlin_ObjCExport_refToObjCImpl<true>(obj);
}
接收一个ObjHeader参数,返回一个id类型的值。
其内部实现为:
convertReferenceToRetainedObjC convertToRetained = (convertReferenceToRetainedObjC)obj->type_info()->writableInfo_->objCExport.convertToRetained;
id retainedResult;
if (convertToRetained != nullptr) {
retainedResult = convertToRetained(obj);
} else {
retainedResult = Kotlin_ObjCExport_refToRetainedObjC_slowpath(obj);
}
其中"obj->type_info()->writableInfo_->objCExport.convertToRetained"对应的汇编代码:
ldr x8, [x8]
and x8, x8, #0xfffffffffffffffc
ldr x8, [x8]
ldr x8, [x8, #0x60] // writableInfo_在TypeInfo中的偏移量为12*8
ldr x8, [x8]
执行到第5行之后,register read x8的值为0。因此会执行else分支中的Kotlin_ObjCExport_refToRetainedObjC_slowpath函数:
static id Kotlin_ObjCExport_refToRetainedObjC_slowpath(ObjHeader* obj) {
const TypeInfo* typeInfo = obj->type_info();
convertReferenceToRetainedObjC convertToRetained = findConvertToRetainedFromInterfaces(typeInfo);
if (convertToRetained == nullptr) {
getOrCreateClass(typeInfo);
convertToRetained = (typeInfo == theUnitTypeInfo) ? &Kotlin_ObjCExport_convertUnitToRetained : &convertKotlinObjectToRetained;
}
typeInfo->writableInfo_->objCExport.convertToRetained = (void*)convertToRetained;
return convertToRetained(obj);
}
通过debug能够知道其执行的是第6行(x8保留的是convertToRetained变量的结果,由于其为nullptr,因此执行的第6行的逻辑):

接着执行到0x101e27310,subs指令会将x10减去x11,并设置cpsr寄存器的标志位。如果相减为0则会设置z标记位,否则z标记位为0:

可以看到cpsr的值为0x20001000:

回头看一下上面的源代码,能够知道当typeInfo是Unit类型会执行Kotlin_ObjCExport_convertUnitToRetained,否则执行convertKotlinObjectToRetained:
static id convertKotlinObjectToRetained(ObjHeader* obj) {
Class clazz = obj->type_info()->writableInfo_->objCExport.objCClass;
RuntimeAssert(clazz != nullptr, "");
return [clazz createRetainedWrapper:obj];
}
最后在Kotlin_ObjCExport_refToRetainedObjC_slowpath函数中会把convert的函数指针赋值给"typeInfo->writableInfo_->objCExport.convertToRetained"缓存起来,下一次Kotlin实例的转换时,就不需要走slowpath直接读取缓存中的函数指针即可。

3.3.2 createRetainedWrapper方法
上一节我们看到kotlin侧的ObjHeader会调用createRetainedWrapper方法来创建一个OC的实例。该方法针对持久化对象和非持久化对象有两种不同的处理方式,由于我们现在看的是Kt2OC2Kt的实例,该实例为非持久化对象。简化之后的源码为:
+ (instancetype)createRetainedWrapper:(ObjHeader*)obj {
KotlinBase* candidate = [super allocWithZone:nil];
bool permanent = obj->permanent();
candidate->permanent = permanent;
candidate->refHolder.initAndAddRef(obj);
if (!isShareable(obj)) {
SetAssociatedObject(obj, candidate);
} else {
id old = AtomicCompareAndSwapAssociatedObject(obj, nullptr, candidate);
if (old != nullptr) {
{
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
candidate->refHolder.releaseRef();
[candidate releaseAsAssociatedObject];
}
return objc_retain(old);
}
}
return candidate;
}
第5行会获取refHolder并将BackRef设置为当前传入obj指针,接着由于isSharedable在当前版本是固定返回true的:
ALWAYS_INLINE bool isShareable(const ObjHeader* obj) {
return true;
}
因此会执行到第9行,AtomicCompareAndSwapAssociatedObject函数的作用是原子性的交换obj的关联对象,如果其关联对象为nullptr,则会把candidate写入到关联对象中;如果关联对象不为nullptr,则会返回该关联对象,并把此次生成的candidate释放掉。并且会增加一次引用计数。
对于Kotlin中声明的类实例无论如何调用objc_retain都不会增加OC定义中的引用计数。这是因为KotlinBase重写了retain方法,该实现中均只会增加kotlin-native自己维护的引用计数。
这是因为objc_retain最终都会调用类自身的retain方法:
id objc_retain(id obj) {
if (obj->isTaggedPointerOrNil()) return obj;
return obj->retain();
}

四、WeakReference实现
我们使用弱引用的目的是:
- 新对象不会影响到原对象的释放时机;
- 原对象释放之后新对象能够被正常置空;
带着这两个目的先来看一下WeakReference在kotlin的定义:
@GCUnsafeCall("Konan_getWeakReferenceImpl")
@Escapes(0b01) // referent escapes.
external internal fun getWeakReferenceImpl(referent: Any): WeakReferenceImpl
public class WeakReference<T : Any> {
public constructor(referred: T) {
pointer = getWeakReferenceImpl(referred)
}
}
从下面的代码我们能知道创建弱引用实例是调用的Konan_getWeakReferenceImpl函数:
RUNTIME_NOTHROW extern "C" OBJ_GETTER(Konan_getWeakReferenceImpl, ObjHeader* referred) {
if (referred->permanent()) {
RETURN_RESULT_OF(makePermanentWeakReferenceImpl, referred);
}
#if KONAN_OBJC_INTEROP
if (IsInstanceInternal(referred, theObjCObjectWrapperTypeInfo)) {
RETURN_RESULT_OF(makeObjCWeakReferenceImpl, referred->GetAssociatedObject());
}
#endif // KONAN_OBJC_INTEROP
RETURN_RESULT_OF(mm::createRegularWeakReferenceImpl, referred);
}
因此这里分为了三类不同的Weak实现:
- 1.用于长生命(permanent)周期的对象;
- 2.ObjC对象的弱引用;
- 3.Kotlin对象的弱引用;
因此我们分别来看一下这三种不同的Weak实现。
4.1 Permanent对象
从字面意思来看,Permanent对象就是拥有长生命周期的对象,对于这个概念我的认识比较浅薄。目前测试来看Kotlin中定义的单例并不是permanent对象,但是字符串常量和全局常量。
回到其WeakReference的实现:
internal class PermanentWeakReferenceImpl(val referred: Any): kotlin.native.ref.WeakReferenceImpl() {
override fun get(): Any? = referred
}
@ExportForCppRuntime
internal fun makePermanentWeakReferenceImpl(referred: Any) = PermanentWeakReferenceImpl(referred)
可以看出:
对于permanent对象,其WeakReference就是该对象自身!
1、是否会影响原对象的生命周期:该对象本身就是长生命周期的对象,不存在销毁的场景;
2、新对象是否能够被自动置空:该对象本身就是长生命周期的对象,不存在销毁的场景;
4.2 ObjC对象
所有NSObject的子类,无论是在iosmain中通过kotlin创建的、还是OC代码中创建的实例均是被认为是ObjC对象。这部分实例的WeakReference源码:
@kotlin.native.internal.ExportForCppRuntime internal fun makeObjCWeakReferenceImpl(objcPtr: NativePtr): ObjCWeakReferenceImpl {
val result = ObjCWeakReferenceImpl()
result.init(objcPtr)
return result
}
返回一个ObjCWeakReferenceImpl实例。真正的实现是在init方法里面:
@kotlin.native.internal.ExportForCppRuntime internal fun makeObjCWeakReferenceImpl(objcPtr: NativePtr): ObjCWeakReferenceImpl {
val result = ObjCWeakReferenceImpl()
result.init(objcPtr)
return result
}
@GCUnsafeCall("Konan_ObjCInterop_initWeakReference")
private external fun ObjCWeakReferenceImpl.init(objcPtr: NativePtr)
static void Konan_ObjCInterop_initWeakReference(KRef ref, id objcPtr) {
KotlinObjCWeakReference* objcRef = [KotlinObjCWeakReference new];
objc_storeWeak(&objcRef->referred, objcPtr);
ref->SetAssociatedObject(objcRef);
}
其完全是基于oc中__weak的实现方式,将objcRef->referred指向了objcPtr。从本节最开始的代码objcPtr就是“referred->GetAssociatedObject())”。这里会生成一个临时KotlinObjCWeakReference实例:
@implementation KotlinObjCWeakReference {
@public id referred;
}
最终内存的引用关系为:

读取逻辑:
static OBJ_GETTER(Konan_ObjCInterop_getWeakReference, KRef ref) {
KotlinObjCWeakReference* objcRef = (KotlinObjCWeakReference*)ref->GetAssociatedObject();
id objcReferred = kotlin::CallWithThreadState<kotlin::ThreadState::kNative>(objc_loadWeakRetained, &objcRef->referred);
KRef result = Kotlin_Interop_refFromObjC(objcReferred, OBJ_RESULT);
kotlin::CallWithThreadState<kotlin::ThreadState::kNative>(objc_release, objcReferred);
return result;
}
在通过get读取WeakReference的原始值时,首先会获取extraData持有的KotlinObjCWeakReference,接着调用objc_loadWeakRetained从OC的weak表中读取出原对象的指针。
该对象是一个OC对象,我们需要调用Kotlin_Interop_refFromObjC转换为最终的kotlin的实例(这部分流程见3.1节)。由于该函数会增加OC对象的引用计数,因此在该实现的最后调用了objc_release将其引用计数-1。
1、是否会影响原对象的生命周期:基于OC的weak原生实现,该引用并不会增加原OC实例的引用计数;
2、新对象是否能够被自动置空:基于OC的weak原生实现,当原对象被释放时,在对象的dealloc过程中调用_object_remove_assocations函数将保留的对应weak指针进行置空;
4.3 Kotlin对象
Kotlin对象的Weak实现调用了createRegularWeakReferenceImpl函数,会先尝试一下调用GetRegularWeakReferenceImpl来获取已经存在weakRef:
auto& extraObject = mm::ExtraObjectData::GetOrInstall(object);
auto* weakRef = extraObject.GetRegularWeakReferenceImpl();
ObjHeader* GetRegularWeakReferenceImpl() noexcept {
auto* pointer = weakReferenceOrBaseObject_.load();
if (hasPointerBits(pointer, WEAK_REF_TAG)) return clearPointerBits(pointer, WEAK_REF_TAG);
return nullptr;
}
可以看到弱引用是保存在weakReferenceOrBaseObject_成员中,WEAK_REF_TAG是在指针的最低位设置为1来标记当前是一个弱引用(由于是字节对齐的,默认情况下至少低8位都是0)。

如果当前的weakReferenceOrBaseObject_不存在弱引用值,就会新创建一个weakRef:
auto* weakRef = makeRegularWeakReferenceImpl(
static_cast<mm::RawSpecialRef*>(mm::WeakRef::create(object)),
object,
holder.slot()
);
// kotlin代码
internal class RegularWeakReferenceImpl(
val weakRef: COpaquePointer,
val referred: COpaquePointer, // TODO: This exists only for the ExtraObjectData's sake. Refactor and remove.
) : WeakReferenceImpl() {
@GCUnsafeCall("Konan_RegularWeakReferenceImpl_get")
external override fun get(): Any?
}
internal fun makeRegularWeakReferenceImpl(weakRef: COpaquePointer, referred: COpaquePointer) = RegularWeakReferenceImpl(weakRef, referred)
并调用GetOrSetRegularWeakReferenceImpl函数将上面得到的weakRef保留起来:
ObjHeader* GetOrSetRegularWeakReferenceImpl(ObjHeader* object, ObjHeader* weakRef) noexcept {
if (weakReferenceOrBaseObject_.compare_exchange_strong(object, setPointerBits(weakRef, WEAK_REF_TAG))) {
return weakRef;
} else {
return clearPointerBits(object, WEAK_REF_TAG); // on fail current value of weakRef is stored to object
}
}
- 如果weakReferenceOrBaseObject_和object相等,则将 weakReferenceOrBaseObject_ 设置为 setPointerBits(weakRef, WEAK_REF_TAG),即带有弱引用标记的新弱引用实现;
- 如果不相等object 参数会被 compare_exchange_strong 修改为 weakReferenceOrBaseObject_ 的实际值。并将object返回;
换句话说就是首次被引用的实例没有生成weakRef则会新创建一个,如果已经存在则直接获取weakRef。在demo中验证了这一点:
val srcObj = Card()
val weakObj = WeakReference(srcObj)
val weakObj2 = WeakReference(srcObj)

接着来看一下get是如何实现的。从上面面是Kotlin侧RegularWeakReferenceImpl的详细定义
可以看到其get方法最终是调用Konan_RegularWeakReferenceImpl_get函数,且该函数内部会调用derefRegularWeakReferenceImpl函数:
// 这里我把宏展开了一下
OBJ_GETTER(mm::derefRegularWeakReferenceImpl, ObjHeader* weakRef) noexcept {
ObjHeader* __obj = asRegularWeakReferenceImpl(weakRef)->weakRef.tryRef(OBJ_RESULT);
return __obj;
}
最终的引用关系如下:

无论生成多少个弱引用实例,均只会生成一个impl实例!
1、是否会影响原对象的生命周期:在垃圾回收过程中,如果系统认为对象已经"死亡"(没有其他强引用指向它),那么 RegularWeakReferenceImpl 内部指向该对象的引用会被置为 null。
2、新对象是否能够被自动置空:由于RegularWeakReferenceImpl内部已经被设置为null,我们通过弱引用尝试访问该对象时,将会得到 null,表示原对象已经被回收。
五、后记
我们从上面的内容可以大致的了解到在iOS中kotlin-native管理内存的方式,不过就像官方的说法“Unfortunately, no special instruments are currently available to automatically detect retain cycles in Kotlin/Native code”,目前并没有一个有效的工具能够帮我们找到跨Runtime的内存引用链条。
我们能做的就是如果发现存在内存泄漏,就需要在Native使用weak或者在Kotlin使用WeakReference来断开引用链条。