#iOS underlying principles – Block nature exploration

nature

A block is essentially an OC object that also has an ISA pointer. Or, a Block is an OC object that encapsulates a function call and its calling environment.

1. Low-level implementation

Write the simplest OC code to top a block, such as:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int abc = 10086;
        void(^block)(int number) = ^(int number) {
            NSLog(@"%d",number);
        };
    }
    return 0;
}
Copy the code

Use the xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m command to convert its OC code to the underlying C++ code and observe the underlying structure of the block.

When we open the compiled main.cpp code, we see that the above code is converted to the following:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int abc = 10086;
        void(*block)(int number) = ((void(*) (int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    }
    return 0;
}
Copy the code

Block code blocks are defined as __main_block_IMPL_0 structures.

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

The struct contains two different struct variables __block_impl and __main_block_desc_0

__block_impl

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

FuncPtr refers to the address of the code block that the block encapsulates. When the block is executed, FuncPtr looks for the code block to be executed and calls.

__main_block_desc_0

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}
Copy the code

Block_size indicates the memory size occupied by the current block.

__main_block_func_0

A block encapsulates a block of code defined as a __main_block_func_0 structure

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int number) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_gf_ct0sq2w17s16j4b1pz5_zx500000gn_T_main_68909d_mi_0,number);
        }
Copy the code

2. Block variable capture

Auto variable

If we change the main function to:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int abc = 10086;
        void(^block)() = ^() {
            NSLog(@"%d",abc);
        };
        abc = 10010;
        
        block();
    }
    return 0;
}
Copy the code

Referencing external variables inside the block, let’s look at the internal constituent structure. Likewise, run the xcrun-sdk iphoneOS clang-arch arm64-rewrite-objc main.m command. As you can see, the __main_block_IMPL_0 structure adds an int variable ABC to store the value of the referenced external variable. Because it is a value store, ABC remains the same value as previously defined after the block is generated, no matter what changes are made to the external variable.

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

Static variables

Because the external variable ABC we define does not have any modifiers before it, which is the default auto variable, the block is value capture. If you declare the external variable static then look at the underlying implementation.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int abc = 10086;
        static int def = 100;
        void(^block)(void) = ^() {
            NSLog(@"abc: %d - def: %d",abc,def);
        };
        abc = 10010;
        def = 200;
        
        block();
    }
    return 0;
}
Copy the code

C++ low-level implementation:

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

Static variables are passed inside the block as Pointers. The block captures the memory address of the external variable directly. If the external variable is changed after the block declaration, the change is synchronized inside the block.

The global variable

If global variables are used, block does not capture them. Because the type of the declared global variable is not released for the entire declaration cycle of the program, the value of the global variable is directly accessed when using blocks. So there is no point in capturing, and those interested can look at the underlying implementation for themselves.

3. Block type

When we declare a block and print its inheritance chain we can see:

void(^block)(void) = ^() {
            NSLog(@"abc");
        };
        
        NSLog(@"%@",[block class]);
        NSLog(@"%@",[[block class] superclass]);
        NSLog(@"%@",[[[block class] superclass] superclass]);
        NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
Copy the code

Output:

2018-06-28 10:37:23.901162+0800 BlockDemo[17574:719984] __NSGlobalBlock__
2018-06-28 10:37:23.901504+0800 BlockDemo[17574:719984] __NSGlobalBlock
2018-06-28 10:37:23.901522+0800 BlockDemo[17574:719984] NSBlock
2018-06-28 10:37:23.901535+0800 BlockDemo[17574:719984] NSObject
Program ended with exit code: 0
Copy the code
Therefore, we can conclude that the inheritance relationship of block is:NSGlobalBlock: __NSGlobalBlock: NSBlock: NSObject

This further proves that blocks are OC objects in nature. And, in the absence of references to external variables, the block is of type NSGlobalBlock.

We define three different blocks that print their actual types:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void(^block1)(void) = ^() {
            NSLog(@"abc");
        };
        NSLog(@"%@",[block1 class]);
        
        int abc = 1;
        void(^block2)(void) = ^() {
            NSLog(@"abc: %d",abc);
        };
        NSLog(@"%@",[block2 class]);
        
        
        NSLog( @"%@", [^(){
            NSLog(@"hello %d",abc);
        } class]);
        
        
    }
    return 0;
}
Copy the code

