Demo portal: BlockTestApp

【 Abstract 】 In this article, first of all, in section 1, I introduce the definition of Block and its comparison with functions in C. Then, section 2 introduces a form of Block syntax that is often used in real-world development for future reference. You know how to use it but you don’t know when? So section 3 will cover the application scenarios of blocks. However, the improper use of Block led to Crash? Therefore, it is necessary to understand the properties of Block capture variables and the resolution of circular references in Section 4. In addition, don’t be lazy. Blocks are weak when you come across them, and distinguish between those that don’t cause circular references. However, if you are unfamiliar with the memory mechanisms of blocks, you can also Crash, so section 5 covers the memory mechanisms of blocks. That’s enough. However, do you want to learn more about how blocks are implemented? Section 6 will briefly introduce clang compilation and Block implementation and its principles.

1. Introduction


Block: Anonymous functions with automatic (local) variables. It is an extension of the C language. It is an extension because C does not allow such anonymous functions.

1.1 Anonymous Functions

An anonymous function is a function that does not carry a function name. What does a function look like in C? Something like this:

int func(int count);
Copy the code

When invoked:

int result = func(10);
Copy the code

Func is its function name. It is also possible to call a function from a pointer, without seeming to use the function name:

int result = (*funcptr)(10);
Copy the code

In practice, when assigning to a function pointer, the address of the function must be obtained by the name of the function. The complete steps should be:

int (*funcptr)(int) = &func;
int result = (*funcptr)(10);
Copy the code

With blocks, you can use anonymous functions, functions that don’t have a function name.

1.2 with automatic variables

On the meaning of “with automatic variables (local variables)”, this is because blocks have the ability to capture external variables. When an external local variable is accessed in a Block, the Block holds its temporary state and automatically captures the value of the variable. Changes to the external local variable do not affect its state.

To capture external variables, look at a classic block interview question:

int val = 10; 
void (^blk)(void) = ^{
    printf("val=%d\n",val);
}; 
val = 2; 
blk(); 
Copy the code

In this code, the output value is val = 10, not 2.

When a block is implemented, it makes a read-only copy of the stack variable defined in the method to which it refers, and then uses that copy within the block. In other words, a block intercepts the instantaneous value of an automatic variable; Or the block captures a copy of the automatic variable.

Since a block captures the instantaneous value of an automatic variable, overwriting the value of an automatic variable used in a block after executing the block syntax does not affect the value of the automatic variable when the block is executed.

So, the answer to the above interview question is 10, not 2.

Another way to solve the problem that blocks cannot modify the value of automatic variables is to use the __block modifier.

__block int val = 10;  
void (^blk)(void) = ^{printf("val=%d\n",val); }; val = 2; blk();Copy the code

The above code only has an extra __block modifier compared to the first code snippet. But the output is 2.

2. 2


Convention: The meanings of the symbols in usage are listed as follows:

  • return_typeRepresents the returned object/keyword, etc. (can be void and omitted)
  • blockNameRepresents the name of the block
  • var_typeRepresents the type of the argument (can be void and omitted)
  • varNameIndicates parameter name

2.1 Syntax of Block declaration and definition, and its distortions

(1) Standard declaration and definition
return_type (^blockName)(var_type) = ^return_type (var_type varName) {
    // ...
};
blockName(var);
Copy the code
When the return type is void
void (^blockName)(var_type) = ^void (var_type varName) {
    // ...
};
blockName(var);
Copy the code

To write with ellipsis

void (^blockName)(var_type) = ^(var_type varName) {
    // ...
};
blockName(var);
Copy the code
(3) When the parameter type is void
return_type (^blockName)(void) = ^return_type (void) {
    // ...
};
blockName();
Copy the code

To write with ellipsis

return_type (^blockName)(void) = ^return_type {
    // ...
};
blockName();
Copy the code
(4) When both the return type and the argument type are void
void (^blockName)(void) = ^void (void) {
    // ...
};
blockName();
Copy the code

To write with ellipsis

void (^blockName)(void) = ^{
    // ...
};
blockName();
Copy the code
(5) the anonymous Block

A Block is an anonymous Block that has no blockName:

^return_type (var_type varName)
{
    //...
};
Copy the code

Typedefs simplify Block declarations

Use typedef to simplify the declaration of a Block:

  • The statement
