第二十四章 自动引用计数
swift使用自动引用计数Automatic Reference Counting (ARC)来追踪和管理我们App里的内存使用情况,在大多情况下,在swift里ARC就意味着内存管理是“刚刚开始工作”,并且我们不需要考虑内存它是如何如何工作如何如何管理的,ARC会通过类实例来自动释放内存,当这些类实例不在被需要的时候。
然而,在某些情况下,ARC会请求有关代码片段之间的关系的更多信息,来进一步为我们管理好内存,将会在本章节中描述各种情况,并且会展示给我们ARC是如何管理我们App的内存。在swift里面使用ARC和在OC里面使用ARC的方法是比价相似的,
引用计数只能应用在类的实例中,结构体和枚举是值类型(value type),不是引用类型,并且也不能传入和引用。
1. How ARC Works (自动引用计数的工作原理)
每次我们创建类的一个新实例的时候,ARC就会在内存里面分配一块地方来存储该实例的相关信息,这是内存就会保留该实例的类型的一些相关信息,和相关联该实例的任意存储属性的值。
另外一方面,当某个实例不在需要的时候,ARC会释放被该实例所使用的内存,释放完之后内存可以用于其他方面,这就会确保该实例不在占用当前内存资源,当实例不在继续使用的时候。
然而,如果ARC释放了一个正在使用中的实例,那么将无法在继续读取该实例的属性,或者调用实例的方法了, 的确如果说我们尝试去读区改实例,那么我们的App将会就此崩溃。
为了确保我们正在使用中的实例不会消失/释放,ARC会追踪当前每一个类的实例引用有多少个属性,常量或变量。哪怕只要是该实例还有一个引用在使用当中,那么此刻ARC将不会释放当前的实例。
为了使上述成为可能,无论什么时候我们给类实例分配属性,常量或变量的时候,都要把该属性,常量或变量创建为该实例的强引用(Strong Reference
),为什么该引用要被称之为强引用是因为它要强有力地保留住该实例,只要强引用在,ARC就不可能再把当前实例释放掉。
2. ARC in Action (实践中的自动引用计数)
下面这个例子向我们展示了ARC是如何工作的。该例从一个简单的类Person
开始的。 该类定义了一个存储常量属性name
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
Person
这个类有一个构造器name
并且该构造器设置了一个实例name的属性,和输出一个信息用来指定该构造器正在进行中。同样的该类也有一个析构器,当析构器在类的实例被释放的时候同样也会输出一个与析构器相关的信息。
下面的这段代码定义的是类型Person?
的三个变量,用来给Person实例设置多个引用。因为这三个变量是一个可选类型的Person
,所以它们会自动构造成我哦一个nil
的值,并且当前并没有引用Person实例。仅仅只是定义了三个类型为Person?的变量而已。
var reference1: Person?
var reference2: Person?
var reference3: Person?
所以我们现在可以创建一个新的Person实例,用来分配新实例给这三个变量中的任何一个。
reference1 = Person(name: "John Appleseed")
// 输出:John Appleseed is being initialized
需要注意的是这个输出信息John Appleseed is being initialized
,只有在我们调用Person类的构造器的时候信息才能输出。这样会确保我们的构造过程已经发生。
因为新的Person
实例已经分配给reference1
变量来,现在就有一个来自reference1到Person实例的强引用了,所以这是ARC会确保Person实例会保留在内存中不会发生释放。
如果我们这时候再分配相同的Person实例给其他两个变量,现在又多了两个强引用指向这个实例就被创建好了。
reference2 = reference1
reference3 = reference1
所以现在又三个强引用指向这个单一的Person
实例。
如果现在我们通过给配nil
值给两个变量的方法来断开两个强引用(包括原始引用),那么就只剩下一个强引用了,这时Person实例才不会被释放。
reference1 = nil
reference2 = nil
ARC还是不会释放Person实例,直到第三个或者最后一个强引用断开,除非在某一个点它确保你不会在继续使用Person实例的时候才会释放Person实例,将其从内存中移除。
reference3 = nil
// 输出:John Appleseed is being deinitialized
3. Strong Reference Cycles Between Class Instances (类实例之间的强引用循环)
在上面的例子中,ARC可以追踪我们创建的新Person实例的引用的数量,和当实例不在使用时释放Person实例。
然而,也是有可能线的,当我们写一段代码在某一个点某一个实例从来不会有任何一个引用。如果两个类的实例相互引用对方。也就是说实例A引用实例B,实例B引用实例A,两个实例相互使对方存留在内存中,像这样的实例相互引用我们称之为强引用循环。
通过在类实例之间定义弱引用(weak reference
)或无主引用(unowned reference
)的关系,来化解类实例之间的相互强引用。整个过程将会在化解类实例之间的强引用循环中有详细描述。在我们解决这个问题之前,先需要了解这样的引用循环使怎么引起的。
下面是不经意之间就创建了一个强引用循环,该例定义了两个类Person
和Apartment
,模拟了一个公寓街区和居住在此的居民。
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") }
}
在上面的例子中,每一个Person
实例都有一个String
类型的name
属性和一个初始化为ni
l的可选apartment
。该apartment属性是可选的公寓是因为每一个人不一定有一个公寓apartment。
相同的道理,每一个Apartment
有一个类型为String的unit
属性,和一个初始化为nil
的可选类型tenant
属性。这个承租人tenant属性是一个可选的类型,是因为每一个公寓不一定一定要有一个承租人。
两个类都定义了一个析构器,用于输出相关信息当某个类的实例从内存中释放之后。不出意料,这会使我们看出来Person
和Apartment
是否被成功释放。
下面这段代码定义了两个可选类型的变量john
和unit4A
,这两个可选类型的变量用于设置特定的Apartment
和Person
实例,这两个变量都是可选类型所以它们初始化的值都是nil
。
var john: Person?
var unit4A: Apartment?
现在我们可以创建一个特定的Person实例和Apartment实例并且分配新的实例给john和unit4A变量。
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
下面的图例是创建和分配两个值给强引用的示例。现在john
的这个变量现在对新的Person
实例有一个强引用。变量unit4A
的情况也是一样的。
所以我们现在可以把这两个实例连接在一起所以Person有了一个apartment,并且apartment有一个tenant,需要注意的是后面的叹号!
(exclamation point),用于打开(unwrap)和读取用于存储在john和unit4A可选变量里的实例值。至此,我们就可以设置这些实例的属性了。
john!.apartment = unit4A
unit4A!.tenant = john
下面是两个 实例链接在一起形成的强引用,
不幸的是,链接这两个实例会在它们之间形成一个强引用循环,Person实例现在有一个强引用到Apartment实例上,Apartment实例有一个强引用到Person实例上。因此,当我们断开john和unit4A变量形成的强引用时,引用计数的数量并不会变成0
个,存储在内存中类的实例也不会被ARC释放。
john = nil
unit4A = nil
需要注意的是,当我们把这两个变量设置为nil
,该强引用循环会阻止Person和Apartment实例被释放,这样会在我们的App中造成内存泄漏
。也就是说要想释放这两个存在在内存中的实例就必须断开实例间的强引用,这样才不会造成,变量设为nil,实例间依然有强引用,占用内存,而此时ARC就无法自动释放这两个实例。
下面就是在我们设置john和unit4A变量为nil,强引用会发生的变化及图例展示。两个实例之间的强引用依然保留着,并没有断开。