Swift 使用自动引用计数(ARC)机制来跟踪和管理应用程序的内存。通常情况下,Swift 内存管理机制会一直起作用,我们无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。

  引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。

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

  1. 当创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
  2. 当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。
  3. 为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。
  4. 将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。

2.自动引用计数实践

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?;
var reference2: Person?; //创建实例
reference1 = Person(name: "GofLee"); //这里才会创建并打印:"GofLee is being initialized\n"
reference2 = reference1; reference1 = nil;
reference2 = nil; //这个时候才会执行析构过程并打印:"GofLee is being deinitialized\n"

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

  • 如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,这就是所谓的循环强引用。

  下面的例子演示了循环强引用是怎么产生的:

class Person
{
let name: String;
var apartment: Apartment?; init(name: String)
{
self.name = name;
} deinit
{
print("\(name) is being deinitialized");
}
} class Apartment
{
let unit: String;
var tenant: Person?; init(unit: String)
{
self.unit = unit;
} deinit
{
print("Apartment \(unit) is being deinitialized");
}
} var john: Person? = Person(name: "John Appleseed");
var unit4A: Apartment? = Apartment(unit: "4A"); john!.apartment = unit4A;
unit4A!.tenant = john; john = nil;
unit4A = nil;

  从上面的代码可以看到,Person实例拥有一个指向Apartment实例的强引用,而Apartment实例也拥有一个指向Person实例的强引用。如下图所示:

  当断开johnunit4A变量所持有的强引用时,引用计数并不会降为0,实例也不会被 ARC 销毁。图解如下:

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

  Swift 提供了两种办法用来解决在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference

  1. 弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
  2. 对于生命周期中会变为nil的实例使用弱引用。相反地,对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。
  3. 和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用是永远有值的。因此,无主引用总是被定义为非可选类型

  示例1:弱引用解决上面循环强引用的问题

class Person
{
let name: String;
var apartment: Apartment?;
//weak var apartment: Apartment?; init(name: String)
{
self.name = name;
} deinit
{
print("\(name) is being deinitialized");
}
} class Apartment
{
let unit: String;
weak var tenant: Person?; //注意此处的weak init(unit: String)
{
self.unit = unit;
} deinit
{
print("Apartment \(unit) is being deinitialized");
}
} var john: Person? = Person(name: "John Appleseed");
var unit4A: Apartment? = Apartment(unit: "4A"); john!.apartment = unit4A;
unit4A!.tenant = john; john = nil; //打印"John Appleseed is being deinitialized\n"
unit4A = nil; //打印"Apartment 4A is being deinitialized\n"

  从上面的代码可以看到,这里Apartmenttenant属性被声明为弱引用,如下图所示:

  当调用john = nil时,由于没有指向Person实例的强引用,所以实例会被销毁,图解如下:

  此时唯一剩下的指向Apartment实例的强引用来自于变量unit4A。如果断开这个强引用,再也没有指向Apartment实例的强引用了:

  示例2:无主引用

//示例业务模型说明:一个客户可能有或者没有信用卡,但是一张信用卡总是关联着一个客户。为了表示这种关系,Customer类有一个可选类型的card属性,但是CreditCard类有一个非可选类型的customer属性。
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"); john!.card = CreditCard(number: , customer: john!);
john = nil;

  【小结】:

  1. 两个属性的值都允许为nil,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决(如上面的Person和Apartment)。
  2. 一个属性的值允许为nil,而另一个属性的值不允许为nil,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决(如上面的CustomerCreditCard)。
  3. 两个属性都必须有值,并且初始化完成后永远不会为nil。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性。示例如下:
//示例业务模型说明:每个国家必须有城市,每个城市必须属于一个国家。为了实现这种关系,Country类拥有一个capitalCity属性,而City类有一个country属性
class Country
{
let name: String;
var capitalCity: City!; //隐式解析可选类型,默认值为nil 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: "China", capitalName: "Beijing");
print("\(country.name)'s capital city is called \(country.capitalCity.name)");

5.闭包引起的循环强引用

  先看一个示例:

class HTMLElement
{
let name: String; //HTML元素名称
let text: String?; //HTML元素显示的文本 lazy var asHTML: Void -> 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;

  当调用paragraph = nil时,我们可以看到HTMLElement的deinit方法并没有调用。说明paragraph没有释放,现在我们用图来分析一下paragraph和默认闭包之间的引用关系:

6.解决闭包引起的循环强引用

  在定义闭包时同时定义捕获列表作为闭包的一部分,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。

  1. 在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用
  2. 相反的,在被捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil。这使我们可以在闭包体内检查它们是否存在。

