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 将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)