Swift-ARC(Automatic Reference Counting)

Swift管理内存方式和OC极其相似,都是采用自动引用计数 ARC机制来跟踪和管理。
ACR 会在实例不再使用的情况下自己释放其占用的内存空间,所以通常情况下不需要我们自己来手动释放。
引用计数应用于类的实例
(注意:结构体枚举值类型So结构体和枚举不是通用引用计数来管理内存的。)

本章结合了Swift 官方文档。挑一些主要点进行描述Swift的引用计数机制
章节如下:

  1. 自动引用计数工作机制
  2. 自动引用计数实践
  3. 类实例之间的循环强引用
  4. 解决实例之间的循环强引用
  5. 闭包(即OC-Block)引起的循环强引用
  6. 解决闭包引起的循环强引用

1.自动引用计数工作机制

简单说就是每当我们创建一个实例的时候ARC都会开辟一块内存空间.供这个实例使用,内存中会包含这个实例及这个实例的类型信息。无论这个实例是被赋值为属性,变量或常量。直到我们不再使用这个实例,ARC会自动释放此实例,并将这块内存空间挪它使用。其机制和OC一样,若访问被释放的实例则Crash。只要这个实例仍存在,强引用就伴随其一直存在,就可访问。

2.自动引用计数实践

若上面这段富有理论性的话大家不太明白,请注意看这个栗子

class Person {
    let name: String
    init(name: String) {//init 构造函数。"人"这个实例对象,具有一个自己的属性"名字",人被初始化了,其名字也同时被初始化。
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {//人挂了,其属性肯定也挂了(”deinit析构函数“ 等同OC dealloc )
        print("\(name) is being deinitialized")
    }
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
/*我们有三个为Person类型的变量. 注意问号"?". 代表可选类型,也就是,可为空。
所以,这时候还没有强引用,这三个变量的初始值都为nil.还没有被创建.*/
/*
接下来我们创建Person类实例对象,给其中一个变量赋值.注意:这时候reference1到Person类实例之间建立强引用,所以ARC保证Person一直存在内存中。可以看到打印。
*/
reference1 = Person(name: "John Appleseed")
// 打印 "John Appleseed is being initialized
/*
此时,我们将Person也赋值给其它两个变量,这时候Person强引用加二
*/
reference2 = reference1
reference3 = reference1
/*
此时我们再将两个变量赋值为nil.则Person就少去两个强引用。注意:此时Person还是存在的哈,因为还存在一个强引用,还有一个变量是它。
*/
reference1 = nil
reference2 = nil
/*
好了,我们可手动销毁最后一个强引用。这时候ARC会释放掉Person,可看打印。
*/
reference3 = nil
// 打印 “John Appleseed is being deinitialized

3.类实例之间的循环强引用

栗子
有两个类,Person Apartment.
Person具有实例属性为apartment,apartment是可选的,所以初始值为nil,所以这时候Preson 并不会产生强引用加一。这块儿注意,这是一会演示产生循环强引用的关键.
Apartment.同Person一样也有个初始化为nil的实例属性Person

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}
 
class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}

现在我在另一个类中写两个实例属性,分别为PersonApartment的初始值为nil的可选属性john,unit4.

var john: Person?
var unit4A: Apartment?

现在创建特定的PersonApartment实例,并将其赋值给john和unit4A变量
注意了:这时候johnPerson之间建立了强引用.unit4AApartment之间也建立了强引用.

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

现在我将unit4A赋值给Johnapartment,将john赋值给unit4Atenant
注意了:关键就在这儿了,这时候johnunit4A 的内部实例属性是有强引用的.所以在下面我将johnunit4A,手动制空,其会被ARC释放吗?析构函数deinit会被调用吗? 答案是肯定不会,这就造成了类实例之间的循环强引用.
为什么?虽然我将johnjohn手动制空,释放了一个强引用,但是其内部是不是还有个实力属性拥有强引用哇?。这就造成了内存泄漏,不得了了。

john!.apartment = unit4A
unit4A!.tenant = john
john = nil
unit4A = nil

那怎么解决呢? 请往下看

4.解决实例之间的循环强引用

Swift 提供了两种方法来解决使用类的属性时,所遇到的循环强引用问题.
弱引用weak reference, 无主引用unowned reference
当其他的实例有更短的生命周期使用弱引用,反之使用无主引用.

  • ****弱引用****
    关键字weak
    当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时。
    修饰可为nil的,可选类型变量。
class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}
class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil
// 打印 “John Appleseed is being deinitialized
unit4A = nil
// 打印 “Apartment 4A is being deinitialized


john为nil时,John再不存在强引用了,彻底挂了

unit4A为nil,unit4A也挂了

