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

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

问题描述

我觉得在创建参考循环时,我总是误解了这一点.在我习惯认为几乎所有您拥有块的地方以及编译器强迫您编写.self之前,这都表明我正在创建参考循环,并且需要使用[weak self] in.

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] in,以便在对象被释放后可以提前退出.例如不再需要一些纯粹的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.

我认为参考文献是这样的:

I think the reference is something like this:

┌─────────┐─────────────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.

其他情况包括缓慢(或不确定长度)的已调度任务,您要在其中执行cancel(例如,在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中等待一些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天全站免登陆