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

Swift桥接ObjC类中非懒加载默认属性内存泄漏原因咨询

内存泄漏原因分析:Swift @objcMembers类 + ObjC KVC反序列化

咱们直接拆解这个泄漏的核心逻辑——问题出在Swift属性初始化时机和ObjC Runtime KVC操作的交互上,结合你提供的场景,具体原因如下:

1. Swift存储属性的默认初始化 vs ObjC KVC执行顺序

Swift的非lazy存储属性有个关键特性:在类实例分配内存后、任何init方法执行前,就会完成默认值的初始化。对你的Pricing类来说:

@objcMembers class Pricing: Mappable {
    var price: Money = Money.zero() // 实例分配后立即初始化
    var vat: Percent = Percent.zero()
    ...
}

当ObjC的initWithJSON:被调用时,price和vat已经持有了Money.zero()和Percent.zero()的实例,引用计数为1。

接下来,initWithJSON:内部通过[self setValue:value forKey:key]赋值新的反序列化实例时,ObjC Runtime会执行以下步骤:

  • 调用属性的getter(比如-[Pricing price],@objcMembers把Swift属性暴露给了ObjC)获取当前值(也就是默认的zero实例)
  • 对新值执行retain
  • 对旧值执行release

2. Swift-ObjC桥接带来的额外Retain

这里的坑点在于:Swift属性的getter被ObjC调用时,桥接层会自动给返回的对象加一次retain。这是因为ObjC的方法返回对象默认遵循“返回值拥有+1引用计数”的内存管理规则,Swift到ObjC的桥接会自动适配这个规则。

所以默认的zero实例的引用计数变化是:

  1. 初始化时:retain count = 1
  2. ObjC getter调用:retain count +=1 → 2
  3. KVC赋值时release旧值:retain count -=1 → 1

这就导致这个默认实例的引用计数永远无法归零,ARC无法释放它,最终造成内存泄漏。

3. 为什么Lazy属性能解决问题

Lazy属性的初始化逻辑是第一次被访问时才执行。在ObjC的initWithJSON:执行setValue:forKey:时,price和vat还没被访问过,所以默认的zero实例根本没有被创建。反序列化直接赋值了新的实例,自然不存在旧实例被泄漏的问题。

补充:为什么其他场景下Money.zero()无泄漏

在纯Swift代码中使用Money.zero()时,不会经过ObjC Runtime的getter调用,也就不会触发桥接层的额外retain。只有当ObjC代码(比如这里的Mappable分类)通过KVC访问Swift属性时,才会出现这个特殊的引用计数问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 07:26:15