[Objective-C] A brief analysis of Block and its capture mechanism

Directory

    • Basic use of Block
      • Block’s statement
      • Implementation of Block
      • Block call
    • Block is used as a formal parameter
    • Block is used as an attribute
      • Give the Block an alias
      • Block copy
    • Block capture mechanism
      • local variables of type auto
      • Brief analysis of __block
      • static type local variables
      • global variables
    • other problems

Basic use of Block

What is Block?

Block (block), OC object that encapsulates function calls and calling environment, Objective-C closure (can access external values internally), equivalent to function pointer in C language, writes a function Inside a function, and OC does not have the syntax for nesting functions (methods)

Block’s statement

void(^blockName)();
int(^blockName2)(int a, int b, int c);

Format: Return value (^block name) (parameter list)
^The symbol representing the block

Implementation of Block

  1. No parameters, no return value
void(^blockName)(void) = ^{<!-- -->

};
  1. There are parameters and no return value
void(^blockName)(int a, int b) = ^(int a, int b){<!-- -->

};
  1. No parameters, return value
int(^blockName)(void) = ^int{<!-- -->
    return 3;
};

The return value of the implementation part can be omitted, like this:

int(^blockName)(void) = ^{<!-- -->
    return 3;
};
  1. There are parameters and return values
int(^blockName)(int a, int b) = ^int(int a, int b){<!-- -->
    return 3 + a * b;
};

The return value int of the implementation part can also be omitted

Block call

//No parameters and no return value
blockName();

//There are parameters and return values
int result = blockName(7, 12);

Now that the declared blockName represents a block, you can call this block either through blockName(7, 12);, or like this:

int result = ^(int a, int b) {<!-- -->
    return 3 + a + b;
}(7, 12);

Block is used as a formal parameter

The declaration of Block as a formal parameter in the method is slightly different from the above format (the name of the block is outside)

Now the following methods are implemented in the Jaxon class and the Jacky class respectively:
Jaxon.h

- (void)askJackyForHelp: (void(^)(int num))blockName isOK: (void(^)(BOOL boolValue))completion;

Jaxon.m

- (void)askJackyForHelp:(void (^)(int))blockName isOK:(void (^)(BOOL))completion {<!-- -->
    blockName(3);

    //The parameters passed into the completion block are either 1 or 0
    completion(arc4random() % 2);
}

Jacky.m

- (void)helpDoWith: (int)num {<!-- -->
    NSLog(@"Help %d times", num);
}

Next call in the main function:

Jaxon* jaxon = [[Jaxon alloc] init];
[jaxon askJackyForHelp:^(int num) {<!-- -->
            Jacky* jacky = [[Jacky alloc] init];
            [jacky helpDoWith: num];
        } isOK:^(BOOL boolValue) {<!-- -->

            //The probability of success and failure are each half
            if (boolValue) {<!-- -->
                NSLog(@"Help successful");
            } else {<!-- -->
                NSLog(@"Help failed");
            }
        }];

operation result:

Please add image description

Can this function as a proxy? Jaxon entrusts Jacky to help with tasks. Jacky is entrusted with things that Jaxon cannot implement. Therefore, the Block block can also be used for interface transmission. value or other programming that requires the use of proxy mode

Block is used as an attribute

Give the Block an alias

It was also mentioned at the beginning of the article that a block is actually an object and can be understood as a data type.

Then you can also use the typedef keyword to alias the Block. See the following example:

typedef void(^Help)(int num);
typedef void(^Finish)(BOOL boolValue);

The above method can also be declared like this:

- (void)askJackyForHelp:(Help)blockName isOK:(Finish)completion;

The attribute keyword of the block generally needs to be copy:

@interface Jaxon : NSObject

//no alias
@property (nonatomic, copy)void(^helpBlock)(int num);
//has alias
//@property (nonatomic, copy)Help helpBlock;
- (void)askMyselfDo;

@end

@implementation Jaxon

- (void)askMyselfDo {<!-- -->
    self.helpBlock(5);
}

@end

main function:

Jaxon* jaxon = [[Jaxon alloc] init];
jaxon.helpBlock = ^(int num) {<!-- -->
    NSLog(@"I did it myself %@ times", @(num));
};
[jaxon askMyselfDo];

