iOS底层原理(四)block

block的本质

1.block的基本用法

// 不带参数无返回值的block
void (^block)(void) = ^{
	NSLog(@"Hello, World!");
};

block();

// 带参数无返回值的block
void (^block)(int, int) =  ^(int a , int b) {
	NSLog(@"this is a block!");
};

 block(10, 20);

2.将block代码转换成C++文件后发现,生成了一个__main_block_impl_0类型的结构体,block是指向这个结构体的指针

int age = 20;
   
void (^block)(int, int) =  ^(int a , int b){
  NSLog(@"this is a block! -- %d", age);
  NSLog(@"this is a block!");
  NSLog(@"this is a block!");
  NSLog(@"this is a block!");
};

block(10, 10);

// 转换后的C++代码
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int age = 10;

        // 定义block变量
        // block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age)
        void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

        // 执行block内部代码
        // __main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的,而且相当于直接把__block_impl里的值都放到__main_block_impl_0里
        // (block->FuncPtr)(block, 10, 10)
        ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
    }
    return 0;
}

3.__main_block_impl_0类型的结构体,里面包含了__block_impl类型的结构体变量impl__main_block_desc_0类型的结构体变量Desc,一个返回值为__main_block_impl_0类型的构造函数,还会生成一个age来存储外面引用的值

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
    
    // 构造函数(类似oc的init)
    // __main_block_func_0的地址传给fp
    //  : age(_age)语法会自动将_ag赋值给age
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock; // isa指向的_NSConcreteStackBlock就是当前block类型
    impl.Flags = flags;
    impl.FuncPtr = fp; // 保存的就是__main_block_func_0的地址,也就是block执行逻辑的函数
    Desc = desc; // 保存的是&__main_block_desc_0_DATA的地址(主要存储的就是__main_block_impl_0的大小)
      
      // 默认返回的就是__main_block_impl_0结构体
  }
};

4.__block_impl类型的结构体里包含isa指针,说明block也是一个OC对象。在__main_block_impl_0的构造函数中isa指向的是_NSConcreteStackBlock类型的地址,侧面说明这个类型也是当前编译时的block的真实类型

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

5.__main_block_func_0是一个封装了block执行逻辑代码的函数,在__main_block_impl_0的构造函数中通过参数void *fp赋值给FuncPtr指针变量,来保存执行代码的地址

// 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  int age = __cself->age; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_1);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_3);
}

6.__main_block_desc_0这个类型的结构体变量__main_block_desc_0_DATA里面的reserved赋值为0,Block_size赋值为sizeof(struct __main_block_impl_0),也就是当前__main_block_impl_0这个结构体的大小。在__main_block_impl_0的构造函数中通过参数desc赋值给Desc

static struct __main_block_desc_0 {
  size_t reserved; // 0
  size_t Block_size; // 计算的就是__main_block_impl_0这个结构体的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

7.__main_block_impl_0的构造函数中 : age(_age)语法会自动将_age赋值给变量int age来保存。而且转换后的调用__main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的,而且相当于直接把__block_impl里的值都放到__main_block_impl_0

总结
  • block本质上也是一个OC对象,它内部也有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象

block的本质结构可以概括为下面这张图

block的变量捕获

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

  • 局部变量默认是被auto修饰的,表示自动变量,离开作用域就销毁。block的捕获该变量是值传递
  • 局部变量被static修饰,会一直在内存中不被释放,block的捕获该变量是指针传递
  • 全局变量因为是一直都在内存中存在的,所以不用捕获

block的类型

  • block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
    • __NSGlobalBlock__ ( _NSConcreteGlobalBlock ) - __NSStackBlock__ ( _NSConcreteStackBlock ) - __NSMallocBlock__ ( _NSConcreteMallocBlock)
  • block的真实类型都是以运行时为准的,通过Clang编译出的C++类型不是最准确的,因为在运行时又会做了一些变动和处理。而且现在LLVM只会生成一种中间文件,和Clang生成的文件有差异

通过下面代码观察block的对应输出

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
       void (^block)(void) = ^{
        NSLog(@"Hello");
	    };
	    