看懂没? 没错,就是weak 关键字修饰Apartment类的实例属性Person
ARC会在实例的引用被销毁的时候自动将其被weak修饰的实例属性设置为nil,所以当john挂了,它也就彻底挂了,弱引用不会对其引用的实例进行强引用!
注意ARC设置弱引用没nil时,它的属性观察器是不会被触发的.
PS:ApartmentPerson的例子很好的演示了,两个类都拥有一个可为nil的可选属性。并且在另一个类中,这两个类的实例属性相互持有。这就产生的潜在的循环强引用,这时候我们用弱引用weak再适合不过了.

  • ****无主引用****
    在属性或变量前面加一个关键字unowned表示这是一个无主引用。
    无主引用和弱引用一样不会牢牢保持一个实例的引用。
    不同就是ARC不会在实例销毁后将其设置为nil。因为其是非可选 的类型的变量,所以不允许被设置为nil
    直接看栗子
class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
    let number: UInt64
   unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: John!)
john = nil
// 打印 “John Appleseed is being deinitialized”
// 打印 ”Card #1234567890123456 is being deinitialized

两个类,Customer信用卡客户,和CreditCard信用卡
信用卡客户:有个名字是肯定的let name: String,信用卡不一定有var card: CreditCard?
信用卡:卡号和卡持有者,是肯定有的,所以都用let修饰.注意正是因为这个用无主引用关键字unowned修饰的实例对象Customer卡片持有者,所以之后相互引用才不会导致循环强引用.然后我们必须通过卡号和持卡者创建信用卡.
看图示

实例对象为Customer的变量john创建时,Customer实例持有对CreditCard实例的强引用。 CreditCard实例创建的时候,对Customer是无主引用的

所以,当我们手动将John制空,会断开John持有的强引用。

Customer,和CreditCard,Customer一个属性值可为nil,一个属性值不可为nil,然后在里一个类中,这两个类相互引用,产生强引用。这种情况无主引用最合适不过了。

  • ****无主引用以及隐式解析可选属性****
    栗子
class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}
class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// 打印 “Canada's capital city is called Ottawa

感叹号(City!)的方式,将Country的capitalCity属性声明为隐式解析可选类型的属性。这意味着像其他可选类型一样,capitalCity属性的默认值为nil,但是不需要展开它的值就能访问它。
capitalCity属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。

5. 闭包引起的循环引用

闭包中会如何产生循环强引用?
闭包中引用了self,比如self.someProperty,self.someMethod
为什么?
这是因为闭包和类相似都是属于引用类型。当我们把闭包赋值给类的某个属性,其实是把这个引用赋值给这个属性。和上面类实例之前的循环强引用一样,会产生两个强引用一直存在。是不是不太好理解?来我们看栗子

不整没用的,直接看图

在上图例子相信大家就会明白,什么是闭包引起的循环强引用,以及怎样解决闭包引起的循环强引用。
有的同学可能不太理解,我靠,这是什么写法?这是Swift的捕获列表。
捕获列表:捕获列表中的每一项都由一对元素组成,一个元素是weak或unowned关键字,另一个元素是类实例的引用(例如self)或初始化过的变量(如delegate = self.delegate!)


****总结:****
ARC自动帮我们管理内存,也是采用垃圾回收机制。
切记:
两个类的实例相互保持对方的强引用时会产生循环强引用。闭包会产生循环强引用,尤其Rx,RAC的回调实现中很敏感。
开发中通常我们不会手动将实例对象制空。若制空,再访问那么,你懂得。
主要说下循环强引用:
循环强引用实质上说白了就是,这个类最终没有释放最终没走deinit方法。为什么?有可能是你的类与实力之间产生的循环强引用,有可能是你的闭包引起的循环强引用。
怎么解决?大家可以参考上面的例子。在实际开发中呢,我们最常见的就是闭包引起的循环引用。这里总结下解决办法

//捕获列表:
        object.block = { [weak self] in // 弱引用
           self.xxx
        }
        object.block = { [unowned self] in //无主引用
           self.xxx
        }
//若引用修饰self:
        weak var ws = self
        object.block = { [unowned self] in
           ws.xxx
        }

weak和unowned区别:
unowned 更像OC的 unsafe_unretained ,而 weak 就是以前的 weak 。
unowned设置以后即使它原来引用的内容已经被释放了,它仍然会保持对被已经释放了的对象的一个 "无效的" 引用,它不能是 Optional 值,也不会被指向 nil 。如果你尝试调用这个引用的方法或者访问成员属性的话,程序就会崩溃。
weak 在引用的内容被释放后,标记为 weak 的成员将会自动地变成 nil (所以被标记为 weak 的变量一定需要是 Optional 值)。
关于两者使用的选择,存在被释放的可能,那就选择用 weak 。开发中用weak 会多一些.

题外话:最近忙了一阵儿,回头续写,写了一半的文章,发现太不干了。所以以后的文章会尽量写一写干的东西。写这么大一篇文章,我觉得直接看总结这部分就好了。😭

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容