weak self、unowned self
weak self、unowned self
Swift中weak self and unowned self的很难理解。虽然自动引用计数(ARC)已经为解决了很多问题,但当不处理值类型时,我们仍然需要管理引用。 在编写闭包等内容时经常会遇到情况,这些情况要求我们细致考虑。
ARC、retain、release
在ARC之前,我们必须手动管理内存和引用。这引起了许多出错和麻烦,许多开发人员可能会知道。当新实例retain对象时,引用计数会上升,而一旦引用被realease,引用计数就会下降。一旦没有对对象的引用,内存就会被释放,这意味着不再需要该对象。
在Swift中需要使用 weak self 和 unowned self来向ARC提供代码中关系之间的必要信息。在不使用 weak 和 unowned的情况下,基本上是在告诉ARC需要一定的“强引用”,并且正在防止引用计数变为零。如果不正确使用这些关键字,我们可能会保留内存,这可能会导致应用程序内存泄漏。如果weak 和 unowned未正确使用,所谓的循环引用也可能发生。
引用计数仅适用于类实例。结构体和枚举是值类型,而不是引用类型,并且不通过引用存储和传递。
何时使用 weak self
首先,弱引用总是被声明为可选变量,因为当其引用被解除分配时,ARC可以自动将其设置为nil。
class Blog {
let name: String
let url: URL
var owner: Blogger?
init(name: String, url: URL) { self.name = name; self.url = url }
deinit {
print("Blog \(name) is being deinitialized")
}
}
class Blogger {
let name: String
var blog: Blog?
init(name: String) { self.name = name }
deinit {
print("Blogger \(name) is being deinitialized")
}
}
一旦这些类中的任何一个被deinit,就会打印出一条消息。在下面的代码示例中将两个实例定义为可选的选项,方法是将它们设置为nil。实际上并没生打印两份声明:
var blog: Blog? = Blog(name: "google", url: URL(string: "www.google.com")!)
var blogger: Blogger? = Blogger(name: "google author")
blog!.owner = blogger
blogger!.blog = blog
blog = nil
blogger = nil
// Nothing is printed
这产生了循环引用。blog强引用其所有者,不会被释放。与此同时,所有者不愿意释放blog。blog不会释放其所有者,其所有者保留其blog,该所有者保留自己......这是无限循环:循环引用。
因此需要引入一个弱引用。在这个例子中,只需要一个弱引用,因为这已经打破了循环。例如可以从blog向其所有者设置一个弱引用:
class Blog {
let name: String
let url: URL
weak var owner: Blogger?
init(name: String, url: URL) { self.name = name; self.url = url }
deinit {
print("Blog \(name) is being deinitialized")
}
}
class Blogger {
let name: String
var blog: Blog?
init(name: String) { self.name = name }
deinit {
print("Blogger \(name) is being deinitialized")
}
}
var blog: Blog? = Blog(name: "google", url: URL(string: "www.google.com")!)
var blogger: Blogger? = Blogger(name: "google author")
blog!.owner = blogger
blogger!.blog = blog
blog = nil
blogger = nil
这不是weak self的例子。然而,它确实解释了这种情况!
对许多人来说,最好的做法是始终使用weak与self内部闭合相结合以避免循环引用。然而,只有当self也持有closure时,才需要这样做。通过默认添加weak,可能最终在许多情况下使用可选类型,而实际上不需要。
在本例中通过手动添加延迟来“伪造”网络请求:
struct Post {
let title: String
var isPublished: Bool = false
init(title: String) { self.title = title }
}
class Blog {
let name: String
let url: URL
weak var owner: Blogger?
var publishedPosts: [Post] = []
init(name: String, url: URL) { self.name = name; self.url = url }
deinit {
print("Blog \(name) is being deinitialized")
}
func publish(post: Post) {
/// Faking a network request with this delay:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.publishedPosts.append(post)
print("Published post count is now: \(self.publishedPosts.count)")
}
}
}
var blog: Blog? = Blog(name: "google", url: URL(string: "www.google.com")!)
var blogger: Blogger? = Blogger(name: "google author")
blog!.owner = blogger
blogger!.blog = blog
blog!.publish(post: Post(title: "Explaining weak and unowned self"))
blog = nil
blogger = nil
这将打印出以下内容:
// google author is being deinitialized
// Published post count is now: 1
// Blog google is being deinitialized
可以看到请求在blog publish之前就完成了。强引用使能够完成发布,并将帖子保存到发布的帖子中。
如果要将发布方法更改为包含弱引用:
func publish(post: Post) {
/// Faking a network request with this delay:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
self?.publishedPosts.append(post)
print("Published post count is now: \(self?.publishedPosts.count)")
}
}
我们将得到以下输出:
// google author is being deinitialized
// Blog google is being deinitialized
// Published post count is now: nil
由于博客在发布请求完成之前就已经发布,我们将永远无法更新我们本地发布帖子的状态。 因此,如果在关闭执行后立即对引用实例进行工作,请确保不要使用weak self。
弱引用、循环引用
一旦closure持有self,self持有closure,就会发生循环引用。 如果有一个包含onPublish闭包的变量,这种情况可能会发生:
class Blog {
let name: String
let url: URL
weak var owner: Blogger?
var publishedPosts: [Post] = []
var onPublish: ((_ post: Post) -> Void)?
init(name: String, url: URL) {
self.name = name
self.url = url
// Adding a closure instead to handle published posts
onPublish = { post in
self.publishedPosts.append(post)
print("Published post count is now: \(self.publishedPosts.count)")
}
}
deinit {
print("Blog \(name) is being deinitialized")
}
func publish(post: Post) {
/// Faking a network request with this delay:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.onPublish?(post)
}
}
}
var blog: Blog? = Blog(name: "google", url: URL(string: "www.google.com")!)
var blogger: Blogger? = Blogger(name: "google author")
blog!.owner = blogger
blogger!.blog = blog
blog!.publish(post: Post(title: "Explaining weak and unowned self"))
blog = nil
blogger = nil
closure持有博客,而博客持有closure。这会导致以下内容被打印出来:
// google author is being deinitialized
// Published post count is now: 1
没有看到blog和publisher被去初始化。这是因为循环引用导致内存无法释放。
在onPublish方法中为博客实例添加一个弱引用解决了我们的保留周期:
onPublish = { [weak self] post in
self?.publishedPosts.append(post)
print("Published post count is now: \(self?.publishedPosts.count)")
}
并产生以下输出:
// google author is being deinitialized
// Published post count is now: Optional(1)
// google is being deinitialized
不再循环引用;当ARC将弱引用设置为nil,不会调用属性观察者。
何时使用 unowned self
与弱引用不同,在使用unowned引用时,引用不会变成可选的。尽管如此,unowned 和 weak都不会产生强引用。
引自苹果文档:
每当弱引用在其生命周期的某个时间点变为零时,就使用弱引用。相反,当您知道在初始化期间设置引用后,该引用将永远不会为零时,请使用unowned的引用。
一般来说,使用unowned时要非常小心。可能是正在访问一个不存在的实例导致崩溃。使用unowned而不是weak的唯一好处是,不必处理可选的。因此,在这些情况下,使用unowned总是更安全。
为什么不需要结构体这样的值类型?
在Swift中有值类型和引用类型。因为对于引用类型实际上有个引用需要处理。这意味着需要将这种关系管理为强、弱或unowned。相反,值类型保留了其数据的唯一副本,唯一的实例。这意味着没有必要在多线程环境中使用弱引用,因为没有引用,而是正在处理的唯一副本。
weak和unowned只与inside closures一起使用吗?
不,只要是引用类型就可以指示任何属性或变量声明weak或unowned。因此这也可以起作用:
download(imageURL, completion: { [weak imageViewController] result in
// ...
})
甚至可以引用多个实例,因为是一个数组:
download(imageURL, completion: { [weak imageViewController, weak imageFinalizer] result in
// ...
})