引用一
1 [self.teacher requestData:^(NSData *data) { 2 self.name = @"case"; 3 }];
此种情况是最常见的循环引用导致的内存泄露了,在这里,self强引用了teacher, teacher又强引用了一个block,而该block在回调时又调用了self,会导致该block又强引用了self,造成了一个保留环,最终导致self无法释放。
self -> teacher -> block -> self
解决方法
1 __weak typeof(self) weakSelf = self; 2 [self.teacher requestData:^(NSData *data) { 3 typeof(weakSelf) strongSelf = weakSelf; 4 strongSelf.name = @"case"; 5 }];
通过__weak的修饰,先把self弱引用(默认是强引用,实际上self是有个隐藏的__strong修饰的),然后在block回调里用weakSelf,这样就会打破保留环,从而避免了循环引用,如下:
self -> teacher -> block -> weakSelf
注意:一般会在block回调里再强引用一下weakSelf(typeof(weakSelf) strongSelf = weakSelf;),因为__weak修饰的都是存在栈内,可能随时会被系统释放,造成后面调用weakSelf时weakSelf可能已经是nil了,后面用weakSelf调用任何代码都是无效的。
引用二
代码
ViewController.m
1 #import "ViewController.h" 2 #import "HYBAController.h" 3 4 @interface ViewController () 5 6 @property (nonatomic, strong) UIButton *button; 7 @property (nonatomic, strong) HYBAController *vc; 8 9 @end 10 11 @implementation ViewController 12 13 - (void)goToNext { 14 __weak __typeof(self) weakSelf = self; 15 HYBAController *vc = [[HYBAController alloc] initWithCallback:^{ 16 [weakSelf.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal]; 17 }]; 18 // self.vc = vc; 19 [self.navigationController pushViewController:vc animated:YES]; 20 } 21 22 23 24 - (void)viewDidLoad { 25 [super viewDidLoad]; 26 27 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 28 [button setTitle:@"进入下一个界面" forState:UIControlStateNormal]; 29 button.frame = CGRectMake(50, 200, 200, 45); 30 button.backgroundColor = [UIColor redColor]; 31 [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal]; 32 [button addTarget:self action:@selector(goToNext) forControlEvents:UIControlEventTouchUpInside]; 33 [self.view addSubview:button]; 34 self.button = button; 35 36 self.title = @"ViewController"; 37 38 } 39 40 @end
HYBAController.h
1 #import <UIKit/UIKit.h> 2 3 typedef void(^HYBCallbackBlock)(); 4 5 @interface HYBAController : UIViewController 6 7 - (instancetype)initWithCallback:(HYBCallbackBlock)callback; 8 9 @end
HYBAController.m
1 #import "HYBAController.h" 2 #import "HYBAView.h" 3 4 @interface HYBAController() 5 6 @property (nonatomic, copy) HYBCallbackBlock callbackBlock; 7 8 @property (nonatomic, strong) HYBAView *aView; 9 @property (nonatomic, strong) id currentModel; 10 11 @end 12 13 @implementation HYBAController 14 15 - (instancetype)initWithCallback:(HYBCallbackBlock)callback { 16 if (self = [super init]) { 17 self.callbackBlock = callback; 18 } 19 20 return self; 21 } 22 23 - (void)viewDidLoad { 24 [super viewDidLoad]; 25 26 self.title = @"HYBAController"; 27 self.view.backgroundColor = [UIColor whiteColor]; 28 29 __block __weak __typeof(_currentModel) weakModel = _currentModel; 30 self.aView = [[HYBAView alloc] initWithBlock:^(id model) { 31 // 假设要更新model 32 weakModel = model; 33 // self.currentModel = model; 34 }]; 35 // 假设占满全屏 36 self.aView.frame = self.view.bounds; 37 [self.view addSubview:self.aView]; 38 self.aView.backgroundColor = [UIColor whiteColor]; 39 40 } 41 42 - (void)viewDidAppear:(BOOL)animated { 43 [super viewDidAppear:animated]; 44 45 NSLog(@"进入控制器:%@", [[self class] description]); 46 } 47 48 - (void)dealloc { 49 NSLog(@"%@-->控制器被dealloc", [[self class] description]); 50 } 51 52 @end
HYBAView.h
1 #import <UIKit/UIKit.h> 2 3 typedef void(^HYBFeedbackBlock)(id model); 4 5 @interface HYBAView : UIView 6 7 - (instancetype)initWithBlock:(HYBFeedbackBlock)block; 8 9 @end
HYBAView.m
1 #import "HYBAView.h" 2 3 @interface HYBAView () 4 5 @property (nonatomic, copy) HYBFeedbackBlock block; 6 7 @end 8 9 @implementation HYBAView 10 11 - (void)dealloc { 12 NSLog(@"dealloc: %@", [[self class] description]); 13 } 14 15 - (instancetype)initWithBlock:(HYBFeedbackBlock)block { 16 if (self = [super init]) { 17 self.block = block; 18 19 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 20 [button setTitle:@"反馈给controller" forState:UIControlStateNormal]; 21 button.frame = CGRectMake(50, 200, 200, 45); 22 button.backgroundColor = [UIColor redColor]; 23 [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal]; 24 [button addTarget:self action:@selector(feedback) forControlEvents:UIControlEventTouchUpInside]; 25 [self addSubview:button]; 26 } 27 28 return self; 29 } 30 31 - (void)feedback { 32 if (self.block) { 33 // 传模型回去,这里没有数据,假设传nil 34 self.block(nil); 35 } 36 } 37 38 @end
以上是正常运行,不存在内存泄露的代码,下面进行细致讨论情况
场景一:Controller之间block传值
1 @interface ViewController () 2 3 // 引用按钮只是为了测试 4 @property (nonatomic, strong) UIButton *button; 5 // 只是为了测试内存问题,引用之。在开发中,有很多时候我们是 6 // 需要引用另一个控制器的,因此这里模拟之。 7 @property (nonatomic, strong) HYBAController *vc; 8 9 @end 10 11 // 点击button时 12 - (void)goToNext { 13 HYBAController *vc = [[HYBAController alloc] initWithCallback:^{ 14 [self.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal]; 15 }]; 16 self.vc = vc; 17 [self.navigationController pushViewController:vc animated:YES]; 18 }
原因:
这里形成了两个环:
-
ViewController->强引用了属性vc->强引用了callback->强引用了ViewController
-
ViewController->强引用了属性vc->强引用了callback->强引用了ViewController的属性button
解决方案:
不声明vc属性或者将vc属性声明为weak引用的类型,在callback回调处,将self.button改成weakSelf.button,也就是让callback这个block对viewcontroller改成弱引用。如就是改成如下,内存就可以正常释放了:
1 - (void)goToNext { 2 __weak __typeof(self) weakSelf = self; 3 HYBAController *vc = [[HYBAController alloc] initWithCallback:^{ 4 [weakSelf.button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal]; 5 }]; 6 // self.vc = vc; 7 [self.navigationController pushViewController:vc animated:YES]; 8 }
笔者尝试过使用Leaks检测内存泄露,但是全是通过,一个绿色的勾,让你以为内存处理得很好了,实际上内存并得不到释放。
针对这种场景,给大家提点建议:
在控制器的生命周期viewDidAppear里打印日志:
1 - (void)viewDidAppear:(BOOL)animated { 2 [super viewDidAppear:animated]; 3 4 NSLog(@"进入控制器:%@", [[self class] description]); 5 }
在控制器的生命周期dealloc里打印日志
1 - (void)dealloc { 2 NSLog(@"控制器被dealloc: %@", [[self class] description]); 3 }
场景二: Controller与View之间Block传值
定义一个view,用于与Controller交互。当点击view的按钮时,就会通过block回调给controller,也就反馈到控制器了,并将对应的数据传给控制器以记录
1 typedef void(^HYBFeedbackBlock)(id model); 2 3 @interface HYBAView : UIView 4 5 - (instancetype)initWithBlock:(HYBFeedbackBlock)block; 6 7 @end 8 9 @interface HYBAView () 10 11 @property (nonatomic, copy) HYBFeedbackBlock block; 12 13 @end 14 15 @implementation HYBAView 16 17 - (void)dealloc { 18 NSLog(@"dealloc: %@", [[self class] description]); 19 } 20 21 - (instancetype)initWithBlock:(HYBFeedbackBlock)block { 22 if (self = [super init]) { 23 self.block = block; 24 25 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 26 [button setTitle:@"反馈给controller" forState:UIControlStateNormal]; 27 button.frame = CGRectMake(50, 200, 200, 45); 28 button.backgroundColor = [UIColor redColor]; 29 [button setTitleColor:[UIColor yellowColor] forState:UIControlStateNormal]; 30 [button addTarget:self action:@selector(feedback) forControlEvents:UIControlEventTouchUpInside]; 31 [self addSubview:button]; 32 } 33 34 return self; 35 } 36 37 - (void)feedback { 38 if (self.block) { 39 // 传模型回去,这里没有数据,假设传nil 40 self.block(nil); 41 } 42 } 43 44 @end
HYBAController,增加了两个属性,在viewDidLoad时,创建了aView属性
1 @interface HYBAController() 2 3 @property (nonatomic, copy) HYBCallbackBlock callbackBlock; 4 5 @property (nonatomic, strong) HYBAView *aView; 6 @property (nonatomic, strong) id currentModel; 7 8 @end 9 10 @implementation HYBAController 11 12 - (instancetype)initWithCallback:(HYBCallbackBlock)callback { 13 if (self = [super init]) { 14 self.callbackBlock = callback; 15 } 16 17 return self; 18 } 19 20 - (void)viewDidLoad { 21 [super viewDidLoad]; 22 23 self.title = @"HYBAController"; 24 self.view.backgroundColor = [UIColor whiteColor]; 25 26 self.aView = [[HYBAView alloc] initWithBlock:^(id model) { 27 // 假设要更新model 28 self.currentModel = model; 29 }]; 30 // 假设占满全屏 31 self.aView.frame = self.view.bounds; 32 [self.view addSubview:self.aView]; 33 self.aView.backgroundColor = [UIColor whiteColor]; 34 } 35 36 - (void)viewDidAppear:(BOOL)animated { 37 [super viewDidAppear:animated]; 38 39 NSLog(@"进入控制器:%@", [[self class] description]); 40 } 41 42 - (void)dealloc { 43 NSLog(@"控制器被dealloc: %@", [[self class] description]); 44 } 45 46 @end
原因,形成的环:
-
vc->aView->block->vc(self)
-
vc->aView->block->vc.currentModel
解决的办法可以是:在创建aView时,block内对currentModel的引用改成弱引用:
1 __weak __typeof(self) weakSelf = self; 2 self.aView = [[HYBAView alloc] initWithBlock:^(id model) { 3 // 假设要更新model 4 weakSelf.currentModel = model; 5 }];
很多类似这样的代码,直接使用成员变量,而不是属性,然后他们以为这样就不会引用self,也就是控制器,从而不形成环:
1 self.aView = [[HYBAView alloc] initWithBlock:^(id model) { 2 // 假设要更新model 3 _currentModel = model; 4 }];