typedef return_type (^BlockTypeName)(var_type);
Copy the code
  • Example 1: Making properties
// Declare typepedef void(^ClickBlock)(NSInteger index); @property (nonatomic, copy) ClickBlock imageClickBlock;Copy the code
  • Example 2: Making method arguments
// Declare typedef void (^handleBlock)(); / / block parameters - (void) requestForRefuseOrAccept: MessageBtnType msgBtnType messageModel: (msgModel messageModel *) handle:(handleBlock)handle{ ...Copy the code

2.3 Common usage of Block

2.3.1 Local position declares a variable of type Block
  • location
return_type (^blockName)(var_type) = ^return_type (var_type varName) {
    // ...
};
blockName(var);
Copy the code
  • example
void (^globalBlockInMemory)(int number) = ^(int number){
     printf("%d \n",number);
};
globalBlockInMemory(90);
Copy the code
The 2.3.2 @interface position declares a property of type Block
  • location
@property(nonatomic, copy)return_type (^blockName) (var_type);
Copy the code
  • example
// Block @property (nonatomic, copy) void (^btnClickedBlock)(UIButton *sender);Copy the code
2.3.3 When defining methods, declare parameters of type Block
  • usage
- (void)yourMethod:(return_type (^)(var_type))blockName;
Copy the code
  • example

UIView+AddClickedEvent.h

- (void)addClickedBlock:(void(^)(id obj))clickedAction;
Copy the code
2.3.4 When the above method is called, Block takes an argument
  • example

UIView+AddClickedEvent.m