	    NSLog(@"%@", [block class]);
	    NSLog(@"%@", [[block class] superclass]);
	    NSLog(@"%@", [[[block class] superclass] superclass]);
    }
    return 0;
}

// 对应的输出:__NSGlobalBlock__ NSBlock NSObject

不同内存区域对应的block类型不同

  • 数据段对应的是__NSGlobalBlock__类型的block
  • 堆段对应的是__NSMallocBlock__类型的block
  • 栈段对应的是__NSStackBlock__类型的block

不同操作对应的block类型不同

  • 没有访问自动变量的block的类型是__NSGlobalBlock__
  • 访问了自动变量的block的类型是__NSStackBlock__
  • __NSStackBlock__的block调用了copy后类型会变为__NSMallocBlock__

每一种类型的block调用copy后的结果如下所示

修改Xcode的Build Setting->Objective-C Automatic Reference CountingNo,使编译环境为MRC,然后输出下面代码可以查看block对应的类型

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        
        // 堆:动态分配内存,需要程序员申请申请,也需要程序员自己管理内存
        void (^block1)(void) = ^{
            NSLog(@"Hello");
        };
        
        int age = 10;
        void (^block2)(void) = ^{
            NSLog(@"Hello - %d", age);
        };
        
        NSLog(@"%@ %@ %@", [block1 class], [[block2 copy] class], [^{
            NSLog(@"%d", age);
        } class]);
    }
    return 0;
}

// 对应的输出:__NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__

注意:block2在MRC环境下的类型为__NSStackBlock__,是存储在栈段的。只有通过copy修饰才会变成__NSMallocBlock__,存储在堆中。在ARC环境下即使不用copy修饰类型也是__NSMallocBlock__,因为编译器会视情况自动进行copy操作

block的copy操作

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上

1.block作为函数返回值

如果不进行copy操作myblock内部的block返回值作用域一结束就会被释放

typedef void (^Block)(void);

Block myblock()
{
    int age = 10;
    return ^{
        NSLog(@"---------%d", age);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       Block block = myblock();
        block();
        NSLog(@"%@", [block class]); // __NSMallocBlock__
    }
    return 0;
}

2.将block赋值给__strong指针时

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
       
        // 强指针Block block
        Block block = ^{
            NSLog(@"---------%d", age);
        };
        
        NSLog(@"%@", [block class]); // __NSMallocBlock__
    }
    return 0;
}

3.block作为Cocoa API中方法名含有usingBlock的方法参数时

NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
          
}];

4.block作为GCD API的方法参数时

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
  
});
   
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  
});

不同环境下block属性的写法

1.MRCblock属性的建议写法

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

2.ARCblock属性的建议写法

// 因为编译器会自动视情况进行copy操作,所以两种写法都没问题,只是为了统一规范建议使用copy来修饰属性@property (strong, nonatomic) void (^block)(void);@property (copy, nonatomic) void (^block)(void);

对象类型的auto变量

当block内部访问了对象类型的auto变量时1.如果block是在栈上,将不会对auto变量产生强引用
@interface Person : NSObject
@property (assign, nonatomic) int age;
@end

@implementation Person

- (void)dealloc
{
    [super dealloc];
    NSLog(@"Person - dealloc");
}
@end

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Block block;
        
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            block = ^{
                NSLog(@"---------%d", person.age);
            };
            
            // MRC环境下对应的内存管理
            [person release];
            NSLog(@"------%@", [block class]);
        }
        
        // 在这里打断点,由于MRC环境下block是在栈区间的,所以不会对age进行强引用,person会随着作用域结束而释放
        NSLog(@"------");
    }
    return 0;
}

2.如果block被拷贝到堆上,会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作

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

@implementation Person

