1. swift中的数组等导致的循环引用
class Person {
// ...
var friends: [Person] = []
// ...
}
let person1 = Person()
let person2 = Person()
person1.friends.append(person2)
person2.friends.append(person1)
像上面这样,由于Person对象强引用着friends数组,同时,它里面包含的对象又是Person址类型,从而产生了循环引用,导致person1和person2都不能释放。你可能想使用unowned或者weak。但是unowned和weak只能用来修饰class类型,而friends是struct类型。所以,这种情况下,只能将friends数组里的元素包装一下,变成unowned或者weak。像下面这样就可以了:
class Unowned<T: AnyObject> {
unowned var value: T
init(_ value: T) {
self.value = value
}
}
class Person {
// ...
var friends: [Unowned<Person>] = []
// ...
}
let person1 = Person()
let person2 = Person()
person1.friends.append(Unowned(person2))
person2.friends.append(Unowned(person1))
这样,加入到friends数组里的person1和person2就都变成unowned类型了。
2. Xcode中的 Debug Memory Graph工具检测循环引用
将app运行起来以后,将有问题的部分跑一边,然后点击下图中红色部分的按钮:

在下图中,左边红色框中,只要用这种紫色框加白色感叹号标注的对象就是产生问题的对象,选中该对象后,右边(红框中)就会展示对象间的引用关系,比如下面这种循环引用:

3. swift closure循环引用问题
先看个两个小例子:
class Person {
let firstName: String
let lastName: String
lazy var fullName: () -> String = {
self.firstName + " " + self.lastName
}
// ...
}
let john = Person(firstName: "A", lastName: "B")
print(john.fullName())
这里,由于closure捕获了当前实例,同时,当前实例又强引用着fullName这个closure,所以导致john对象一直不能释放。
var x = 5
var y = 5
let someClosure = { [x] in
print("\(x), \(y)")
}
x = 6
y = 6
someClosure() // Prints 5, 6
print("\(x), \(y)") // Prints 6, 6
在上面这个小例子中,由于x在someClosure这个closure的capture list中,所以在定义这个closure的时候,就会捕获x的拷贝。而y不在capture list中,closure只能捕获他的引用,这意味着只有在closure运行的时候才能知道y的值,而不是在捕获的时候。
capture list的这个特性使得我们给closure中使用的对象添加unowned,weak成为可能,比如,上面fullName closure这个例子中,就可以像下面这样来打破引用循环:
class Person {
let firstName: String
let lastName: String
lazy var fullName: () -> String = { [unowned self] in
self.firstName + " " + self.lastName
}
// ...
}
这里,我们在capture list中使用unowend或者weak 来捕获self, 这样就不存在强引用的问题了。上面这种捕获的写法是一种简写,全写应该是像这样的:
lazy var fullName: () -> String = { [unowned unownedSelf = self] in
unownedSelf.firstName + " " + unownedSelf.lastName
}
这里要千万注意unowned和weak的选择。weak修饰的是optional类型,如果对象销毁了,它修饰的变量就会变成nil;而unowned则不同,它修饰的不是optional类型,如果去访问一个已经销毁的对象,就会触发运行时错误,所以使用unowned一定要保证使用的时候对象是存在的。
网友评论