This post was originally posted on my blog

preface

Before you start, ask the following questions.

  • How does block work? What is the essence?
  • __blockWhat is the function of? What is the use of attention?
  • Why is the property modifier copy for block? What are the implications of using blocks?
  • Once a block is not copied, it is not on the heap
  • Block is modifying NSMutableArray. Do I need to add __block?

If you are not familiar with it now, I hope to have a new understanding after reading this article.

takeaway

This article explains blocks from the following aspects

  • Basic use of blocks
  • Layout of blocks in memory
  • Block captures and analyzes variables
  • MRC versus ARC
  • __blockThe analysis of the
  • Memory management problems in block. Procedure
  • Circular reference problems caused by blocks

What is a block

Let’s start with what closures are. On Wikipedia, closures are defined as

In programming languages, A closure is a function or reference to a function together with a referencing environment – a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.

In translation, the expression is

A closure is a function (or pointer to a function), plus context variables (sometimes called free variables) outside of which the function executes.

  • Blocks are really objective-C implementations of closures.

Basic use of blocks

  • A block is also essentially an OC object with an ISA pointer inside it

  • A block is an OC object that encapsulates a function call and its environment

  • The underlying structure of a block is shown below

No parameters no definition and use of return values

Void (^MyBlockOne)(void) = ^{NSLog(@)"No parameter, no return value"); }; / / call MyBlockOne ();Copy the code

No parameter has the definition and use of return values

Int (^MyBlockTwo)(void) = ^{NSLog(@)"Return value with no parameters");
    return2; }; Int res = MyBlockTwo();Copy the code

Definition and use of return values with or without parameters

Void (^MyBlockThree)(int a) = ^(int a){NSLog(@)"No return value a = %d",a); }; / / call MyBlockThree (10);Copy the code

The definition and use of parameters and return values

Int (^MyBlockFour)(int a) = ^(int a){NSLog(@)"Return value a = %d",a);
    return a * 2;
};
MyBlockFour(4);
Copy the code

Typedef definition Block

In practice, we often need a block as an attribute, so we can define a block

Eg: Define a block that takes parameters and returns values

typedef int (^MyBlock)(int a, int b);
Copy the code

When you define a property, you can hold the block as follows

@property (nonatomic,copy) MyBlock myBlockOne;
Copy the code

Block implementation

self.myBlockOne = ^int(int a, int b) {
      return a + b;
};
Copy the code

call

self.myBlockOne(2, 5);
Copy the code

Block types and data structures

Block data structure analysis

Generate CPP files

The following code

int age = 20;
void (^block)(void) =  ^{
     NSLog(@"age is %d",age);
 };
        
block();

Copy the code
  • Open the terminal and CD to the current directory

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

To generate the main CPP

Block structure analysis

int age = 20; / / block definition of void block (*) (void) = ((void (*) ()) & __main_block_impl_0 (__main_block_func_0 (void *), &__main_block_desc_0_DATA, age)); / / block call ((void (__block_impl *) (*)) ((__block_impl *) block) - > FuncPtr) ((__block_impl *) block);Copy the code

The above code removes some of the cast code and is left with something like this

int age = 20; void (*block)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, age ); FuncPtr(FuncPtr);Copy the code

Block is a struct object, __main_block_IMPL_0

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, struct __main_block_desc_0 *desc, int _age, int flags=0) : Age (_age) {//isa points to _NSConcreteStackBlock indicating that this block is of type impl. Isa = &_NSConcretestackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

The first struct __block_impl impl;

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

The second struct is __main_block_desc_0;

static struct __main_block_desc_0 { size_t reserved; size_t Block_size; // struct __main_block_impl_0 size of memory}Copy the code

The third one in the structure is age

That is, the captured local variable age

__main_block_func_0

Static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int age = __cself->age; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_7f3f1b_mi_0,age); }Copy the code

Let me draw it as a picture

Variable to capture

In fact, the above code we can already see the variable capture, here continue to analyze in detail

