如何使用dispatchQueue创建引用循环?

2024-03-25

我觉得我一直误解了创建引用循环的时间。在我以前认为几乎任何有块并且编译器都会强迫你编写的地方.self那么这是我正在创建引用循环并且需要使用的标志[weak self] in.

但以下设置不会创建引用循环。

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

该块仅保留self3 秒。然后释放它。如果我使用async代替asyncAfter然后几乎是立即的。

据我了解,这里的设置是:

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

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

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

据我了解[weak self]仅用于参考循环以外的原因,即控制流量块的。例如

您想保留该对象并运行您的块然后释放它吗?真实的情况是即使视图已从屏幕上删除,也要完成此事务......

或者你想使用[weak self] in这样,如果您的对象已被释放,您可以提前退出。例如不再需要一些纯粹的 UI,例如停止加载旋转器


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

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

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

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

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

EDIT:

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 的内存图中我看到一个循环,而不是一条直线。我得到紫色指示器。我认为这个设置非常类似于存储的闭包如何造成泄漏。这与your两个例子,其中执行是从未完成。在这个例子中执行是finished,但由于引用,它仍然保留在内存中。

我认为参考是这样的:

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

You say:

据我了解,这里的设置是:

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

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

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

因此,GCD 保留对已调度任务的队列的引用。队列保留对已分派的块/项的强引用。排队的块保留对其捕获的任何引用类型的强引用。当分派任务完成时,它会解析对任何捕获的引用类型的任何强引用,并从队列中删除(除非您在其他地方保留自己对它的引用。),通常从而解决任何强引用循环。


抛开这一点不谈,如果没有[weak self]可能会给你带来麻烦的是 GCD 由于某种原因(例如调度源)保留对块的引用。典型的例子是重复定时器:

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 object:

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

其他场景包括缓慢(或无限长)的分派任务,您希望cancel它(例如,在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一直持续到任务完成或未完成。

  • 如果我们只是要在任务完成时更新 UI,那么如果视图控制器已被关闭,则无需让视图控制器及其在层次结构中的视图等待某些 UI 更新。

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

  • 通常,分派的任务不够重要,无需担心其寿命self。例如,您可能有一个URLSession当请求完成时,完成处理程序将 UI 更新分派回主队列。当然,理论上我们会想要[weak self](因为没有理由为已被忽略的视图控制器保留视图层次结构),但话又说回来,这又给我们的代码增加了噪音,通常没有什么实质性的好处。


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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用dispatchQueue创建引用循环? 的相关文章

随机推荐