3

Right now I have something like this:

- (void)viewDidLoad
{
    MyObject *myObject = nil;
    @autoreleasepool
    {
        myObject = [[MyObject alloc] init];

        [myObject doSomethingWithBlock:^{
            NSLog(@"Something Happened");
        }];
    }

    NSLog(@"End of method");
}

And the doSomethingWithBlock: has the following:

- (void)doSomethingWithBlock:(void(^)())aBlock
{
    [self performSelector:@selector(something:) withObject:aBlock afterDelay:4.0f];
}

And the something:

- (void)something:(void(^)())aBlock {
    aBlock();
 }

I understand that the block is being passed between stacks frames, so it's kept alive until it actually is executed and it is disposed. But why when the "End of method" is called does myObjct still exists? I know that I am not referencing the object from within the block, so shouldn't it be released within the autorelease pool? Or is this a compiler detail (if it should actually release it there, or after the method returns), and I shouldn't care about it?


Someone pointed out that performSelector will retain the target so I used this instead:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 4 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    aBlock();
});
Rui Peres
  • 25,741
  • 9
  • 87
  • 137
  • It is declared outside of the autorelease pool, so it should exist after it. It's all about scope where the object is declared, not allocated. By your logic class variables allocated inside an autorelease pool should be nil'd at the end of the pool and not usable elsewhere. – Putz1103 Mar 04 '14 at 17:26
  • @Putz1103 could you put it as an answer? it makes sense. Thanks. – Rui Peres Mar 04 '14 at 17:29
  • `performSelector` retains the object until the selector has been executed (see http://stackoverflow.com/questions/4414699/the-effect-on-retain-count-of-performselectorwithobjectafterdelayinmodes). This is unrelated to the autorelease pool. – Martin R Mar 04 '14 at 17:39
  • @MartinR check the edit. The behaviour is the same with `dispatch_after` – Rui Peres Mar 04 '14 at 17:41
  • @JackyBoy: With dispatch_after, the object *is* deallocated before the block is called. Override `dealloc` in your `MyObject` class and set a breakpoint to verify. – Martin R Mar 04 '14 at 17:44
  • @MartinR you are right. The dealloc is being called before the block. – Rui Peres Mar 04 '14 at 17:46
  • @JackyBoy: So should we close this as a duplicate of http://stackoverflow.com/questions/4414699/the-effect-on-retain-count-of-performselectorwithobjectafterdelayinmodes? – Martin R Mar 04 '14 at 17:48
  • @MartinR yes, my first question was with the lifecycle of the block, which then I saw it was related to the fact of being passed between stack frames. Thanks. – Rui Peres Mar 04 '14 at 17:49
  • Possible duplicate of [the effect on retain count of performSelector:withObject:afterDelay:inModes](http://stackoverflow.com/questions/4414699/the-effect-on-retain-count-of-performselectorwithobjectafterdelayinmodes) – Martin R Mar 04 '14 at 17:51
  • What is the question here? The title refers to "block lifecycle", the statement "I understand that the block is being passed between stacks frames, so it's kept alive" isn't correct, which is what @RobMayoff explained well in his short lived and now deleted answer because he thought he had misunderstood the question... – CRD Mar 04 '14 at 18:00

2 Answers2

1

It is not clear to me whether the question is about the lifetime of the block, as per the title, or the lifetime of the object referenced by myObject, so we'll cover both.

First, the stack allocation of blocks in an optimisation and something programmers should not really need to be aware of. Unfortunately when blocks were first introduced the compiler was not smart enough to handle this optimisation completely automatically, hence the need to know about Block_copy() etc. But the compiler is now much smarter and the programmer can pretty much forget about stack allocation of blocks.

This answer applies to Xcode 5.0.2/Clang 4.2. Use a different (earlier) version and YMMV.

Second, the block:

^{ NSLog(@"Something Happened"); }

is not actually stack allocated at all. As it references no values from the environment it is statically allocated and never needs to be placed on the stack or moved to the heap - another optimisation. To get a stack allocated block just change it to:

^{ NSLog(@"Something Happened: %p", self); }

which will be stack allocated (and don't worry about possible retain cycles, that's another topic...)

With that out of the way, let's look at the lifetimes:

The object:

The variable myObject has a lifetime of at most the call to viewDidLoad, the variable is created on entry to the method and destroyed on exit, if precise lifetime semantics are in force, or earlier if not needed and precise lifetime semantics are not in force - the latter is the default allowing the compiler to optimise storage use (independent of ARC). By default local variables have a strong ownership qualifier, so an ownership interest is asserted for any reference stored in one. ARC will release that ownership interest when another reference is stored in the variable or when the variable itself is destroyed. The latter will happen in this case and the variable is destroyed at some time after the last use of it and the end of the method - depending on how the compiler does its optimisations.

All this means that the object referenced by myObject may or may not be still around at your call to NSLog(). In the case of using performSelector:withObject:afterDelay: that call will assert ownership over the object until after the selector has been executed -= so the object will live. In the case of using dispatch_after the object is not required and so will not stay alive.

The block:

As mentioned above as written the block is statically allocated so lifetime is easy - it always exists. More interesting is modifying it as above to make it stack allocated.

If the block is stack allocated it will exist in the stack frame of the viewDidLoad call and unless moved or copied to the heap it will disappear when that call returns.

The call to doSomethingWithBlock: will be passed a reference to this stack allocated block. This is possible as doSomethingWithBlock: expects a block and code within it can move it to the heap if needed as it knows it is a block.

Now it gets interesting. Within doSomethingWithBlock: in the performSelector:withObject:afterDelay: case the block is passed as the withObject: parameter which is of type id - which means the called code expects a standard Objective-C object and those live on the heap. This is a case of type information loss, the caller has a block, the callee only sees an id. So at this point the compiler inserts code to copy the block to the heap and passes that new heap block to performSelector:withObject:afterDelay: - which treats it as it would any other object. [Older compilers did not always do this, the programmer had to know that type information loss was about to occur and manually insert code to move the block to the heap.]

In the case of dispatch_after, the function argument is block typed so the compiler just passes a stack allocated block on. However the dispatch_after function copies the block to the heap, as per its documentation, and there is no problem.

So in either case a stack allocated block is copied to the heap before viewDidLoad finishes and its stack frame is destroyed taking with it the stack allocated block.

HTH.

CRD
  • 52,522
  • 5
  • 70
  • 86
  • 1
    "So at this point the compiler inserts code to copy the block to the heap and passes that new heap block to performSelector:withObject:afterDelay:" It's also not guaranteed by the ARC specification. So this behavior is implementation-specific. Plus it is not officially documented AFAIK. – newacct Mar 05 '14 at 06:51
  • @CRD thanks for the answer. The title and the question are not clear, so I will change it asap. One thing. You say here: `As mentioned above as written the block is statically allocated so lifetime` but above you say: `is not actually stack allocated at all. As it references no values from the environment it is statically allocated and never needs to be placed on the stack or moved to the heap`. Which is it? – Rui Peres Mar 05 '14 at 08:43
  • @JackyBoy - Yes its a bit tortuous isn't it :-( The block *as written in the question* makes no references to its environment so the compiler can, and does, statically allocate it (i.e. it compiles a constant representation of the block, just like it can compile constant `NSString`s). However if you modify the block to reference something, I picked `self` above, then it will be stack allocated - and that is the interesting case. Hope that's better! – CRD Mar 05 '14 at 08:50
  • @CRD can you give me a quick reference, where it says that a block, that doesn't make reference to its surroundings is statically allocated? – Rui Peres Mar 05 '14 at 09:50
  • 1
    @JackyBoy - See [Block Spec](http://clang.llvm.org/docs/BlockLanguageSpec.html). Under "The Block Type" a block may reside in "global memory". Under "Block Literal Expressions" a block literal "may may be used as the initialization value for Block variables at global or local static scope". None of this *requires* constant blocks to be statically allocated, it *allows* it - and standard compiler optimisation does the rest. Try assigning your "Something Happened" block to a static, then modify it as above to be non-constant and see what the compiler says. HTH – CRD Mar 05 '14 at 15:56
  • In reference to the very first comment, from @newacct, the answer was careful to include *This answer applies to Xcode 5.0.2/Clang 4.2. Use a different (earlier) version and YMMV*. For more details on what the compiler does, and the docs say, see [Some questions about objective-c block and copy](http://stackoverflow.com/questions/17000926/some-questions-about-objective-c-block-and-copy). – CRD Mar 08 '14 at 11:10
0

It is declared outside of the autorelease pool, so it should exist after it. It's all about scope where the object is declared, not allocated. By your logic class variables allocated inside an autorelease pool should be nil'd at the end of the pool and not usable elsewhere.

Example

-(void)function
{
    MyObject *object;  //MyObject declared here
    if(someBoolean)
    {
        object = [[MyObject alloc] init];  //MyObject allocated here
    }

    //This is outside the scope of allocation, 
    //but it's still inside the scope of declaration.
    //If ARC referenced the scope of allocation your 
    //object would not be valid here, as ARC would 
    //release it as out of scope.
}
Gavin
  • 8,204
  • 3
  • 32
  • 42
Putz1103
  • 6,211
  • 1
  • 18
  • 25
  • `[MyObject alloc] init]` does not create an autoreleased object, so this has nothing to do with the autorelease pool. – Martin R Mar 04 '14 at 17:40
  • This isn't completely correct. Declaration scope does matter of course, as you say, and once that scope is exited and the variable destroyed ARC will release the reference object. However ARC is also able to release a reference while a variable is still alive but after the last point of use of that variable. – CRD Mar 04 '14 at 17:45
  • @MartinR True, it is not an autoreleased object, but if you notice, this answer doesn't even have an autorelease pool in the example code, it is just talking about scope, and showing how the variable is declared outside of the scope that `@autoreleasepool` was creating. – Gavin Mar 04 '14 at 17:46