- (void)dealloc
{
    NSLog(@"Person - dealloc");
}
@end

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Block block;
        
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
           // __strong Person *weakPerson = person;
            __weak Person *weakPerson = person;
            
            block = ^{
                NSLog(@"---------%d", weakPerson);
            };
            
            NSLog(@"------%@", [block class]);
        }
        
        // 在这里打断点,在ARC环境下block会自动拷贝到堆区间,切换修饰符__strong和__weak,person分别会不释放和释放
        NSLog(@"------");
    }
    return 0;
}

将上面代码文件转换成C++文件可以看出,block内部的__main_block_desc_0结构体会调用copy函数copy函数内部会调用_Block_object_assign函数,而_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
    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*);
  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, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

3.如果block从堆上移除,会调用block内部的dispose函数dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量(release)

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

4.block只有引用的是基本数据类型才不会生成copydispose函数

5.如果用static修饰对象类型,那么生成的C++代码如下

Block block;        
{
    static NSString *string = @"haha";
    block = ^{
        NSLog(@"---------%@", string);
    };
}

// 生成的C++代码        
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  // string变量的类型是NSString **
  NSString *__strong *string;
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString *__strong *_string, int flags=0) : string(_string) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

注意:代码里有__weak,转换C++文件可能会报错cannot create __weak reference in file using manual reference,可以指定支持ARC、指定运行时系统版本

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 源文件
```### __block修饰符

#### __block修饰基本数据类型
看下面代码,怎样可以在`block`内部修改`age`的值

typedef void (^Block)(void);

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

    int age = 10;
    
    Block block1 = ^{
        // age = 20;
        NSLog(@"age is %d", age);
    };
    
    block1();
    
    NSLog(@"age的内存地址 - %p", &age);
}
return 0;

}


1.用`static`来修饰`age属性`,`block`内部引用的是`age`的地址值,可以根据地址去修改`age`的值。但不好的是`age属性`会一直存放在内存中不销毁,造成多余的内存占用,而且会改变`age属性`的性质,不再是一个`auto变量`了

static int age = 10;


2.用`__block`来修饰属性,底层会生成`__Block_byref_age_0`类型的结构体对象,里面存储着`age`的真实值

__block int age = 10;


3.转换成`C++文件`来查看内部结构,会根据`__main_block_impl_0`里生成的`age`对象来修改内部的成员变量`age`而且在外面打印的`age`属性的地址值也是`__Block_byref_age_0`结构体里的成员变量`age`的地址,目的就是不需要知道内部的真实实现,所看到的就是打印出来的值

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;
__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;
}
};

int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

    // __Block_byref_age_0 age = {0, &age, 0, sizeof(__Block_byref_age_0), 10};  
    __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};

    Block block1 = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));

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


}
return 0;

}


##### 总结:

- `__block`可以用于解决`block`内部无法修改`auto`变量值的问题
- 编译器会将`__block`变量包装成一个对象
- 其实修改的变量是`__block`生成的对象里面存储的变量的值,而不是外面的`auto变量`,但是内部生成的相同的变量的地址和外面的`auto变量`地址值是一样的,所以修改了内部的变量也会修改了外面的`auto变量`
- `__block`不能修饰全局变量、静态变量(static)

##### __block的内存管理
1.程序编译时,`block`和`__block`都是在栈中的,这时并不会对`__block`变量产生强引用

2.因为`__block`也会包装成`OC对象`,所以`block`底层也会生成`copy函数`和`dispose函数`  

3.当`block`被`copy`到堆时,会调用`block`内部的`copy函数`,`copy函数`内部会调用`_Block_object_assign`函数,`_Block_object_assign`函数会对`__block`变量形成强引用(retain)

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), __main_block_copy_0, __main_block_dispose_0};

static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {_Block_object_assign((void)&dst->age, (void)src->age, 8/BLOCK_FIELD_IS_BYREF/);}


实际上这时`__block`修饰的变量因为被包装成了`OC对象`,所以也会被拷贝到堆上,如果再有`block`强引用`__block`,由于`__block`变量已经拷贝到堆上了,就不会再拷贝了,下图可以很好的表达出关系

![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024131050-2137722604.jpg)

3.当`block`从堆中移除时,会调用`block`内部的`dispose函数`,`dispose函数`内部会调用`_Block_object_dispose`函数,`_Block_object_dispose`函数会自动释放引用的`__block`变量(release)

static void __main_block_dispose_0(struct __main_block_impl_0src) {_Block_object_dispose((void)src->age, 8/BLOCK_FIELD_IS_BYREF/);}


如果有多个`block`同时持有着`__block`变量,那么只有所有的`block`都从堆中移除了,`__block`变量才会被释放

![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024131097-86614401.jpg)

##### __block和OC对象在block中的区别

看下面的代码,在`block`中的本质区别是什么

__block int age = 10;
NSObject *obj = [[NSObject alloc] init];

Block block1 = ^{
age = 20;

NSLog(@"age is %d", age);
NSLog(@"obj is %p", obj);
};


转成`C++文件`发现,`__block`生成的对象就是强引用,而`NSObject`对象会根据修饰符`__strong`或者`__weak`来区分是否要进行`retain操作`

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
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, __Block_byref_age_0 *_age, int flags=0) : obj(_obj), age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {_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/);}

**注意:`__weak`不能修饰基本数据类型,编译器会报`__weak' only applies to Objective-C object or block pointer types; type here is 'int'`警告**

