iOS基础 - block用作属性时为什么要使用copy特性修饰
▶ 为什么 block 用作属性时使用 copy 修饰
我们使用 Xcode 的 MRC模式,一步步验证:当 block用作属性时,我们分别使用 assign 和 copy 特性修饰会发生什么状况
// - ViewController .m:
1 #import "ViewController.h" 2 #import "SecondViewController.h" 3 4 @implementation ViewController 5 6 - (void)viewDidLoad { 7 [super viewDidLoad]; 8 self.view.backgroundColor = [UIColor cyanColor]; 9 } 10 11 // 进入下一页 12 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 13 14 SecondViewController *secVC = [SecondViewController new]; 15 [self.navigationController pushViewController:secVC animated:NO]; 16 [secVC release]; // MRC模式下,不需要 release。因为 pushViewController 会自动管理入栈对象的引用计数 17 } 18 19 @end
// - SecondViewController .h
#import <UIKit/UIKit.h> typedef void(^BlockColor)(void); @interface SecondViewController : UIViewController // 使用 assign 修饰 block @property(nonatomic,assign)BlockColor blockColor_assign; // 使用 copy 修饰 block @property(nonatomic,copy)BlockColor blockColor_copy; @property(nonatomic,copy)NSString *blockString;; @end
// - SecondViewController .m
1 #import "SecondViewController.h" 2 @implementation SecondViewController 3 4 - (void)dealloc { 5 NSLog(@"SecondViewController dealloc!"); 6 self.blockString = nil; 7 //------------- 测试二 ----------------- 8 // // 调用出了函数的栈区 block,程序 crash 9 // // 原因:blockColor_copy 在栈区,出了方法后会被系统收回 10 // self.blockColor_copy(); 11 12 //------------- 测试三:以下状况都会造成 程序crash ----------------- 13 // // 状况1:原因是 _blockColor_copy 已经销毁 14 // self.blockColor_copy(); 15 16 // // 状况2:原因是点语法实质上走的 setter方法 17 // // 我们知道在 setter 中会有新旧值的判断,会再次 release 已经销毁了的 _blockColor_copy 18 // // 以下我们重写的 setter方法 19 // self.blockColor_copy = nil; 20 // // Block_release(_blockColor_copy) 或 [_blockColor_copy release] 21 22 [super dealloc]; 23 } 24 25 // 当使用 copy 修饰 block属性 时,苹果内部 setter方法 的实现 26 - (void)setBlockColor_copy:(BlockColor)blockColor_copy{ 27 if(_blockColor_copy != blockColor_copy){ 28 Block_release(_blockColor_copy); 29 _blockColor_copy = Block_copy(blockColor_copy); 30 } 31 } 32 33 - (void)viewDidLoad { 34 [super viewDidLoad]; 35 36 //----------------------- 测试一 ------------------------ 37 // // 使用 assign 修饰 block属性 且使用了点语法 38 // SecondViewController *secVC1 = self; 39 // NSLog(@"测试一:secVC1.retainCount = %lu",secVC1.retainCount);// N 40 // 41 // // block 具体实现 42 // self.blockColor_assign = ^{ 43 // secVC1.view.backgroundColor = [UIColor redColor]; 44 // }; 45 // // 回调 block 46 // secVC1.blockColor_assign(); 47 // 48 // NSLog(@"测试一:secVC1.retainCount = %lu self.blockColor_assign = %@",secVC1.retainCount,self.blockColor_assign); 49 // // N(引用计数不会+1)。 <__NSStackBlock__: 0x7ffeee58bb70> 50 // // 返回上级页面时触发 dealloc 51 52 53 //----------------------- 测试二 ------------------------ 54 // // 使用 copy 修饰 block属性 且不使用点语法 55 // SecondViewController *secVC2 = self; 56 // NSLog(@"测试二:secVC2.retainCount = %lu",secVC2.retainCount);// N 57 // _blockColor_copy = ^(){ 58 // secVC2.view.backgroundColor = [UIColor redColor]; 59 // }; 60 // ; 61 // 62 // NSLog(@"测试二:secVC2.retainCount = %lu self.blockColor_copy = %@",secVC2.retainCount,self.blockColor_copy); 63 // // N <__NSStackBlock__: 0x7ffeec37db70> <__NSStackBlock__: 0x7ffeec37db70> 64 // 65 // // 返回上级页面时触发 dealloc 66 // // 注:如果在 dealloc 方法回调 blockColor_copy 则程序 crash 67 68 69 //----------------------- 测试三 -------------------------- 70 // // 使用 copy 修饰 block属性 且走点语法 71 // SecondViewController *secVC3 = self; 72 // NSLog(@"测试三:secVC3.retainCount = %ld",secVC3.retainCount);// N 73 // 74 // secVC3.blockColor_copy = ^(){ 75 // 76 // secVC3; // 引用了对象本身,引用计数 +1 77 // secVC3.blockString = @"block测试";// 引用了成员变量,同样使 self 的引用计数 +1 78 // }; 79 // secVC3.blockColor_copy(); 80 // 81 // NSLog(@"测试三:secVC3.retainCount = %ld secVC3.blockColor_copy = %@",secVC3.retainCount,secVC3.blockColor_copy);// block 转移到了堆区 82 // // N + 1 <__NSMallocBlock__: 0x6000520adfd0> 83 // 84 // // block 使用完毕后,进行销毁 85 // // 如果不对 _blockColor_copy 进行 release 操作,则造成内存泄露 86 // // 返回上级页面是,不会触发 dealloc 87 // Block_release(_blockColor_copy); 88 89 //--------------------- 测试四 ----------------------- 90 // 使用 __block 修饰对象,防止该对象在 block内部 引用计数 +1 91 __block SecondViewController *secVC4 = self; 92 NSLog(@"测试四:secVC4.retainCount = %ld",secVC4.retainCount);// N 93 94 secVC4.blockColor_copy = ^(){ 95 secVC4.view.backgroundColor = [UIColor grayColor]; 96 }; 97 secVC4.blockColor_copy(); 98 NSLog(@"测试四:secVC4.retainCount = %ld self.blockColor_copy = %@",secVC4.retainCount,self.blockColor_copy); 99 // N <__NSMallocBlock__: 0x600051d9cfd0> 100 // 使用 __block 修饰对象后,不会产生内存泄露 101 // 不需对 _blockColor_copy 进行 release操作,返回上一页面时会触发 dealloc 102 103 } 104 105 @end
▶ 应用场景
MRC模式 下模拟登录页面:当登录成功或失败时进行 block回调,处理相应的业务逻辑
// - LoginMarger.h
1 #import <Foundation/Foundation.h> 2 3 // 使用 typedef 可以极大地简化代码 4 typedef void(^SuccessBlock)(NSString *userName); // 登录成功 5 typedef BOOL(^FailBlock)(NSString *errorMessage); // 登录失败 6 7 @interface LoginMarger : NSObject 8 { 9 // 没有使用 typedef 前,我们需要这样声明 block型 成员变量 10 void(^_successBlockA)(NSString *userName); 11 // 使用 typedef 后 12 SuccessBlock _successBlockB; 13 } 14 15 // 不使用 typedef 时,声明属性、方法 16 @property(nonatomic,copy)void(^successBlockB)(NSString *userName); 17 18 - (void)loginWithUserName:(NSString *)name passWord:(NSString *)passWord successfulBlockB:(void(^)(NSString *userName))successBlock failBlockB:(BOOL (^)(NSString *errorMessage))failBlock; 19 20 // 使用 typedef 时,声明属性、方法 21 @property(nonatomic,copy)SuccessBlock successBlock; 22 - (void)loginWithUserName:(NSString *)name passWord:(NSString *)passWord successfulBlock:(SuccessBlock)successBlock failBlock:(FailBlock)failBlock; 23 24 @end
// - LoginMarger.m
1 #import "LoginMarger.h" 2 @implementation LoginMarger 3 4 -(void)dealloc{ 5 Block_release(_successBlock); 6 [super dealloc]; 7 } 8 9 - (void)loginWithUserName:(NSString *)name passWord:(NSString *)passWord successfulBlockB:(void (^)(NSString *))successBlock failBlockB:(BOOL (^)(NSString *))failBlock{ 10 // todosomethings 11 } 12 13 -(void)loginWithUserName:(NSString *)name passWord:(NSString *)passWord successfulBlock:(SuccessBlock)successBlock failBlock:(FailBlock)failBlock{ 14 15 // 简单判定:输入不为空 16 if (name.length == 0) { 17 NSLog(@"请输入账号"); 18 }else if(passWord.length == 0){ 19 NSLog(@"请输入密码"); 20 }else{ 21 if ([name isEqualToString:@"xxx"]&&[passWord isEqualToString:@"xxx"]) { 22 23 // // 方式一:在函数内部 block回调 24 // successBlock(name); 25 26 // 方式二:在函数外部 block回调,通常把 block 声明成属性以配合使用 27 // 给 block型 成员变量赋值时,务必使用点语法!防止 block 出了该方法被销毁 28 self.successBlock = successBlock; 29 [self callBackIt:name];// 方法外部调用 30 31 }else{ 32 BOOL result = failBlock([NSString stringWithFormat:@"非法键入信息"]); 33 if (result) { 34 //DoSomething... 35 }else{ 36 //DoSomething... 37 } 38 } 39 } 40 } 41 42 // 在方法外部 block回调 43 -(void)callBackIt:(NSString*)name{ 44 self.successBlock(name); 45 } 46 47 @end
// - LoginViewController.h
#import <UIKit/UIKit.h> @interface LoginViewController : UIViewController @property (retain, nonatomic) UITextField *nameTF; // 姓名 @property (retain, nonatomic) UITextField *passwordTF;// 密码 @end
// - LoginViewController.m
1 #import "LoginViewController.h" 2 #import "LoginMarger.h" 3 #define WIDTH [UIScreen mainScreen].bounds.size.width 4 #define HEIGHT [UIScreen mainScreen].bounds.size.height 5 @implementation LoginViewController 6 -(void)dealloc{ 7 8 NSLog(@"销毁 %@",self); 9 self.nameTF = nil; 10 self.passwordTF = nil; 11 [super dealloc]; 12 } 13 14 - (void)viewDidLoad { 15 [super viewDidLoad]; 16 17 // 遍历出姓名、密码控件 18 for (int i = 0; i<2; i++){ 19 UITextField *TF_i = [[UITextField alloc] init]; 20 TF_i.frame = CGRectMake(30, 80*(i+1)+45, WIDTH-60, 45); 21 TF_i.layer.cornerRadius = 4.0f; 22 TF_i.backgroundColor = [UIColor brownColor]; 23 TF_i.textColor = [UIColor blackColor]; 24 [self.view addSubview:TF_i]; 25 26 if (i == 0) { 27 self.nameTF = TF_i; 28 TF_i.placeholder = @"请输入账号"; 29 }else{ 30 self.passwordTF = TF_i; 31 TF_i.placeholder = @"请输入密码"; 32 } 33 } 34 35 // 登录 36 UIButton *nextBT = [UIButton buttonWithType:UIButtonTypeCustom]; 37 nextBT.frame = CGRectMake(100, 280, WIDTH-200, 45); 38 nextBT.backgroundColor = [UIColor redColor]; 39 [nextBT setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal]; 40 [nextBT setTitleColor:[UIColor blackColor] forState:UIControlStateHighlighted]; 41 [nextBT setTitle:@"登陆" forState:UIControlStateNormal]; 42 nextBT.layer.cornerRadius = 8; 43 [nextBT addTarget:self action:@selector(makeLogin) forControlEvents:UIControlEventTouchUpInside]; 44 [self.view addSubview:nextBT]; 45 46 } 47 48 -(void)makeLogin{ 49 50 NSString *userName01 = self.nameTF.text; 51 NSString *passWord01 = self.passwordTF.text; 52 53 LoginMarger * login = [[LoginMarger alloc] init]; 54 [login loginWithUserName:userName01 passWord:passWord01 successfulBlock:^(NSString *userName) { 55 56 NSLog(@"%@,老板在小黑屋等你",userName01); 57 } failBlock:^BOOL(NSString *errorMessage) { 58 59 NSLog(@"%@",errorMessage); 60 return arc4random() % 2; 61 }]; 62 } 63 64 @end
运行效果
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)