导读:本期聚焦于画家创作的《Swift内存管理机制是怎么工作的?ARC原理与weak、unowned正确使用场景解析》,敬请观看详情。为什么Swift应用里明明写完了对象却迟迟不释放?这往往和自动引用计数机制有关。ARC在编译期插入retain与release调用,通过跟踪强引用数量决定对象生命周期。当两个实例互相强引用时,计数无法归零就会产生循环引用,此时必须借助弱引用或无主引用来打破闭环。weak会产生可选类型并在原对象释放后自动置nil,unowned则假定引用始终有效,访问已释放对象会触发崩溃。理解二者差异、在闭包捕获列表里声明捕获方式,是避免内存泄漏的关键。

Swift并没有传统意义上的垃圾回收器,而是采用编译器层面的自动引用计数,也就是ARC,来接管堆上对象的内存生命周期。每一个类实例在创建时都会带有一个内部引用计数器,当有新的强引用指向它,计数加一;当某个强引用消失,计数减一。一旦计数变为零,系统立刻回收这块内存,同时调用析构器。这套机制对开发者基本透明,不需要手动调用retain或者release,但理解它的计数变化规律,是写出稳定程序的前提。

Swift内存管理机制是怎么工作的?ARC原理与weak、unowned正确使用场景解析

ARC底层工作原理与引用计数规则

在编译阶段,Swift编译器会在每个可能引起引用变化的时机自动插入维护计数的逻辑。比如把一个对象赋值给变量、当作参数传入函数、或者存进数组,都会让强引用计数增加;而当变量离开作用域、被重新赋值、或者容器移除元素,计数相应减少。值得注意的是,只有类类型受ARC管理,结构体、枚举等值类型由于默认在栈上拷贝,不涉及引用计数。这也是为什么Swift推荐优先使用值类型来减少隐式共享带来的复杂度。

我们可以借助一个最简例子观察引用关系。下面代码中,person变量第一次指向实例时计数为一,第二个变量指向同一实例后计数变为二,函数结束后其中一个局部变量销毁,计数回落,但只有两个引用都断开,实例才会真正释放并打印析构信息。

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("(name) 被初始化")
    }
    deinit {
        print("(name) 被释放")
    }
}

func demo() {
    let p1 = Person(name: "张三")
    let p2 = p1
    // 此时 Person 实例强引用计数为 2
}
demo()
// 函数退出后 p1、p2 均销毁,计数归零,触发 deinit

这种基于计数的方式性能很高,没有全局扫描开销,但也带来一个本质缺陷:只要引用关系形成闭环,计数就永远无法归零。因此开发者必须主动识别闭环并在合适位置插入非强引用,这正是后续weak与unowned存在的意义。

循环引用的常见场景与问题代码

最常见的循环引用发生在两个类实例互相持有对方强引用的时候。比如一个客户对象保存了对应的银行卡对象,而银行卡又反过来强引用客户,离开作用域后两者计数都停留在一,内存泄漏。另一种更隐蔽的场景是闭包捕获:闭包默认对其内部使用的外部变量进行强捕获,如果闭包又被该变量自己所持有的属性引用,就形成了自我闭环。很多网络回调、动画 completion 里的泄漏都源于此。

下面展示一个典型的双向强引用问题。Customer持有Card,Card的owner又指回Customer,demo函数结束后两个对象都没有释放,控制台不会打印任何析构消息,说明泄漏已经发生。

class Customer {
    let id: Int
    var card: Card?
    init(id: Int) {
        self.id = id
    }
    deinit { print("Customer (id) 释放") }
}

class Card {
    let number: String
    var owner: Customer?
    init(number: String) {
        self.number = number
    }
    deinit { print("Card (number) 释放") }
}

func demo() {
    let c = Customer(id: 1)
    let card = Card(number: "6222")
    c.card = card
    card.owner = c
}
demo()
// 此处无释放日志,发生循环引用

闭包造成的循环引用也极其普遍。例如某个控制器有一个完成处理的闭包属性,而闭包内部又使用了self,同时闭包被控制器自身存储,就会互相牵制。解决思路同样是打破强闭环,只不过语法上要在闭包参数列表前使用捕获列表来声明弱或无主捕获。

weak与unowned的正确选择和使用方式

weak声明的引用不会增加实例的引用计数,而且因为被指向的对象可能随时释放,所以weak变量必须是可选类型。当目标对象销毁后,Swift运行时会自动把weak引用置为nil,因此访问它是安全的,只需做解包判断。它适合两个对象生命周期相对独立、可能一方先消失的场景,比如视图对控制器的弱引用、委托模式中的delegate。

unowned同样不增加计数,但它假设引用对象在自己访问时一定还活着,因此类型是非可选的。如果对象已经释放而你又访问了unowned属性,程序会直接崩溃。它适用于两个实例生命周期严格绑定、同时生灭的情况,例如信用卡必定有持卡人且持卡人未销户前卡不离场。下面用weak修复前面的相互引用问题,仅把Card的owner改为弱引用即可打破闭环。

class Customer {
    let id: Int
    var card: Card?
    init(id: Int) { self.id = id }
    deinit { print("Customer (id) 释放") }
}

class Card {
    let number: String
    weak var owner: Customer?
    init(number: String) { self.number = number }
    deinit { print("Card (number) 释放") }
}

func demo() {
    let c = Customer(id: 1)
    let card = Card(number: "6222")
    c.card = card
    card.owner = c
}
demo()
// 现在会正常打印两个释放日志

在闭包中则要通过捕获列表区分使用。若闭包可能在self销毁后才执行,用[weak self]并在内部解包;若闭包和self同生共死、必定在self有效期内跑完,可用[unowned self]省去解包。错误地把本应weak的地方写成unowned,是线上崩溃的高发原因,因此不确定生命周期时一律优先weak更稳妥。

实际工程中的排查与编码规范

在真实项目里,可以借助Xcode的Memory Graph Debugger抓取对象引用图,直观看到哪两个节点形成了环。配合Instrument的Leaks模板,能定位到未释放实例的创建堆栈。日常编码应当建立约定:Delegate、DataSource一律weak;闭包捕获self先思考是否需弱引用;在模型层双向关联至少有一侧是非强引用。

此外,还需注意闭包内嵌套闭包时捕获列表只作用于当前层,内层若也要用self必须再次声明。结构体嵌套类、或值类型持有引用类型时虽不形成闭环,但也可能意外延长生命周期,应审视是否必要。把这些规则沉淀为代码评审检查项,能大幅降低Swift项目中的内存隐患。

class ViewController {
    var completion: (() -> Void)?
    func load() {
        completion = { [weak self] in
            guard let self = self else { return }
            self.render()
        }
    }
    func render() { print("渲染界面") }
}

综上,Swift的ARC以编译期计数实现高效内存回收,而weak与unowned是开发者手中最直接的破环工具。理清对象从属关系、在捕获列表中谨慎声明引用方式,才能既享受自动管理便利,又避开隐蔽的泄漏与崩溃。

SwiftARCweak_unowned修改时间:2026-08-18 07:38:31

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。