Variable types Inside the block access
Local variable auto Square root Value passed
Local variable static Square root Pointer passed
The global variable x Direct access to the

Local variable auto

  • The local variables that we normally write, by default, have auto.
Run the code

For example, the following code

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

Is equivalent to

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

The output

20

Analysis of the

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

To generate the main CPP

As is shown in

int age = 20;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 25;

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;

NSLog((NSString *)&__NSConstantStringImpl__var_folders_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_d36452_mi_5);
Copy the code

__main_block_IMPL_0 (age = 25); __main_block_impl_0 (age = 25)

Local variable static

Static modified local variables are not destroyed

Run the code

eg

static int height  = 30;
int age = 20;
void (^block)(void) =  ^{
     NSLog(@"age is %d height = %d",age,height);
};
age = 25;
height = 35;
block();
        
Copy the code

The execution result is

age is 20 height = 35

Copy the code

As you can see, changing height outside the block still affects the value inside the block

Analysis of the

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

To generate the main CPP

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *height = __cself->height; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_3146e1_mi_4,age,(*height));
        }

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; 



        static int height = 30;
        int age = 20;
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
        age = 25;
        height = 35;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
Copy the code

So as you can see, age is passed directly, height is passed *height so you’re just passing in the memory address and changing it.

The global variable

Run the code
int age1 = 11;
static int height1 = 22;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) =  ^{
            NSLog(@"age1 is %d height1 = %d",age1,height1);
        };
        age1 = 25;
        height1 = 35;
        block();

    }
    return 0;
}
Copy the code

The output is

age1 is 25 height1 = 35


Copy the code
Analysis of the

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

To generate the 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, 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_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_4e8c40_mi_4,age1,height1); } 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)); age1 = 25; height1 = 35; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }return 0;
}
Copy the code

As you can see from the CPP file, the global variables age1 and Height1 are not captured. When accessing the global variables age1 and Height1, they are accessed directly

summary

Variable types Inside the block access
Local variable auto Square root Value passed
Local variable static Square root Pointer passed
The global variable x Direct access to the
  • Auto modifies local variables that are passed in value
  • Static modifier local variable that is pointer passed

It makes sense, too, because local variables decorated with auto are destroyed when they leave scope. If it is passed as a pointer, the variable may already be destroyed by the time it is accessed. The program will go wrong. Global variables are accessible everywhere, so there is no need to capture them.

Block type

Block is also an OC object

Before we look at block types, let’s clarify the concept that a block with a pointer to ISA is an OC object, as shown in the following code

void (^block)(void) =  ^{
      NSLog(@"123");
};

NSLog(@"block.class = %@",[block class]);
NSLog(@"block.class.superclass = %@",[[block class] superclass]);
NSLog(@"block.class.superclass.superclass = %@",[[[block class] superclass] superclass]);
NSLog(@"block.class.superclass.superclass.superclass = %@",[[[[block class] superclass] superclass] superclass]);
Copy the code

The output is

iOS-block[18429:234959] block.class = __NSGlobalBlock__ iOS-block[18429:234959] block.class.superclass = __NSGlobalBlock  iOS-block[18429:234959] block.class.superclass.superclass = NSBlock iOS-block[18429:234959] block.class.superclass.superclass.superclass = NSObjectCopy the code

The block type in the above code is __NSGlobalBlock, and the inheritance relationship can be expressed as __NSGlobalBlock__ : __NSGlobalBlock: NSBlock: NSObject

There are three types of blocks

There are three types of blocks, which you can see by calling the class method or isa pointer. They are all derived from the NSBlock type

  • __NSGlobalBlock__ (_NSConcreteGlobalBlock)
  • __NSStackBlock__ (_NSConcreteStackBlock)
  • __NSMallocBlock__ (_NSConcreteMallocBlock)

The three different types and environments correspond as follows

Block type The environment
__NSGlobalBlock__ The auto variable is not accessed
__NSStackBlock__ The auto variable is accessed
__NSMallocBlock__ __NSStackBlock__Call the copy

Its memory allocation is as follows

Run code view

Under the MRC

