This chapter explains the use of blocks and the underlying data structure, as well as the use of points that need to be paid attention to.

Block nature

As mentioned in previous articles, classes are objects and metaclasses are objects, essentially structures. Is the same true of blocks? Blocks have these characteristics:

  • A block is also essentially an OC object that also has an ISA pointer inside it
  • A block is an OC object that encapsulates a function call and its environment

Let’s take a quick look at what blocks look like when compiled

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^(void){
            NSLog(@"hello word");
        };
        block();

    }
    return 0;
}
Copy the code

Cxcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.mm -o main. CPP

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, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; // Block type impl.Flags = Flags; impl.FuncPtr = fp; // execute function address Desc = Desc; //desc stores the value of __main_block_desc_0 (0, sizeof(__main_block_impl_0))}}; Static void __main_block_func_0(struct __main_block_impl_0 *__cself) {NSLog((NSString) *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_b7cca8_mii_0); } 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)}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; // define block void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_data); // execute block block->FuncPtr(block); }return 0;
}
Copy the code

Eventually the block is converted to a __main_block_IMPL_0 structure, which is assigned to the variable block, The passing arguments are __main_block_func_0 and __main_block_desc_0_DATA to execute the __main_block_IMPL_0 constructor, The __main_block_desc_0_DATA function is assigned to __main_block_IMPL_0 ->FuncPtr, and the executive function is block->FuncPtr(block), Before deleting redundant code is ((void (__block_impl *) (*)) ((__block_impl *) block) – > FuncPtr) ((__block_impl *) block); So why can block be cast directly to __block_impl? Since the first line of the __main_block_IMPL_0 structure is __block_impl, the memory address of __main_block_IMPL_0 is the same as the memory address of __block_IMPl, forcing the conversion will not be a problem.

Variable to capture

Variable capture is divided into three types:

Variable types Whether the capture is inside the block access The internal variable is assumed to be a
Local variable auto will Value passed a
Local variable static will Pointer passed *a
The global variable Don’t Direct access to the empty

Auto variable capture

The auto variable, usually omitted, is accessed by value pass. If you don’t know anything about value pass, you can read this blog post. Look at this example

int age = 10;
void (^block)(void) = ^(void){
    NSLog(@"age is %d",age); }; age = 20; block(); // Age is 10Copy the code

Any questions? Age =20 before the block is executed, why is the output 10? Convert this code to C/C ++ as follows:

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int age; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int age = __cself->age; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_baf352_mii_0,age); } 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)}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int age = 10; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age)); age = 20; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }return 0;
}
Copy the code

The __main_block_IMPL_0 structure has an extra variable age, __main_block_impl_0(void *fp, struct __main_block_desc_0 * DESC, int _age, int flags=0) : Age (_age) stores the value of age directly in __main_block_IMPL_0.age, which is stored in the heap, and the previous age is stored in the data segment. The variable that performs block access is __main_block_IMPL_0.age on the heap, so age is 10 is output.

Static variable capture

Let’s use an example to illustrate the difference between static and auto:

void(^block)(void);
void test(){
    int age = 10;
    static int level = 12;
    block = ^(void){
        NSLog(@"age is %d,level is %d",age,level);
    };
    age = 20;
    level = 13;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test(a); block(); }return0; } // Output: age is 10,level is 13Copy the code

Convert to source code:

void(*block)(void); struct __test_block_impl_0 { struct __block_impl impl; struct __test_block_desc_0* Desc; int age; int *level; __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int *_level, int flags=0) : age(_age), level(_level) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __test_block_func_0(struct __test_block_impl_0 *__cself) { int age = __cself->age; // bound by copy int *level = __cself->level; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_b26797_mii_0,age,(*level)); } static struct __test_block_desc_0 { size_t reserved; size_t Block_size; } __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)}; voidtest(){
    int age = 10;
    static int level = 12;
    block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &level));
    age = 20;
    level = 13;
}

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        test(a); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }return 0;
}
Copy the code

When test() is executed, the age variable is retrieved, but the value of age is stored in the block structure, and the address of level is stored in __test_block_impl_0.level. No matter when the level value is changed, the read level value is the latest because it is read directly from the address. So age is 10,level is 13.

The global variable

Global do not capture, access when direct access. So let’s test that out

int age = 10;
static int level = 12;
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        void(^block)(void) = ^(void){
            NSLog(@"age is %d,level is %d",age,level);
        };
        age = 20;
        level = 13;
        block();
    }
    return 0;
}
Copy the code

