iOSswift学习

[译]Swift中的weak self和unowned self

2019-10-20  本文已影响0人  李白的苹果

在找关于weak和unowned方面的知识,看到的一篇文章。

原文来自Weak self and unowned self explained in Swift

对于我们大多数人来说,Swift中的weak self和unowned self是很难理解的。尽管自动引用计数(ARC)已经为我们解决了很多问题,但是我们还是需要处理引用当我们不使用值类型的时候。

在大多数情况下,使用可选类型的时候会默认添加weak,但其实并不需要这样做

什么是ARC,retain和release

我们需要这些基础知识来完全理解weak self和unowned self是做什么的。理解内存管理,最好的办法就是读Swift的官方文档Automatic Reference Counting in Swift documentation.

在没有ARC的时候,我们必须手动管理内存和引用。这会造成许多难以解决的bug和各种头痛的问题。当一个新的实例retain一个对象时引用计数会增加,当引用被released时引用计数会减少。一旦一个对象的引用不存在时,内存会被释放,这意味着这个对象再也不被需要了。

在Swift中,我们需要在代码中必要的时候用到weak self和unowned self。不用weak和unowned基本上意味着需要对某个对象保持强引用(strong),并且不然其引用计数变为0。不正确的使用这些关键词可能会导致内存泄漏或者循环引用。

引用计数仅仅对类的实例有效,对于结构体和枚举这些值类型来说,并不需要通过引用来存储和传递

什么时候用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")
    }
}

一旦这些类的实例被释放,会打印出一条信息。在随后的例子中,我们定义了两个可选类型,并把他们置为nil,一些人可能会认为会打印出信息,但实际上并不会发生:

var blog: Blog? = Blog(name: "SwiftLee", url: URL(string: "www.avanderlee.com")!)
var blogger: Blogger? = Blogger(name: "Antoine van der Lee")

blog!.owner = blogger
blogger!.blog = blog

blog = nil
blogger = nil

// Nothing is printed

这是因为循环引用,blog强引用了blogger,blogger强引用了blog,互相释放不了,造成了无限循环。

所以,需要引入弱引用,在这个例子中,只需要一个弱引用来打破这个循环,例如我们可以设置blog对blogger为弱引用:

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: "SwiftLee", url: URL(string: "www.avanderlee.com")!)
var blogger: Blogger? = Blogger(name: "Antoine van der Lee")

blog!.owner = blogger
blogger!.blog = blog

blog = nil
blogger = nil

// Blogger Antoine van der Lee is being deinitialized
// Blog SwiftLee is being deinitialized
那weak self呢?

我知道,这不是一个关于weak self的例子,但是它可以解释。

我们很多人,为了避免闭包中的循环引用问题会一直加上weak self。然而,只有在self和闭包相互引用的时候才需要weak self。很多情况下,其实并不需要weak self。

为了说明这个问题,我们为Blog引入了publish方法,通过手动添加delay来模拟网络请求。

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: "SwiftLee", url: URL(string: "www.avanderlee.com")!)
var blogger: Blogger? = Blogger(name: "Antoine van der Lee")

blog!.owner = blogger
blogger!.blog = blog

blog!.publish(post: Post(title: "Explaining weak and unowned self"))
blog = nil
blogger = nil

会打印出以下信息:

// Blogger Antoine van der Lee is being deinitialized
// Published post count is now: 1
// Blog SwiftLee is being deinitialized

可以看出在blog释放前,完成了网络请求。强引用让我们完成了publish并且把这个post存储在published post中。

因此,如果在执行闭包后立即对引用实例进行处理,请确保不要使用弱自我。

弱引用和循环引用

当self引用闭包,并且闭包会捕获self时会发生循环引用。如果我们有一个闭包变量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: "SwiftLee", url: URL(string: "www.avanderlee.com")!)
var blogger: Blogger? = Blogger(name: "Antoine van der Lee")

blog!.owner = blogger
blogger!.blog = blog

blog!.publish(post: Post(title: "Explaining weak and unowned self"))
blog = nil
blogger = nil

结果会打印出以下信息:

// Blogger Antoine van der Lee is being deinitialized
// Published post count is now: 1

虽然我们看到网络请求发送成功了,但是并没有看到blog被释放。这是因为存在循环引用,内存并没有被释放。

通过在obPublish闭包中添加blog的弱引用,可以解决循环引用的问题:

onPublish = { [weak self] post in
    self?.publishedPosts.append(post)
    print("Published post count is now: \(self?.publishedPosts.count)")
}

结果会打印以下信息:

// Blogger Antoine van der Lee is being deinitialized
// Published post count is now: Optional(1)
// Blog SwiftLee is being deinitialized

数据存储在本地,并且所有的实例都被释放,不存在循环引用了!

最后,总结本小节,很容易知道:

当ARC将弱引用设置为nil时,不会调用属性观察器

什么时候用unowned self ?

不像弱引用,当使用unowned时,引用不会变成可选的。但是weak和unowned都不会建立强引用。

引用Apple官方文档:

Use a weak reference whenever it is valid for that reference to become nil at some point during its lifetime. Conversely, use an unowned reference when you know that the reference will never be nil once it has been set during initialization.
当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时。相比之下,当其他实例有相同的或者更长生命周期时,请使用无主引用。

总之,当使用unowned时要非常小心。这可能会让你使用一个已经不存在的实例,从而造成crash。相比weak,用unowned唯一的好处就是可以不用处理可选类型。所以在很多情况下使用weak总是安全的。

我们为什么不需要在值类型(例如结构体)中使用这些?

在Swift中,有值类型和引用类型。这已经说得够清楚了,引用类型需要你考虑其引用。这意味这你需要管理这些关系,例如:strong,weak,unowned。而值类型是保持了一个data的copy,一个单独的实例。这意味着在多线程环境下完全不需要用弱引用,因为没有引用,而是我们正在使用的唯一copy。

在闭包中,weak和unowned仅仅用于修饰self吗?

不,当然不是。只要是引用类型,都可以。所以,这样也是可行的:

download(imageURL, completion: { [weak imageViewController] result in
    // ...
})

并且,你甚至可以引用多个实例,因为它基本上是一个数组:

download(imageURL, completion: { [weak imageViewController, weak imageFinalizer] result in
    // ...
})

讨论

总的来说,这是一个让人头晕的问题。最好通过阅读Swift官方文档来开始,这会讨论的更加深入。另外,如果你不确定,优先使用weak,它可以避免烦人的bug。此外,如果需要在闭包内部完成工作,请不要使用weak并确保您的代码正在执行。

上一篇下一篇

猜你喜欢

热点阅读