iOS内存管理(三)ARC内存泄漏

2017-10-09  本文已影响75人  朽木自雕也

最近在对一个项目的内存泄漏进行排查,发现存在很多内存泄漏,还有第三方类库里面存在对象释放不掉的情况

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

image

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);
    }
}
上一篇下一篇

猜你喜欢

热点阅读