本文分析了使用 AFNetworking 组件时遇见的内存泄漏问题的根本原因,并给出解决方案。
当第一次学会使用 Leaks 时,便拿了公司的 APP 项目练手测试了一下。结果测试刚开始,便出现了 Leaks 经典的小红叉。查看了一下小红叉的原因,就是 AFNetworking。由于 APP 在启动时,需要使用 AFNetworking 请求并下载相关的资源更新。当时并没有对这个小红叉的成因继续深究下去,趁最近有空,回过头来研究一下。
1. Demo 测试
编写一个 Demo,复现一下当时内存泄漏的情况。AFNetworking 的版本为 3.2.1。
#import "ViewController.h"
#import <AFNetworking.h>
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
button.frame = CGRectMake(0, 0, 100, 50);
button.center = self.view.center;
[button setTitle:@"Request" forState:UIControlStateNormal];
[button addTarget:self action:@selector(requestButtonEvent) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)requestButtonEvent {
NSString *url = @"https://www.baidu.com";
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:url parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@", responseObject);
NSLog(@"%@", [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"Error: %@", error);
}];
}
@end
每当点击一次 Request 按钮,就会调用 AFNetworking 的 get 接口请求百度首页。然后,使用 Leaks 来查看一下内存泄漏的情况。
在 Leaks 的图中,可以看出每点击一次 Request 按钮,内存就会上涨一次,而且附带着内存泄漏的情况发生。查看下方的 Cycles & Roots,可以看到总共有两个引用环存在,刚好对应两次点击 Request 事件。
查看这两个引用环的场景,发现原因都是相同的。AFHTTPSessionManager 对象强引用 NSURLSession 类型的变量 _session,NSURLSession 类通过 delegate 强引用 AFHTTPSessionManager 对象。这个引用环的存在,导致 AFHTTPSessionManager 对象和 NSURLSession 对象都无法释放,造成了内存泄漏。
2. 内存泄漏原因分析
与常见的 Block 造成的循环引用不同,这是由 Delegate 造成的循环引用。