主要是分享几个平时容易遇到的导致内存泄漏的情景,如果有遇到其它情景会继续补充,也欢迎大家提出来还有哪些常见的情景,如果有哪些理解错误的地方,也希望各位朋友能够支持,非常感谢。
1 .block循环引用.
情景:
self.animations = ^{
self.testArray= @[@"haha"];
};
分析:
如上的代码所示:self对animations这个block强引用,block捕获了testArray对象,同样也对testArray持有强引用,而self对testArray也持有强引用。三者之间的引用:self -> animations -> testArray -> self。三者之间相互引用,引用计数无法变为0,导致内存泄漏。
解决方案:
1. 最常用的方法是使用对self使用__weak修饰, __weak typeof (self) weakSelf = self,代码如下
__weak typeof (self) weakSelf = self;
self.animations = ^{
weakSelf.testArray= @[@"haha"];
};
注意:是不是使用weakSelf就万事大吉了呢?当然不是。如果在多线程下,使用weakSelf会出现什么情况呢?
在多线程下,在某一个线程中,对str做了一次release操作了,内存被销毁了,但是指针还没有被置nil。由于是多线程,在销毁的瞬间,同时又有其他线程在访问str这个对象,导致crash。(如果没有出现crash,可以加大循环的次数,改成1000)。
__weak MyViewController *wself = self;
self.completionHandler = ^(NSInteger result) {
[wself.property removeObserver: wself forKeyPath:@"pathName"];
};
那要怎么解决呢:但是使用__strongtypeof(weakSelf) strongSelf = weakSelf;这样一来,self 所指向对象的引用计数变成 2,即使主线程中的 self 因为超出作用于而释放,对象的引用计数依然为 1,避免了对象的销毁。
__weak MyViewController *wself =self;
self.completionHandler = ^(NSIntegerresult) {
__strong typeof(wself) sself = wself;// 强引用一次
[sself.property removeObserver: sself forKeyPath:@"pathName"];
};
2. delegate循环引用
情景:
控制器a,视图b是控制器a的子视图。 b.delegate = a。
分析:
视图b是控制器a的子视图,a对b强引用,而b对delegate强引用。b.delegate=a,delegate对控制器a强引用。引用关系:a->b->delegate->a。
解决方案:
b的delegate使用weak修饰:@property (nonatomic, weak) id<BDelegate> delegate;
注意:不能使用assign代理weak,assign是修饰数值类型的变量的,因为基本数值类型是存在栈的,不需要手动管理内存。如果拿assign去修饰一个对象,会导致内存被销毁了,但是指针没有被置为nil,这个对象就会变成野指针,下次访问这个野指针的时候,谁也无法知道这个野指针是访问了什么样的内存,导致crash。
3. NSTimer没有销毁
[NSTimer timerWithTimeInterval:1 target:self selector:@selector(viewDidLoad) userInfo:nil repeats:YES];
系统为了保证定时器能够正常运行,会对target一直强引用,知道定时器结束。所以即使页面已经pop了,但是页面并没有被销毁。即使使用__weak typeof (self) weakSelf = self; target传weakSelf也没有用,因为系统会一直对这个target强引用。苹果的官方文档是这么说的:
那要怎么销毁呢?在不需要定时器的时候,关闭定时器,把定时器置nil
[_timer invalidate];//关闭定时器
_timer = nil;//把定时器置nil
注意:不要在dealloc里面写着两句代码,因为_timer被循环引用着,压根就不会走到dealloc方法来。
4. 非OC对象内存处理,没有手动释放
对于一些非OC对象,使用完毕后其内存仍需要我们手动释放。
举个例子,比如获取图片的像素点方法中:
- (uint32_t*)pixelBRGABytesFromImageRef:(CGImageRef)imageRef {
NSUInteger iWidth = CGImageGetWidth(imageRef);
NSUInteger iHeight = CGImageGetHeight(imageRef);
NSUIntegeriBytesPerPixel = 4;
NSUIntegeriBytesPerRow = iBytesPerPixel * iWidth;
NSUIntegeriBitsPerComponent = 8;
uint32_t*pixels = (uint32_t*)malloc(iWidth * iHeight *sizeof(uint32_t));
memset(pixels,0, iWidth * iHeight *sizeof(uint32_t));
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(pixels,
iWidth,
iHeight,
iBitsPerComponent,
iBytesPerRow,
colorspace,
kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
CGRectrect =CGRectMake(0 , 0 , iWidth , iHeight);
CGContextDrawImage(context , rect ,imageRef);
CGColorSpaceRelease(colorspace);
CGContextRelease(context);
CGImageRelease(imageRef);
return pixels;
}
在如上代码中的CGImageRef类型变量非OC对象,其需要手动执行释放操作CGImageRelease(imageRef) CGColorSpaceRelease(colorspace); CGContextRelease(context),否则会造成大量的内存泄漏导致程序崩溃。其他的对于CoreFoundation框架下的某些对象或变量需要手动释放、C语言代码中的malloc等需要对应free等都需要注意。
5.循环体中多次创建对象,造成内存暴涨
如图所示,在一个很大的循环体中,创建临时数组(如果图片存的是大体积的data或者image),会导致内存暴涨。
解决方案:
可以在循环里内,创建一个@autoreleasepool{},临时变量的创建在@autoreleasepool{}里面,就能保证除了@autoreleasepool{}的作用域之后,临时变量会被及时销毁。
拓展
可是大家知道为什么使用__weak修饰就能避免循环引用?使用__weak修饰的对象,引用计数不会+1,这个对象会被加入到一个weak hash表中,以对象的内存地址a(假设内存地址为a)作为key,对象作为value,存到一个hash表中。当这个对象引用计数为0的时候,会通过key为a在hash表中寻找对象,找到对象之后把对象置为nil。