OC笔记 - block工作原理 02:内部引用auto对象


前言

1 - 先来回顾下 block的几种类型

2 - 每种类型的 block调用 copy后的结果

3 - ARC中编译器会根据情况自动将栈区的 block拷贝到堆上的几种情况

① block作为函数返回值时

 1 #import 
 2 
 3 typedef void (^MyBlock)(void);
 4 
 5 MyBlock textOne(int x){
 6     
 7     
 8     // 全局 block
 9     if (x == 100) {
10         
11         MyBlock blockG = ^(){
12             NSLog(@"返回一个全局 block");
13         };
14         return blockG;
15     }
16 
17     // 栈区 block
18     int age = 10;
19     MyBlock blockS = ^(){
20         NSLog(@"返回一个栈区 block %d",age);
21     };
22     return blockS;
23 }
24 
25 int main(int argc, const char * argv[]) {
26     
27     
28     // MRC
29     @autoreleasepool {
30         
31         // 接受返回的 block
32         MyBlock blockS = textOne(10);
33         MyBlock blockG = textOne(100);
34 
35 
36         //-------------------------- ARC
37         // 全局型 block正常调用
38         NSLog(@"%@",blockG); // <__NSGlobalBlock__: 0x100001030>
39         blockG();
40         
41         // 栈区 block
42         blockS();// crash
43         NSLog(@"%@",blockS); //  <__NSMallocBlock__: 0x100743720>
44         // ARC中会把返回值 block自动拷贝到堆区
45         
46         //--------------------------- MRC
47         // 全局型 block正常调用
48         NSLog(@"%@",blockG); // <__NSGlobalBlock__: 0x100001030>
49         blockG();
50 
51         // 栈区 block
52         NSLog(@"%@",blockS); //
53         blockS();// crash
54     }
55     
56     return 0;
57     
58 }

② 将 block赋值给强指针时

 1 #import 
 2 typedef void (^MyBlock)(void);
 3 int main(int argc, const char * argv[]) {
 4     
 5     @autoreleasepool {
 6         
 7         int age = 10;
 8         // 强制针 block
 9         MyBlock block = ^(){
10             
11             NSLog(@"%d",age);
12         };
13     
14         NSLog(@"%@",block);
15         // MRC
16         // <__NSStackBlock__: 0x7ffeefbff538>
17         
18         // ARC
19         // <__NSMallocBlock__: 0x100440970>
20     }
21     
22     return 0;
23     
24 }

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

1   NSArray *arry;
2   [arry enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
3         
4   }];

④ block作为 GCD API方法参数时

dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^{
        
});

4 - 修饰 block属性

5 - 当 block内部引用了对象,那么 ARC中的 block将会被优化进堆区,而 MRC中 block依旧留在栈区。以下代码简单验证

① ARC模式

// - main.m

 1 #import 
 2 
 3 // 定义一个 Person类
 4 @interface Person : NSObject
 5 @property(nonatomic,copy)NSString *name;
 6 
 7 @end
 8 
 9 @implementation Person
10 
11 - (void)dealloc{
12     
13     NSLog(@"%@ dealloc",self);
14 }
15 
16 @end
17 
18 typedef void (^BSBlock)(void);
19 // 默认强引用
20 BSBlock block2;
21 
22 int main(int argc, const char * argv[]) {
23     
24     // 作用域A
25     {
26         
27         //--------------------------测试一
28         Person *p1 = [Person new];
29         p1.name = @"1测";
30         NSLog(@" p1 %@",p1);// 
31 
32         // 没有引用强指针指向的 block
33         ^(){
34             NSLog(@"%@",p1.name);
35         };
36 
37 
38         NSLog(@"没有指针指向的 block代码块 %@",^(){
39              NSLog(@"%@",p1.name);
40         });//block 在栈区 <__NSStackBlock__: 0x7ffeefbff528>
41         
42 
43         
44         // --------------------------测试二
45         Person *p2 = [Person new];
46         p2.name = @"QW";
47         NSLog(@"p2  %@",p2);
48         // 有了强制针指向的 block
49         block2 = ^(){
50             NSLog(@"%@",p2.name); // 
51         };
52         // ARC中只要有强制针指向 block,它就会转移到堆区
53         NSLog(@" block2 %@",block2);
54          // <__NSMallocBlock__: 0x10292bab0>
55         
56     }
57     
58     
59     // 测试一
60     // p1 出了 作用域A 立刻销毁: dealloc
61     
62     // 测试二
63     // p2 不会销毁,依旧在内存当中(内存泄露)
64     
65   
66     
67     NSLog(@"----------------------------");
68 
69     return 0;
70     
71 }

