Block mutates a variable that is indirectly in it's scope

51 views Asked by At

This is the most minimal example I could think of:

#import <Foundation/Foundation.h>
@interface ObjCClass:NSObject
@property void (^aBlockInner) ();
-(void)method;
-initWithBlock:(void (^)())aBlock;
@end

@implementation ObjCClass
- (void)method{
    self->_aBlockInner();
}
-(instancetype)initWithBlock:(void (^)())aBlock
{
    self->_aBlockInner = aBlock;
    return self;
}
@end

struct cppClass{
    cppClass(){
        objCClassInstance = [[ObjCClass alloc] initWithBlock:^{
            modifyY();
        }];
        [objCClassInstance method];
    }

    void modifyY() { 
        y++;
    }

    int y = 0;
    ObjCClass* objCClassInstance;
};

int main()
{
    cppClass a{};
    NSLog(@"Y is:%d",a.y);
}

The member variable y is supposed to stay untouched as blocks are supposed to copy their “captures”. Though, the final print outputs:

Y is:1

Have I misunderstood Objective-C blocks?

To compile on a macOS do: clang++ main.mm -framework Foundation

1

There are 1 answers

0
The Dreams Wind On

Much more minimal example would be as follows:

struct MClass {
    int i{};
    
    MClass() {
        void(^block)() = ^{
            ++i;
        };
        block();
    }

};

int main() {
    MClass var;

    NSLog(@"%d", var.i);
}

This snippet has exactly the same "problem", and i member variable gets changed within a block. This happens, because when you refer to member variables from inside member functions, this implicitly adds this pointer to the expression, so it's not actually i member variable that gets captured, but this pointer which encompasses it. The block above can be equivalently rewritten like this:

void(^block)() = ^{
    ++this->i;
};

The block makes a copy of the pointer to the same object, not object itself. However, if you had the owning object referred to by value, and not a pointer, you would not be able in fact, alter the object, because the copies a block makes are constant:


struct MClass {
    int i{};
};

int main() {
    MClass var;

    void(^block)() = ^{
        // Compile-time error: Read-only variable is not assignable
        ++var.i;
    };
    block();

    NSLog(@"%d", var.i);
}

And this can only be done with use of the __block modifier:

__block MClass var;
void(^block)() = ^{
    ++var.i;
};