如何使用 dispatchQueues 创建参考循环? [英] How can I create a reference cycle using dispatchQueues?

查看:17
本文介绍了如何使用 dispatchQueues 创建参考循环?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我觉得我一直误解了何时创建引用循环.在我过去认为几乎任何你有一个块的地方,编译器都强迫你写 .self 之前,这表明我正在创建一个引用循环,我需要使用 [弱自我]在.

I feel that I've always misunderstood that when reference cycles are created. Before I use to think that almost any where that you have a block and the compiler is forcing you to write .self then it's a sign that I'm creating a reference cycle and I need to use [weak self] in.

但以下设置不会创建参考循环.

But the following setup doesn't create a reference cycle.

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution


class UsingQueue {
    var property : Int  = 5
    var queue : DispatchQueue? = DispatchQueue(label: "myQueue")

    func enqueue3() {
        print("enqueued")
        queue?.asyncAfter(deadline: .now() + 3) {
            print(self.property)
        }
    }

    deinit {
        print("UsingQueue deinited")
    }
}

var u : UsingQueue? = UsingQueue()
u?.enqueue3()
u = nil

块只保留self 3 秒.然后释放它.如果我使用 async 而不是 asyncAfter 那么它几乎是立即的.

The block only retains self for 3 seconds. Then releases it. If I use async instead of asyncAfter then it's almost immediate.

据我了解这里的设置是:

From what I understand the setup here is:

self ---> queue
self <--- block

队列只是块的外壳/包装器.这就是为什么即使我 nil 队列,块也会继续执行.他们是独立的.

The queue is merely a shell/wrapper for the block. Which is why even if I nil the queue, the block will continue its execution. They’re independent.

那么有没有只使用队列并创建引用循环的设置?

据我所知,[weak self] 仅用于引用循环以外的其他原因,即控制块的流程.例如

From what I understand [weak self] is only to be used for reasons other than reference cycles ie to control the flow of the block. e.g.

是否要保留对象并运行块然后释放它?一个真实的场景是即使视图已从屏幕上移除也完成此事务...

Do you want to retain the object and run your block and then release it? A real scenario would be to finish this transaction even though the view has been removed from the screen...

或者你想在中使用[weak self],这样如果你的对象已经被释放,你可以提前退出.例如不再需要一些纯粹的 UI,比如停止加载微调器

Or you want to use [weak self] in so that you can exit early if your object has been deallocated. e.g. some purely UI like stopping a loading spinner is no longer needed

FWIW 我明白,如果我使用闭包,那么事情就会有所不同,即如果我这样做:

FWIW I understand that if I use a closure then things are different ie if I do:

import PlaygroundSupport
import Foundation

PlaygroundPage.current.needsIndefiniteExecution
class UsingClosure {
    var property : Int  = 5

    var closure : (() -> Void)?

    func closing() {
        closure = {
            print(self.property)
        }
    }

    func execute() {
        closure!()
    }
    func release() {
        closure = nil
    }


    deinit {
        print("UsingClosure deinited")
    }
}


var cc : UsingClosure? = UsingClosure()
cc?.closing()
cc?.execute()
cc?.release() // Either this needs to be called or I need to use [weak self] for the closure otherwise there is a reference cycle
cc = nil

在闭包示例中,设置更像是:

In the closure example the setup is more like:

self ----> block
self <--- block

因此,这是一个引用循环,除非我将块设置为捕获到 nil,否则不会解除分配.

Hence it's a reference cycle and doesn't deallocate unless I set block to capturing to nil.

class C {
    var item: DispatchWorkItem!
    var name: String = "Alpha"

    func assignItem() {
        item = DispatchWorkItem { // Oops!
            print(self.name)
        }
    }

    func execute() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: item)
    }

    deinit {
        print("deinit hit!")
    }
}

使用以下代码,我能够创建泄漏,即在 Xcode 的内存图中我看到一个循环,而不是一条直线.我得到紫色指标.我认为这种设置与存储闭包如何产生泄漏非常相似.这与您的两个示例不同,其中执行从未完成.在此示例中,执行完成,但由于引用,它仍保留在内存中.

With the following code, I was able to create a leak ie in Xcode's memory graph I see a cycle, not a straight line. I get the purple indicators. I think this setup is very much like how a stored closure creates leaks. And this is different from your two examples, where execution is never finished. In this example execution is finished, but because of the references it remains in memory.

我认为参考是这样的:

┌─────────┐─────────────self.item──────────────▶┌────────┐
│   self  │                                     │workItem│
└─────────┘◀︎────item = DispatchWorkItem {...}───└────────┘

推荐答案

你说:

据我了解这里的设置是:

From what I understand the setup here is:

self ---> queue
self <--- block

队列只是块的外壳/包装器.这就是为什么即使我 nil 队列,块也会继续执行.他们是独立的.

The queue is merely a shell/wrapper for the block. Which is why even if I nil the queue, the block will continue its execution. They’re independent.

self 恰好对队列有一个强引用这一事实是无关紧要的.一种更好的思考方式是 GCD 本身保留对所有排队的调度队列的引用.(它类似于一个自定义的 URLSession 实例,在该会话上的所有任务完成之前不会被释放.)

