0

I saw a solution to preventing block retain cycles here

But I am having trouble wrapping my head around why or even how it works.

In the example, a weak self reference is made and acted upon. I can see how this Breaks the cycle. However, within the block a strong reference is created. Wouldn't this recreate the retain cycle that we were trying to prevent in the first place?

Let's say for example that self is 0x123 Then weakself is also pointing to 0x123. Then strongSelf would get set to 0x123 inside the block.
Wouldn't this make a retain cycle?(self has strong reference to block and strongSelf has a strong reference to self)

Community
  • 1
  • 1
DerrickHo328
  • 4,664
  • 7
  • 29
  • 50

2 Answers2

1

Let's say for example that self is 0x123 Then weakself is also pointing to 0x123. Then strongSelf would get set to 0x123 inside the block. Wouldn't this make a retain cycle?

Actually, no; they do not all point directly at the same thing. The fact is that an ARC weak reference really does (behind the scenes) interpose an extra object into the mix. Let's call it a scratchpad object. It gives the illusion that we are pointing at the same object, but we are actually pointing through the scratchpad object, which does not retain the thing it points to. That's how ARC weak references work (also known as zeroing weak references); they involve special extra go-between scratchpad objects that can be retained without themselves retaining, thus breaking the chain.

In a retain cycle, A has a retain on B and B has a retain on A. Each will put a release on the other in its own dealloc, but that moment will never come.

In the block situation, A is self and B is the block. self put a retain on the block for whatever reason it did so (often rather obscure, having to do with copying, observers, etc.; it doesn't always happen, and in fact it happens much more rarely than most people seem to suppose). The block put a retain on self merely by virtue of the fact that it is a closure and self is referred to with in the block.

But by doing the weak-strong dance, we prevent this. What passes into the block is weakself, which is actually a reference through the scratchpad object. The block can retain this, but it, in turn, does not retain self. Hence there is no retain cycle.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • See, for example, https://mikeash.com/pyblog/friday-qa-2010-07-16-zeroing-weak-references-in-objective-c.html which shows how ARC weak references can work under the hood. See also the WWDC video from the year ARC was introduced, where the scratchpad objects are explicitly mentioned. – matt Jul 07 '14 at 21:19
  • How they work is not as important as the meaning behind them, which this answer obfuscates. – Steven Fisher Jul 07 '14 at 21:37
  • @StevenFisher No other kind of answer can explain what the OP asks, namely, how the debugger can make it appear that `self`, `weakself`, and `strongself` all point at the same thing and yet the retain from the block does not pass all the way through to that object. The answer is that ARC weak references are a conjuring trick, and I am showing you what is up the magician's sleeve. – matt Jul 07 '14 at 22:05
  • It *is* useful to understand the magic, but it looks like you're answering "Actually, no" to the question "Wouldn't this make a retain cycle?" Clearly, there is a retain cycle created while the block is actually holding a strong reference to the original `self` via `strongSelf`? Can you edit your answer to make it clear you're disagreeing with the pointer values, not the presence of a (temporary) retain loop? Or is that what you disagree with? – Jesse Rusak Jul 07 '14 at 22:24
1

Within the block a strong reference is created. Wouldn't this recreate the retain cycle that we were trying to prevent in the first place?

Yes, it does, but only temporarily. When strongSelf is initialized, it forms a strong reference to the current weakSelf value. (Or nil, if that object has been deallocated.) Once the block finishes running, this (local) strong reference will be released, breaking the cycle.

The problem is not a retain cycle per se (they happen all the time), but a long-lived retain cycle that keeps its objects alive for longer than expected.

Jesse Rusak
  • 56,530
  • 12
  • 101
  • 102
  • 1
    Why would the block be able to "release its local strong reference" when "it finishes running" in the `weakself` case but not in the retain cycle case? After all, "it finishes running" and has a "local strong reference" just as much in both situations. – matt Jul 07 '14 at 21:02
  • @matt Because in the weakself case, the strong reference is a temporary local value which goes out of scope when the block returns. In the case where the reference from the block to self is strong, the block maintains a strong reference to self for as long as the block itself is allocated. (In case the block is called again, for example.) – Jesse Rusak Jul 07 '14 at 21:05
  • If that were all it takes, then saying `id myOtherSelf = self` would solve the problem, since now `myOtherSelf` is a temporary local value that goes out of scope. – matt Jul 07 '14 at 21:07
  • @matt Sorry, I'm not being clear. When I say that the block releases its local strong reference when it "finished running", I'm talking about the `strongSelf` local being released when the *block* finishes running. In the case you suggest, the `myOtherSelf` is not local to the block; the block would keep a strong reference to its value for the block's lifetime and so make a (more permanent) retain loop. – Jesse Rusak Jul 07 '14 at 21:14
  • But `weakself` isn't local to the block either. You have not explained the difference. And the reason you're not explaining the difference is that what you're saying is _not_ the difference. The difference is the nature of weak references - not something to do with local vs. global references. – matt Jul 07 '14 at 21:15
  • @matt I've tried to clarify my answer. I think you're trying to get at the fact that the weakself is not a strong reference, but I think the asker understands this. Yes, I'm assuming you understand that when a block closes over `weakself`, it does not form a strong reference to the target object. The question seems, to me, to be about what happens when that weak reference is turned into a strong one. – Jesse Rusak Jul 07 '14 at 21:18
  • But your answer is still wrong. The block does _not_ release its reference to `weakself`; that reference is bound into the closure and lives as long as the block does. "When the block finishes running" is totally irrelevant. What's important is solely that `weakself` is itself a weak reference to `self`! – matt Jul 07 '14 at 21:23
  • @matt I think my edit makes it fairly clear that it is the `strongSelf` reference that is broken when the block returns. Yes, the weakself reference is not broken for the lifetime of the block, but that's OK, because it is a weak reference. – Jesse Rusak Jul 07 '14 at 21:28