一、循环引用和内存泄漏
1、block循环引用
typedef void(^BlockTest)(void);
@interface WGBlockTestViewController ()
@property (nonatomic, copy) BlockTest blockTest;
@end
- (void)blockTest04{
self.blockTest = ^{
[self doSomething];
};
}
- (void)doSomething{
WGLog(@"测试循环引用调用方法");
}
-(void)dealloc{
WGLog(@"没有循环引用");
}
分析:
因为WGBlockTestViewController强引用blockTest,而在blockTest04方法blockTest里对self强引用,造成两者相互持有。
解决:加上 __weak typeof(self) weakSelf = self;打破这个环
- (void)blockTest05{
__weak typeof(self) weakSelf = self;
self.blockTest = ^{
[weakSelf doSomething];
};
}
这里再抛出一个问题,看代码:
- (void)blockTest07{
__weak typeof(self) weakSelf = self;
self.blockTest = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf doSomething];
});
};
}
如果我在5秒内就让WGBlockTestViewController出栈
这时候 [weakSelf doSomething];就不会执行了。
如果要执行怎么办呢?
解决:
- (void)blockTest07{
__weak typeof(self) weakSelf = self;
self.blockTest = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongSelf doSomething];
});
};
}
分析:
strongSelf会使WGBlockTestViewController引用计数+1;
即使WGBlockTestViewController被pod,也不会回收(因为strongSelf是持有WGBlockTestViewController的);
等到[strongSelf doSomething]执行完,strongSelf销毁后;WGBlockTestViewController才会销毁。
注意:
这里既有 __weak typeof(self) weakSelf = self;
又有 __strong typeof(weakSelf) strongSelf = weakSelf;
为什么不会循环引用呢?
因为strongSelf是局部变量,在栈中,当block执行完就销毁了,所以不会循环引用
2、NSTimer
#import "FirstViewController.h"
#import "SecondViewController.h"
@interface FirstViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.title = @"FirstViewController";
[self nstimer];
}
- (void)nstimer{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
}
- (void)timerMethod{
NSLog(@"还在执行");
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
SecondViewController *secondC = [[SecondViewController alloc] init];
[self.navigationController pushViewController:secondC animated:YES];
}
- (void)dealloc{
NSLog(@"FirstViewController没有内存泄露");
}
@end
执行结果:
如图当FirstViewController执行pod后依然没有被销毁,timer还在执行,出现内存泄漏;
分析:
NSTimer是在runloop中的,而NSTimer强引用FirstViewController导致控制器不会被销毁,出现内存泄漏。
解决方案:
方案1:
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[_timer invalidate];
_timer = nil;
}
结果:
可以达到销毁的效果,但是存在一个问题:当push一个页面再回来也会导致_timer被销毁了,这时候如果还需要执行_timer怎么办呢?重新创建?
方案2:
-(void)didMoveToParentViewController:(UIViewController *)parent{
if (nil == parent) {
[_timer invalidate];
_timer = nil;
}
}
当调用willMoveToParentViewController方法或didMoveToParentViewController方法时,要注意他们的参数使用:
当某个子视图控制器将从父视图控制器中删除时,parent参数为nil。
即:[将被删除的子试图控制器 willMoveToParentViewController:nil];
当某个子试图控制器将加入到父视图控制器时,parent参数为父视图控制器。
即:[将被加入的子视图控制器 didMoveToParentViewController:父视图控制器];
方案3:runtime动态添加类和方法
@interface FirstViewController ()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) id object;
@end
@implementation FirstViewController
static const void *key = @"key";
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.title = @"FirstViewController";
[self nstimer];
}
- (void)nstimer{
//新建一个类
_object = [[NSObject alloc] init];
//给类动态添加方法
class_addMethod([_object class], @selector(timerMethod), (IMP)objMethod, "v@:");
//target对象改为_object
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_object selector:@selector(timerMethod) userInfo:nil repeats:YES];
//关联属性
objc_setAssociatedObject(_object, key, self, OBJC_ASSOCIATION_ASSIGN);
}
void objMethod(id self, SEL _cmd){
//注意:这里的self不是控制器,是_object,获取到的obj才是控制器
id obj = objc_getAssociatedObject(self, key);
[obj timerMethod];
}
- (void)timerMethod{
NSLog(@"还在执行");
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
SecondViewController *secondC = [[SecondViewController alloc] init];
[self.navigationController pushViewController:secondC animated:YES];
}
- (void)dealloc{
[self.timer invalidate];
_timer = nil;
NSLog(@"FirstViewController没有内存泄露");
}
@end
结果:达到了要求
方案4:消息转发
.h
#import <Foundation/Foundation.h>
@interface WGProxy : NSProxy
@property (nonatomic, weak) id objct;
@end
.m
#import "WGProxy.h"
@implementation WGProxy
//消息转发机制
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [self.objct methodSignatureForSelector:sel];
}
//转发给了self.objct
-(void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:self.objct];
}
@end
#pragma mark -方案4
- (void)nstimer01{
self.proxy = [WGProxy alloc];
self.proxy.objct = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self.proxy selector:@selector(timerMethod) userInfo:nil repeats:YES];
}
结果:和方案3一样达到效果,其实和方案3思路是一样的
3、野指针(EXC_BAD_INSTRUCTION)
#import "SecondViewController.h"
@interface SecondViewController ()
@property (nonatomic, assign) UIView *subView;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.title = @"SecondViewController";
[self.view addSubview:self.subView];
}
-(UIView *)subView{
if (!_subView) {
_subView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 50, 50)];
_subView.backgroundColor = [UIColor yellowColor];
}
return _subView;
}
@end
分析:
WGPerformanceTest[8085:597448] *** -[UIView setBackgroundColor:]: message sent to deallocated instance 0x7f9996c179a0
向一个已经dealloc的对象发送消息
原因是因为属性声明用的assign;
@property (nonatomic, assign) UIView *subView;
PS:这里就涉及到assign和weak的区别:
还是上一份代码,看下subView分别用assign和weak修饰执行的结果
assign:修饰的对象释放后,指针不会自动清空,依然指向销毁的对象,这就造成了野指针
weak:修饰的对象释放后,指针会被自动清空(nil)