Into the c/c + +

int age = 10; static int level = 12; 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; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_45cab9_mii_0,age,level); } 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)}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); age = 20; level = 13; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }return 0;
}
Copy the code

Int age = 10; static int level = 12; The __main_block_IMPL_0 structure and its constructors do not store values or Pointers. The reason is that when __main_block_func_0 is executed, the age and level variables can be accessed directly because the global valid area is global and does not disappear outside main. Basically speaking, it is beyond the execution area and may disappear will be captured, will not disappear will not be captured.

Let’s look at the more complicated case, how are references to object types handled?

@interface FYPerson : NSObject
@property (nonatomic,copy) NSString * name;
@end

@implementation FYPerson
- (void)test{
    void (^block)(void) = ^{
        NSLog(@"person is %@",self);
    };
    
    void (^block2)(void) = ^{
        NSLog(@"name is %@",_name);
    };
}
@end




struct __FYPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __FYPerson__test_block_desc_0* Desc;
  FYPerson *self;
  __FYPerson__test_block_impl_0(void *fp, struct __FYPerson__test_block_desc_0 *desc, FYPerson *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __FYPerson__test_block_func_0(struct __FYPerson__test_block_impl_0 *__cself) {
  FYPerson *self = __cself->self; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_FYPerson_c624e0_mi_0,self);
    }
static void __FYPerson__test_block_copy_0(struct __FYPerson__test_block_impl_0*dst, struct __FYPerson__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __FYPerson__test_block_dispose_0(struct __FYPerson__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __FYPerson__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __FYPerson__test_block_impl_0*, struct __FYPerson__test_block_impl_0*);
  void (*dispose)(struct __FYPerson__test_block_impl_0*);
} __FYPerson__test_block_desc_0_DATA = { 0, sizeof(struct __FYPerson__test_block_impl_0), __FYPerson__test_block_copy_0, __FYPerson__test_block_dispose_0};

struct __FYPerson__test_block_impl_1 {
  struct __block_impl impl;
  struct __FYPerson__test_block_desc_1* Desc;
  FYPerson *self;
  __FYPerson__test_block_impl_1(void *fp, struct __FYPerson__test_block_desc_1 *desc, FYPerson *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __FYPerson__test_block_func_1(struct __FYPerson__test_block_impl_1 *__cself) {
  FYPerson *self = __cself->self; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_FYPerson_c624e0_mi_1,(*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_FYPerson$_name))); } static void __FYPerson__test_block_copy_1(struct __FYPerson__test_block_impl_1*dst, struct __FYPerson__test_block_impl_1*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/); } static void __FYPerson__test_block_dispose_1(struct __FYPerson__test_block_impl_1*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/); } static struct __FYPerson__test_block_desc_1 { size_t reserved; size_t Block_size; void (*copy)(struct __FYPerson__test_block_impl_1*, struct __FYPerson__test_block_impl_1*); void (*dispose)(struct __FYPerson__test_block_impl_1*); } __FYPerson__test_block_desc_1_DATA = { 0, sizeof(struct __FYPerson__test_block_impl_1), __FYPerson__test_block_copy_1, __FYPerson__test_block_dispose_1}; static void _I_FYPerson_test(FYPerson * self, SEL _cmd) { void (*block)(void) = ((void (*)())&__FYPerson__test_block_impl_0((void *)__FYPerson__test_block_func_0, &__FYPerson__test_block_desc_0_DATA, self, 570425344)); void (*block2)(void) = ((void (*)())&__FYPerson__test_block_impl_1((void *)__FYPerson__test_block_func_1, &__FYPerson__test_block_desc_1_DATA, self, 570425344)); }Copy the code

Block and block2 are both struct__fyPerson__test_block_IMPL_1, which internally references a pointer to the FYPerson object, which is a local variable that needs to be captured. The second block that accesses the _name captures the FYPerson object. To access the _name, you need to access the FYPerson object first and then the _name, essentially accessing the Person. name, so you capture the FYPerson object.

Verify that a block is an object type:

Void (^block)(void)=^{NSLog(@)"Hello, World!");
		};
		NSLog(@"Own class:%@ its dad class:%@ its grandpa class:%@ its grandpa's tclass:%@",[block class],[[block class] superclass],[[[block class] superclass]superclass],[[[[block class] superclass]superclass]  superclass]); // The output is: own class: __NSGlobalBlock__ its parent class:__NSGlobalBlock its grandparent class:NSBlock its grandparent's tclass:NSObjectCopy the code

