Swift基础 自动引用计数和析构器

本文介绍了Swift语言中自动引用计数(ARC)的工作原理,包括如何通过引用计数判断对象是否仍在使用,如何解决类之间的循环引用问题,以及如何处理闭包引起的循环引用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当每次创建新实例时,ARC会分配一块内存,用于储存实例信息。当实例不再使用时,ARC便会释放实例所占用的内存。

引用计数

那怎么判断实例是否还在使用?在Swift中,ARC会跟踪和计算实例正在被多少个变量或常量所引用,只要还有一个变量或常量保持实例的“强引用”,那么就说实例还在使用中,不能释放其内存。

一旦计算实例不再使用,便会调用实例的析构函数“deinit”

class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
    deinit {
        print("person is deinit")
    }
}

var person: Person? = Person(name: "Johan")
var person1 = person
var person2 = person
person1 = nil   // 没有执行deinit
person2 = nil   // 没有执行deinit
person = nil    // 执行deinit

类之间循环引用问题

在Java的知道,判断实例是否还在使用并不使用引用计数的方式,因为引用计数的方式有一个致命的问题,就是类的循环引用,简单的来说,实例A持有实例B,实例B持有实例A,导致A,B实例都不能释放。

有这么一个例子,居民和公寓的关系。居民可以住在公寓上,也可以住在其他地方;公寓可以有居民,也可能是空房子,两者的关系都是可选关系,可有可没有。

class Person {
    var apartment: Apartment?
    deinit {
        print("person is deinit")
    }
}

class Apartment {
    var person: Person?
    deinit {
        print("apartment is deinit")
    }
}

var person: Person? = Person()
var apartment: Apartment? = Apartment()
person?.apartment = apartment
apartment?.person = person

apartment = nil // 并没有执行apartment实例的deinit方法
person = nil    // 并没有执行person实例的deinit方法

如例,虽然apartment为nil了,但是person中还持有apartment实例的引用,所以apartment实例不能被释放,person同理。要解决这种循环引用,使用的方式弱引用。(在Java也是这么解决的,所以很容易理解)

弱引用

在变量前加“weak”

class Person {
    var apartment: Apartment?
    deinit {
        print("person is deinit")
    }
}

class Apartment {
    // 弱引用,可选使用弱引用
    weak var person: Person?
    deinit {
        print("apartment is deinit")
    }
}

var person: Person? = Person()
var apartment: Apartment? = Apartment()
person?.apartment = apartment
apartment?.person = person

apartment = nil // 执行apartment实例的deinit方法
person = nil    // 执行person实例的deinit方法

例子中,apartment实例一旦为nil,person中的apartment引用也跟着释放,所以apartment实例被引用为0,所以可以释放apartment实例的内存。因为apartment都已经释放了,也就不再持有person的引用,一旦person为nil,同样被引用数为0,ARC便会释放person实例的内存。这样就解决了类之间的循环引用问题!

无主引用

在变量前加“unowned”

在Swift中,只有可选变量才能修改为nil,如果有一种情况,也属于类之间的循环引用,这就得用无主引用来解决了。

如这么一个例子,个人和银行卡之间的关系。个人可以没有银行卡,也可以有银行卡;但是银行卡必须属于一个人。两者的关系是,一个可有可无(可选),一个是必须有(非可选)。

class Custom {
    var card: BankCard?
    deinit {
        print("custom is deinit")
    }
}

class BankCard {
    // 无主引用,非可选使用无主引用
    unowned var custom: Custom
    init(custom: Custom) {
        self.custom = custom
    }
    deinit {
        print("bank card is deinit")
    }
}

var johan: Custom? = Custom()
var abcCard: BankCard? = BankCard(custom: johan!)
johan?.card = abcCard

johan = nil     // 执行johan实例的deinit方法
abcCard = nil   // 执行abcCard实例的deinit方法

解决闭包引起的循环引用问题

我们可以在闭包使用“self”关键字访问实例的属性值或者实例方法,这是由于闭包拥有“捕获”上下文的能力,而实例又持有闭包(闭包是引用类型,感觉有点像匿名内部类)的引用,这就引起单个类中循环引用的问题。

class HTMLElement {

    var name: String
    var text: String?

    lazy var asHtml: (Void) -> String = {
        if let text = self.text {
            return "<\(self.name)>\(self.text)<\(self.name)>"
        } else {
            return "<\(self.name)"
        }
    }

    init(name: String) {
        self.name = name
    }

    deinit {
        print("html element is deinit")
    }

}

var element: HTMLElement? = HTMLElement(name: "p")
element?.text = "hello"
element?.asHtml

element = nil   // 并没有执行element实例的deinit方法

由于闭包asHtml持有self实例,所以就算element置为nil,也不能释放内存。解决办法是定义捕获列表:

class HTMLElement {

    var name: String
    var text: String?

    lazy var asHtml: (Void) -> String = {
        // 定义捕获列表,并设置为无主引用(语法倒是很奇怪)
        [unowned self] (Void) -> String in        // (Void) -> String 可省略,为了看得清楚点
        if let text = self.text {
            return "<\(self.name)>\(self.text)<\(self.name)>"
        } else {
            return "<\(self.name)"
        }
    }

    init(name: String) {
        self.name = name
    }

    deinit {
        print("html element is deinit")
    }

}

var element: HTMLElement? = HTMLElement(name: "p")
element?.text = "hello"
element?.asHtml
element = nil       // 执行element实例的deinit方法

我们定义了捕获列表之后,element置为nil后,就可以正常被释放了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值