Note that the following code is tested under MRC

Note that the following code is tested under MRC

Note that the following code is tested under MRC

Because the compiler does a lot of optimizations with ARC, it often doesn’t see the essence,

  • Change to MRC method:Build SettingsThe inside of theAutomatic Reference CountingChange to the NO

As shown in the figure below

Let me write it in code

void (^block)(void) =  ^{
       NSLog(@"123");
};

NSLog(@"No access to Auto block.class = %@",[block class]);
        
        
auto int a = 10;
void (^block1)(void) =  ^{
      NSLog(@"a = %d",a);
};
        
NSLog(@"Access auto Block1. class = %@",[block1 class]);
               
NSLog(@"Page view auto and copy block1-copy. Class = %@",[[block1 class] copy]);
Copy the code

The output is

Os-block [23542:349513] does not access Auto Block. class = __NSGlobalBlock__ ios-block [23542:349513] accesses Auto Block1. class = __NSStackBlock__ ios-block [23542:349513] Access Volume Auto and copy block1-copy. Class = __NSStackBlock__Copy the code

And you can see that

Block type The environment
__NSGlobalBlock__ The auto variable is not accessed
__NSStackBlock__ The auto variable is accessed
__NSMallocBlock__ __NSStackBlock__Call the copy

Is consistent with the

Under the ARC

Under ARC, the output from the above code looks like the following because the compiler has copied it

Ios-block [24197:358752] does not access Auto Block. class = __NSGlobalBlock__ ios-block [24197:358752] accesses Auto Block1. class = __NSMallocBlock__ ios-block [241971:358752] Access Volume Auto and copy Block1-copy. Class = __NSMallocBlock__Copy the code

Block of copy

As mentioned earlier, in an ARC environment, the compiler automatically copies blocks on the stack to the heap as appropriate, as in the following cases

The copy of

  • 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
Block as a function return value

// define Block typedef void (^YZBlock)(void); // the function YZBlock returns Blockmyblock()
{
    int a = 6;
    return ^{
        NSLog(@"--------- %d",a);
    };
}

YZBlock Block = myblock();
Block();
NSLog(@" [Block class] = %@", [Block class]);
Copy the code

The output is

iOS-block[25857:385868] --------- 6
iOS-block[25857:385868]  [Block class] = __NSMallocBlock__
Copy the code

If the above code output __NSStackBlock__ under MRC, in ARC, automatically copy, so __NSMallocBlock__

Assign a block to__strongWhen the pointer
// define Block typedef void (^YZBlock)(void); int b = 20; YZBlock Block2 = ^{ NSLog(@"abc %d",b);
};
NSLog(@" [Block2 class] = %@", [Block2 class]);

Copy the code

The output is

iOS-block[26072:389164]  [Block2 class] = __NSMallocBlock__
Copy the code

If the above code output __NSStackBlock__ under MRC, in ARC, automatically copy, so __NSMallocBlock__

Block is used as a Cocoa API method name containing a method parameter called usingBlock

eg:


NSArray *array = @[@1,@4,@5];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            // code
}];
Copy the code
Block as a method parameter of the GCD API

eg

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
            
});    
       
        
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //code to be executed after a specified delay
});
Copy the code

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);

The auto variable of object type

Example a

Let’s start with a simple example of defining a class YZPerson that contains only one dealloc method

@interface YZPerson : NSObject
@property (nonatomic ,assign) int age;
@end


@implementation YZPerson

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

@end
Copy the code

The following code uses

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

I’m sure you can see what it’s going to say, yes, it’s going to destroy the person, and then it’s going to print —– because the person is in curly braces, and when the curly braces are done, the person is going to be destroyed.

iOS-block[1376:15527] -[YZPerson dealloc]
iOS-block[1376:15527] -----
Copy the code

Example 2

The example above, isn’t that easy? What about the one below,

// define block typedef void (^YZBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { YZBlock block; { YZPerson *person = [[YZPerson alloc]init]; person.age = 10; block = ^{ NSLog(@"---------%d", person.age);
            };
            
             NSLog(@"block.class = %@",[block class]);
        }
        NSLog(@"Block destroyed");

    }
    return 0;
}