You can see that blocks inherit from the base class, so blocks are OC objects.

The classification of the block

There are three types of blocks, as shown below. You can see the specific type by calling the class method or isa pointer, which ultimately inherits from the NSBlock type.

  • NSGlobalBLock (_NSConcreteGLobalBlock)
  • NSStackBlock (_NSConcreteStackBlock)
  • NSMallocBLock (_NSConcreteMallocBlock)

Memory allocation in an application looks like this:

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the program area. The text area -- -- -- -- -- -- -- -- -- -- -- -- -- -- - the data area. Data area < -- -- -- -- -- -- -- -- -- _NSConcreteGlobalBlock global variables (storage) -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the heap < -- -- -- -- -- -- -- -- -- _NSConcreteMallocBlock (dynamic application free memory areas) -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- stack < -- -- -- -- -- -- -- -- -- _NSConcreteStackBlock (save storage local variables) ---------------Copy the code
Block type The environment
NSGlobalBLock The auto variable is not accessed
NSStackBlock Accessing the auto variable
NSMallocBLock NSStackBlockCall the copy

Verify that MRC is required. Locate the project file and set Project ->Object -c Automatic Reference Counting= to NO

int age = 10;

void(^block1)(void)=^{
	NSLog(@"block1");
};
void(^block2)(void)=^{
	NSLog(@"block2 %d",age);
};
void(^block3)(void)=[block2 copy];
NSLog(@"block1:%@ block2:%@ block3:%@ ",[block1 class],[block2 class],[block3 class]); Block1 :__NSGlobalBlock__ block2:__NSStackBlock__ block3:__NSMallocBlock__Copy the code

Blocks that do not access auto are __NSGlobalBlock__, blocks that access auto are __NSStackBlock__, and blocks that manually call copy are __NSMallocBlock__. __NSMallocBlock__ is on the heap and requires the programmer to manually release [block3 release]; If not released, memory leaks may occur.

Each type of block calls to copy result in the following

Block type The configuration storage domain of the copy source Print effect
NSGlobalBLock The heap Copy from stack to heap
NSStackBlock The data area of the program Do nothing
NSMallocBLock The heap Reference count +1

In an ARC environment, the compiler automatically copies blocks on the stack to the heap as appropriate, as in the following case

  • Block as a function return value
  • Assigns a block to a __strong pointer
  • Block is used as a Cocoa API method name containing a method parameter called usingBlock
  • Block as a method parameter of the GCD API

Test in ARC environment:


typedef void (^FYBlock)(void);
typedef void (^FYBlockInt)(int);
FYBlock myBlock() {return ^{
		NSLog(@"Ha ha");
	};
};
FYBlock myBlock2(){
	int age = 10;
	return ^{
		NSLog(@"Ha ha % d",age);
	};
};
int main(int argc, const char * argv[]) {
	@autoreleasepool {
		FYBlock block = myBlock();
		FYBlock block2 = myBlock2();
		int age = 10;
		FYBlock block3= ^{
			NSLog(@"Strong pointer block %d",age);
		};
		NSLog(@"No access variable :%@ Access layout variable :%@ Strong pointer :%@",[block class],[block2 class],[block3 class]);
	}
	return0; } // Output no access variable :__NSGlobalBlock__ Access local variable :__NSMallocBlock__ Strong pointer :__NSMallocBlock__Copy the code

In the arc environment, the block without access to variables is __NSGlobalBlock__, the local variable is __NSMallocBlock__, and the strong pointer is __NSMallocBlock__. The strong pointer automatically copies from stack to heap. Change from system management to developer manual management.

So here are some suggestions:

Recommended writing method of block attribute under MRC

  • @property (copy, nonatomic) void (^block)(void);

Recommended way to write block properties under ARC

  • @property (strong, nonatomic) void (^block)(void);
  • @property (copy, nonatomic) void (^block)(void);

Object type data interacts with blocks

The __block modifier is used to read the address of the object directly into the block structure. The __weak modifier is a weak reference, and the default is a strong reference. Let’s look at this code

//FYPerson.h
@interface FYPerson : NSObject
@property (nonatomic,assign) int age;
@end

//FYPerson.m
@implementation FYPerson
@end

//main.m
typedef void (^FYBlock)(void);