- (void)addClickedBlock:(void(^)(id obj))clickedAction{ self.clickedAction = clickedAction; // : check if there is an interactive event, if not... All Gesture interaction events are added to gestureRecognizersif(! [self gestureRecognizers]) { self.userInteractionEnabled = YES; // : add click event UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)]; [self addGestureRecognizer:tap]; } } - (void)tap{if(self.clickedAction) { self.clickedAction(self); }}Copy the code

2.4 Rare uses of Block

2.4.1 Inline use of blocks

This form is not commonly used and is called immediately after an anonymous Block is declared:

^return_type (var_type varName)
{
    //...
}(var);
Copy the code
2.4.2 Recursive invocation of Block

Recursive calls are the basis for many algorithms, especially when the loop terminating condition cannot be predicted in advance. Note: Since the Block internally references itself, __block must be used here to avoid circular references.

__block return_type (^blockName)(var_type) = [^return_type (var_type varName)
{
    if (returnCondition)
    {
        blockName = nil;
        return; } / /... // blockName(varName); } copy]; [first call] blockName(varValue);Copy the code
2.4.3 Block as the return value

The return value of a method is a Block that can be used in some “factory mode” methods:

  • Usage:
- (return_type(^)(var_type))methodName
{
    return ^return_type(var_type param) {
        // ...
    };
}
Copy the code
  • Example: 🌰 in the navigation framework
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if([attribute isKindOfClass:NSArray.class]) { NSAssert(! self.hasLayoutRelation, @"Redefinition of constraint relation");
            NSMutableArray *children = NSMutableArray.new;
            for (id attr in attribute) {
                MASViewConstraint *viewConstraint = [self copy];
                viewConstraint.secondViewAttribute = attr;
                [children addObject:viewConstraint];
            }
            MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
            compositeConstraint.delegate = self.delegate;
            [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
            return compositeConstraint;
        } else{ NSAssert(! self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;
            returnself; }}; }Copy the code

3. Application scenarios of Block

3.1 Responding to Events

Scenario: UIViewContoller has a UITableView and is a proxy for it, loading CellView through UITableView. Now you need to listen to a button in the CellView (distinguishable by the tag value) and respond.

As in section 2.3.2 above, we declare a Block property in cellView. h at the @interface position. To set the activation event to call a Block, we then set cellView. m as follows:

// Activate the event#pragma mark - Button click event
- (IBAction)btnClickedAction:(UIButton *)sender {
    if(self.btnClickedBlock) { self.btnClickedBlock(sender); }}Copy the code

Then, In the appropriate place of viewcontroller.m (- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{… Set the Block property of the CellView via the setter method. The Block writes the logic to be executed when the button is clicked.

/ / response event cell. BtnClickedBlock = ^ (UIButton * sender) {/ / tag message read [weakSelf requestToReadedMessageWithTag: sender. Tag]; / / refresh the current cell [tableView reloadRowsAtIndexPaths: @ [indexPath] withRowAnimation: UITableViewRowAnimationNone]; };Copy the code

In fact, it is possible to pass an event even if the Block does not pass any arguments. But in this case, the activator of the event cannot be distinguished (which button in the cell? . That is:

// Block @property (nonatomic, copy) void (^btnClickedBlock)(void);Copy the code
// Activate the event#pragma mark - Button click event
- (IBAction)btnClickedAction:(UIButton *)sender {
    if(self.btnClickedBlock) { self.btnClickedBlock(); }}Copy the code
/ / response event cell. BtnClickedBlock = ^ {/ / tag message read [weakSelf requestToReadedMessageWithTag: nil]; / / refresh the current cell [tableView reloadRowsAtIndexPaths: @ [indexPath] withRowAnimation: UITableViewRowAnimationNone]; };Copy the code

3.2 Data Transfer

The response event above, it’s actually passing data, but it’s passing UIButton. As shown below, SubTableView is a VC property and subview.

  • Transfer number

SubTableView.h

@property (strong, nonatomic) void (^handleDidSelectedItem)(int indexPath);
Copy the code

SubTableView.m

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    _handleDidSelectedItem ? _handleDidSelectedItem(indexPath) : NULL;
}
Copy the code

VC.m

[_subView setHandleDidSelectedItem:^(int indexPath) {
        [weakself handleLabelDidSearchTableSelectedItem:indexPath];
    }];
Copy the code
- (void)handleLabelDidSearchTableSelectedItem:(int )indexPath {
    if (indexPath==0) {
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"telprompt:%@", self.searchNullView.telLabel.text]]];
    }else if(indexPath==1){ [self.navigationController popViewControllerAnimated:YES]; }}Copy the code
  • Transfer object

For example, in the HYBNetworking framework, pass the Block of the data object returned by the interface on a successful request:

[HYBNetworking postWithUrl:kSearchProblem refreshCache:NO params:params success:^(id response) { typeof(weakSelf) strongSelf = weakSelf; // [KVNProgress dismiss]; NSString *stringData = [response mj_JSONString]; stringData = [DES3Util  decrypt:stringData]; NSLog(@"stirngData: %@", stringData); . }Copy the code

3.3 Chained syntax

Chain programming idea: The core idea is to take a block as the return value of a method, and the return value is the type of the caller, and return the method as a setter, so you can achieve continuous calls, that is, chain programming.

A typical chaining programming use for navigation is as follows:

[self.containerView addSubview:self.bannerView]; [self.bannerView mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.equalTo(self.containerView.mas_leading);  make.top.equalTo(self.containerView.mas_top); make.trailing.equalTo(self.containerView.mas_trailing); Make. Height. EqualTo (@ (kViewWidth (131.0)));}];Copy the code

Now, simply use the chain programming idea to implement a simple calculator:

3.3.1 Declaring a method in the caculatemaker. h file add:
  • CaculateMaker.h
//  CaculateMaker.h
//  ChainBlockTestApp

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface CaculateMaker : NSObject

@property (nonatomic, assign) CGFloat result;

- (CaculateMaker *(^)(CGFloat num))add;

@end
Copy the code
3.3.2 Implementing the Add method in the Caculatemake. m file:
  • CaculateMaker.m
//  CaculateMaker.m
//  ChainBlockTestApp


#import "CaculateMaker.h"@implementation CaculateMaker - (CaculateMaker *(^)(CGFloat num))add; {return ^CaculateMaker *(CGFloat num){
        _result += num;
        return self;
    };
}

@end
Copy the code
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  • ViewController.m
CaculateMaker *maker = [[CaculateMaker alloc] init];
maker.add(20).add(30);
Copy the code

4. Use blocks

4.1 Capture automatic variables and the __block specifier

We saw earlier that the automatic variable is captured in the function where the block is located. But you can’t change it, otherwise it’s a “compile error.” But you can change global variables, static variables, and global static variables. In fact, the two characteristics are not difficult to understand:

  • You cannot change the value of an automatic variable because the block captures the const value of the automatic variable, which has the same name and cannot be changed

  • You can change the value of a static variable: a static variable belongs to a class, not to a particular variable. Because you don’t call the self pointer inside a block. So block can be called.

Another way to solve the problem that blocks cannot modify the value of automatic variables is to use the __block modifier.

4.2 Intercepting Objects

For capturing ObjC objects, different from basic types; Block causes the reference count of an object to change.

@interface MyClass : NSObject {  
    NSObject* _instanceObj;  
}  
@end  
  
@implementation MyClass  
  
NSObject* __globalObj = nil;  
  
- (id) init {  
    if (self = [super init]) {  
        _instanceObj = [[NSObject alloc] init];  
    }  
    return self;  
}  
  
- (void) test {  
    static NSObject* __staticObj = nil;  
    __globalObj = [[NSObject alloc] init];  
    __staticObj = [[NSObject alloc] init];  
  
    NSObject* localObj = [[NSObject alloc] init];  
    __block NSObject* blockObj = [[NSObject alloc] init];  
  
    typedef void (^MyBlock)(void) ;  
    MyBlock aBlock = ^{  
        NSLog(@"% @", __globalObj);  
        NSLog(@"% @", __staticObj);  
        NSLog(@"% @", _instanceObj);  
        NSLog(@"% @".localObj);  
        NSLog(@"% @", blockObj);  
    };  
    aBlock = [[aBlock copy] autorelease];  
    aBlock();  
  
    NSLog(@"%d", [__globalObj retainCount]);  
    NSLog(@"%d", [__staticObj retainCount]);  
    NSLog(@"%d", [_instanceObj retainCount]);  
    NSLog(@"%d"[localObj retainCount]);  
    NSLog(@"%d", [blockObj retainCount]);  
}  
@end  
  
int main(int argc, charchar *argv[]) {  
    @autoreleasepool {  
        MyClass* obj = [[[MyClass alloc] init] autorelease];  
        [obj test];  
        return0; }}Copy the code

The command output is 1 1 1 2 1.

The __globalObj and __staticObj locations in memory are determined, so the Block copy does not retain the object.

_instanceObj does not directly retain the _instanceObj object itself when a Block copy occurs, but retains self. So you can read and write the _instanceObj variable directly in the Block. LocalObj During Block copy, the system automatically retains the object and increases its reference count. Block obj does not retain on Block copy.

4.3 Circular references caused by blocks

So generally, after we set the Block, we call back the Block at the appropriate time, and we don’t want to call back the Block after it’s already been released, so we need to copy the Block, copy it into the heap, so we can use it later.

Blocks can cause circular references because they retain external variables when copied to the heap. If a Block references its host object, it is more likely to cause circular references. For example:

  • TestCycleRetain
- (void) dealloc {
    NSLog(@"no cycle retain");
} 

- (id) init {
    self = [super init];
    if (self) {

        #if TestCycleRetainCase1Myblock = ^{[selfdoSomething];
        };
  
        #elif TestCycleRetainCase2__block TestCycleRetain * weakSelf = self; self.myblock = ^{ [weakSelfdoSomething];
        };

        #elif TestCycleRetainCase3// Do not loop reference __weak TestCycleRetain * weakSelf = self; self.myblock = ^{ [weakSelfdoSomething];
        };

        #elif TestCycleRetainCase4__unsafe_unretained TestCycleRetain * weakSelf = self; self.myblock = ^{ [weakSelfdoSomething];
        };

        #endif NSLog(@"myblock is %@", self.myblock);
    }
    return self;
} 

