项目开发有一段时间了,一直没有时间检测内存情况,今天检测了一下内存,综合了几种情况
一、内存泄漏检测方法
(1) 可以使用系统自带的leaks,在测试app的时候打开leaks,很准确哦
(2)也可使用第三方,可以检测某个UIViewController和UIView中内存泄漏,推荐使用轻量级的内存泄漏检测工具。
1、MLeaksFinder
介绍:MLeaksFinder:精准 iOS 内存泄露检测工具
2、PLeakSniffer
介绍:iOS内存泄漏自动检测工具PLeakSniffer
二、常见内存泄漏情况
1、UIViewController 和 子UIView中双向持有对方
@interface MyViewController: UIViewController
//1.MyViewController 中强引用 childView
@property (nonatomic, strong) ChildView *childView;
@end
-----------------------------------------------------
@interface ChildView : UIView
@property (nonatomic, strong) MyViewController *controller;
-(instancetype)initWithController:(MyViewController *)controller;
@end
//implement
-(instancetype)initWithController:(MyViewController *)controller{
self = [super init];
if(self){
//2.这一步让View,强引用 MyViewController
self.controller = controller;
}
return self;
}
如上代码所示,1,2中让两个对象双向强引用,导致两者都不会被释放。
解决方案:
把2这一步换成weak
@property (nonatomic, weak) MyViewController *controller;
2、Delegate循环引用
还是引用上面的案例,这次我们改成delegate版本
@interface MyViewController: UIViewController
//1.MyViewController 中强引用 childView
@property (nonatomic, strong) ChildView *childView;
@end
//implement
self.childView = [[ChildView alloc]init];
//2.delegate 设置为 MyViewController
self.childView.delegate = self;
-----------------------------------------------------
@protocol ChildViewProtocol
@end
@interface ChildView : UIView
//strong delegate代理器
@property(nonatomic, strong) id<ChildViewProtocol> *delegate
@end
1、MyViewController强引用持有一个ChildView
2、ChildView.delegate 设置为 MyViewController
3、这里可以看出ChildView强引用id == MyViewController
因此也造成了循环引用,导致不能被销毁
解决方案
把3中强引用改为弱引用
@property(nonatomic, weak) id<ChildViewProtocol> *delegate
3、block使用双向持有
这个比较复杂,首先我们了解下block原理:
A look inside blocks: Episode 1
A look inside blocks: Episode 2
这儿有两篇文章对block具体实现讲的很详细。总结一下文章的观点,block的struct声明如下:
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
在这个struct,我们看到一个isa指针,这里和OC对象很类似。在OC对象中isa指向的是其Class或者metaClass, 在这里isa指针,指向3种类型的Block
_NSConcreteGlobalBlock 全局Block,在编译的时候则已经存在
_NSConcreteStackBlock 栈上分配的Block,也就是在方法体内部声明的block
_NSConcreteMallocBlock 堆上分配的Block,也就是一个对象的成员变量或属性
而出现循环引用的情况,大多数都是_NSConcreteMallocBlock使用不恰到导致。下面看一个具体案例:
//1.定义一个TestBlock
typedef void (^TestBlock)();
//2.TestBlock的初始化
TestBlock getBlock() {
char e = 'E';
void (^returnedBlock)() = ^{
printf("%c\n", e);
};
return returnedBlock;
}
首先我们先看下2,在栈空间创建一个returnedBlock,这个block在方法体执行完后会自动销毁。在return returnedBlock,在ARC中其实系统会自动帮你做一次copy操作,而这次copy操作则让block从_NSConcreteStackBlock变为了_NSConcreteMallocBlock。
如果还不清楚,可以看下MRC情况下,block一般使用方案:
TestBlock getBlock() {
char e = 'E';
void (^returnedBlock)() = ^{
printf("%c\n", e);
};
//3.手动copy,然后autoRelease
return [[returnedBlock copy] autorelease];
}
在3中,很容易看出需要手动进行一次copy操作,而这次copy操作让这个block 的retainCount属性 +1.
block 循环引用 案例1
所以block本质上类似上面第1,2案例的ChildView
//1、self有个指针强引用 completionBlock
@property(nonatomic, readwrite, copy) completionBlock completionBlock;
@property(nonatomic, strong) UIView *successView;
self.completionBlock = ^ {
//2、在这里使用self,则堆中block空间会生成一个指针指向self,形成了一个双向强引用
self.successView.hidden = YES;
};
如注释1,2所示,隐形中形成双向引用,解决方案:
//生成一个对 self 的弱引用
__weak typeof(self) weakSelf = self;
self.completionBlock = ^ {
weakSelf.successView.hidden = YES;
};
block 循环引用 案例2
最近在研究 ReactiveCocoa,这个框架对 self 弱引用,强引用进行封装,如@weakify(self) @strongify(self),这有篇文章对着两个宏定义进行剖析 剖析@weakify 和 @strongify,文章分析最终结果为:
"@weakify(self)" == "__weak __typeof__ (self) self_weak_ = self;"
"@strongify(self)" == "__strong __typeof__ (self) self = self_weak_;"
所以案例1方案另一种写法为:
//利用 ReactiveCocoa 方案
@weakify(self)
self.completionBlock = ^ {
@strongify(self)
self.successView.hidden = YES;
};
block 循环引用 案例3
下面继续介绍@weakify(self) @strongify(self)在使用中遇到的坑。如果block里面嵌套block,那该如何解决,先看下面案例:
@weakify(self)
//1、给selectedButton绑定一个点击事件
[[self.selectedButton rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
//2、点击以后做数据操作
self.do(^(BOOL result){
//3、操作完成后,回调处理
@strongify(self)
self.success = result;
})
}];
1、创建一个selectedButton的点击事件 2、点击事件触发后,进行do操作 3、最后对操作后的事件进行处理
self.success = result
当利用instrument测试代码的时,我们会发现这个block会造成循环引用。如果对@weakify(self) @strongify(self)不理解,很难发现其中问题。原因在哪里?我们按照上面宏替换标准对这个进行替换。
__weak __typeof__ (self) self_weak_ = self;
[[self.selectedButton rac_signalForControlEvents:UIControlEventTouchUpInside] //1
subscribeNext:^(id x) {
self.do(^(BOOL result){ //2
__strong __typeof__ (self) self = self_weak_;
self.success = result; //3
})
}];
我们对上面代码中self进行分析
这里是强引用self持有一个block block 持有一个strong的self 因此会导致循环引用
这里的self实际上是self_weak_ 没有问题 所以问题出现在2处!那下面我进行多次尝试修复这个问题。
尝试修改1
@weakify(self)
[[self.selectedButton rac_signalForControlEvents:UIControlEventTouchUpInside] //1
subscribeNext:^(id x) {
@strongify(self)
@weakify(self)
self.do(^(BOOL result){ //2
@strongify(self)
self.success = result; //3
})
}];
这样修改最能适应@weakify(self) @strongify(self)在ReactiveCocoa成对出现的理念,且在2处使用的肯定是 weak 的self,确实没有问题。
但总感觉有点奇怪,利用上面的宏替换很容易看出,在第一次@strongify(self)时,self == self_weak_ self已经是weak,所以我们没必要再在后面进行新的 weakify 和 strongify,对上面的代码进行改进,如下
尝试修改2
@weakify(self)
[[self.selectedButton rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
@strongify(self)
self.do(^(BOOL result){
self.success = result;
})
}];
总结:多层嵌套的block,只需要对最外层block传入的self进行weak化即可。
block 循环引用 案例4
再列举一个容易犯的错误,代码如下:
@property (nonatomic, assign) BOOL success;
@weakify(self)
[[self.selectedButton rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
@strongify(self)
_success = false; //1
}];
问题就出现在1处。我们知道对属性的访问,可以使用 self.success 和 _success两种方式,当然两者有一些区别
self.success 能支持懒加载方式 调用 success 的 get 方法。
_success是对实例变量的访问。 在 iOS 5.0 之后已经支持属性到实例的映射,也就是省略 @sychronise _success = self.success;
但在block中使用,得特别注意,self.success 会使用 @strongify(self) 所生成的self_weak_ ,而_success 不会!不会!不会!
所以block 强引用指向 strong 的 self,调用其实例变量。所以上诉代码 _success会造成循环引用。
4、NSNotificationCenter,KVO 问题
关于事件监听,属性监听,会自动retain self,所以在 dealloc 时需要对监听进行释放。
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self removeObserver:self forKeyPath:@"" context:nil];
5、NSTimer Animator 停止
对于NSTimer 和 持续的Animator动画,需要在 dealloc时停止。
[NSTimer invalidate];