int main(int argc, const char * argv[]) {
	@autoreleasepool {
		FYBlock block ;
			FYPerson *person = [[FYPerson alloc]init];
			person.age = 10;
		__weak typeof(person) __weakPerson = person;
			block = ^{
				NSLog(@" %d",__weakPerson.age);
			};
		
		block();
	}
	return 0;
}
Copy the code

Convert to CPP using the following command

Xcrun-sdk iphoneOS clang-arch arm64-rewrite-objc-fobjc-arc-fobjc-Runtime =ios-8.0.0 main.m -o main.cppCopy the code

Key structure code:

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; FYPerson *__weak __weakPerson; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, FYPerson *__weak ___weakPerson, int flags=0) : __weakPerson(___weakPerson) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { FYPerson *__weak __weakPerson = __cself->__weakPerson; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_c0_7nm4_r7s4xd0mbs67ljb_b8m0000gn_T_main_7f0272_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)__weakPerson, sel_registerName("age")));
   }
Copy the code

FYPerson *__weak __weakPerson is a __weak modifier object when the block inside is changed to block = ^{NSLog(@” %d”,person.age); }; After converting the source code

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

Person is defined by __storng by default. In ARC, block references external variables, and the system copies the block to the heap, which is managed by the developer

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*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->__weakPerson, (void*)src->__weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/); } static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->__weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/); }Copy the code

Void (*copy) and void (*dispose) are +1 and -1 on the reference count of block.

If the block is copied to the heap

  • The copy function inside the block is called
  • Copy calls the _Block_object_assign function internally
  • The _Block_object_assign function performs operations based on the __strong, __weak, and __unsafe_unretained modifier of the auto variable, generating a retained or weak reference

If a block is removed from the heap

  • Dispose function inside the block is called
  • The _Block_object_dispose function is called internally
  • The _Block_object_dispose function automatically releases the referenced auto variable (release, reference count -1, if 0, destroy).
function Call time
The copy function Blocks on the stack are copied to the heap
The dispose function Blocks on the heap are discarded

The title

When will person be released?

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
	NSLog(@"---%d",person.age);
});
}
Copy the code

3 seconds later, the dispatch will strong-reference the block, and the block will strong-reference the Person. When the block is released, the Person will be freed because there are no other references.

Transformation 1: When will the person be released

FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
__weak FYPerson *__weakPerosn = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
	NSLog(@"---%d",__weakPerosn.age);
});
Copy the code

__weak does not strongly reference perOSN, dispatch_block is released immediately. Transformation 2: When will the person be released

FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
__weak typeof(person) __weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
	NSLog(@"---%d",__weakPerson.age);
	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
		NSLog(@"---%d",person.age);
	});
});
Copy the code

Person is strongly referenced by an internal block, then the person will not be released before block destruction, __weakPerson will not be destroyed after execution, NSLog(@”– –%d”,person.age) will be destroyed after execution. The answer is 4 seconds after the NSLog(@”– %d”,person.age) completes, the person is destroyed.

Transformation 3: When will the person be released

FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
__weak typeof(person) __weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
	NSLog(@"---%d",person.age);
	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
		NSLog(@"---%d",__weakPerson.age);
	});
});
Copy the code

Person is strongly referenced to the first block, and person is weakly referenced to the second block, only to be released when the first block completes execution.

Modify block external variables

To modify a variable, you first need the valid area of the variable, or the address that the block holds. Example 1:

int age = 10; FYBlock block = ^{ age = 20; };Copy the code

Static int age = 10; static int age = 10; static int age = 10; Or we can change int age = 10 to a global variable, which is not captured in a block, but is essentially compiled into c functions, which can access global variables anywhere.

__block nature

__block is essentially a modified object or primitive type. When compiled, a structure __Block_byref_age_0 is generated, in which *__forwarding refers to the structure itself, modifying the value of the variable with (age->__forwarding->age) = 20.

struct __Block_byref_age_0 { void *__isa; __Block_byref_age_0 *__forwarding; int __flags; int __size; int age; / / 10}; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_age_0 *age; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_age_0 *age = __cself->age; // bound by ref (age->__forwarding->age) = 20; NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_043d00_mi_0,(age->__forwarding->age)); }Copy the code

Age has one outside the block, one inside the block, are they the same? Let’s explore:

