weak self、unowned self

木木木大约 6 分钟iOSSwift区分

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
    // ...
})
上次编辑于:
贡献者: perhapsdone