如何用DispatchQueue创建引用循环?兼谈引用循环认知误区
Great question—this is such a common point of confusion when working with Swift closures, dispatch queues, and memory management. Let’s break this down clearly.
First: What Actually Creates a Reference Cycle?
A reference cycle only forms when there’s a bidirectional chain of strong references between two (or more) reference-type objects, and nothing breaks that chain. In other words:
- Object A strongly references Object B
- Object B strongly references Object A
- Neither side releases their strong reference automatically
Your initial misunderstanding is super relatable—using self in a closure doesn’t automatically create a cycle. It only does so if that closure is itself strongly referenced back to self (or part of a chain that loops back).
Why Your First Dispatch Queue Example Doesn’t Create a Cycle
Let’s look at your UsingQueue code:
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
Here’s the reference chain:
self(UsingQueue instance) strongly referencesqueue- The closure you pass to
asyncAfterstrongly referencesself - But
queuedoes not strongly reference the closure long-term. It only holds the closure temporarily while it’s waiting to execute, then releases it as soon as the closure finishes running.
Once you set u = nil, the only strong reference to the UsingQueue instance is the closure. When the closure runs 3 seconds later, it prints the property and then gets released—breaking the last strong reference, so deinit gets called. No cycle here!
Can You Create a Reference Cycle Only Using DispatchQueue?
Short answer: No. DispatchQueue itself doesn’t store closures long-term (beyond the time they’re waiting to execute or running). To form a cycle with a queue, you need an intermediate object that creates the bidirectional strong reference chain.
Your edited DispatchWorkItem example is a perfect illustration of this—but the cycle isn’t with the queue, it’s with the DispatchWorkItem:
class C { var item: DispatchWorkItem! var name: String = "Alpha" func assignItem() { item = DispatchWorkItem { print(self.name) } } func execute() { DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: item) } deinit { print("deinit hit!") } }
The cycle here is:
self(C instance) strongly referencesitem(DispatchWorkItem)item’s closure strongly referencesself
The DispatchQueue is just executing the work item—it doesn’t hold a strong reference to it after execution. Even after the work item runs, the cycle remains because self still holds a strong reference to item, and item’s closure still holds a strong reference to self. That’s why the instance never deinitializes unless you set item = nil or use [weak self] in the closure.
Your Correct Observation: [weak self] Isn’t Just for Avoiding Cycles
You’re spot-on here—[weak self] has two key use cases:
- Prevent reference cycles: As we’ve covered, breaking that bidirectional strong reference chain.
- Control object lifecycle:
- Use a strong reference (default, or
[unowned self]if you’re sure the object will exist) when you need to guarantee the object stays alive until the closure finishes (e.g., completing a payment transaction even if the user navigates away from the view). - Use
[weak self]when you want to exit early if the object has already been deallocated (e.g., stopping a loading spinner if the view controller was already dismissed).
- Use a strong reference (default, or
Recap: Reference Cycle Creation Conditions
To sum up, a reference cycle forms if and only if:
- You have two or more reference-type objects (classes, not structs/enums)
- They form a closed loop of strong references (A → B → ... → A)
- No part of that loop is broken by releasing a strong reference (e.g., setting a property to
nil) or usingweak/unownedto break a link in the chain.
内容的提问来源于stack exchange,提问作者mfaani