Copy the code

When a block is of type __NSMallocBlock__, the block can preserve the life of the person, because the person is not destroyed after leaving the braces. The person is destroyed when the block is destroyed

Ios-block [3186:35811] blocke. class = __NSMallocBlock__ ios-block [3186:35811] block destroy ios-block [3186:35811] -[YZPerson dealloc]Copy the code

Analysis of the

If the main. M is generated into main. CPP, you can see the following code: xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m

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

It’s obviously this block that contains YZPerson star person.

The MRC block refers to the instance object

The above example, is not very simple, that if MRC

// define block typedef void (^YZBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { YZBlock block; { YZPerson *person = [[YZPerson alloc]init]; person.age = 10; block = ^{ NSLog(@"---------%d", person.age);
            };
            
            NSLog(@"block.class = %@",[block class]); // under MRC, need to manually release [person release]; } NSLog(@"Block destroyed"); // MRC requires manually releasing [block release]; }return 0;
}
Copy the code

The output is

iOS-block[3114:34894] block.class = __NSStackBlock__ iOS-block[3114:34894] -[YZPerson dealloc] iOS-block[3114:34894] Block to destroyCopy the code

The difference is that there is no NSLog(@”block destruction “); [YZPerson Dealloc] has already been executed. In other words, when the person leaves the braces, it’s destroyed.

When a block is of type __NSStackBlock__, the block cannot save the life of the person

Under MRC [block copy] references the instance object

Under MRC, blocks are copied

// define block typedef void (^YZBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { YZBlock block; { YZPerson *person = [[YZPerson alloc]init]; person.age = 10; block = [^{ NSLog(@"---------%d", person.age);
            } copy];
            
			NSLog(@"block.class = %@",[block class]); // under MRC, need to manually release [person release]; } NSLog(@"Block destroyed");
        [block release];
    }
    return 0;

Copy the code

If the block is of type __NSMallocBlock__, the block can preserve the life of the person

Ios-block [3056:34126] block.class = __NSMallocBlock__ ios-block [3056:34126] block destroy ios-block [3056:34126] -[YZPerson dealloc]Copy the code

__weakmodified

  • The following code
// define block typedef void (^YZBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { YZBlock block; { YZPerson *person = [[YZPerson alloc]init]; person.age = 10; __weak YZPerson *weakPerson = person; block = ^{ NSLog(@"---------%d", weakPerson.age);
            };
            
             NSLog(@"block.class = %@",[block class]);
        }
       
        NSLog(@"Block destroyed");
    }
    return 0;
}
Copy the code
  • The output is
iOS-block[3687:42147] block.class = __NSMallocBlock__ iOS-block[3687:42147] -[YZPerson dealloc] iOS-block[3687:42147] Block to destroyCopy the code
  • Generate CPP files

Note:

  • When using clang to convert OC to C++ code, you may encounter the following problem: cannot create __weak reference in file using manual reference

  • Arc-sdk iphoneOS clang-arch arm64-rewrite-objc-fobjc-arc-fobjc-Runtime =ios-8.0.0 main.m

Once generated, you can see the following code, which in the MRC case is significantly more generated because ARC automatically copied it

Void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); // Dispose function void (*dispose)(struct __main_block_impl_0*);Copy the code
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; // YZPerson *__weak weakPerson; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, YZPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; 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*); // Dispose function void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0 }; Static void __main_block_copy_0(struct __main_block_impl_0* DST, _Block_object_assign((void*)& DST ->person, (void*) SRC ->person, struct __main_block_impl_0* SRC) { 3/*BLOCK_FIELD_IS_OBJECT*/); } // The _Block_object_dispose function is called 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

summary

Whether it’s MAC or ARC

  • When the block is__NSStackBlock__When the type is in the stack space, strong references are not made to external objects regardless of whether strong or weak are used outside
  • When the block is__NSMallocBlock__Type, in heap space, block is internal_Block_object_assignThe function will be based onstrongorweakStrong or weak references to external objects.

