OC语言 - block:其内部引用变量或对象、用作参数时的内存状况

MRC 环境 

1 - 基本型变量

① block 内部引用基本型局部变量:该变量在 block 中只读(block 定义时截获了变量的值)。变量在其内部作为常量使用,即使变量的值在 block 外部改变,也不会影响它在 block 内部的值

1     int base = 100;
2     long (^sum)(int, int) = ^ long (int a, int b) {
3         // base++;// 编译错误
4         return base + a + b;
5     };
6     base = 0;// 在 block 外部修改整型变量值,并不会影响其在 block 内部的值
7     NSLog(@"%ld",sum(6,78));// 184

② 内部引用基本型静态全局变量、全局变量:全局变量或静态全局变量在内存中的地址是固定的,block 在读取该变量值的时是直接从其所在内存中读出。就是说获取到的值不是在定义时所截获的常量,这就意味着在外部对变量的修改会影响内部的变量,同样内部对变量进行修改也会影响到外部的变量

复制代码
1     static int base = 100;
2     long (^sum)(int, int) = ^ long (int a, int b) {
3         
4         base++;
5         return base + a + b;
6     };
7     base = 0;
8     NSLog(@"%ld\n",sum(1,2));// 4
9     NSLog(@"%d\n", base);// 1   
复制代码

③ 用 __block 修饰了基本型变量时可以理解为等效于全局变量、静态变量

复制代码
1     __block int base = 100;
2     long (^sum)(int, int) = ^ long (int a, int b) {
3         
4         base++;
5         return base + a + b;
6     };
7     base = 0;
8     NSLog(@"%ld\n",sum(1,2));// 4
9     NSLog(@"%d\n", base);// 1
复制代码

2 - 对象不同于基本型,在 block 内部会对引用的话会造成引用计数的变化

// - MyClass.h

复制代码
 1 #import <Foundation/Foundation.h>
 2 
 3 @interface MyClass : NSObject{
 4     
 5     NSObject *_instanceObj;
 6 }
 7 
 8 - (void)test;
 9 
10 @end
复制代码

// - MyClass.m

复制代码
 1 #import "MyClass.h"
 2 
 3 typedef void(^MyBlock)(void);
 4 
 5 @implementation MyClass
 6 
 7 NSObject *_globalObj = nil;
 8 
 9 - (id)init {
10     
11     if (self = [super init]) {
12         _instanceObj = [[NSObject alloc] init]; // 成员变量
13     }
14     return self;
15 }
16 
17 
18 - (void)test{
19     
20     // 静态对象
21     static NSObject *_staticObj = nil;
22     _staticObj = [[NSObject alloc] init];
23     
24     // 全局对象
25     _globalObj = [[NSObject alloc] init];
26     
27     // 实例对象
28     NSObject *localObj = [[NSObject alloc] init];
29     
30     // __block 修饰的对象
31     __block NSObject *blockObj = [[NSObject alloc] init];
32     
33     MyBlock aBlock = ^{
34         
35         NSLog(@"%@", _globalObj);
36         NSLog(@"%@", _staticObj);
37         NSLog(@"%@", localObj);
38         NSLog(@"%@", blockObj);
39         
40         NSLog(@"%@", _instanceObj);
41     };
42     
43     aBlock = [aBlock copy];
44     aBlock();
45     NSLog(@"%lu", [_globalObj retainCount]);// 1
46     NSLog(@"%lu", [_staticObj retainCount]);// 1
47     NSLog(@"%lu", [localObj retainCount]);// 2
48     NSLog(@"%lu", [blockObj retainCount]);// 1
49     NSLog(@"%lu", [_instanceObj retainCount]);// 1
50     NSLog(@"%lu", [self retainCount]);// 2 :因为 block 内部使用到了当前类的成员变量 _instanceObj,使 self 引用计数 +1
51 }
52 
53 @end
复制代码

// - main.m

复制代码
 1 #import <Foundation/Foundation.h>
 2 #import "MyClass.h"
 3 
 4 int main(int argc, const char * argv[]) {
 5     
 6     MyClass *obj = [[MyClass alloc] init];
 7     [obj test];
 8     [obj release];
 9     return 0;
10 }
复制代码

