0

I have read that If you assign a new object to a weak property, the object will be released after the assignment. Even the warning from the compiler is same.

@interface RetainCycleObjCViewController ()
{
}
@property (nonatomic, weak) void (^weakBlock)(void);
@end

@implementation RetainCycleObjCViewController
- (void)viewDidLoad {
    [super viewDidLoad];

 _weakBlock = ^void{
        NSLog(@"Execution inside a weakBlock");
    };

_weakBlock();
}
@end

I get the same warning for weakBlock: Assigning block literal to a weak variable; object will be released after assignment

But when I execute the _weakBlock() in next line, it still prints the statement. How is it possible? because the newly created block object should have been removed from memory by now, given the 0 reference count?

nr5
  • 4,228
  • 8
  • 42
  • 82
  • 1
    It's not going to deallocate until it goes out of scope.. `NSBlock` can be allocated on the stack OR heap (when copied).. but a local block is stack allocated and will only deallocate when it goes out of scope. – Brandon Oct 21 '18 at 12:40
  • @Brandon's comment is correct, in general, but in this particular example, that block will be a global and will be "live" well beyond that scope. – bbum Oct 21 '18 at 14:52

1 Answers1

6

You aren't actually allocating a block in that code. The body of the block is all static content; nothing changes post compilation.

If you do this:

    _weakBlock = ^void{
        NSLog(@"Execution inside a weakBlock");
    };
    NSLog(@"%@", [_weakBlock class]);

You'll see this:

2018-10-21 09:33:48.827423-0500 kdkdkdkds[9367:1777827] __NSGlobalBlock__

The compiler recognized the block doesn't capture anything that might change at runtime and, accordingly, created a global static block which will never be allocated.

As @Brandon said in a comment, if you declared a local variable in your scope and caused a stack block to be allocated, that wouldn't go away until the end of the scope.

@autoreleasepool {
    void __weak (^_weakBlock)(void);
    int k = 2;
    _weakBlock = ^void{
        NSLog(@"Execution inside a weakBlock (%d)", k);
    };
    NSLog(@"C; %@", [_weakBlock class]);
    NSLog(@"P; %p", _weakBlock);
}
2018-10-21 09:36:31.585458-0500 kdkdkdkds[9392:1780803] C; __NSStackBlock__
2018-10-21 09:36:31.586291-0500 kdkdkdkds[9392:1780803] P; 0x7ffeefbff4c0

Now, instance variable assignment changes the rules slightly because you are assigning out of scope.

@interface SpongeBob:NSObject
@end

@interface SpongeBob ()
{
}
@property (nonatomic, weak) void (^weakBlock)(void);
@end

@implementation SpongeBob
- (instancetype)init
{
    self = [super init];
    if (self) {
        int k = 2;
        _weakBlock = ^void{
            NSLog(@"Execution inside a weakBlock (%d)", k);
        };
        NSLog(@"C; %@", [_weakBlock class]);
        NSLog(@"P; %p", _weakBlock);
    }
    return self;
}
@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [[SpongeBob alloc] init];
    }
    return 0;
}

2018-10-21 09:43:39.518545-0500 kdkdkdkds[9480:1787889] C; __NSStackBlock__
2018-10-21 09:43:39.518883-0500 kdkdkdkds[9480:1787889] P; 0x7ffeefbff488

Wait. What? If that value is really being assigned to the ivar, then this is a ticking time bomb. So, let's try something:

If we add:

- (void)squarePants
{
    NSLog(@"C; %@", [_weakBlock class]);
    NSLog(@"P; %p", _weakBlock);
}

And then call it:

    SpongeBob *bob = [[SpongeBob alloc] init];
    [bob squarePants];

It crashes, as expected, because the stack block is no longer valid as init's scope

(This seems rather mean. Then again, if you eliminate the warning, the crash goes away.)

Now, if you copy the block on assignment, then the immediate deallocation on assignment does kick in:

    _weakBlock = [^void{
        NSLog(@"Execution inside a weakBlock (%d)", k);
    } copy];

2018-10-21 09:48:04.295762-0500 kdkdkdkds[9510:1792549] C; (null)
2018-10-21 09:48:04.296167-0500 kdkdkdkds[9510:1792549] P; 0x0
bbum
  • 162,346
  • 23
  • 271
  • 359
  • "It crashes, as expected" – do you slander the Obj-C compiler? ;-) I think what your sample shows is a corner-case bug in the compiler. Eons ago now Apple finally said you didn't need to put `copy` on block properties any longer as finally the compiler moves stack blocks to the heap as needed automatically; and assigning to an outer-scope variable is one where the move is needed. Change that weak property to a strong one and the move occurs, leave it as weak and change the assignment to: `void (^block)(void) = ^void{...}; _weakBlock = block;` and the move occurs. (cont...) – CRD Oct 22 '18 at 14:46
  • (...cont) When the compiler is figuring out it needs to throw out the warning it should also notice its about to store a stack block reference somewhere it shouldn't and handle it but it misses this corner case. Compiler bug, please report. (And please no-one don't claim this isn't a bug, `localvar temp = a; b = temp;` should have the same observable semantics as `b = a;`) – CRD Oct 22 '18 at 15:00
  • "no-one don't" oops :-( – CRD Oct 22 '18 at 17:05
  • @CRD I filed a bug when I wrote this capturing that particular corner case. – bbum Oct 23 '18 at 15:07
  • Without some serious shenanigans, I don't see an obvious path via which the block gains a strong reference in that code path. An object? Sure; internally, it could create a strong reference to self, causing the weak assignment to remain valid beyond the scope. I suppose because the stack block survives beyond the weak assignment, one might play shenanigans internally to cause the block to become strongly referenced, but that'd imply a copy which would mean the value weakly assigned is invalid. – bbum Oct 23 '18 at 15:12
  • @bbum What I could get out of your answer is, even if it is a weak reference to the block, its life will stay intact until the `init's` scope or `didload` scope in my case. Similar to your `squarePants` method I called the `weakBlock` on another method. But it still executes the NSLog statement without any crash. Am I missing something? – nr5 Nov 01 '18 at 05:33
  • @nr5 Your block isn't on the stack or heap at all. That is the first part of this answer's discussion. Like `@"foo"` constant strings, blocks that are immutable at runtime-- don't capture state-- actually statically compiled and will be valid for the entire app session. – bbum Nov 01 '18 at 06:41