- (void) doSomething {
    NSLog(@"do Something");
}
Copy the code
  • main
int main(int argc, char * argv[]) {
    @autoreleasepool {
        TestCycleRetain * obj = [[TestCycleRetain alloc] init];
        obj = nil;
        return0; }}Copy the code
  • In the MRC case, using __block eliminates circular references.
  • In the case of ARC, a weak reference is needed to resolve the circular reference issue. After iOS 5, __weak can be used, but before that __unsafe_unretained is used. The weakness of __unsafe_unretained is that it will not be retained if the pointer is released

In the previous use of a block, we used __weak, but there is a danger that you don’t know when self will be released. To ensure that it won’t be released inside the block, we add __strong. More often you need to use strongSelf, as follows:

__weak __typeof(self) weakSelf = self; 
self.testBlock =  ^{
       __strong __typeof(weakSelf) strongSelf = weakSelf;
       [strongSelf test]; 
});
Copy the code

4.4 Utility macro definition: Avoid circular references caused by blocks

  • The first step

Write the following macros directly (not recommended) to the project’s testapp-prefix. PCH file or indirectly to the project’s imported header file:

/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- strong and weak reference -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --#ifndef weakify
#if DEBUG
#if __has_feature(objc_arc)
#define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) autoreleasepool{} __block __typeof__(object) block##_##object = object;
#endif
#else
#if __has_feature(objc_arc)
#define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) try{} @finally{} {} __block __typeof__(object) block##_##object = object;
#endif
#endif
#endif

