2

Using self. in blocks causes retain cycles, so I need to create a reference to weakSelf. I understand this

BUT!

If from my block I call a method which uses self", does this too cause a retain cycle? For instance, if I reload a UITableView from a block and in some of my UITableView delegate methods I call self., am I causing a retain cycle? Does that mean I have to pass around this weak reference everywhere? Seems hokey.

jscs
  • 63,694
  • 13
  • 151
  • 195
MobileMon
  • 8,341
  • 5
  • 56
  • 75
  • it depends on e.g. who is responsible to release / keep alive the block. – holex Dec 19 '14 at 16:41
  • 1
    No, you are not. If the `self` is not used directly from the block then the `self` is not captured. The best thing is to stop thinking about retains and start thinking about ownership and capturing. If the block doesn't own the `self` reference then you are okey. Methods don't capture anything, using `self` in them has a different meaning than using `self` in a block. – Sulthan Dec 19 '14 at 16:58
  • 1
    References to `self` in blocks don't automatically cause retain cycles. Cycles occur only if the block itself is retained directly or indirectly by the object `self` refers to. – jlehr Dec 19 '14 at 17:01

4 Answers4

5

I might be misreading your question, but your wording:

If from my block I call a method which uses "self.", does this too cause a retain cycle? For instance if I reload a UITableView from a block and in some of my UITableView delegates I call "self.", I'm causing a retain cycle? That means I have to pass around this weakReference everywhere?

suggests you are misunderstanding what self is. Hopefully if so the following will help and not hinder your understanding...

What is self?

The identifier self is just the name of one of the parameters to your method, it is just passed implicitly rather then explicitly like the other parameters. For example if you have a class:

@implementation MyClass

- (void) someMethod:(NSInteger)value
{
   ... self ... value
}

@end

then the method is effectively (i.e. bending the facts just a little for clarity):

- (void) someMethod:(NSInteger)value withObject:(MyClass *)self
{
   ... self ... value
}

When an instance method is called the value passed for the self parameter is a reference to the instance the method should operate on, e.g. the call

MyClass *someInstance = ...

[someInstance someMethod:42];

is effectively a call to:

someMethod:42 withObject:someInstance

Strong reference cycles

An object - which includes both instances of classes and blocks - is kept alive as long as there exists a strong reference to the object.

If an object A holds a strong references, e.g. in an instance variable or property, to an object B, then B will be kept alive at least (there could be other strong references to B) as long as A is alive.

If an object A holds a strong reference to B and B holds one to A then you have a strong reference cycle - each object is keeping the other alive and neither will ever be collected. This could lead to a memory leak - where unused memory is never collected - unless both A and B are meant to live from creation till the end of the program.

Further, you do not create a strong reference cycle simply by having references stored in the local variables and parameters of methods. By their nature such variables, and therefore their contents, are transient and are destroyed when the method returns.

Using self in blocks

using "self." in blocks causes retain cycles so I need to create a reference to weakSelf.

Not quite. When you use self in a block, either directly or indirectly by referencing an instance variable, then the compiler will warn you that you may create a reference cycle. (Note: There are other ways to create reference cycles, both with and without using blocks, and the compiler will not warn you at all. Managing cycles is just something you need to be aware of.)

You will only actually create a cycle if you store a reference to the block in the object referenced by self. However this is not bad in itself, as long as at some point you break the cycle manually - say by storing nil in the variable referencing the block - the cycle need not be problematic at all.

Finally...

You have nothing per se to worry about with your:

UITableView delegates I call "self."

as that self is just a local parameter to the delegate whose initial value, at some point going back up the call chain, came from you evaluating your weakSelf reference and determining that it was not nil and then calling methods on it.

HTH

