You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中objc_msgSend调用异常:iOS侧BaseId实例属性赋值失败排查

问题排查:Kotlin/Native动态创建OC实例时初始化方法未生效

我最近在做Kotlin/Native框架向iOS项目暴露接口的开发,想通过动态创建BaseId子类实例的方式提供给iOS调用,但遇到了一个奇怪的问题:动态创建的实例属性没有被正确初始化,直接调用OC的初始化方法却能正常工作。以下是相关代码和现象:

我的Kotlin实现代码

actual fun create(typeName: String, id: String): BaseId {
 val objCClass = NSClassFromString(typeName)
 var selector = NSSelectorFromString("alloc")
 val instance = objc_msgSend(objCClass, selector)
 selector = NSSelectorFromString("initWithId:")
 // ???????? this msgSend not work !
 objc_msgSend(instance, selector, id)
 // --------------------------------
 return instance as BaseId
}

iOS端调用代码

// UserId extends BaseId
let typeName = NSStringFromClass(UserId.classForCoder())
let userId = framework.create(typeName: typeName, id: "testId") as! UserId
// ???????? userId创建成功但userId.id为空,不符合预期的"testId"
let userId2 = UserId.init(id: "testId2")
// userId2正常,userId2.id == "testId2"

UserId对应的OC头文件

__attribute__((objc_subclassing_restricted)) __attribute__((swift_name("UserId")))
@interface CommonUserId : CommonBaseId
- (instancetype)initWithId:(NSString *)id __attribute__((swift_name("init(id:)"))) __attribute__((objc_designated_initializer));
- (NSString *)testFun __attribute__((swift_name("testFun()")));
@property NSString *testProperty __attribute__((swift_name("testProperty")));
@end;

问题根源

你这里的核心问题是忽略了objc_msgSend调用初始化方法的返回值。在Objective-C中,init系列方法(包括initWithId:)是会返回初始化后的实例对象的——虽然大部分情况下它和alloc出来的实例是同一个,但某些初始化逻辑可能会返回不同的实例(比如单例、缓存实例等),而且Kotlin/Native的objc_msgSend不会自动帮你处理这个返回值,你必须手动接收并使用它。

你的代码里调用objc_msgSend(instance, selector, id)后,直接用了原来的instance变量,但实际上这个初始化方法的返回值才是真正完成初始化的实例,原来的instance可能并没有被正确赋值。

修复后的代码

修改Kotlin代码,接收初始化方法的返回值即可解决问题:

actual fun create(typeName: String, id: String): BaseId {
 val objCClass = NSClassFromString(typeName)
 var selector = NSSelectorFromString("alloc")
 var instance = objc_msgSend(objCClass, selector)
 selector = NSSelectorFromString("initWithId:")
 // 接收初始化后的实例,替换原变量
 instance = objc_msgSend(instance, selector, id)
 return instance as BaseId
}

如果想要更严谨,可以指定objc_msgSend的返回类型,避免潜在的类型转换问题:

import kotlinx.cinterop.*

actual fun create(typeName: String, id: String): BaseId {
 val objCClass = NSClassFromString(typeName) ?: error("Class $typeName not found")
 val allocSelector = NSSelectorFromString("alloc")
 val instance = objc_msgSend(objCClass, allocSelector) as CPointer<ObjCObjectVar>
 val initSelector = NSSelectorFromString("initWithId:")
 val initializedInstance = objc_msgSend(instance, initSelector, id.cstr) as CPointer<ObjCObjectVar>
 return initializedInstance.asStableRef<BaseId>().get()
}

补充说明

为什么直接在iOS端调用UserId.init(id: "testId2")正常?因为Swift会自动处理init方法的返回值,它底层会正确接收objc_msgSend的返回结果并赋值给userId2,而你的Kotlin代码没有做这一步,导致使用的是alloc出来但未完成初始化的实例,所以属性是空的。

内容的提问来源于stack exchange,提问作者Lei Wu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:11:58