1

I'm currently thinking about how to prevent strong reference cycles when using blocks that retain self. The usual way seems to be to just use a weak reference to self:

@property (strong, nonatomic) NSOperationQueue *queue;

- (void)methodA {
    __weak id *weakSelf = self;
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf methodB];
    }];
    [self.queue addOperation:operation];
}

But what if methodB looks like this:

- (void)methodB {
    [self someOtherMethod];
}

Would this still cause a strong reference cycle? Or would methodB receive the weak reference to self from methodA as its reference to self? (That is, is methodB's reference to self just a strong reference to the weak reference from methodA?)

aus
  • 93
  • 1
  • 9
  • How is this a reference cycle given only one object is being used? – trojanfoe Dec 10 '12 at 13:21
  • My understanding is this: If the block had a strong reference to self and not a weak one, then the object referred to by self could not be freed because the block would hold a reference to it. The block on the other hand could not be freed because it is referenced by self (via queue.) The question is, does methodB with a syntactically strong reference to self re-introduce this cycle even though the block's reference is weak. – aus Dec 10 '12 at 14:26
  • you might be right, however it's not `free`d is it, it's `release`d (retain count--) so when the block terminates the object will be destroyed, so it'll *probably* be OK. – trojanfoe Dec 10 '12 at 14:28
  • I guess you could say deallocated instead of freed. I meant removing the object from memory after the retain count reached zero. – aus Dec 10 '12 at 14:34
  • @trojanfoe Lets assume the queue gets suspended and all strong references to the object (referred to by self) but the ones within the block are removed. – aus Dec 10 '12 at 14:40
  • But after the queue is resumed you'd want those objects to exist wouldn't you? – trojanfoe Dec 10 '12 at 14:43
  • There would be no way to resume the queue if the containing object isn't referenced to by anyone except the suspended blocks anymore. But that's not my problem. My question is more general: Does the weak reference really prevent a strong reference cycle even though methodB refers to a strongly referenced self? – aus Dec 10 '12 at 15:29
  • Switch on compiler warnings, and the compiler will tell you where exactly your code is wrong. – gnasher729 Feb 03 '15 at 18:00

2 Answers2

1

Or would methodB receive the weak reference to self from methodA as its reference to self? (That is, is methodB's reference to self just a strong reference to the weak reference from methodA?)

A method does not "receive" a strong or weak reference. It receives a reference. "Strong" or "weak" only apply to a variable (usually, an instance variable or variable captured by a block).

weakSelf is a weak reference in the block. Because weakSelf is a zeroing weak reference (__weak), either it points to a valid object, or its value is nil. If the former, it points to a valid object, and methodB is called on it. If the latter, sending a message to nil does nothing, so nothing happens.

You ask if the self in methodB is a strong reference. A strong reference means that it is retained, so if self were a strong reference in methodB, that means it retains self at the beginning of the method and releases it at the end. But why does it matter whether the method retains an argument such as self? Retain cycles refer to objects strongly referencing each other. A function or method will run and then stop; any retains they do on local variables must be temporary by the memory management rules, and have no effect on retain cycles. (The technical answer is no, self is not retained in ARC, and arguments including self are generally never retained in MRC. But as I said this really isn't relevant.)

newacct
  • 119,665
  • 29
  • 163
  • 224
  • That's not strictly true. In ARC, `self` *is* a strong reference. However, as an optimization, in a given method call, ARC simply borrows it's caller's reference to `self` instead of retaining/releasing it. This does mean that you can inadvertently dealloc `self` while it's in use by causing the only remaining reference to it to go out of scope (not counting the `self` variable), but that's a small price for the performance win of not retaining/releasing `self` all the time. – Lily Ballard Dec 11 '12 at 02:03
  • @KevinBallard: that's exactly what I would call a not-strong reference. For any other object parameter to a function that is a strong reference in ARC, it could not be inadvertently deallocated like that. – newacct Dec 11 '12 at 02:34
  • Theoretically, it is a strong reference. The fact that it doesn't maintain an active retain on `self` is merely an optimization. ARC is free to avoid spurious retains/releases on other strong variables too if it can prove that they aren't needed (e.g. the same object is in two variables that have the same lifetime, and the compiler knows that, so only one actually needs to be retained). – Lily Ballard Dec 11 '12 at 08:11
  • @KevinBallard: It's not "an optimization" if it behaves differently. With other strong variables, ARC retains them if it can't prove it isn't needed (so the behavior is indistinguishable from if it always retained it). With `self`, ARC explicitly never retains it, even when it can't prove it isn't needed. So the behavior with `self` is indistinguishable to something that is unsafe_unretained. – newacct Dec 11 '12 at 19:57
  • No it's not. Trivial counterexample: `self = nil` actually releases the old value of `self`. The behavior around `self` where it's not constantly retained/released is considered an optimization, one that unfortunately introduces the possibility of destroying `self` while it's not being used, but that is necessary for good performance. – Lily Ballard Dec 11 '12 at 20:06
  • @KevinBallard: `self = nil` is only allowed in `init` which is `ns_consumes_self`, so that's obviously different. I'm talking about any normal method. You keep talking about this stuff about "optimization", but an optimization, by definition, is something done that does not affect the behavior as defined by the spec, e.g. not retaining an object argument because it can prove that it does not make a difference. Thus, a strong object argument "accidentally being destroyed" cannot happen. The ARC spec specifically says that `self` is never retained even when it might be unsafe. – newacct Dec 12 '12 at 01:56
  • @KevinBallard: and anyway, I just said "self is not retained", which you must agree with. And this whole thing is irrelevant because the point of the whole answer is that whether it is retained or not is irrelevant to the question. – newacct Dec 12 '12 at 01:57
-1

I finally found a part in the documentation that kind of answers this question. So it really looks like methodB receives the block's weak reference to self.

aus
  • 93
  • 1
  • 9
  • Nope. Any read from a weak reference is evaluated as `objc_loadWeak(ref)`, which returns an autoreleased reference to the given variable, or possibly as `objc_loadWeakRetained(ref)` which returns a strong reference. Either way, the method is called on the object itself, not on a weak reference. – Lily Ballard Dec 11 '12 at 02:04