##### __forwarding指针
- 在栈中,`__block`中的`__forwarding指针`指向自己的内存地址
- 复制到堆中之后,`__forwarding指针`指向堆中的`__block`
- 堆中的`__forwarding`指向堆中的`__block`
- 这样的目的都是为了不论访问的`__block`是在栈上还是在堆上,都可以通过`__forwarding指针`找到存储在堆中的`auto变量`

![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024130733-2067416101.jpg)

#### __block修饰对象类型
1.看下面代码,用`__block`修饰的对象类型什么时候被释放

typedef void (^Block)(void);

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

    {
        Person *person = [[Person alloc] init];
        person.age = 10;
        
        __block Person *weakPerson = person;

        block = ^{
            NSLog(@"---------%d", weakPerson.age);
        };
        
       
        NSLog(@"------%@", [block class]);
    }
    
    // 在这里打断点观察person是否会被释放
    NSLog(@"------");
}
return 0;

}


2.转换成`C++文件`可以发现,`__block`底层生成的结构体里面会引用着该对象类型,并且默认是用`__strong`来修饰,而且内部也会对应的生成`copy`和`dispose`函数

struct __Block_byref_weakPerson_0 {
void __isa;
__Block_byref_weakPerson_0 __forwarding;
int __flags;
int __size;
void (
__Block_byref_id_object_copy)(void
, void);
void (
__Block_byref_id_object_dispose)(void*);
Person *__strong weakPerson;
};


3.我们看`main函数`里会将`__Block_byref_id_object_copy_131`和`__Block_byref_id_object_dispose_131`赋值给`__Block_byref_weakPerson_0`这个结构体对象

int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Block block;

    {
        Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10);

        // __Block_byref_weakPerson_0 weakPerson = {0, &weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};
        __attribute__((__blocks__(byref))) __Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};

        block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_weakPerson_0 *)&weakPerson, 570425344));


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_wn_kcs2c07n3mqd02d77tvjgtjr0000gn_T_main_8be7f8_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block, sel_registerName("class")));
    }

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_wn_kcs2c07n3mqd02d77tvjgtjr0000gn_T_main_8be7f8_mi_2);
}
return 0;

}


4.找到这两个值能发现也是分别会调用`_Block_object_assign`和`_Block_object_dispose`这两个函数,而且传的对象就是`__Block_byref_weakPerson_0`内部的`weakPerson`这个对象,也就是说这个结构体内部也会对`weakPerson`这个对象进行着`retain`和`release`的操作

