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实例的强引用。如下图所示:

  当断开john和unit4A变量所持有的强引用时,引用计数并不会降为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"

  从上面的代码可以看到,这里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;

  【小结】:

  1. 两个属性的值都允许为nil,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决(如上面的Person和Apartment)。
  2. 一个属性的值允许为nil,而另一个属性的值不允许为nil,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决(如上面的Customer和CreditCard)。
  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. Android随笔--使用ViewPager实现简单地图片的左右滑动切换

    Android中图片的左右切换随处可见,今天我也试着查阅资料试着做了一下,挺简单的一个小Demo,却也发现了一些问题,话不多说,上代码~: 使用了3个xml文件作为ViewPager的滑动page,布 ...

  2. zzzzz

    Extension Method: Return another string if string is null or emptyJust a tiny little extension metho ...

  3. Raspberry Pi3 ~ Eclipse中添加wiringPi 库函数

    这篇是在博客园原创 转载注明出处啊 以前用单片机.STM32之类的时候都是在一个集成的开发环境下进行的 比如Keil.IAR等 那么linux下编程,eclipse是个不错的选择 关于树莓派的GPIO ...

  4. python27+django创建app

    python manage.py startapp polls创建一个叫polls的app 编辑文件 polls/models.py : 1 from django.db import models ...

  5. linux 条件变量

    互斥量就是一把锁,在访问数据时能保证同一时间内只有一个线程访问数据,在访问完以后再释放互斥量上的锁. 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条 ...

  6. js运动 模仿淘宝幻灯

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  7. 第三百五十一天 how can I 坚持

    是应该喜欢还是厌烦这种状态,犹豫不定,毫无目标. 人不贪,谁信,我嘴上说我不贪,可是内心已经把我出卖了,要不怎么股票会被套呢. 别人贪婪时我恐惧,别人恐惧时我贪婪,我成了什么,别人贪婪时,我狂妄,别人 ...

  8. Innodb中的事务隔离级别和锁的关系(转)

    原文:http://tech.meituan.com/innodb-lock.html 前言: 我们都知道事务的几种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式.同时数据库 ...

  9. 【多线程】Java并发编程:并发容器之CopyOnWriteArrayList(转载)

    原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容 ...

  10. POJ 1410 Intersection(判断线段交和点在矩形内)

    Intersection Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 9996   Accepted: 2632 Desc ...