iOS内存泄漏检测

项目开发有一段时间了,一直没有时间检测内存情况,今天检测了一下内存,综合了几种情况

一、内存泄漏检测方法

(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];

猜你喜欢

转载自blog.csdn.net/sun_cui_hua/article/details/79207617