static void __Block_byref_id_object_copy_131(void *dst, void src) {
_Block_object_assign((char
)dst + 40, *(void * ) ((char)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void src) {
_Block_object_dispose(
(void * ) ((char)src + 40), 131);


5.我们把第一段代码中的`weakPerson`加上`__weak`修饰符,再运行程序会发现,当作用域结束后,`person`对象也会被释放了

typedef void (^Block)(void);

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

    {
        Person *person = [[Person alloc] init];
        person.age = 10;
        
        __block __weak Person *weakPerson = person;

        block = ^{
            NSLog(@"---------%d", weakPerson.age);
        };
        
       
        NSLog(@"------%@", [block class]);
    }
    
    // 在这里打断点观察person是否会被释放
    NSLog(@"------");
}
return 0;

}


6.我们转换成`C++文件`能发现,`__Block_byref_weakPerson_0`里面的`person`对象修饰符变成了`__weak`

struct __Block_byref_weakPerson_0 {
void __isa;
__Block_byref_weakPerson_0 __forwarding;
int __flags;
int __size;
void (
__Block_byref_id_object_copy)(void
, void);
void (
__Block_byref_id_object_dispose)(void*);
Person *__weak weakPerson;
};


##### 总结:
- `__block`修饰的对象类型也会生成一个新的结构体对象,并且只会被`block`进行强引用,同`__block`修饰基本数据类型是一样的
- `__block`内部也会生成该对象类型的成员变量,而且会根据不同的修饰符`__strong`和`__weak`来对应着该对象类型是否被强引用
- `__block`内部也会生成`copy`和`dispose`函数
	- 当`__block`变量被`copy`到堆时,会调用`__block`变量内部的`copy函数`,`copy函数`内部会调用`_Block_object_assign`函数,`_Block_object_assign`函数会根据所指向对象的修饰符`(__strong、__weak、__unsafe_unretained)`做出相应的操作,形成强引用(retain)或者弱引用
	- 如果`__block`变量从堆上移除,会调用`__block`变量内部的`dispose函数`,`dispose函数`内部会调用`_Block_object_dispose`函数,`_Block_object_dispose`函数会自动释放指向的对象(release)

**注意:在MRC环境下即使用\_\_block修饰,\_\_block内部只会对auto变量进行弱引用,无论加不加__weak,block还没有释放,\_\_block修饰的变量就已经释放了,这点和在ARC环境下不同**

### 循环引用
`block`在使用中很容易就会造成循环引用问题,例如下面的代码

typedef void (^Block) (void);

@interface Person : NSObject
@property (copy, nonatomic) Block block;
@property (assign, nonatomic) int age;

  • (void)test;
    @end

@implementation Person

  • (void)test {
    // 内部循环引用
    self.block = ^{
    NSLog(@"age is %d", self.age);
    };
    }
    @end

int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;

    // 循环引用
    person.block = ^{
        NSLog(@"age is %d", person.age);
    };
}

NSLog(@"111111111111");
return 0;

}


`person`对象里面的`block`属性强引用着`block`对象,而`block`对象内部也会有一个`person`的成员变量指向这个`Person对象`,这样就会造成循环引用,谁也无法释放

![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024130088-1235768827.jpg)


#### 解决方法
##### 在ARC环境下
让其中一个指针变成弱引用

![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024129668-1990190805.jpg)

1.用`__weak`解决,不会产生强引用,当指向的对象销毁时,会自动让指针置为`nil`

@implementation Person

  • (void)test {
    __weak typeof(self) weakSelf = self;

    self.block = ^{
    NSLog(@"age is %d", weakSelf.age);
    };
    }
    @end

int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;

    // __weak Person *weakPerson = person;
    __weak typeof(person) weakPerson = person;
    person.block = ^{
        NSLog(@"age is %d", weakPerson.age);
    };
}

NSLog(@"111111111111");
return 0;

}


2.用`__unsafe_unretained`解决,不会产生强引用,但是是不安全的,当指向的对象销毁时,指针存储的地址值不变,仍然是指向着那块已经被回收的内存空间,那么再访问这个这个变量就会造成野指针错误