It makes sense, because the block itself is on the stack, and it can disappear at any time, so how can you save someone else’s life?

  • When the block internally accesses the auto variable of the object type

  • If the block is on the stack, there is no strong reference to the auto variable

  • If the block is copied to the heap

    • The copy function inside the block is called
    • Copy is called internally_Block_object_assignfunction
    • _Block_object_assignThe function will depend on the modifier of the auto variable(__strong, __weak, __unsafe_unretained)Do something to form a strong or weak reference
  • If a block is removed from the heap

    • Dispose function inside the block is called
    • The dispose function is called internally_Block_object_disposefunction
    • _Block_object_disposeThe function automatically releases the referenced auto variable (release)
function Call time
The copy function Blocks on the stack are copied to the heap
The dispose function Blocks on the heap are discarded

__block

To start with a simple example, look at the code below

// define block typedef void (^YZBlock)(void); int age = 10; YZBlock block = ^{ NSLog(@"age = %d", age);
};
block();
Copy the code

The code is simple, run, output

age = 10

The above example accesses an external local variable in a block, so the question is, what if I want to change the value of an external local variable in a block?

Three ways to modify local variables

Write as a global variable

We define a as a global variable, so we can access it anywhere,

// define block typedef void (^YZBlock)(void); int age = 10; int main(int argc, const char * argv[]) { @autoreleasepool { YZBlock block = ^{ age = 20; NSLog(@"Age = %d", age);
        };
        
        block();
        NSLog(@Age = %d, age);
    }
    return 0;
}
Copy the code

This is easy. The output is zero

Age is equal to 20 after the block is modifiedCopy the code

This is not a problem for the output, because global variables are accessible everywhere, and the memory address of age can be manipulated directly inside the block. After the block is called, the address referenced by the global variable age has been changed to 20, so this is the print above

Static Modifies local variables

// define block typedef void (^YZBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { static int age = 10; YZBlock block = ^{ age = 20; NSLog(@"Age = %d", age);
        };
        
        block();
        NSLog(@Age = %d, age);
    }
    return 0;
}
Copy the code

The output of the code above is

Age is equal to 20 after the block is modifiedCopy the code

If the main. M is generated into main. CPP, you can see the following code: xcrun-sdk iphoneos clang-arch arm64-rewrite-objc main.m

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 (*age) = 20; NSLog((NSString *)&__NSConstantStringImpl__var_folders_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_5dbaa1_mi_0, (*age)); }Copy the code

When static is applied to a local variable, the block contains an int *age member, which captures the address of age. That way, of course, you can change the local variable age inside the block.

  • The above two methods can achieve the goal of modifying local variables inside the block, but doing so will result in memory being unable to be freed. Global variables, or static variables, cannot be destroyed in time and will remain in memory. Most of the time, we just need to use it temporarily, and when we don’t use it, we can destroy it. Then the third kind, which is the protagonist of today__blockBig time

__blockTo modify

The following code

// define block typedef void (^YZBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { __block int age = 10; YZBlock block = ^{ age = 20; NSLog(@"Age = %d",age);
        };
        
        block();
        NSLog(@Age = %d,age);
    }
    return 0;
}
Copy the code

The output is the same as the above two types

Age is equal to 20 after the block is modifiedCopy the code

__blockAnalysis of the

  • The terminal executes the line of instructionxcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.mthemain.mgeneratemain.cpp

The first is the __Block_byref_age_0 structure

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_age_0 *age; __Block_byref_age_0 *age; __Block_byref_age_0 struct *_age flags __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; //fp is the function address Desc = Desc; }};Copy the code

A closer look at the __Block_byref_age_0 structure shows that the first member variable is an isa pointer and the second isa pointer to itself, __forwarding

