▶ 为什么 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

运行效果

iOS开发blcok为啥要用copy ios block为什么用copy修饰_引用计数