The fact that self happens to have a strong reference to the queue is inconsequential. A better way of thinking about it is that a GCD, itself, keeps a reference to all dispatch queues on which there is anything queued. (It’s analogous to a custom URLSession instance that won’t be deallocated until all tasks on that session are done.)

因此,GCD 会一直引用带有已调度任务的队列.队列保持对分派块/项目的强引用.排队块保持对它们捕获的任何引用类型的强引用.当分派的任务完成时,它会解析对任何捕获的引用类型的任何强引用,并从队列中删除(除非您将自己的引用保留在别处.),通常从而解决所有强引用循环.

So, GCD keeps reference to the queue with dispatched tasks. The queue keeps a strong reference to the dispatched blocks/items. The queued block keeps a strong reference to any reference types they capture. When the dispatched task finishes, it resolves any strong references to any captured reference types and is removed from the queue (unless you keep your own reference to it elsewhere.), generally thereby resolving any strong reference cycles.

暂且不说,[weak self] 的缺失会给你带来麻烦的是 GCD 出于某种原因保留对块的引用,例如调度源.经典的例子是重复计时器:

Setting that aside, where the absence of [weak self] can get you into trouble is where GCD keeps a reference to the block for some reason, such as dispatch sources. The classic example is the repeating timer:

class Ticker {
    private var timer: DispatchSourceTimer?

    func startTicker() {    
        let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".ticker")
        timer = DispatchSource.makeTimerSource(queue: queue)
        timer!.schedule(deadline: .now(), repeating: 1)
        timer!.setEventHandler {                         // whoops; missing `[weak self]`
            self.tick()
        }
        timer!.resume()
    }

    func tick() { ... }
}

即使我启动上述计时器的视图控制器被关闭,GCD 也会继续触发这个计时器并且 Ticker 不会被释放.正如调试内存图"功能所示,在 startTicker 例程中创建的块保持对 Ticker 对象的持久强引用:

Even if the view controller in which I started the above timer is dismissed, GCD keeps firing this timer and Ticker won’t be released. As the "Debug Memory Graph" feature shows, the block, created in the startTicker routine, is keeping a persistent strong reference to the Ticker object:

如果我在该块中使用 [weak self] 作为调度队列上调度的计时器的事件处理程序,这显然可以解决.

This is obviously resolved if I use [weak self] in that block used as the event handler for the timer scheduled on that dispatch queue.

其他场景包括缓慢(或不确定长度)分派任务,您希望在其中取消它(例如,在deinit中):

Other scenarios include a slow (or indefinite length) dispatched task, where you want to cancel it (e.g., in the deinit):

class Calculator {
    private var item: DispatchWorkItem!

    deinit {
        item?.cancel()
        item = nil
    }

    func startCalculation() {
        let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".calcs")
        item = DispatchWorkItem {                         // whoops; missing `[weak self]`
            while true {
                if self.item?.isCancelled ?? true { break }
                self.calculateNextDataPoint()
            }
            self.item = nil
        }
        queue.async(execute: item)
    }

    func calculateNextDataPoint() {
        // some intense calculation here
    }
}

综上所述,在绝大多数 GCD 用例中,[weak self] 的选择不是强引用循环之一,而只是我们是否介意是否强引用对 self 的引用一直存在,直到任务完成与否.

All of that having been said, in the vast majority of GCD use-cases, the choice of [weak self] is not one of strong reference cycles, but rather merely whether we mind if strong reference to self persists until the task is done or not.

  • 如果我们只是在任务完成后更新 UI,那么如果视图控制器已被解除,则无需让视图控制器及其层次结构中的视图等待一些 UI 更新.

  • If we’re just going to update the the UI when the task is done, there’s no need to keep the view controller and its views in the hierarchy waiting some UI update if the view controller has been dismissed.

如果我们需要在任务完成后更新数据存储,那么如果我们想确保更新发生,我们绝对不想使用 [weak self].

If we need to update the data store when the task is done, then we definitely don’t want to use [weak self] if we want to make sure that update happens.

通常,分派的任务不够重要,无需担心 self 的生命周期.例如,当请求完成时,您可能有一个 URLSession 完成处理程序将 UI 更新分派回主队列.当然,我们理论上会想要 [weak self](因为没有理由为已被解雇的视图控制器保留视图层次结构),但是这又给我们的代码增加了噪音,通常很少物质利益.

Frequently, the dispatched tasks aren’t consequential enough to worry about the lifespan of self. For example, you might have a URLSession completion handler dispatch UI update back to the main queue when the request is done. Sure, we theoretically would want [weak self] (as there’s no reason to keep the view hierarchy around for a view controller that’s been dismissed), but then again that adds noise to our code, often with little material benefit.

无关,但游乐场是测试记忆行为的可怕场所,因为它们有自己的特质.在实际应用程序中执行此操作要好得多.另外,在实际的应用程序中,您可以使用调试内存图"功能,您可以在其中查看实际的强引用.请参阅https://stackoverflow.com/a/30993476/1271826.

Unrelated, but playgrounds are a horrible place to test memory behavior because they have their own idiosyncrasies. It’s much better to do it in an actual app. Plus, in an actual app, you then have the "Debug Memory Graph" feature where you can see the actual strong references. See https://stackoverflow.com/a/30993476/1271826.

这篇关于如何使用 dispatchQueues 创建参考循环?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