Struct __Block_byref_age_0 {void *__isa; struct __Block_byref_age_0 {void *__isa; //isa pointer __Block_byref_age_0 *__forwarding; // a pointer to itself int __flags; int __size; int age; // Use the value};Copy the code

Look at the code inside the main function

__Block_byref_age_0 __attribute__((__blocks__(byref)))) __Block_byref_age_0 age = { (void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; Block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));Copy the code

The code is too long, so let’s simplify it and get rid of some of the strong code

__Block_byref_age_0 __attribute__((__blocks__(byref)))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; __Block_byref_age_0 __Block_byref_age_0 age = {0, // Assign to __isa (__Block_byref_age_0 *)&age,// assign to __forwarding, which is its own pointer 0, // Assign to __flags sizeof(__Block_byref_age_0),// assign to __size 10 // age uses the value}; Block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344)); YZBlock block = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_data, &age, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); // simplify to block->FuncPtr(block);Copy the code

Where the second (__Block_byref_age_0 *)&age in the __Block_byref_age_0 structure is assigned to the second __Block_byref_age_0 in the code structure above *__forwarding, so __forwarding stores Pointers to itself

__Block_byref_age_0 __Block_byref_age_0 age = {0, // Assign to __isa (__Block_byref_age_0 *)&age,// assign to __forwarding, which is its own pointer 0, // Assign to __flags sizeof(__Block_byref_age_0),// assign to __size 10 // age uses the value};Copy the code

The __Block_byref_age_0 structure has the following code: the second __forwarding holds Pointers to itself, and the fifth age holds local variables

Struct __Block_byref_age_0 {void *__isa; struct __Block_byref_age_0 {void *__isa; //isa pointer __Block_byref_age_0 *__forwarding; // a pointer to itself int __flags; int __size; int age; // Use the value};Copy the code

When called, it finds the pointer through __forwarding and fetches the age value.

(age->__forwarding->age));
Copy the code

summary

  • __block can be used to solve the problem of not being able to modify the value of the auto variable inside a block

  • __block cannot modify global or static variables.

    • The compiler will take__blockA variable is wrapped as an object

The call is to find the memory in which age is located from the __Block_byref_age_0 pointer, and then modify the value

Memory management Problems

Bloc accesses the OC object

The following code

When the inside of the block accesses the outside OC object

eg:

// define block typedef void (^YZBlock)(void); int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *obj = [[NSObject alloc]init]; YZBlock block = ^{ NSLog(@"%p",obj);
        };
         block();
    }
    return 0;
}

Copy the code

Use clang at the terminal to convert OC to C++ code

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

Copy from stack to heap, __main_block_desc_0 contains copy and dispose

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

Copy calls __main_block_copy_0

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

Its internal _Block_object_assign strongly or weakly references it based on the strong or weak modifiers in the code.

Check the __main_block_impl_0

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; //strong strong reference NSObject *__strong obj; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, int flags=0) : obj(_obj) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

The _Block_object_assign modifier is strong, so it is strongly referenced when called.

From the preceding information

  • When a block is on the stack, there is no strong reference to a __block variable

  • When a block is copied to the heap

    • The copy function inside the block is called
    • Copy is called internally_Block_object_assignfunction
    • _Block_object_assignFunction to__blockVariables form strong references (retain)
  • When a block is removed from the heap

    • Dispose function inside the block is called
    • The dispose function is called internally_Block_object_disposefunction
    • _Block_object_disposeThe function will release the reference automatically__block variable (release)

copy

When I copy it,

  • The copy function inside the block is called
    • Copy is called internally_Block_object_assignfunction
    • _Block_object_assignFunction to__blockVariables form strong references (retain)

As we know, the following code

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

The local variable age is on the stack, it refers to age inside the block, but when a block is copied from the stack to the heap, how can we guarantee that the next time a block accesses age, it will get access to it? Because we know that local variables on the stack are going to be destroyed at any time.

Suppose we now have two blocks on the stack, block0 and block1, that reference the __block variable on the stack. Now copy block0, we know that blocks on the stack copy to the heap, that is, block0 copies to the heap, and since block0 holds a __block variable, it copies that __block variable to the heap, At the same time, block0 on the heap is a strong reference to the __block variable on the heap, so that block0 can access the __block variable at any time.

Again, block0 was copied to the heap, and now if block1 is copied to the heap, there is no need to copy it again, because the variables have been copied to the heap, just copy block1 from the heap to the variable on the heap.

The release of

When it’s released

  • Dispose function inside the block is called
    • The dispose function is called internally_Block_object_disposefunction
    • _Block_object_disposeThe function will release the reference automatically__block variable (release)

In the above code, if there is only one block referencing a __block variable on the heap, the __block variable on the heap is destroyed when the block is destroyed, but if there are two blocks referencing a __block variable, the __block variable is discarded only when both blocks are discarded.

In fact, at the end of the day, it comes down to who uses it, who is responsible

Object typeAuto variable,__blockvariable

The auto variable num, __block variable int, obj and weakObj2 are listed below

 __block int age = 10;
    int num = 8;
    NSObject *obj = [[NSObject alloc]init];
    NSObject *obj2 = [[NSObject alloc]init];
    __weak NSObject *weakObj2 = obj2;
    YZBlock block = ^{
        NSLog(@"age = %d",age);
        NSLog(@"num = %d",num);
        NSLog(@"obj = %p",obj);
        NSLog(@"weakObj2 = %p",weakObj2);
        NSLog(@"Age = %d",age);
	};
    
block();
Copy the code

Execute terminal instruction

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

The generated code is shown below

The object type modified by __block

  • When a __block variable is on the stack, there is no strong reference to the object to which it points

  • When a __block variable is copied to the heap

    • Will be called__blockCopy function inside a variable
    • Copy is called internally_Block_object_assignfunction
    • _Block_object_assignThe function is based on the modifier of the object it points to (__strong,__weak,__unsafe_unretained) to retain strong references or weak references (note: this is only retained for ARC, not MRC)
  • If the __block variable is removed from the heap

    • Will be called__blockDispose function inside a variable
    • The dispose function is called internally_Block_object_disposefunction
    • _Block_object_disposeThe function automatically releases the object it points to.

__blockthe__forwardingPointer to the

__forwarding struct __Block_byref_obj_0 {void *__isa; __Block_byref_obj_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); NSObject *__strong obj; }; Age ->__forwarding->ageCopy the code

