4

I am using automatic reference counting. And I want object to live until some callback will be executed:

 Foo *obj = [[Foo alloc] init];
 [obj someMethod: @"AAA", ^(NSError * _Nullable error) {
    //callback
  });

I need obj to be alive until "callback" will be called, but I don't actually use it in callback. For now I "solve" it with:

[obj someMethod: @"AAA", ^(NSError * _Nullable error) {
    //callback
    NSLog(@"To make sure that obj alive print it: %@", obj);
  });

But this looks weird. May be there is some language construction for such case, or there is some typical workaround for this except printing to log?

user1244932
  • 7,352
  • 5
  • 46
  • 103
  • @matt Doesn't help, with long enough delay between call of `someMethod` and calling callback `obj` is destroyed no matter how I define it `__block Foo *obj = ` or just `Foo *obj = ` – user1244932 Sep 30 '20 at 01:36
  • Why do you need it? – Roman Podymov Sep 30 '20 at 09:13
  • @user1244932: declaring ```__block Foo *obj;``` doesn't change the behaviour, if ```obj``` isn't actually referenced inside the block. If it is referenced, then ```__block``` isn't needed to keep ```obj``` alive. – kisch Sep 30 '20 at 09:34
  • I think it's only depends on `Foo` class and it's methods implementation. `Foo` should retain self until callback call – Cy-4AH Sep 30 '20 at 09:41
  • @RomanPodymov Because of program do not work properly if `Foo` object would be destroyed before callback would be called. – user1244932 Sep 30 '20 at 10:15
  • @user1244932 Could you please add more code? It is interesting to know how to reproduce this issue. – Roman Podymov Sep 30 '20 at 17:13
  • @RomanPodymov I suppose you can reconstruct full picture from my other question: https://stackoverflow.com/questions/64128421/wkwebview-in-background-several-strange-assertion – user1244932 Oct 01 '20 at 19:18

2 Answers2

2

If you use the outer variable obj inside the block, then its value is captured into the block. This creates an additional reference to the object, so that automatic reference counting keeps it alive until the block has executed.

To keep that reference, you don't need to pass it to another function, like NSLog(). It's sufficient to just store it in a local variable.

Foo *obj = [[Foo alloc] init];
[obj someMethod: @"AAA", ^(NSError * _Nullable error) {
    Foo* keepObj = obj; // keeps obj alive
    //callback
});

Edit: improved solution after feedback from skaak: the unused local variable name is not needed, and you can signal the intention with a cast to void.

Foo *obj = [[Foo alloc] init];
[obj someMethod: @"AAA", ^(NSError * _Nullable error) {
    //callback
    (void) obj; // release reference that kept obj alive
});
kisch
  • 414
  • 2
  • 6
  • But this would be "unused variable". So compiler in release mode can just remove it? – user1244932 Sep 30 '20 at 02:27
  • In this case, the compiler can't optimise away the variable, because it would have side effects - specifically, keeping and releasing the object reference. I tested it: it stays. Apart from that, the warning "unused variable" tells you that this way of keeping the object alive is not very expressive; one should at least comment the intention. – kisch Sep 30 '20 at 02:37
  • @kish - I am thingking, you can just do ```obj;``` inside block. Can you test that too? I am sure compiler will complain but I think it will do. This whole thing is about variable going in and out of scope right. You can do all sorts of nice things with that in Obj-C such as this guy does https://github.com/jspahrsummers/libextobjc/blob/master/README.md – skaak Sep 30 '20 at 06:45
  • @skaak: yes, your proposal works. I like it because it doesn't introduce the extra local variable name. You can even do ```(void) obj;```, which kind of nearly writes out the intention. – kisch Sep 30 '20 at 09:24
  • @kisch - thanks I appreciate you testing it and giving the feedback ... I would not do the ```void``` thing, that is for those who do not know how to turn off compiler warnings ... thanks, I see you also updated your answer. – skaak Sep 30 '20 at 10:05
1

To manually manage a life cycle of your object you can capture it by setting nil inside the block e.g.:

__block Foo *obj = [Foo new];
[obj someMethod:@"AAA" block:^(NSError * _Nullable error) {
    NSLog(@"start block");
    
    ...
    
    obj = nil;
    NSLog(@"end block");
}];

NSLog(@"finish");


Prints:

start block
Foo dealloc
end block
finish
iUrii
  • 11,742
  • 1
  • 33
  • 48