② MRC模式

 1 #import 
 2 
 3 // 定义一个 Person类
 4 @interface Person : NSObject
 5 @property(nonatomic,copy)NSString *name;
 6 
 7 @end
 8 
 9 @implementation Person
10 
11 - (void)dealloc{
12     
13     NSLog(@"%@ dealloc",self);
14     [super dealloc];
15 }
16 
17 @end
18 
19 typedef void (^BSBlock)(void);
20 // 默认强引用
21 BSBlock block2;
22 
23 int main(int argc, const char * argv[]) {
24     
25     // 作用域A
26     {
27         
28         //--------------------------测试一
29         Person *p1 = [Person new];
30         p1.name = @"1测";
31         NSLog(@" p1 %@",p1);// 
32 
33         // 没有引用强指针指向的 block
34         ^(){
35             NSLog(@"%@",p1.name);
36         };
37 
38 
39         NSLog(@"没有指针指向的 block代码块 %@",^(){
40              NSLog(@"%@",p1.name);
41         });// block 在栈区 <__NSStackBlock__: 0x7ffeefbff538>
42 
43         [p1 release];
44         
45         // --------------------------测试二
46         Person *p2 = [Person new];
47         p2.name = @"QW";
48         NSLog(@"p2  %@",p2); // 
49         
50         // 有了强指针指向的 block
51         block2 = ^(){
52             NSLog(@"%@",p2.name);
53         };
54     
55         // MRC中,严格地遵守 block内存规则,内部引用了实例对象其依旧在栈区
56         NSLog(@" block2 %@",block2);// <__NSStackBlock__: 0x7ffeefbff560>
57         [p2 release];
58         
59     }
60     
61     
62     // 测试一
63     // p1 出了 作用域A 立刻销毁:
64     
65     // 测试二
66     // p2  同样销毁
67 
68     NSLog(@"----------------------------");
69 
70     return 0;
71     
72 }

注:不管是对对象的强引用还是弱引用,栈区 block是不会影响对象的内存状况

底层实现原理:ARC中

1 - 强引用 auto对象

 1 #import 
 2 // Person类
 3 @interface Person : NSObject
 4 @property(nonatomic,copy)NSString *name;
 5 
 6 @end
 7 
 8 @implementation Person
 9 
10 - (void)dealloc{
11     
12     NSLog(@"%@ dealloc",self);
13 }
14 
15 @end
16 
17 typedef void (^BSBlock)(void);
18 BSBlock block2;
19 
20 int main(int argc, const char * argv[]) {
21     
22     // 作用域A
23     {
24 
25         Person *p2 = [Person new];
26         p2.name = @"QW";
27    
28         block2 = ^(){
29             NSLog(@"%@",p2.name);
30         };
31     
32     }
33     // 内存泄露:p2出了作用域不会销毁
34     NSLog(@"-----------------");
35     return 0;
36     
37 }

C++ 代码:第 4行有个 Person *p2指向了实例对象,从表象上看我们似乎知道了内存泄露的原因。因为 block内部有了新的强指针指向了实例对象,也就意味着只要 block不销毁,实例对象就不会消亡

 1 struct __main_block_impl_0 {
 2   struct __block_impl impl;
 3   struct __main_block_desc_0* Desc;
 4   Person *p2;
 5   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_p2, int flags=0) : p2(_p2) {
 6     impl.isa = &_NSConcreteStackBlock;
 7     impl.Flags = flags;
 8     impl.FuncPtr = fp;
 9     Desc = desc;
10   }
11 };

