Swift4 基础部分: Automatic Reference Counting(自动引用计数)

本文是学习《The Swift Programming Language》整理的相关随笔,基本的语法不作介绍,主要介绍Swift中的一些特性或者与OC差异点。

系列文章:

Swift uses Automatic Reference Counting (ARC) to track and 
manage your app’s memory usage. In most cases, this means 
that memory management “just works” in Swift, and you do 
not need to think about memory management yourself. ARC 
automatically frees up the memory used by class instances 
when those instances are no longer needed.
  • Swift中是引用自动引用计数来处理app中的内存占用。也就是说你不需要去考虑app中的内存的管理。当类的实例不再被使用时,ARC会自动释放其占用的内存。
Reference counting only applies to instances of classes. 
Structures and enumerations are value types, not reference 
types, and are not stored and passed by reference.
  • 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。

ARC的工作机制(How ARC Works)

Every time you create a new instance of a class, ARC 
allocates a chunk of memory to store information about 
that instance. This memory holds information about the 
type of the instance, together with the values of any 
stored properties associated with that instance. 

Additionally, when an instance is no longer needed, ARC 
frees up the memory used by that instance so that the 
memory can be used for other purposes instead. This 
ensures that class instances do not take up space in 
memory when they are no longer needed.
  • 当你每次创建一个类的新的实例的时候,ARC 会分配一大块内存用来储存实例的信息。内存中会包含实例的类型信息,以及这个实例所有相关属性的值。此外,当实例不再被使用时,ARC会释放实例所占用的内存,并让释放的内存其他使用。这就确保了不再被使用的实例,不会一直占用内存空间。

ARC的实践(ARC in Action)

例子:

class Person {
    let name:String;
    init(name: String){
        self.name = name;
        print("\(name) is being initialized");
    }
    
    deinit {
        print("\(name) is being deinitialized");
    }
}

var reference1: Person? = Person(name:"xz");
var reference2: Person? = reference1;
var reference3: Person? = reference1;

reference1 = nil;
reference2 = nil;
reference3 = nil;

执行结果:

xz is being initialized
xz is being deinitialized

类实例之间的循环强引用(Strong Reference Cycles Between Class Instances)

循环引用在OC中也是常见的,直接看一个例子:


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"); }
}

var john: Person?;
var unit4A: Apartment?;

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

// 以下是核心引发的例子
john!.apartment = unit4A;
unit4A!.tenant = john;

john = nil;
unit4A = nil;

解决类实例之间的强循环引用(Resolving Strong Reference Cycles Between Class Instances)

Swift provides two ways to resolve strong reference cycles 
when you work with properties of class type: weak 
references and unowned references.
  • Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。

弱引用(Weak References)

A weak reference is a reference that does not keep a 
strong hold on the instance it refers to, and so does not 
stop ARC from disposing of the referenced instance. This 
behavior prevents the reference from becoming part of a 
strong reference cycle. You indicate a weak reference by 
placing the weak keyword before a property or variable 
declaration.
  • 弱引用不会牢牢保持住引用的实例,并且不会阻止 ARC 销毁被引用的实例。这种行为阻止了引用变为循环强引用。声明属性或者变量时,在前面加上weak关键字表明这是一个弱引用。

直接改写一下上述的例子:

class Apartment {
    let unit: String;
    init(unit: String) { self.unit = unit; }
    weak var tenant: Person?; // 注意此处
    deinit { print("Apartment \(unit) is being deinitialized"); }
}

执行结果:

John Appleseed is being deinitialized
Apartment 4A is being deinitialized

无主引用(Unowned References)

Like a weak reference, an unowned reference does not keep 
a strong hold on the instance it refers to. Unlike a weak 
reference, however, an unowned reference is used when the 
other instance has the same lifetime or a longer lifetime. 
You indicate an unowned reference by placing the unowned 
keyword before a property or variable declaration.
  • 和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用拥有同样或者更长的生命周期相对其他的实例。
An unowned reference is expected to always have a value. 
As a result, ARC never sets an unowned reference’s value 
to nil, which means that unowned references are defined 
using nonoptional types.
  • 无主引用一直都是有值的,ARC无法在实例被销毁后将无主引用设为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: Int;
    unowned let customer: Customer;
    init(number: Int, 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

无主引用以及隐式解析可选属性(Unowned References and Implicitly Unwrapped Optional Properties)

当相互引用的属性都不允许为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

闭包中的循环强引用(Strong Reference Cycles for Closures)

与OC中的block引起的循环强引用一致,直接看一下例子:

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world");
print(paragraph!.asHTML());
paragraph = nil;

执行结果:

<p>hello, world</p>

疑问:

  • 为什么deinit函数没有执行? 因为asHTML的闭包"捕获"了self,同时asHTML的属性持有了闭包的强引用。二者之间产生了循环强引用。

解决闭包引起的循环强引用(Resolving Strong Reference Cycles for Closures)

You resolve a strong reference cycle between a closure and a 
class instance by defining a capture list as part of the 
closure’s definition.
  • 为解决闭包和类实例之间的循环强引用,可以定义闭包时同时定义捕获列表作为闭包的一部分。

具体捕获列表的语法参考如下:

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}
Define a capture in a closure as an unowned reference when the 
closure and the instance it captures will always refer to each 
other, and will always be deallocated at the same time.

Conversely, define a capture as a weak reference when the 
captured reference may become nil at some point in the future.
  • 将闭包内的捕获定义为无主引用,当闭包和捕获的实例总是互相引用时并且总是同时销毁时。相反的,将闭包内的捕获定义为弱引用,当捕获引用有时可能会是nil时。

上述的例子中,显然无主引用可以解决上述闭包引起的循环强引用问题,具体代码如下:

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
         [unowned self] () ->String in // 可简写为[unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world");
print(paragraph!.asHTML());
paragraph = nil;

执行结果:

<p>hello, world</p>
p is being deinitialized
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容