iOS内存管理(三)ARC内存泄漏
最近在对一个项目的内存泄漏进行排查,发现存在很多内存泄漏,还有第三方类库里面存在对象释放不掉的情况
AFNetWorking
作为一位iOS开发者,网络请求类AFNetWorking是再熟悉不过了,如果你不知道,那你就太奥特了。对于AFNetWorking的使用通常会对用参数、网址环境切换、网络状态监测、请求错误信息等进行封装。在封装请求类时需要注意的是需要请求队列管理者AFHTTPSessionManager声明为单例创建形式。对于这个问题,AFNetWorking的作者在github上也指出建议使用者在相同配置下保证AFHTTPSessionManager只有一个,进行全局管理,因此我们可以通过单例形式进行解决。
@implementation MXHTTPRequst
+ (AFHTTPSessionManager *)manager {
static AFHTTPSessionManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
});
return manager;
}
+ (void)GET:(NSString *)url parameters:(NSDictionary *)getDictionary returnData:(void (^)(NSData * resultData,NSError * error))returnBlock {
//请求队列管理者 单例创建形式 防止内存泄漏
AFHTTPSessionManager *manager = [self manager];
[manager GET:url parameters:getDictionary progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
returnBlock(responseObject,nil);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
returnBlock(nil,error);
}];
}
@end
Block循环引用
Block循环引用的问题是老生常谈了,至今网上已经有很多文章解释原理及造成循环引用的原因,在下就不逼逼了。总之记住一句话“对象之间不要出现封闭的环”,就能防止Block循环引用。我就拿MJRefresh列举一下吧,其他的代码太多了😆:
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
self.page = 1;
[self.dataArr removeAllObjects];
[self loadData];
}];
若在MJRefresh的执行Block中调用当前self或者属性,一定要注意循环引用问题
#pragma mark - 构造方法
+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
{
MJRefreshHeader *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
}
上面为MJRefreshNormalHeader的构造方法,注意看一下这一行代码“cmp.refreshingBlock = refreshingBlock”,refreshingBlock是属于MJRefreshHeader的强引用属性,最后header会成为我们自己tableView的强引用属性mj_header,也就是说self.tableView强引用header, header强引用refreshingBlock,如果refreshingBlock里面强引用self,就成了循环引用,如图:
image
所以需要破掉这个环就得使用weakSelf
__weak id weakSelf = self;
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
__strong typeof(self) strongSelf = weakSelf;
strongSelf.page = 1;
[strongSelf.dataArr removeAllObjects];
[strongSelf loadData];
}];
结果为
image
delegate循环引用
delegate是一个非常基础的问题了,吧修饰符改成weak就👌:@property (nonatomic,weak)id delegate;
这里就不再赘述
比较形象的事UITextView和UIViewController
NSTimer循环引用
对于定时器NSTimer,使用不正确也会造成内存泄漏问题,在初始化传入target参数时记得用weak,还有当不需要定时器时需要执行这两行代码:
[_timer invalidate];
_timer = nil;
非OC对象内存处理
对于iOS开发,ARC模式已发扬光大多年,可能很多人早已忘记当年retain、release的年代,但ARC的出现并不是说我们完全可以忽视内存泄漏的问题。对于一些非OC对象,使用完毕后其内存仍需要我们手动释放。
举个例子,比如常用的滤镜操作调节图片亮度
CIImage *beginImage = [[CIImage alloc]initWithImage:[UIImage imageNamed:@"yourname.jpg"]];
CIFilter *filter = [CIFilter filterWithName:@"CIColorControls"];
[filter setValue:beginImage forKey:kCIInputImageKey];
[filter setValue:[NSNumber numberWithFloat:.5] forKey:@"inputBrightness"];//亮度-1~1
CIImage *outputImage = [filter outputImage];
//GPU优化
EAGLContext * eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
eaglContext.multiThreaded = YES;
CIContext *context = [CIContext contextWithEAGLContext:eaglContext];
[EAGLContext setCurrentContext:eaglContext];
CGImageRef ref = [context createCGImage:outputImage fromRect:outputImage.extent];
UIImage *endImg = [UIImage imageWithCGImage:ref];
_imageView.image = endImg;
CGImageRelease(ref);//非OC对象需要手动内存释放
在如上代码中的CGImageRef类型变量非OC对象,其需要手动执行释放操作CGImageRelease(ref),否则会造成大量的内存泄漏导致程序崩溃。其他的对于CoreFoundation框架下的某些对象或变量需要手动释放、C语言代码中的malloc等需要对应free等都需要注意。
地图类处理
如果项目中使用抵触相关类,一定要检测内存情况,因为地图是相当耗费内存的,因此根据文档实现地图相关功能的同时,还必须注意内存的正确释放,大体需要注意的有需在使用完毕时将地图、代理赋值为nil,注意地图中的大头针的服用,并且在使用完毕时清空大头针数组等:
- (void)clearMapView{
self.mapView = nil;
self.mapView.delegate =nil;
self.mapView.showsUserLocation = NO;
[self.mapView removeAnnotations:self.annotations];
[self.mapView removeOverlays:self.overlays];
[self.mapView setCompassImage:nil];
}
大次数循环内存暴涨问题
for (int i = 0; i < 100000; i++) {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏,解决方法在循环体内加入自己的@autoreleasepool,用来及时释放大量的临时变量,减少内存占用峰值。
for (int i = 0; i < 100000; i++) {
@autoreleasepool{
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
}