@implementation Person

  • (void)test {
    __unsafe_unretained typeof(self) weakSelf = self;

    self.block = ^{
    NSLog(@"age is %d", weakSelf.age);
    };
    }
    @end

int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;

    __unsafe_unretained Person *weakPerson = person;
    person.block = ^{
        NSLog(@"age is %d", weakPerson.age);
    };
}

NSLog(@"111111111111");
return 0;

}


3.用``__block``解决,用`__block`修饰对象会造成三者相互引用造成循环引用,需要手动调用block

int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
person.age = 10;

    person.block = ^{
        NSLog(@"age is %d", person.age);
        person = nil;
    };
}

person.block();

NSLog(@"111111111111");
return 0;

}


`block`内部也需要手动将`person`置空,这个`person`是`__block`内部生成的指向`Person对象`的变量

![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024130614-1682005821.jpg)

##### 在MRC环境下
1.用`__unsafe_unretained`解决,同`ARC环境下`一样,只是`MRC`不支持`__weak`

Person *person = [[Person alloc] init];
__unsafe_unretained typeof(person) weakPerson = person;

person.block = [^{
NSLog(@"age is %d", weakPerson.age);
} copy];

[person release];

2.用`__block`解决,在`MRC`中,`__block`对象里是不会对`person对象`进行强引用的,所以不会造成循环引用

__block Person *person = [[Person alloc] init];
person.age = 10;

person.block = [^{
NSLog(@"age is %d", person.age);
} copy];

[person release];

### 面试题
#### 1.看下面代码,分别输入的值是什么

int a = 10;
static int b = 10;

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

	auto int age = 10;
	static int height = 10;
	
	void (^block)(void) = ^{
		NSLog(@"age is %d, height is %d", age, height);
		NSLog(@"a is %d, b is %d", a, b);
	};
	
	age = 20;
	height = 20;
	a = 20;
	b = 20;
	
	block();
	    
	// 输出结果为:age=10,height=20,a=20,b=20 
}
return 0;

}


`age`是自动变量,是值传递

`height`表示的是指针传递,`block`捕获的是该变量的地址

而`a、b`都为全局变量,所以`block`根本不用捕获,需要时直接拿取当前最新的值就可以了

int a = 10;
static int b = 10;

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *height;

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
auto int age = 10;
static int height = 10;

	void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));

	age = 20;
	height = 20;
	a = 20;
	b = 20;

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;

}


#### 2.看下面代码,block内部会不会捕获self

@interface Person : NSObject

@property (copy, nonatomic) NSString *name;

  • (instancetype)initWithName:(NSString *)name;
    @end

@implementation Person

  • (void)test
    {
    void (^block)(void) = ^{
    NSLog(@"-------%d", [self name]);
    };
    block();
    }

  • (instancetype)initWithName:(NSString *)name
    {
    if (self = [super init]) {
    self.name = name;
    }
    return self;
    }

@end


会捕获。因为`self`本质也是一个局部变量,`block`内部会生成一个变量来保存`Person对象`的地址

struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

// 函数都会生成隐式参数self和_cmd
static void _I_Person_test(Person * self, SEL _cmd) {
void (block)(void) = ((void ()())&__Person__test_block_impl_0((void )__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
((void (
)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}


#### 3.\_\_block的作用是什么?有什么使用注意点

可以将修饰的变量包装成一个对象,解决在`block`内部无法修改外部变量的问题。

`__block`内部会进行内存管理,还有在`MRC环境下`是不会对对象进行强引用

#### 4.block的属性修饰词为什么是copy?使用block有哪些使用注意?

`block`一旦没有进行`copy操作`,就不会在堆上。放到堆上的目的是方便我们来控制他的生命周期,可以更有效的进行内存管理。

注意循环引用
posted on 2021-04-07 02:42  FunkyRay  阅读(243)  评论(0编辑  收藏  举报