Output:

2018-06-28 10:48:32.096859+0800 BlockDemo[17719:728991] __NSGlobalBlock__
2018-06-28 10:48:32.097224+0800 BlockDemo[17719:728991] __NSMallocBlock__
2018-06-28 10:48:32.097243+0800 BlockDemo[17719:728991] __NSStackBlock__
Copy the code

We can conclude that,

Block types are as follows__NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__

They are located in memory respectively:

So how do they tell the difference? You can use the following table to illustrate:

Block type conditions
NSGlobalBlock The auto variable is not accessed inside the block
NSStackBlock The block accesses the auto variable internally
NSMallocBlock NSStackBlockCall the copy

NSStackBlock copies blocks from stack to heap for programmers to manage. As shown in the following table:

Block type Storage domain Copy effect
NSGlobalBlock The data area of the program Nothing changes
NSStackBlock The stack Copy from stack to heap
NSMallocBlock The heap The reference counter increments by 1

4. In some cases, the system automatically copies blocks from the stack to the heap

1. When a block is returned as a function

typedef void(^MyBlock)(void);

MyBlock testFunc() {
    int a = 10;
    MyBlock myBlock = ^ {
        NSLog(@"test --- %d",a);
    };
    return myBlock;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyBlock myB = testFunc();
        NSLog(@"%@",[myB class]);
    }
    return 0;
}
Copy the code

If this code is in an MRC environment, it will crash. The variable accessed by the Block has been released. In the ARC environment, if the return value of the parameter is block, the system automatically copies the block and changes it to NSMallocBlock.

2. When a Block is referenced by a strong pointer, the copy operation is automatically performed

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int abc = 10;
        MyBlock myB = ^ {
            NSLog(@"+++ %d",abc);
        };
        NSLog(@"%@",[myB class]);
    }
    return 0;
}
Copy the code

As shown in the MRC environment: __NSStackBlock__. In the ARC environment: __NSMallocBlock__

3. Copy is automatically performed when Block is used as a cocoa API or GCD API method parameter

Such as:

   NSArray *array = @[@1];
        [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
        }];
Copy the code
  static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            
        });
Copy the code

For MAC blocks, copy is required. For ARC blocks, either strong or copy is used. Both automatically copy the block, so there is no difference.

5.Block internally references objects

Observe the following code

int main(int argc, const char * argv[]) { @autoreleasepool { { XWPerson *person = [[XWPerson alloc] init]; person.age = 10; ^{ NSLog(@"person -- %ld",(long)person.age); } (); } NSLog(@"*******"); } return 0; }Copy the code

Notice that the XWPerson is released when the braces in the function are completed, and the block is a stack block, __NSStackBlock__. Blocks stored on the stack are released with braces even if they reference objects.

If the above code is changed to:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyBlock myBlock;
        {
            XWPerson *person = [[XWPerson alloc] init];
            person.age = 10;
            myBlock = ^{
                NSLog(@"person -- %ld",(long)person.age);
            };
            myBlock();
            
        }
        
        NSLog(@"*******");
    }
    return 0;
}
Copy the code

We can see that the Person object is still not freed when we execute to ****, at which point the block has strongly referenced the Person object. Because the block is a strong pointer reference, the type is heap block __NSMallocBlock__. Why do heap blocks strongly reference external objects?

Iphoneos clang-arch arm64-rewrite-objc-fobjc-arc-fobjc-Runtime =ios-8.0.0 main.m

Block is defined as:

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

Main_block_desc_0 is defined as follows:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} 
Copy the code

Compared with the previous block referencing the base member type, its main_block_DESc_0 has two more parameters, copy and dispose. And the __main_block_IMPL_0 block itself is passed in.

When a block performs copy, it does

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/); }Copy the code

Methods. The _Block_object_assign method that is eventually called does a reference count on the person object introduced by the block. If the strong modifier is used, the reference count is increased by 1. If the weak modifier is used, the reference count remains the same.

Dispose method is called when the block completes, and dispose is called at the bottom

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/); }Copy the code

Method to reduce the reference count of the object members referenced inside the block by 1. If the external object is decorated with strong, the reference count is reduced by 1 after copy is increased by 1. If the weak modifier is used, it will no longer hold the referenced external object because it is freed. However, whether the referenced external object will be freed depends on whether its reference count is 0.