Age ->__forwarding->age

This is because if the __block variable is on the stack, it can be accessed directly, but if it has been copied to the heap, it can be accessed on the stack. Therefore, __forwarding should find the address on the heap first, and then value it

conclusion

  • When blocks are on the stack, there are no strong references to any of them

  • When blocks are copied to the heap, they are processed by the copy function

    • __blockVariable (suppose the variable name is a)
  • _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

  • The auto variable of the object type (assuming the variable name is p) _Block_object_assign((void*)& DST ->p, (void*) SRC ->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

  • Dispose (void*) SRC ->a, 8/*BLOCK_FIELD_IS_BYREF*/); Dispose (void*) SRC ->a, 8/*BLOCK_FIELD_IS_BYREF*/);

  • The auto variable of the object type (assuming the variable name isp) _Block_object_dispose((void*) SRC ->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

Circular reference problem

Continue exploring the issue of circular references to blocks.

If you look at the code below, you have a Person class with two properties, block and age

#import <Foundation/Foundation.h>

typedef void (^YZBlock) (void);

@interface YZPerson : NSObject
@property (copy, nonatomic) YZBlock block;
@property (assign, nonatomic) int age;
@end


#import "YZPerson.h"

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

Copy the code

The following code is displayed in main.m

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        YZPerson *person = [[YZPerson alloc] init];
        person.age = 10;
        person.block = ^{
             NSLog(@"person.age--- %d",person.age);
        };
        NSLog(@"-- -- -- -- -- -- -- --");

    }
    return 0;
}
Copy the code

The output is only

iOS-block[38362:358749] ——–

That is, the program ends without freeing the person, causing a memory leak.

Circular reference reason

Here’s a line of code that has a person pointer that points to a YZPerson object

