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

为何停止引用CALayer后它仍被保留?

CALayer 延迟释放的原因解析

现象说明

将CALayer添加为子图层后,它不会在取消引用时立即释放,而是会被保留到当前执行块结束;但父UIView在取消引用后会按预期立即释放。

调试用代码

class DebugCALayer: CALayer {
  let id: String
  init(_ id: String) {
    self.id = id
    print("DebugCALayer init \(id)")
    super.init()
  }

  required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }

  deinit { print("DebugCALayer deinit \(id)") }
}

class DebugUIView: UIView {
  private let id: String
  init(_ id: String) {
    print("DebugUIView init \(id)")
    self.id = id
    super.init(frame: .zero)
    layer.addSublayer(DebugCALayer(id))
  }

  required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") }

  deinit { print("DebugUIView deinit \(id)") }
}

基础测试与结果

测试代码

final class CALayerReleaseTests: XCTestCase {
  override func setUp() async throws {
    print("setup")
  }

  override func tearDown() {
    print("tear down")
  }

  func testBaseline() throws {
    for i in 0..<2 {
      _ = DebugUIView("\(i)")
    }
  }
}

日志输出

setup
init DebugUIView 0
init DebugCALayer 0
deinit DebugUIView 0
init DebugUIView 1
init DebugCALayer 1
deinit DebugUIView 1
tear down
deinit DebugCALayer 1
deinit DebugCALayer 0

可以看到:UIView实例在取消引用后立即销毁,但CALayer要等到测试执行完毕(tear down之后)才被释放。

异步场景下的正常释放

如果在取消引用实例后,触发一次主线程异步任务,CALayer就能按预期立即释放:

修改后的测试代码

func testDispatch() throws {
  for i in 0..<2 {
    _ = DebugUIView("\(i)")
    let exp = expectation(description: "")
    DispatchQueue.main.async {
      exp.fulfill()
    }
    waitForExpectations(timeout: 1)
  }
}

日志输出

setup
init DebugUIView 0
init DebugCALayer 0
deinit DebugUIView 0
deinit DebugCALayer 0
init DebugUIView 1
init DebugCALayer 1
deinit DebugUIView 1
deinit DebugCALayer 1
tear down

核心原因

这一现象由Core Animation的事务机制导致:

  • Core Animation会将所有图层操作(比如addSublayer)打包到一个事务中,默认在主线程RunLoop的下一个周期结束时提交事务。
  • 事务提交前,Core Animation内部会临时持有这些图层的引用,防止图层在事务处理完成前被释放,这就导致CALayer的释放被延迟到当前RunLoop循环结束(即当前执行块完成后)。
  • UIView的生命周期由ARC直接管理,没有被Core Animation的事务机制额外持有,因此能在取消引用后立即释放。
  • 使用DispatchQueue.main.async时,会触发一次RunLoop循环,事务被提交,Core Animation的临时引用被释放,CALayer也就随之被ARC销毁。

注:在Playground中运行相同代码会得到不同结果,因为Playground的RunLoop运行机制和正式测试环境存在差异。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 09:55:58