typedef void (^FYBlock)(void); struct __Block_byref_age_0 { void *__isa; struct __Block_byref_age_0 *__forwarding; int __flags; int __size; int age; / / 10}; struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(void); void (*dispose)(void); }; struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; struct __Block_byref_age_0 *age; // by ref }; int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... __block int age = 10; NSLog(@" age1:%p",&age);
        FYBlock block = ^{
            age = 20;
            NSLog(@"age is %d",age);
        };
        struct __main_block_impl_0 *main= (__bridge struct __main_block_impl_0 *)block;
        NSLog(@" age1:%p age2:%p",&age,&(main->age->__forwarding->age));
	}
	return0; } Output: age1: 0x7ffEEFBFF548 age1:0x100605358 age2:0x100605358Copy the code

After the __block modifier, the age is the same as the age address in the __Block_byref_age_0 structure, indicating that the age has been copied.

Example:

	__block	int age = 10;
        NSLog(@" age1:%p",&age);
        NSObject *obj=[[NSObject alloc]init];
        FYBlock block = ^{
            
            NSLog(@"age is %d,obj is %p",age,&obj);
        };
Copy the code

Command compilation

Xcrun-sdk iphoneOS clang-arch arm64-rewrite-fobjc-arc-fobjc-Runtime =ios-8.0.0 main.mCopy the code

Extract the main functions:

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *__strong obj;
  __Block_byref_age_0 *age; // by ref
};
Copy the code

The __main_block_IMPL_0 structure makes a strong reference to age and holds the address of the structure, copies age to the heap, and converts age to the __Block_byref_age_0 object, __main_block_IMPL_0 can be assigned to __Block_byref_age_0->__forwarding->age. __Block_byref_age_0 is an object that requires memory management, __main_block_copy_0 presents code for _Block_object_assign and _Block_object_dispose to manage memory for __Block_byref_age_0.

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) { _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/); _Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/); } static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/); _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/); }Copy the code

Age and obj are the same object structure. Obj is just a strong reference without address transformation because OBj itself is on the heap, and blocks are on the heap, so there is no need to copy new OBj to manage it.

See circular referencing is a bad example

typedef   void (^FYBlock)(void);
@interface FYPerson : NSObject

@property (nonatomic,copy) FYBlock blcok;
@end

@implementation FYPerson
- (void)dealloc{
	NSLog(@"%s",__func__);
}
@end


int main(int argc, const char * argv[]) {
	@autoreleasepool {
        NSLog(@" age1:%p",&age);
        FYPerson *obj=[[FYPerson alloc]init];
		[obj setBlcok:^{
			NSLog(@"%p",&obj);
		}];
		NSLog(@"-- -- -- -- -- -- -- -- -- -- -- -- -- -");
	}
	return 0;
}

Copy the code

The output is:

Age1:0x7FFEEFBff4e8 Block The execution is complete --------------Copy the code

Obj strongly references block by copy, and block forcibly references OBj by default __strong, which is A<—->B. Mutual references cause it to fail to free when it should be freed at the end of execution. The main change to

FYPerson *obj=[[FYPerson alloc]init];
		__weak typeof(obj) weakObj = obj;
		[obj setBlcok:^{
			NSLog(@"%p",&weakObj);
		}];
Copy the code

As a result,

Age1:0x7ffeefBff4e8 Block Completed -------------- -[FYPerson dealloc]Copy the code

An __weak or __unsafe__unretain weak reference to obj is used. When the block execution is complete, obj is released, and the block is released normally without strong reference to each other.

__weakand__unsafe__unretain

__weak and __unsafe__unretain are both weak references to OBj and do not affect the normal release of OBj. The difference is that __weak will be set to nil after the release, and __unsafe__unretain will not handle the memory. Let’s verify this conclusion in detail:

typedef   void (^FYBlock)(void);
@interface FYPerson : NSObject
@property (nonatomic,assign) int age ;
@end
@implementation FYPerson
-(void)dealloc{
	NSLog(@"%s",__func__);
}
@end
struct __Block_byref_age_0 {
	void *__isa;
	struct __Block_byref_age_0 *__forwarding;
	int __flags;
	int __size;
	int age;
};
struct __block_impl {
	void *isa;
	int Flags;
	int Reserved;
	void *FuncPtr;
};
struct __main_block_desc_0 {
	size_t reserved;
	size_t Block_size;
	void (*copy)(void);
	void (*dispose)(void);
};
struct __main_block_impl_0 {
	struct __block_impl impl;
	struct __main_block_desc_0* Desc;
	FYPerson *__unsafe_unretained __unsafe_obj;
};