operation result:
Please add a picture description

Block copy

Regarding the copy keyword, the editor also briefly understands it, and will analyze the underlying principles in detail later:

In the ARC environment, the compiler will automatically copy the block on the stack to the heap according to the situation, such as the following situations: : When manually calling the copy` method of block;

  • When block is used as a function return value (used a lot in the Masonry framework);
  • When assigning block to the __strong pointer;
  • When block is used as a method parameter in the Cocoa API whose method name contains usingBlock;
  • block as a method parameter of GCD API.

How to write block as an attribute:
Writing strong or copy under ARC will make a strong reference to the block and automatically copy the block from the stack to the heap;
It is recommended to write copy so that it is consistent under MRC and ARC.

Block is stored in the stack area when it is first created, and copy to the heap area when used.

Block capture mechanism

In order to ensure that Block can access external variables normally, Block has a variable capture mechanism.

Local variables of auto type

auto Variables: Normally defined variables are of auto type by default, but are omitted.

auto int age = 20;

Local variables of auto type will be captured inside the block block, and the access method is value transfer

int age = 10;
NSLog(@"%d %p", age, & amp;age);
void(^blockName)(void) = ^ {<!-- -->
    NSLog(@"%d %p", age, & amp;age);
};
age = 20;
//It can be printed out, indicating that the block block can access external information.
blockName();
NSLog(@"%d %p", age, & amp;age);

Please add a picture description
According to the running results, the following two points can be drawn:

  • When a local variable of type auto is captured inside a block, an identical member variable will be automatically generated inside the block to store the value of this variable, so the printed The age address outside the block is different from the internal age address.
  • Due to value transfer, modifying the value of the external age variable will not affect the variables inside the block.

A brief analysis of __block

External variables can only becalled inside the Block and cannot be modified:

Please add image description

By default, Block uses a read-only copy of captured external variables, so the value of external variables cannot be directly modified inside Block.

The solution is as follows:

  • The variable is modified with static (reason: capturing a local variable of static type is pointer passing, and the memory address of the variable can be accessed)
  • global variables
  • __block (We only want to use this variable temporarily and change it temporarily, but if we change it to static variables and global variables, they will always be in memory)

When a variable is modified by __block, block can modify external global variables:

__block int age = 10;
NSLog(@"%d %p", age, & amp;age);
void(^blockName)(void) = ^ {<!-- -->
    age = 30;
    NSLog(@"%d %p", age, & amp;age);
};
blockName();
NSLog(@"%d %p", age, & amp;age);

Please add a picture description

Local variables of static type

Local variables of static type will be captured inside the block, and the access method is pointer passing

static int age = 10;
NSLog(@"%d %p", age, & amp;age);
void(^blockName)(void) = ^ {<!-- -->
    NSLog(@"%d %p", age, & amp;age);
};
age = 20;
blockName();
NSLog(@"%d %p", age, & amp;age);

Please add image description

  • When a local variable of type static is captured inside a block, a pointer of the same type will be generated inside the block , pointing to the address of the age variable captured inside.
  • Due to pointer passing, modifying the value of the external age variable will affect the age variable inside the block

Global variables

Global variables will not be captured inside the block, and the access method is direct access

int _age = 10;
static int _height = 175;

int main(int argc, const char * argv[]) {<!-- -->
    @autoreleasepool {<!-- -->
        
        NSLog(@"%d %p", _age, & amp;_age);
        void(^blockName)(void) = ^ {<!-- -->
            _age = 30;
            NSLog(@"%d %p", _age, & amp;_age);
        };
        blockName();
        _age = 20;
        NSLog(@"%d %p", _age, & amp;_age);
    }
    return 0;
}

Please add an image description

Other questions

For local variables of object type, block will capture them together with their ownership modifier

Why do local variables need to be captured but global variables do not?

  • Because of the scope, global variables can be directly accessed anywhere, so there is no need to capture them;
  • Local variables cannot be directly accessed from the outside, so they need to be captured;
  • Local variables of auto type may be destroyed, and their memory will disappear. It is impossible for the block to access that memory when executing code in the future, so its value is captured;
  • Static variables will always be stored in memory, so just capture their address.