_globalObj、 _staticObj 在内存中的位置是确定的,所以对 block 进行 copy 时并不会 retain 对象

_instanceObj 在对 block 进行 copy 时也没有直接 retain 对象,但 retain 了 self

localObj 在对 block 进行 copy 时,系统自动 retain 对象,增加其引用计数

blockObj 在对 block 进行 copy 时不会 retain 对象

block 引起的内存问题:循环引用

1 - 把 block 在拷贝到堆上的时会 retain 其内部所引用的外部对象,那么如果此时 block 中如果引用了它的宿主或宿主的实例,很有可能引起循环引用

① MRC 环境

// - main

复制代码
 1 #import <Foundation/Foundation.h>
 2 #import "MyClass.h"
 3 
 4 int main(int argc, const char * argv[]) {
 5   
 7     MyClass *mycl = [[MyClass alloc] init];
 8     [mycl release];
 9     return 0;
10         
13 }
复制代码

// - MyClass.h

1 #import <Foundation/Foundation.h>
2 
3 typedef void(^MyBlock)(void);
4 @interface MyClass : NSObject
5 
6 @property(nonatomic,copy)MyBlock myBlock;
7 
8 @end

// - MyClass.m

复制代码
 1 #import "MyClass.h"
 2 @implementation MyClass
 3 - (void)dealloc{
 4     
 5     NSLog(@"no cycle retain");
 6     [super dealloc];
 7 }
 8 
 9 - (instancetype)init{
10     self = [super init];
11     if (self) {
12         
13         NSLog(@"%d",(int)[self retainCount]);// 1
14         
15 //        // 测试一:产生循环引用问题,self 无法销毁
16 //        self.myBlock = ^{
17 //
18 //            [self doSomething];
19 //        };
20 //        NSLog(@"%d",(int)[self retainCount]);// 2
21 //        self.myBlock();
22         
23         
24 //        // 测试二:不会产生循环引用问题
25 //        __block MyClass *blockSelf = self;
26 //        self.myBlock = ^{
27 //            [blockSelf doSomething];
28 //        };
29 //        NSLog(@"%d",(int)[self retainCount]);// 1
30 //        self.myBlock();
31     
32         
33 //        // 测试三:会产生循环引用问题
34 //        __weak MyClass *weakSelf = self;
35 //        self.myBlock = ^{
36 //            [weakSelf doSomething];
37 //        };
38 //        NSLog(@"%d",(int)[self retainCount]);// 1
39 //        self.myBlock();
40         
41         // 测试四:不会产生循环引用问题
42         __unsafe_unretained MyClass *weakSelf = self;
43         self.myBlock = ^{
44 
45             [weakSelf doSomething];
46         };
47         NSLog(@"%d",(int)[self retainCount]);// 1
48         self.myBlock();
49     }
50     return self;
51 }
52 
53 
54 - (void)doSomething{
55     
56     NSLog(@"do Something");
57 }
58 
59 @end
复制代码

注:MRC 环境下 __unsafe_unretained 和 __block 修饰对象后,Myclass 可正常销毁

② ARC 环境

// - MyClass.h

复制代码
1 #import <Foundation/Foundation.h>
2 
3 typedef void(^MyBlock)(void);
4 
5 @interface MyClass : NSObject
6 
7 @property(nonatomic,copy)MyBlock myBlock;
8 
9 @end
复制代码

// - MyClass.m