  示例:解决上面闭包的循环强引用

class HTMLElement
{
let name: String;
let text: String?; lazy var asHTML: Void -> String = {
[unowned self] (void) -> String 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");
} }

16.ARC的更多相关文章

  1. 我的Objective-C系列文章

    做iOS开发有一段时间了,也有自己上线的App产品,也在坚持着发表技术博客总结自己所学的东西.在写博客的时候虽然博文中不免有错别字,但每句话都是在斟酌之后所写的,每篇博客所粘贴的代码都是经过调试运行无 ...

  2. ios 面试题 0

    1.__block和__weak修饰符的区别:  1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型.  2.__weak只能在ARC模式下使用,也只能修饰 ...

  3. Objective-C系列

    我的Objective-C系列文章和坚持写博客的感想   做iOS开发有一段时间了,也有自己上线的App产品,也在坚持着发表技术博客总结自己所学的东西.在写博客的时候虽然博文中不免有错别字,但每句话都 ...

  4. Swift学习目录

    本学习基于苹果官方Swift学习材料,保留了原版90%左右的内容(一些项目开发中基本不用的知识点没有整理),并根据理解进行整理.如对原版感兴趣,可以直接单击链接阅读和学习. 第一部分 基础篇 1.基本 ...

  5. LaTeX:图形的填充(生成阴影图形)

    将内网和外网看到的综合整理. 韦恩图Venn \documentclass{standalone} \usepackage{tikz} %导出为图片需要安装imagemagick %https://t ...

  6. Canvas链式操作

        Canvas 链式操作 canvas有个非常麻烦的地方就是不支持链式操作,导致书写极其繁琐,刚刚学习了canvas的链式操作. 下面是代码 改进之后的写法,犀利得多啊! 1.canvas = ...

  7. ARC内存管理机制详解

    ARC在OC里面个人感觉又是一个高大上的牛词,在前面Objective-C中的内存管理部分提到了ARC内存管理机制,ARC是Automatic Reference Counting---自动引用计数. ...

  8. iOS开发ARC内存管理技术要点

    本文来源于我个人的ARC学习笔记,旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇 ...

  9. block使用小结、在arc中使用block、如何防止循环引用

    引言 使用block已经有一段时间了,感觉自己了解的还行,但是几天前看到CocoaChina上一个关于block的小测试主题: [小测试]你真的知道blocks在Objective-C中是怎么工作的吗 ...

随机推荐

  1. js获取当前url信息

    window.location 属性 描述 hash 设置或获取 href 属性中在井号"#"后面的分段. host 设置或获取 location 或 URL 的 hostname ...

  2. trunc的使用

    1.日期比较时精确到日,可以使用 TRUNC(sysdate,'dd')函数.函数支持格式有:yyyy MM  dd  hh Mi可以用 select TRUNC(sysdate,'yyyy') fr ...

  3. Android Volley源码分析

    今天来顺手分析一下谷歌的volley http通信框架.首先从github上 下载volley的源码, 然后新建你自己的工程以后 选择import module 然后选择volley. 最后还需要更改 ...

  4. java-No exception of type ConfigurationException can be thrown; an exception type must be a subclass of Throwable

    功能:读配置文件 java菜鸟:导入工程在报名处就开始报错,第一次遇到 import org.apache.commons.lang3.StringUtils; import org.apache.c ...

  5. MySQL与Oracle 差异比较之七其它

    其它 编号 类别 ORACLE MYSQL 注释 1 内连接的更改 1.select a.*, b.*, c.*, d.*  from a, b, c, d where a.id = b.id   a ...

  6. 【转】PHP字符转义相关函数小结

    文章中有不正确的或者说辞不清的地方,麻烦大家指出了--- 与PHP字符串转义相关的配置和函数如下: 1.magic_quotes_runtime 2.magic_quotes_gpc 3.addsla ...

  7. Android实例] android获取web服务器端session并验证登陆

    传统网页实现用户登陆一般采用session或cookie记录用户基本信息又或者两者结合起来使用.android也可以采用session实现用户登陆验证并记录用户登陆状态时的基本信息,session是在 ...

  8. 什么是SPF?如何设置企业邮箱的SPF呢?(TXT记录)

    什么是SPF?   (Sender Policy Framework) 的缩写,一种以IP地址认证电子邮件发件人身份的技术,是非常高效的垃圾邮件解决方案. 接收邮件方会首先检查域名的SPF记录,来确定 ...

  9. Win7安装IDL8.0以及破解

    参考:http://bbs.06climate.com/forum.php?mod=viewthread&tid=5230 1.下载IDL8.1和证书 下载地址:http://yunpan.c ...

  10. js保留小数点后N位的方法介绍

    js保留小数点后N位的方法介绍 利用toFixed函数 代码如下 复制代码 <script language="javascript"> document.write( ...