2 - 弱引用 auto对象

 1 int main(int argc, const char * argv[]) {
 2     
 3     // 作用域A
 4     {
 5         
 6         Person *p2 = [Person new];
 7         __weak Person *weakPerson = p2;
 8         weakPerson.name = @"QW";
 9    
10         block2 = ^(){
11             NSLog(@"%@",weakPerson.name);
12         };
13     
14     }
15     // p2出了作用域立刻销毁
16     NSLog(@"-----------------");
17     return 0;
18     
19 }

C++ 代码:注意第 4行,变成了 Person *__weak weakPerson

 1 struct __main_block_impl_0 {
 2   struct __block_impl impl;
 3   struct __main_block_desc_0* Desc;
 4   Person *__weak weakPerson;
 5   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
 6     impl.isa = &_NSConcreteStackBlock;
 7     impl.Flags = flags;
 8     impl.FuncPtr = fp;
 9     Desc = desc;
10   }
11 };

3 - __main_block_copy_0和__main_block_dispose_0。不管是强引用还是弱引用,只要 block内部引用了对象类型,那么 __main_block_desc_0这个结构体会发生变化。新增的两个函数就是用来管理对象内存的

1 // copy时调用
2 static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
3     
4     // _Block_object_assign 会根据对象的修饰符做出相应的操作
5     _Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
6 }
1 // release时调用
2 static void __main_block_dispose_0(struct __main_block_impl_0*src) {
3     
4     // 销毁对象
5     _Block_object_dispose((void*)src->p2, 3/*BLOCK_FIELD_IS_OBJECT*/);
6     
7 }

实战:ARC中

1 - 在下面的代码中,只要触发 touchesBegan方法,那么 Person实例对象立刻销毁。注意 ⑤和 ⑥

 1 #import "ViewController.h"
 2 
 3 // 新定义一个 Person
 4 @interface Person : NSObject
 5 
 6 @end
 7 
 8 @implementation Person
 9 
10 - (void)dealloc{
11     NSLog(@"Person dealloc");
12 }
13 @end
14 
15 @interface ViewController()
16 
17 @end
18 
19 @implementation ViewController
20 
21 - (void)viewDidLoad {
22     [super viewDidLoad];
23     self.view.backgroundColor = [UIColor cyanColor];
24     
25 }
26 
27 
28 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
29 
30     Person *sp = [[Person alloc] init];
31     
32 }
33 
34 @end

① 3秒后销毁:先执行完输出语句后销毁

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    
    Person *sp = [[Person alloc] init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",sp);
    });
    
}

② 点击试图后立即销毁,并在 3秒后执行输出语句 null

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    
    
    Person *sp = [[Person alloc] init];
    __weak Person *weakPerson = sp;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",weakPerson);
    });
    
}

③ 点击试图后立即销毁,并在 1+2 = 3秒后执行输出语句 null

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    
    
    Person *sp = [[Person alloc] init];
    __weak Person *weakPerson = sp;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakPerson);
        });

    });
    
}

④ 1+2 = 3秒后销毁:先执行完输出语句后销毁

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    
    
    Person *sp = [[Person alloc] init];
    __weak Person *weakPerson = sp;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",sp);
        });

    });
    
}

⑤ 1秒后输出语句,再过 2秒后执行输出语句。最终依旧是 1+2 = 3秒后销毁

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    
    
    Person *sp = [[Person alloc] init];
    __weak Person *weakPerson = sp;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        NSLog(@"%@",weakPerson);
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
             NSLog(@"%@",sp);
         });
 
     });
     
 }

⑥ 1秒后输出语句后立刻销毁,再过 2秒后执行输出语句 null。最终是 1秒后销毁

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    
    
    Person *sp = [[Person alloc] init];
    __weak Person *weakPerson = sp;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        NSLog(@"%@",sp);
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakPerson);
        });

    });
    
}

注:block内部会自动管理持有对象的生命周期,但是对象何时销毁关键看 block什么时候结束

结语

1 - block内部对象类型的 auto变量