#ifndef strongify
#if DEBUG
#if __has_feature(objc_arc)
#define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object;
#else
#define strongify(object) autoreleasepool{} __typeof__(object) object = block##_##object;
#endif
#else
#if __has_feature(objc_arc)
#define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object;
#else
#define strongify(object) try{} @finally{} __typeof__(object) object = block##_##object;
#endif
#endif
#endif
Copy the code
  • The second step

When setting the Block body, use it as follows.

@weakify(self);
[footerView setClickFooterBlock:^{
        @strongify(self);
        [self handleClickFooterActionWithSectionTag:section];
}];
Copy the code

4.5 Self in all blocks must be weak?

Obviously the answer is not always, but there are situations where you can use self directly, such as calling a system method:

[UIView animateWithDuration: 0.5 animations: ^ {NSLog (@"% @", self);
    }];
Copy the code

Since this block exists in a static method, self does not hold the static method, although the block strongly references it, so it is perfectly possible to use self inside the block.

In addition, let’s look at an example of the layout of the navigation code. Will self cause a circular reference?

[self.headView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.centerY.equalTo(self.otherView.mas_centerY);
}];
Copy the code

It is not inevitable that a block will create a circular reference, whether it is a circular reference depends on whether it holds strong references to each other. The block uses self, and the block holds a reference to self, but self doesn’t directly or indirectly hold the block, so it doesn’t create a circular reference. You can see the source code for the navigation:

  • View+MASAdditions.m
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { self.translatesAutoresizingMaskIntoConstraints =  NO; MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; block(constraintMaker);return [constraintMaker install];
}
Copy the code
  • MASConstraintMaker.m
- (id)initWithView:(MAS_VIEW *)view {
    self = [super init];
    if(! self)return nil;
    
    self.view = view;
    self.constraints = NSMutableArray.new;
    
    return self;
}
Copy the code

The holding chain looks like this and does not form a reference loop:

Self ->self. HeadView ·· MASConstraintMaker

Notice that the Block body as a method parameter is not held by any party. Therefore, we can rest assured that using self. XXX in the navigation will not be looping. And there may be problems with weakSelf inside this block, because if mas_qeual gets a nil parameter, it should cause the program to crash.

Since UIView does not strongly hold a block, this block is just a stack block and does not make a circular reference condition. One of the properties of a block on a stack is that it exits the stack after execution, and once it exits the stack, it will be released. If you look at the mas_makexxx method implementation, you will see that this block is called very quickly, and the stack will be destroyed as soon as it is done, so you can simply use self without a circular reference. In addition, this is the same as using self in a network request.

Block and memory management

Blocks fall into three types based on their location in memory:

  • NSGlobalBlock is a block in the global area, which is set in the program’s data area (.data area).

  • NSStackBlock is located on the stack, out of scope, and the stack Block and __block variables are destroyed.

  • NSMallocBlock is located in the heap area and is not affected at the end of variable scope.

Note: With ARC on, there will only be blocks of type NSConcreteGlobalBlock and NSConcreteMallocBlock.

As their name suggests, blocks can be stored in three ways: stack, global, and heap. To get the value of isa in the block object, you can get one of the above, and start with which blocks are stored on the stack, heap, or global.

5.1 In the global area: GlobalBlock

There are two ways to generate a block in the global area:

  • Global variables are defined with block syntax
void(^block)(void) = ^ { NSLog(@"Global Block"); }; intmain() {}Copy the code
  • When an expression in the block syntax does not use an automatic variable that should be intercepted
int(^block)(int count) = ^(int count) {
        return count;
    };
 block(2);
Copy the code