CRD
  • 52,522
  • 5
  • 70
  • 86
  • "You will only actually create a cycle if you store a reference to the block in the object referenced by self" - Thanks for this! If you add a simple pseudo example to what you said in that one single line, it would be awesome :) – Ayyappa May 06 '20 at 15:22
  • @Ayyappa – Instance `a` calls a method on instance `b`; the called method takes a block argument which it will use *after* the method has returned so it stores it in an strong instance variable/property; `a` passes a block which references itself via `self` => you have a strong reference cycle: `a` references `b`, which references the block, which references `a`. – CRD May 07 '20 at 10:10
  • @Ayyappa – Instance `a` calls a method on instance `b`; the called method takes a block argument which it will only use *during* the method; `a` passes a block which references itself via `self` => you have a *temporary* cycle: `a` references `b`, `b` has a reference to the block stored in a *parameter* (which is just a *local variable*), the block references `a; that cycle goes as soon as `b`'s method returns when the parameter variable is destroyed. HTH – CRD May 07 '20 at 10:14
3

First of all: self does NOT cause a retain cycle. This is an urban legend. The incorrectness is obvious: A single reference can never cause a cycle. The usage of self inside a block causes a retain cycle, if the block is directly or indirectly referred by self, too, for example via a property.

To your Q:

If you "call" a method inside the block, the message probably has the receiver self, so you have a usage of self inside the block. For this reason it is captured and retained.

If you really have no usage of self inside the block by neither using self nor using a property of self you do not have a usage of self and it is not captured, therefore not retained. But in this case you can have a dangling pointer or a nil'd reference.

Amin Negm-Awad
  • 16,582
  • 3
  • 35
  • 50
  • Agreed. By the way, regarding references to `self` inside the block, you also have to be wary of referencing ivars inside the block, too, as that also has an implicit reference to `self` (e.g. referring to `someIvar` is equivalent to `self->someIvar`). – Rob Dec 19 '14 at 17:12
  • It's not a cycle, still it's retained. This is no urban legend, it's just wrong wording – dogsgod Dec 19 '14 at 19:21
  • I do not think so, that it is only wording. The people really think, that it is a cycle. It they would mean retaining, they would be glad. But they are not, because they think, it is always a cycle. – Amin Negm-Awad Dec 19 '14 at 20:34
2

You don't need to worry about references while the block is executing - eventually it finishes doing whatever it does, and all these references go away.

What you need to worry about are the references that are captured when the block is created. These references stay until the block goes away. So if your block has a reference to "self", that reference is there just because the block exists. And if you store that block in a property of self, you have a cycle.

So if you store a block as a property in self, then the block shouldn't capture self. That's easily done by letting it access and capture a weak copy of self. Remember that when the block is executing, the weak copy of self may be nil. Which means the self object has already left our world, and your block might not need to do anything.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • I disagree with this part: "eventually it finishes doing whatever it does". What if it doesn't? What if this is a success completion block to some server call that never succeeded? – cpt.neverm1nd Dec 19 '14 at 17:14
  • Well then you have a deeper architecture problem with your code. – KPM May 26 '15 at 10:34
1

Short answer: no, in this situation self is not retained.

Long answer

First of all, retaining self and a reference cycle are not the same thing. Reference cycle is a cycle of strong references between a number of objects: A->B->C->A is a retain cycle.

The general idea is, you want to always guarantee that if you are referencing self in a block, you don't reference this block strongly, and don't reference it through a chain of strong references. In reality, retain cycles can be used purposefully if you are making sure you're breaking the retain cycle under certain conditions. Not that I personally recommend this.

Take a look at documentation on Apple's website. It clearly states that values are captured in blocks, and capturing an object reference retains this object in block.

Basically what this means is that referencing an object in a block increments its retainCount by 1, and when this block gets deallocated, retainCount is decremented by 1.

However, when using a __weak pointer in a block, the retain count is untouched.

Here's an example:

- (void) doSomething {
   NSLog(@"%@", self);
}

- (void) callBlock {
   __weak typeof(self) weakSelf = self;
   dispatch_block_t block = ^{
      [weakSelf doSomething];
   };
}

When you write [obj method:params] this actually translates into following call: objc_msgSend(obj, @selector(method:), params). One of the features of Objective-C is that if you call a method on nil pointer, it returns nil. This is guaranteed by the fact that objc_msgSend(nil, @selector(anyselector), ...) always return nil. Note that SEL is just a const char[] under the covers, so it doesn't affect retain counts by any means.

Hence when the block will be executed, if your object was deallocated, the weak weakSelf variable will be nullified, and the block's body will translate into objc_msgSending to zero, which does nothing except of wasting few CPU cycles.

To sum it up, the Objective-C messaging system is implemented in such a way that calling a method does not retain this object or this method or this method's implementation, as it's a simple function call.

cpt.neverm1nd
  • 264
  • 5
  • 7