YZPerson *person = [[YZPerson alloc] init];
Copy the code

after

 person.block = ^{
             NSLog(@"person.age--- %d",person.age);
        };
Copy the code

Then, with a strong pointer to Person inside the block, the code generates the CPP file

Xcrun-sdk iphoneOS clang-arch arm64-rewrite-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; // strong pointer to person YZPerson *__strong person; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, YZPerson *__strong _person, int flags=0) : person(_person) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

And the block is a property of Person

@property (copy, nonatomic) YZBlock block;
Copy the code

When the program exits, the local person variable is destroyed, but since MJPerson and block are directly and strongly referenced to each other, neither can be freed.

__weakResolving circular references

To solve the above problem, just use the __weak modifier

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        YZPerson *person = [[YZPerson alloc] init];
        person.age = 10;
        
        __weak YZPerson *weakPerson = person;
        
        person.block = ^{
            NSLog(@"person.age--- %d",weakPerson.age);
        };
        NSLog(@"-- -- -- -- -- -- -- --");

    }
    return 0;
}
Copy the code

After the compilation is complete

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; // Block inner weakPerson is weak reference YZPerson *__weak weakPerson; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, YZPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

When a local variable disappears, for YZPseson, there’s only one pointer to it, it’s destroyed, and then the block is destroyed.

__unsafe_unretainedResolving circular references

In addition to __weak above, you can also use __unsafe_unretained references

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        YZPerson *person = [[YZPerson alloc] init];
        person.age = 10;
        
        __unsafe_unretained YZPerson *weakPerson = person;
        
        person.block = ^{
            NSLog(@"person.age--- %d",weakPerson.age);
        };
        NSLog(@"-- -- -- -- -- -- -- --");

    }
    return 0;
}
Copy the code

For the CPP file is

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

__unsafe_unretained can be used as a solution for recurring references, but it’s best not to use __unsafe_unretained

  • __weak: does not generate a strong reference, and the pointer is set to nil automatically when the object it points to is destroyed
  • __unsafe_unretained: Does not generate a strong reference, unsafe, pointer to the destruction of the object, the stored address value remains the same

__blockResolving circular references

eg:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       __block YZPerson *person = [[YZPerson alloc] init];
        person.age = 10;
        person.block = ^{
            NSLog(@"person.age--- %d",person.age); Person = nil; }; Person.block () must be called once; NSLog(@"-- -- -- -- -- -- -- --");
    }
    return 0;
}
Copy the code

Circular references can also be addressed in the above code. But notice that person.block(); You have to call it once, in order to do person = nil; .

The corresponding result is as follows

  • In the following code, the block will__blockGenerate strong references
__block YZPerson *person = [[YZPerson alloc] init];
person.block = ^{
        NSLog(@"person.age--- %d",person.age); Person = nil; };Copy the code
  • The Person object itself is a strong reference to the block
@property (copy, nonatomic) YZBlock block;
Copy the code
  • __blockMake a strong reference to Person
struct __Block_byref_person_0 { void *__isa; __Block_byref_person_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); // '__block' creates a strong reference to person YZPerson *__strong person; };Copy the code

So their reference relationship is shown in the figure

When person = nil is done,__block releases references to person, and all are released. But you have to call Person = nil to do that; otherwise, you can’t break the circular reference

summary

From the previous analysis, we know that, under ARC, the above three ways to compare, the best is __weak

Pay attention to MRC

Under MRC, __unsafe_unretained or __block can be used only because the weak pointer __weak is not supported

The end of the

Back to the original question

  • How does block work? What is the essence?

  • What does __block do? What is the use of attention?

  • Why is the property modifier copy for block? What are the implications of using blocks?

  • Once a block is not copied, it is not on the heap

  • Block is modifying NSMutableArray. Do I need to add __block?

Do you now have your own answer in mind?

References:

Implementation of Objective-C Blocks

A look inside blocks: Episode 3 (Block_copy)

Basic principles of iOS

More information, welcome to pay attention to the individual public number, not regularly share a variety of technical articles.