▶ 为什么 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
运行效果