和OC同样,swift开发中也是使用自动引用计数ARC(Auto Reference Counteting)来自动管理内存的,因此咱们不须要过多考虑内存管理.当某个类实例不须要用到的时候,ARC会自动释放其占用的内存.swift
ARC仅仅能对类的实例作内存管理,也就是只能针对引用类型.结构体和枚举都是值类型,不能经过引用的方式来传递和存储,因此ARC也就不能对它们进行内存管理.api
什么状况下会致使循环引用闭包
在swift中,每建立一个实例,ARC都会为其分配一块内存空间,而在不使用的时候,ARC会释放和收回那个实例所占的内存空间,该实例的属性和方法也就不可以被访问,若是要访问就会致使程序崩溃.函数
怎么肯定实例不被使用了?ARC会自动追踪实例被多少常量和变量引用.每追踪到一个,自动引用计数会加一,减小一个引用自动引用计数会减一,若是当自动引用计数变为0的时候,ARC就会收回内存,销毁实例.对象
下面是一个自动引用计数的实例:内存
class Person {element
let name: String开发
init(name: String) {get
self.name = namestring
print("\(name)正在被初始化")
}
deinit {
print("\(name)即将被销毁") // person3 = nil时打印
}
}var person1: Person? // 可选类型的变量,方便置空var person2: Person?var person3: Person?
person1 = Person(name: "Dariel") //建立Person实例并与person1创建了强引用
person2 = person1 // 只要有一个强引用在,实例就能不被销毁
person3 = person1 // 目前该实例共有三个强引用
person1 = nil
person2 = nil // 由于还有一个强引用,实例不会被销毁
person3 = nil // 最后一个强引用被断开,ARC会销毁该实例
上面的例子中建立的 Person 实例最后引用计数变为了0被销毁了,但现实世界并不会一直都这么美好, ARC这种机制也有本身的局限性,请看下面的例子:
class People {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment? // 人住的公寓属性deinit {
print("People被销毁")
}
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: People? // 公寓中的人的属性
deinit {
print("Apartment被销毁")
}
}
var people1: People? = People(name: "Dariel") // 定义两个实例变量var apartment1: Apartment? = Apartment(unit: "4A")
people1!.apartment = apartment1 // 二者相互引用
apartment1?.tenant = people1 // 并且彼此都是强引用
people1 = nil
apartment1 = nil // 两个引用都置为nil了,但实例并无销毁
这一次直接建立了两个实例, People 中有一个 Apartment 的属性, Apartment 中又有一个 People 属性,当咱们建立了两个实例后分别给实例中的这两个属性赋完值,又将两个可选变量赋值为nil,并无看到两个实例被销毁的打印信息( deinit 函数会在实例被销毁的时候打印).
也就是说ARC并无销毁两个对象.那么问题在哪里?
当两个可选变量被赋值为nil时,ARC并无以为这两个实例已经不在使用了.由于两个实例的相互赋值时使得各自的引用计数+1,这也就是发生循环引用了.
怎么解决循环引用
1. 若是产生循环引用的两个属性都容许为nil,这种状况适合用弱引用来解决
随便哪个可选类型的属性前面均可以加 weak ,但记住只要加一个就好了.
话很少说上代码:
class OtherPeople {
let name: String
init(name: String) { self.name = name }
var apartment: OtherApartment? // 人住的公寓属性
deinit { print("\(name)被销毁") }
}
class OtherApartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: OtherPeople? // 加一个weak关键字,表示该变量为弱引用
deinit { print("\(unit)被销毁") }
}
var otherPeople1: OtherPeople? = OtherPeople(name: "Dariel") // 定义两个实例变量var otherApartment1: OtherApartment? = OtherApartment(unit: "4A")
otherPeople1!.apartment = otherApartment1 // 二者相互引用
otherApartment1?.tenant = otherPeople1 // 但tenant是弱引用
otherPeople1 = nil
otherApartment1 = nil // 实例被销毁,deinit中都会打印销毁的信息
在 OtherPeople 和 OtherApartment 两个类中,相互引用的两个属性都为可选类型,那么能够在一个属性的前面添加 weak 关键字,使该变量变为弱引用.
对的,没错,这个weak仍是之前OC里面的那个weak.
2. 若是产生循环引用的两个属性一个容许为nil,另外一个不容许为nil,这种状况适合用无主引用来解决
只能在不能为nil的那个属性前面加 unowned 关键字,就是说 unowned 设置之后即便它原来引用的内容已经被释放了,它仍然会保持对被已经释放了的对象的一个 "无效的" 引用,它不能是 Optional 值,也不会被指向 nil。若是尝试去调用这个引用的方法或者访问成员属性的话,程序就会崩溃.
无主引用的例子:
class Dog {
let name: String
var food: Food?
init(name: String) {
self.name = name
}
deinit { print("\(name)被销毁") }
}class Food {
let number: Int
unowned var owner: Dog // owner是一个无主引用
init(number: Int, owner: Dog) {
self.number = number
self.owner = owner
}
deinit { print("食物被销毁") }
}
var dog1: Dog? = Dog(name: "Kate")
dog1?.food = Food(number: 6, owner: dog1!) // dog强引用food,而food对dog是无主引用
dog1 = nil // 这样就能够同时销毁两个实例了
Dog 的 food 属性能够为空,而 Food 的 owner 属性不能为空,咱们把 owner 设为无主引用.
3. 若是产生循环引用的两个属性都必须有值,不能为nil,这种状况适合一个类使用无主属性,另外一个类使用隐式解析可选类型
隐式解析可选类型: 相似可选类型,默认值能够设置为nil
两个属性一个在类型后面加 ! 设置为隐式解析可选类型,另外一个在属性前面加unowned 关键字,设置为无主属性.
class Country {
let name: String
var capitalCity: City! // 初始化完成后能够当非可选类型使用
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
deinit { print("Country实例被销毁") }
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
deinit { print("City实例被销毁") }
}
// 这样一条语句就可以建立两个实例var country: Country? = Country(name: "China", capitalName: "HangZhou")print(country!.name) // Chinaprint(country!.capitalCity.name) // HangZhou
country = nil // 同时销毁两个实例
Country 的 City 属性后加!为隐式解析可选属性,相似可选类型,capitalCity属性的默认值为nil,一旦在 Country 的构造函数中给 name 属性赋完值后, Country 的整个初始化过程就完成了,就能将 self 做为参数传递给 City 的构造函数了.
总而言之,就是一条语句建立两个实例,还不产生循环引用.
闭包也是引用类型,怎么解决闭包的循环强引用
闭包中对任何其余元素的引用都是会被闭包自动持有的。若是咱们在闭包中写了self 这样的东西的话,那咱们其实也就在闭包内持有了当前的对象。这里就出现了一个在实际开发中比较隐蔽的陷阱:若是当前的实例直接或者间接地对这个闭包又有引用的话,就造成了一个 self -> 闭包 -> self 的循环引用
怎样避免这种状况呢?
能够在闭包开始的时候添加一个标注,来表示这个闭包内的某些要素应该以何种特定的方式来使用
看例子:
class Element {
let name: String
let text: String?
lazy var group:() -> String = { // 至关于一个没有参数返回string的函数
[unowned self] in // 定义捕获列表,将self变为无主引用
if let text = self.text { // 解包
return "\(self.name), \(text)"
}else {
return "\(self.name)"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit { print("\(name)被销毁") }
}
var element1: Element? = Element(name: "Alex", text: "Hello")print(element1!.group()) // Alex, Hello,闭包与实例相互引用
element1 = nil // self为无主引用,实例能被销毁
在闭包中定义一个捕获列表 [unowned self] ,将self变为无主引用.这样就可以在避免产生循环强引用了.
小结
解决循环引用的三种方法,这三种方法的产生主要仍是swift中要考虑属性为空的状况.
· 若是产生循环引用的两个属性都容许为nil,这种状况适合用弱引用来解决.
· 若是产生循环引用的两个属性一个容许为nil,另外一个不容许为nil,这种状况适合用无主引用来解决.
· 若是产生循环引用的两个属性都必须有值,不能为nil,这种状况适合一个类使用无主属性,另外一个类使用隐式解析可选类型 .
文章来源:简书