Even though the block is inside the loop, the address of BLK is always the same. This block is in the global segment. Note: For blocks that do not capture autovariables, _NSConcretStackBlock is still shown in the rewrite-objc version of Clang, but it is not.

5.2 Stack memory: StackBlock

In this case, it cannot be compiled under non-ARC, but it can be compiled under ARC.

  • Block syntax uses captured automatic variables in an expression
NSInteger i = 10; 
block = ^{ 
     NSLog(@"%ld", i); 
};
block;
Copy the code

A block that is set on the stack is destroyed if its scope ends. Similarly, since the __block variable is also configured on the stack, it will also be destroyed if it ends in scope.

In addition, for example

typedef void (^block_t)() ;  

-(block_t)returnBlock{  
    __block int add=10;  
    return^ {printf("add=%d\n",++add);
    };  
}  
Copy the code

5.3 In heap memory: MallocBlock

A block in the heap cannot be created directly. It needs to be copied from a block of type _NSConcreteStackBlock. Because a copy of a block ends up calling the _Block_copy_internal function.

void(^block)(void);

int main(int argc, const char * argv[]) {
   @autoreleasepool {

       __block NSInteger i = 10;
       block = [^{
           ++i;
       } copy];
       ++i; 
       block();
       NSLog(@"%ld", i);
   }
   return 0;
}
Copy the code

We copy the generated block on the stack, and both the block and __block variables are copied from the stack to the heap. The code above, with and without copy, under non-ARC and ARC one is stack and one is Malloc. This is because the default for ARC is Malloc (even so, there are some exceptions for ARC, which we’ll discuss below).

There is a huge difference between blocks in ARC and non-ARC. In most cases, ARC will copy stack blocks directly onto the heap by default. So, when is a Block on the stack copied to the heap?

  • When Block’s copy instance method is called
  • Block is returned as a function return value
  • When a Block is assigned to a class or member variable of Block type with the __strong modifier ID
  • When passing a Block in a Cocoa framework method or GCD API that contains a usingBlock in its method name

Big difference between blocks in ARC and non-ARC

  • In ARC, the class that captures the block with the external variable would be NSMallocBlock or NSStackBlock, and if the block is assigned to a variable, In this process, the _Block_copy is executed to change the original NSStackBlock into an NSMallocBlock; But if the block is not assigned to a variable, then its type is NSStackBlock; The class for a block that doesn’t capture an external variable is going to be an NSGlobalBlock that’s not on the heap, it’s not on the stack, it’s going to be in a code segment just like a C function.

  • In non-ARC, blocks that capture external variables will be nsStackBlocks placed on the stack. Blocks that do not capture external variables will be the same as in ARC.

For example,

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self testBlockForHeapOfARC];
}
-(void)testBlockForHeapOfARC{
    int val =10;
    typedef void (^blk_t)(void);
    blk_t block = ^{
        NSLog(@"blk0:%d",val);
    };
    block();
}
Copy the code

Even so, there are a few exceptions under ARC:

exception

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self testBlockForHeap0];
}

#pragma mark - testBlockForHeap0 - crash
-(NSArray *)getBlockArray0{
    int val =10;
    return [NSArray arrayWithObjects:
            ^{NSLog(@"blk0:%d",val); }, ^{NSLog(@"blk1:%d",val); },nil]; } -(void)testBlockForHeap0{
    
    NSArray *tempArr = [self getBlockArray0];
    NSMutableArray *obj = [tempArr mutableCopy];
    typedef void (^blk_t)(void);
    blk_t block = (blk_t){[obj objectAtIndex:0]};
    block();
}
Copy the code

This code will make an exception on the last line of BLK () because the blocks in the array are on the stack. Because val is on the stack. The solution is to call the copy method. In this scenario, ARC will not add a copy for you, because ARC is not sure and has taken the conservative step of not adding a copy. So ARC is also an exception exit.

Exceptional improvement 1

Copy the block to the heap by calling the copy function of the block:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self testBlockForHeap1];
}

-(void)testBlockForHeap1{
    
    NSArray *tempArr = [self getBlockArray1];
    NSMutableArray *obj = [tempArr mutableCopy];
    typedef void (^blk_t)(void);
    blk_t block = (blk_t){[obj objectAtIndex:0]};
    block();
}