复制代码
 1 #import "MyClass.h"
 2 @implementation MyClass
 3 - (void)dealloc{
 4     
 5     NSLog(@"no cycle retain");
 6 }
 7 
 8 - (instancetype)init{
 9     self = [super init];
10     if (self) {
11         
12         
13 //        // 测试一:会产生循环引用问题
14 //        self.myBlock = ^{
15 //            [self doSomething];
16 //        };
17 //        self.myBlock();
18         
19         
20 //        // 测试二:会产生循环引用问题
21 //        __block MyClass *blockSelf = self;
22 //        self.myBlock = ^{
23 //            [blockSelf doSomething];
24 //        };
25 //        self.myBlock();
26         
27         
28 //        // 测试三:不会产生循环引用问题
29 //        __weak MyClass *weakSelf = self;
30 //        self.myBlock = ^{
31 //            [weakSelf doSomething];
32 //        };
33 //        self.myBlock();
34         
35 //        // 测试四:不会产生循环引用问题
36 //        __unsafe_unretained MyClass *weakSelf = self;
37 //        self.myBlock = ^{
38 //
39 //            [weakSelf doSomething];
40 //        };
41 //        self.myBlock();
42         
43     }
44     return self;
45 }
46 
47 - (void)doSomething{
48     NSLog(@"do Something");
49 }
50 
51 @end
复制代码

// - main.m

 1 #import <Foundation/Foundation.h>
 2 #import "MyClass.h"
 3 
 4 int main(int argc, const char * argv[]) { 6         
 7     MyClass *mycl = [[MyClass alloc] init];
 8     // mycl = nil;// 销毁对象:ARC 会自动帮你管理 mycl 内存状况,无需置 nil
 9     return 0;12     
13 }

注:ARC 环境下 __weak 和__unsafe_unretained 修饰对象后,Myclass 可正常销毁

MRC 环境下 block 用作参数时的内存分布

1 - 当 block 用作参数时如果被 copy 的话,同样会把作为形参的 block 搞到堆区

复制代码
 1 #import <Foundation/Foundation.h>
 2 
 3 typedef long(^sumBLK)(int x,int y);
 4 // block 作参
 5 void bar(sumBLK parm_sumBlk);
 6 
 7 int main(int argc, const char * argv[]) {
 8 
 9     int base = 100;
10     sumBLK SBlock = ^long(int a, int b) {
11 
12         return base + a + b;
13     };
14 
15     // 传参
16     bar(SBlock);
17 
18     return 0;
19 }
20 
21 void bar(sumBLK parm_sumBlk){
22 
23     // 定义一个 block 用来接收参数
24     void(^blk2)(sumBLK blkSum) = ^(sumBLK sum){
25 
26         NSLog(@"sum-%@",sum);
27         NSLog(@"parm_sumBlk-%@",parm_sumBlk);
28         // NSLog(@"blkSum-%@",blkSum);// 报错
29 
30         NSLog(@"%ld",sum(12,12));// 124
31     };
32 
33     // copy 前
34     blk2(parm_sumBlk);
35     // sum         地址 <__NSStackBlock__: 0x7ffeefbff558>
36     // parm_sumBlk 地址 <__NSStackBlock__: 0x7ffeefbff558>
37 
38 
39     // copy 后的
40     blk2 = [blk2 copy];
41     blk2(parm_sumBlk);
42        
44     // !!! 重点  !!!
45     // parm_sumBlk 地址 <__NSMallocBlock__: 0x103075d90>
46 
47     Block_release(blk2);
48 
49 }
复制代码

注:ARC 环境下,block 用作参数时不论其是否有过拷贝,它的所有打印均是在堆区,是所有!因为在 ARC 环境下当 block 用作参数时系统会帮你优化到堆区

小结

1 - block 作为 C 语言的扩展,并不是高新技术,和其他语言的闭包或 lambda 表达式是一回事 

2 - ARC 中的 block 在引用局部变量时,按理说应该在栈区,但是系统帮助我们做了优化:当引入局部变量时 block 立刻进入堆区

3 - block 和函数的区别

① block 可以访问函数以外、语法作用域以内的外部变量的值:block 不仅可以实现函数的功能,还能携带函数的执行环境

② block 本质是 OC 对象,可以使用自动释放池管理内存

4 - 可以这么理解 block 其实包含两个部分内容

① block 执行的代码(在编译时已生成)

② 一个包含 block 执行时需要的所有外部变量值的数据结构:block 将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上

 

posted on   低头捡石頭  阅读(107)  评论(0编辑  收藏  举报

编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示