16.ARC
Swift 使用自动引用计数(ARC)机制来跟踪和管理应用程序的内存。通常情况下,Swift 内存管理机制会一直起作用,我们无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。
引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
1.自动引用计数的工作机制
- 当创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
- 当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。
- 为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。
- 将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。
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
实例的强引用。如下图所示:
当断开john
和unit4A
变量所持有的强引用时,引用计数并不会降为0
,实例也不会被 ARC 销毁。图解如下:
4.解决实例之间的循环强引用
Swift 提供了两种办法用来解决在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。
- 弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
- 对于生命周期中会变为nil的实例使用弱引用。相反地,对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。
- 和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用是永远有值的。因此,无主引用总是被定义为非可选类型。
示例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"
从上面的代码可以看到,这里Apartment
的tenant
属性被声明为弱引用,如下图所示:
当调用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;
【小结】:
- 两个属性的值都允许为nil,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决(如上面的Person和Apartment)。
- 一个属性的值允许为nil,而另一个属性的值不允许为nil,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决(如上面的
Customer
和CreditCard
)。 - 两个属性都必须有值,并且初始化完成后永远不会为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.解决闭包引起的循环强引用
在定义闭包时同时定义捕获列表作为闭包的一部分,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。
- 在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为
无主引用
。 - 相反的,在被捕获的引用可能会变为
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的更多相关文章
- 我的Objective-C系列文章
做iOS开发有一段时间了,也有自己上线的App产品,也在坚持着发表技术博客总结自己所学的东西.在写博客的时候虽然博文中不免有错别字,但每句话都是在斟酌之后所写的,每篇博客所粘贴的代码都是经过调试运行无 ...
- ios 面试题 0
1.__block和__weak修饰符的区别: 1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型. 2.__weak只能在ARC模式下使用,也只能修饰 ...
- Objective-C系列
我的Objective-C系列文章和坚持写博客的感想 做iOS开发有一段时间了,也有自己上线的App产品,也在坚持着发表技术博客总结自己所学的东西.在写博客的时候虽然博文中不免有错别字,但每句话都 ...
- Swift学习目录
本学习基于苹果官方Swift学习材料,保留了原版90%左右的内容(一些项目开发中基本不用的知识点没有整理),并根据理解进行整理.如对原版感兴趣,可以直接单击链接阅读和学习. 第一部分 基础篇 1.基本 ...
- LaTeX:图形的填充(生成阴影图形)
将内网和外网看到的综合整理. 韦恩图Venn \documentclass{standalone} \usepackage{tikz} %导出为图片需要安装imagemagick %https://t ...
- Canvas链式操作
Canvas 链式操作 canvas有个非常麻烦的地方就是不支持链式操作,导致书写极其繁琐,刚刚学习了canvas的链式操作. 下面是代码 改进之后的写法,犀利得多啊! 1.canvas = ...
- ARC内存管理机制详解
ARC在OC里面个人感觉又是一个高大上的牛词,在前面Objective-C中的内存管理部分提到了ARC内存管理机制,ARC是Automatic Reference Counting---自动引用计数. ...
- iOS开发ARC内存管理技术要点
本文来源于我个人的ARC学习笔记,旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇 ...
- block使用小结、在arc中使用block、如何防止循环引用
引言 使用block已经有一段时间了,感觉自己了解的还行,但是几天前看到CocoaChina上一个关于block的小测试主题: [小测试]你真的知道blocks在Objective-C中是怎么工作的吗 ...
随机推荐
- ORACLE学习笔记 索引和约束
/*** 约束 ***/ * 如果某个约束只作用于单独的字段,即可以在字段级定义约束,也可以在表级定义约 束,但如果某个约束作用于多个字段, 必须在表级定义约束* 在定义约束时可以通过CONSTRA ...
- 折腾了半天,终于搞定了apache的rewrite功能
基本步骤和网上其它文章说得基本一样.只是在具体操作的时候或多或少存在些问题 一 打开 apache 的配置文件 httpd.conf . 二 将#loadmodule rewrite_module m ...
- php 采用fpdf乱码问题
步骤1.首先下载fpdf http://www.fpdf.org/en/download.php(本人用的是1.7版本) 步骤2.下载中文包 http://www.fpdf.org/download/ ...
- 7、NFC技术:让Android自动运行程序
用于描述NDEF格式数据的两个重要的类 NdefMessage:描述NDEF格式的信息 NdefRecord:描述NDEF信息的一个信息段 NdefMessage和NdefRecord是Androi ...
- MIME邮件格式
转自:http://kptu.iteye.com/blog/890180 排版做了调整. Q.什么是MIME?什么是MIME邮件? A. MIME, 全称为"Multipurpose Int ...
- 使用Ajax在javascript中调用后台C#函数
使用Ajax在javascript中调用后台C#函数 最近一段时间在紧跟一个网站的项目,数据库中用户表的UserName要求是唯一的,所以当用户选定一个用户名进行注册时要首先检查该用户名是否已被占用, ...
- C语言实现strlen
strlen: #ifndef STRLEN_H #define STRLEN_H #include <stdio.h> // 参考微软的写法 int cat_strlen(const c ...
- NET知识大纲
第一部分 C#编程基础 1.(30)变量.运算符(+.-.*./.++.--.括号.==.!=.>.<.>=.<=.&&.||).流程控制(if.while.f ...
- django 搭建自己的博客
原文链接:http://www.errdev.com/post/4/ 每一个爱折腾的程序员都有自己的博客,好吧,虽然我不太喜欢写博客,但是这样骚包的想法却不断涌现.博客园虽好,可以没有完全的掌控感,搭 ...
- 王家林的“云计算分布式大数据Hadoop实战高手之路---从零开始”的第十一讲Hadoop图文训练课程:MapReduce的原理机制和流程图剖析
这一讲我们主要剖析MapReduce的原理机制和流程. “云计算分布式大数据Hadoop实战高手之路”之完整发布目录 云计算分布式大数据实战技术Hadoop交流群:312494188,每天都会在群中发 ...