-(NSArray *)getBlockArray1{
    int val =10;
    return [NSArray arrayWithObjects:
            [^{NSLog(@"blk0:%d",val); } copy], [^{NSLog(@"blk1:%d",val); } copy],nil]; }Copy the code

The type of this Block can be seen by typing a breakpoint:

Exceptional improvement 2

For example, in the following code, the block in the addBlockToArray method is of type _NSConcreteStackBlock, which is copied into the heap in the testBlockForHeap2 method, Become a block of type _NSConcreteMallocBlock:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self testBlockForHeap2];
}

- (void)addBlockToArray:(NSMutableArray *)array {

    int val =10;
    [array addObjectsFromArray:@[
         ^{NSLog(@"blk0:%d",val); }, ^{NSLog(@"blk1:%d",val); }]]. } - (void)testBlockForHeap2{

    NSMutableArray *array = [NSMutableArray array];
    [self addBlockToArray:array];
    typedef void (^blk_t)(void);
    blk_t block = (blk_t){[array objectAtIndex:0]};
    block();
}
Copy the code

Make a breakpoint, where the Block type is:

5.4 Block Replication

  • Call copy in the global block and do nothing
  • You call copy on the stack so you copy to the heap
  • Call block reference count increment on the heap
-(void) stackOrHeap{  
    __block int val =10;  
    blkt1 s= ^{  
        return++val; }; s(); blkt1 h = [s copy]; h(); }Copy the code

No matter where the block configuration is, copying with the copy method will cause no problems. In an ARC environment, if you’re not sure if you want to copy the block, just copy it.

As a final note, with ARC on, there will only be blocks of type NSConcreteGlobalBlock and NSConcreteMallocBlock by default, except for the above exceptions.

6. The underlying research method of Block

6.1 Research tool: Clang

To see how the compiler implements blocks, we need to use Clang. Clang provides a command to rewrite the source code of Objetive C into C language, which can be used to study the specific source code implementation of block.

First CD to the code file directory

cd /Users/ChenMan/iOSTest/BlockTestApp
Copy the code

Then run the clang command

clang -rewrite-objc main.m
Copy the code

The code for main.m is written as follows

#include <stdio.h>

int main(int argc, char * argv[]) {
    @autoreleasepool {
        typedef void (^blk_t)(void);
        blk_t block = ^{
            printf("Hello, World! \n");
        };
        block();
//        returnUIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

Execution status:

You’ll see main.cpp

6.2 Implementation Analysis

Only some of the key code is selected here.

Int main(int argc, char * argv[]) {int main(int argc, char * argv[]) {

int main(int argc, char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; typedef void (*blk_t)(void); blk_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }}Copy the code

__main_block_impl_0 is a C++ implementation of the block (_0 at the end represents the block in main), which is also a structure.

(1) __main_block_impl_0

__main_block_IMPL_0 is defined as follows:

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code
(2) __block_impl

__block_impl is defined as follows:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
Copy the code

Its structural members are as follows:

  • Isa, pointer to the owning class, which is the type of the block
  • Flags, the flag variable, is used when implementing internal operations on blocks
  • Reserved: the variable is Reserved
  • FuncPtr, pointer to the function called when the block executes

As you can see, it contains a pointer to isa (anything that contains a pointer to ISA is an object), which means the block is also an object (in Runtime, objects and classes are represented as structures).

(3) __main_block_desc_0

__main_block_desc_0 is defined as follows:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
Copy the code

The meanings of its structural members are as follows:

  • Reserved: Reserved field
  • (struct __main_block_IMPL_0)

While defining the __main_block_desc_0 structure, the above code also creates and assigns a value to __main_block_desc_0_DATA to initialize __main_block_IMPL_0 in the main function.

(4) __main_block_func_0

In the main function above, __main_block_func_0 is also a C++ implementation of block

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            printf("Hello, World! \n");
        }
Copy the code
Summary: Comprehensive knowable
  • __main_block_impl_0theisaThe pointer points to_NSConcreteStackBlock.
  • From the main function main.cpp,__main_block_impl_0theFuncPtrIt points to the function__main_block_func_0.
  • __main_block_impl_0theDescIt also points to the definition__main_block_desc_0Is created when__main_block_desc_0_DATA, which records the block structure size and other information.

These are the results of the conversion according to compilation. Of course, since the clang rewrite is implemented in a different way than LLVM, readers with a keen low-level interest can dig deeper.