6. Block internally modifies the value of an external variable.

We know that if the external variable captured inside the block is of type auto, the value type of the variable is generated inside the block, and the external variable cannot be modified by the value inside the block. How many ways can I change the value of an external variable inside a block?

1. Use the static modifier for external variables

A static variable block gets its memory address and can be modified directly.

2. Use the __block

If you use a static variable modifier, the lifetime of the variable will be extended indefinitely, which is not our design philosophy. Therefore, we can use __block to modify the external variable, which can be modified inside the block. How does __block implement this requirement?

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int a = 10;
        MyBlock block = ^{
            a = 20;
            NSLog(@"a --- %d",a);
        };
        block();
    }
    return 0;
}
Copy the code

Convert it to c++ using xcrun-sdk iphoneos clang-arch arm64-rewrite-objc-fobjc-arc-fobjc-runtime =ios-8.0.0 main.m:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

We know that external member variables decorated with __block are defined as __Block_byref_a_0 objects! Its declaration is:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};
Copy the code

Variables declaring type __block inside main are initialized this way:

     __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0.sizeof(__Block_byref_a_0), 10};
Copy the code

Where __forwarding saves the memory address of the original variable A, size is the memory size of the current variable, and 10 saves the value of the original variable.

Thus, when we modify the original variable inside the block:

(a->__forwarding->a) = 20;
Copy the code

External variables can be changed inside a block by taking the address of the original variable directly.

7. Memory management of __block and object type auto variables

An auto variable and a __block modifier for an object type captured inside a block. If blocks are on the stack, they are not memory-managed, that is, external variables are not strongly referenced

If the block is copied to the heap, the internal copy function is called for memory management of external __block modified variables and auto variables of the object type.

When a block is removed from memory, dispose function is also called to dispose of the referenced external variables.

8. Circular references

Using blocks is easy to create circular references. If a block defined in a class internally references attributes outside the class, including the class’s own self, self will strongly reference the block, and the block will strongly reference self. Causes Self not to be released. The following code creates a circular reference:

.h
#import <Foundation/Foundation.h>

@interface XWPerson : NSObject

@property (nonatomic, assign) NSInteger age;

@property (nonatomic, copy)  void(^personBlock)(void);

@end
Copy the code
.m #import "XWPerson.h" @implementation XWPerson - (void)test { self.personBlock = ^{ NSLog(@"%d",self.age); // a circular reference is generated even if _age is used here. }; } - (void)dealloc { NSLog(@" XWPerson -- dealloc -- age:%ld",(long)_age); } @endCopy the code

The essence of a circular reference is that, in an internal block implementation, self is captured inside the block and strong is strongly referenced. The following code looks like this:

struct __XWPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __XWPerson__test_block_desc_0* Desc;
  XWPerson *const __strong self;
  __XWPerson__test_block_impl_0(void *fp, struct __XWPerson__test_block_desc_0 *desc, XWPerson *const __strong _self, int flags=0) : self(_self) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

9. Avoid circular references

1. (ARC) __weak: A weak reference object that sets the pointer to nil when the object to which it points is destroyed. Therefore, the problem is usually solved by __weak.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        XWPerson *person1 = [[XWPerson alloc] init];
        person1.age = 18;
        __weak typeof(person1) weakPerson = person1;
        person1.personBlock = ^{
            NSLog(@"%ld",(long)weakPerson.age);
        };
        person1.personBlock();
    }
    return 0;
}
Copy the code

2. (ARC/MRC) __unsafe_unretained: a weak reference object. When the referenced object is destroyed, the pointer is not set to nil. Reference this object again may generate access to the zombie object error, resulting in a crash, so it is not recommended!

__unsafe_unretained XWPerson *person1 = [[XWPerson alloc] init];

3. (ARC/MRC) __block: Use __block to decorate an object. In the ARC context – this block must be called and the external variables referenced must be set to nil manually inside the block. Because MRC does not increment the reference count by one for objects that reference __block modifications, there is no need to manually set nil and blocks are not required.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block XWPerson *person1 = [[XWPerson alloc] init];
        person1.age = 18;
        person1.personBlock = ^{
            NSLog(@"%ld",(long)person1.age);
            person1 = nil;
        };
        person1.personBlock();
    }
    return 0;
}
Copy the code