int main(int argc, const char * argv[]) {
	@autoreleasepool {
	    // insert code here...
		FYBlock block;
		{
			FYPerson *obj=[[FYPerson alloc]init];
			obj.age = 5;
			__weak typeof(obj) __unsafe_obj = obj;
			block = ^{
				
				NSLog(@"obj->age is %d obj:%p",__unsafe_obj.age,&__unsafe_obj);
			};
			struct __main_block_impl_0 *suct = (__bridge struct __main_block_desc_0 *)block;
			NSLog(@"inside struct->obj:%p",suct->__unsafe_obj); Struct __main_block_impl_0 *suct = (__bridge struct __main_block_desc_0 *)block; NSLog(@"outside struct->obj:%p",suct->__unsafe_obj); // breakpoint 2 block(); NSLog(@"----end------");
	}
	return 0;
}
Copy the code

Print the obj command using LLDB at breakpoint 1

(lldb) p suct->__unsafe_obj->_age
(int) $0Struct ->obj:0x102929d80Copy the code

Check the value of obj again at breakpoint 2 and an error is reported that the memory cannot be read

-[FYPerson dealloc]
outside struct->obj:0x0
p suct->__unsafe_obj->_age
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
Copy the code

Out of the valid range of obj, obj has been reset to nil, 0x0000000000000000. Across obj, __weak is __unsafe_unretained.

(lldb) p suct->__unsafe_obj->_age
(int) $0 = 5
inside struct->obj:0x10078c0c0

Copy the code

Look at the address again at breakpoint 2 and see the value of age

-[FYPerson dealloc]
outside struct->obj:0x10078c0c0
(lldb) p suct->__unsafe_obj->_age
(int) The $1 = 5
Copy the code

__unsafe_unretained memory did not reset to empty in time after obj destruction.

What do we do when we leave a page and need to perform an action again? Practical application A:

-(void)test{
	__weak typeof(self) __weakself = self;
	[self setBlcok:^{
		dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
			NSLog(@"perosn :%p",__weakself);
		});
	}];
	self.blcok();
}

int main(int argc, const char * argv[]) {
	@autoreleasepool {
		{
        	FYPerson *obj=[[FYPerson alloc]init];
			[obj test];
			NSLog(@"Block execution completed --------------");
		}
		NSLog(@"The person is dead");
	}
	return0; } output: block is done -- -- -- -- -- -- -- -- -- -- -- -- -- -- - [FYPerson dealloc] person diedCopy the code

Take a quick look, everywhere is right! A weak reference to self using __weak does not cause an infinite loop, and when self dies, the block dies too, causing a problem, self and block co-exist, but this takes 3 seconds to execute, after which self is dead and the block is dead, which is obviously not in line with our business requirements. So we peel off the relationship between block and self, make block strongly reference self, and self doesn’t hold the block to satisfy the business. As follows:

    __block typeof(self) __weakSelf = self;//__block或者没有修饰符
    dispatch_async(dispatch_get_main_queue(), ^{
        sleep(2);
        NSLog(@"obj:%@",__weakSelf->_obj);
    });
//perosn :0x0
Copy the code

When self doesn’t hold a block, the block can strongly reference self, and when the block executes, it frees itself, and it frees self. When self holds a block, the block must weakly reference self, then it frees self, and otherwise it will loop.

conclusion

  • blockThe essence is to encapsulate the function call and the calling environmentThe structure of the bodyobject
  • __blockDecorated variables are encapsulated asThe structure of the bodyObjects that were previously in the data segment will be copied to the heap, and those that were previously in the heap will not be affectedautoObjects in theblockInternal cannot modify the problem inMRCEnvironment,__blockThere is no strong reference to a variable.
  • blockDo not usecopyIs not moved from the global or stack area to the heap, usingcopyAnd then it’s managed by the originator
  • useblockNote that a circular reference cannot be generated, and a reference cannot become a ring. If you actively make one of the references weak, you will not generate a circular reference.
  • __weakModified object,blockNo strong references to objects in executionblockThe value may have been set by the system tonil.__unsafe_unretainedMemory does not reset to empty in time after the destruction of a modifier.

The CPP we are looking at is the compiled code, is the Runtime consistent with what we are looking at? Listen next time.

Data download

  • Download of learning materials
  • demo code
  • Runtime runnable source

This article is less pictures, I think or follow the code to knock again, more impressed.


The most afraid of a mediocre life, but also comfort their ordinary valuable.

AD time