iOS ARC 的内存管理问题
iOS Memory management issue with ARC
我创建了一个名为“ConnectionManager”的 class,它将处理所有网络请求并从服务器获取数据,然后调用完成处理程序。
ConnectionManager.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "UIAlertView+CustomAlertView.h"
#import <Crashlytics/Crashlytics.h>
@interface ConnectionManager : NSObject<NSURLSessionDataDelegate>
@property (weak, nonatomic) NSMutableData *receivedData;
@property (weak, nonatomic) NSURL *url;
@property (weak, nonatomic) NSURLRequest *uploadRequest;
@property (nonatomic, copy) void (^onCompletion)(NSData *data);
@property BOOL log;
-(void)downloadDataFromURL:(NSURL *)url withCompletionHandler:(void (^)(NSData *data))completionHandler;
-(void)uploadDataWithRequest:(NSURLRequest*)request withCompletionHandler:(void (^)(NSData *data))completionHandler;
@end
ConnectionManager.m
#import "ConnectionManager.h"
@implementation ConnectionManager
-(void)uploadDataWithRequest:(NSURLRequest*)request withCompletionHandler:(void (^)(NSData *data))completionHandler{
// Instantiate a session configuration object.
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Configure Session Configuration
[configuration setAllowsCellularAccess:YES];
// Instantiate a session object.
NSURLSession *session=[NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
// Assign request for later call
self.uploadRequest = request;
// Create an upload task object to perform the data uploading.
NSURLSessionUploadTask *task = [session uploadTaskWithRequest:self.uploadRequest fromData:nil];
// Assign completion handler
self.onCompletion = completionHandler;
// Inititate data
self.receivedData = [[NSMutableData alloc] init];
// Resume the task.
[task resume];
}
-(void)downloadDataFromURL:(NSURL *)url withCompletionHandler:(void (^)(NSData *data))completionHandler{
// Instantiate a session configuration object.
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Configure Session Configuration
[configuration setAllowsCellularAccess:YES];
// Instantiate a session object.
NSURLSession *session=[NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
// Assign url for later call
self.url = url;
// Create a data task object to perform the data downloading.
NSURLSessionDataTask *task = [session dataTaskWithURL:self.url];
// Assign completion handler
self.onCompletion = completionHandler;
// Inititate data
self.receivedData = [[NSMutableData alloc] init];
// Resume the task.
[task resume];
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.receivedData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error) {
if (error.code == -1003 || error.code == -1009) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"Unable to connect to the server. Please check your internet connection and try again!" delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:@"retry",nil];
[alert showWithCompletion:^(UIAlertView *alertView, NSInteger buttonIndex) {
if (buttonIndex==1) {
// Retry
if (self.url) {
NSURLSessionDataTask *retryTask = [session dataTaskWithURL:self.url];
[retryTask resume];
}else{
NSURLSessionUploadTask *task = [session uploadTaskWithRequest:self.uploadRequest fromData:nil];
[task resume];
}
}else{
self.onCompletion(nil);
}
}];
}];
}else{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"An unkown error occured! Please try again later, thanks for your patience." delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:@"retry",nil];
[alert show];
CLS_LOG(@"Error details: %@",error);
self.onCompletion(nil);
}];
}
}
else {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.onCompletion(self.receivedData);
}];
}
}
@end
这是我用来调用它的一段代码:
-(void)loadDataFromServer{
NSString *URLString = [NSString stringWithFormat:@"%@get_people_number?access_token=%@", global.baseURL, global.accessToken];
URLString = [URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:URLString];
ConnectionManager *connectionManager = [[ConnectionManager alloc] init];
[connectionManager downloadDataFromURL:url withCompletionHandler:^(NSData *data) {
if (data != nil) {
// Convert the returned data into an array.
NSError *error;
NSDictionary *number = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (error != nil) {
CLS_LOG(@"Error: %@, Response:%@",error,[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
}
else{
[_mapView updateUIWithData:[number objectForKey:@"number"]];
}
}
}];
}
我在使用 Instruments 时发现,即使在从服务器获取数据并调用完成处理程序之后,所有 ConnectionManager 类型的对象都是持久的。
我试图将完成处理程序 属性 从复制更改为强,但我得到了相同的结果。将其更改为 weak 会导致崩溃并且永远不会被调用。
请有人指导我正确的方法。
经过大量研究,我发现 URL Session 对象仍然存在。
基于Apple Documentation Life Cycle of URL Session有两种情况:
使用系统提供的委托(通过不设置委托),这里系统将负责使 NSURLSession 对象无效。
使用自定义委托。当您不再需要会话时,通过调用 invalidateAndCancel(以取消未完成的任务)或 finishTasksAndInvalidate(以允许未完成的任务在使对象失效之前完成)来使其无效。
为了修复上面的代码,我只更改了委托方法 "didCompleteWithError",如下所示:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error) {
if (error.code == -1003 || error.code == -1009) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"Unable to connect to the server. Please check your internet connection and try again!" delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:@"retry",nil];
[alert showWithCompletion:^(UIAlertView *alertView, NSInteger buttonIndex) {
if (buttonIndex==1) {
// Retry
if (self.url) {
NSURLSessionDataTask *retryTask = [session dataTaskWithURL:self.url];
[retryTask resume];
}else{
NSURLSessionUploadTask *task = [session uploadTaskWithRequest:self.uploadRequest fromData:nil];
[task resume];
}
}else{
[session finishTasksAndInvalidate];
self.onCompletion(nil);
}
}];
}];
}else{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"An unkown error occured! Please try again later, thanks for your patience." delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:@"retry",nil];
[alert show];
[session finishTasksAndInvalidate];
self.onCompletion(nil);
}];
}
}
else {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[session finishTasksAndInvalidate];
self.onCompletion(self.receivedData);
}];
}
}
我创建了一个名为“ConnectionManager”的 class,它将处理所有网络请求并从服务器获取数据,然后调用完成处理程序。
ConnectionManager.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "UIAlertView+CustomAlertView.h"
#import <Crashlytics/Crashlytics.h>
@interface ConnectionManager : NSObject<NSURLSessionDataDelegate>
@property (weak, nonatomic) NSMutableData *receivedData;
@property (weak, nonatomic) NSURL *url;
@property (weak, nonatomic) NSURLRequest *uploadRequest;
@property (nonatomic, copy) void (^onCompletion)(NSData *data);
@property BOOL log;
-(void)downloadDataFromURL:(NSURL *)url withCompletionHandler:(void (^)(NSData *data))completionHandler;
-(void)uploadDataWithRequest:(NSURLRequest*)request withCompletionHandler:(void (^)(NSData *data))completionHandler;
@end
ConnectionManager.m
#import "ConnectionManager.h"
@implementation ConnectionManager
-(void)uploadDataWithRequest:(NSURLRequest*)request withCompletionHandler:(void (^)(NSData *data))completionHandler{
// Instantiate a session configuration object.
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Configure Session Configuration
[configuration setAllowsCellularAccess:YES];
// Instantiate a session object.
NSURLSession *session=[NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
// Assign request for later call
self.uploadRequest = request;
// Create an upload task object to perform the data uploading.
NSURLSessionUploadTask *task = [session uploadTaskWithRequest:self.uploadRequest fromData:nil];
// Assign completion handler
self.onCompletion = completionHandler;
// Inititate data
self.receivedData = [[NSMutableData alloc] init];
// Resume the task.
[task resume];
}
-(void)downloadDataFromURL:(NSURL *)url withCompletionHandler:(void (^)(NSData *data))completionHandler{
// Instantiate a session configuration object.
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Configure Session Configuration
[configuration setAllowsCellularAccess:YES];
// Instantiate a session object.
NSURLSession *session=[NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
// Assign url for later call
self.url = url;
// Create a data task object to perform the data downloading.
NSURLSessionDataTask *task = [session dataTaskWithURL:self.url];
// Assign completion handler
self.onCompletion = completionHandler;
// Inititate data
self.receivedData = [[NSMutableData alloc] init];
// Resume the task.
[task resume];
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.receivedData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error) {
if (error.code == -1003 || error.code == -1009) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"Unable to connect to the server. Please check your internet connection and try again!" delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:@"retry",nil];
[alert showWithCompletion:^(UIAlertView *alertView, NSInteger buttonIndex) {
if (buttonIndex==1) {
// Retry
if (self.url) {
NSURLSessionDataTask *retryTask = [session dataTaskWithURL:self.url];
[retryTask resume];
}else{
NSURLSessionUploadTask *task = [session uploadTaskWithRequest:self.uploadRequest fromData:nil];
[task resume];
}
}else{
self.onCompletion(nil);
}
}];
}];
}else{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"An unkown error occured! Please try again later, thanks for your patience." delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:@"retry",nil];
[alert show];
CLS_LOG(@"Error details: %@",error);
self.onCompletion(nil);
}];
}
}
else {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.onCompletion(self.receivedData);
}];
}
}
@end
这是我用来调用它的一段代码:
-(void)loadDataFromServer{
NSString *URLString = [NSString stringWithFormat:@"%@get_people_number?access_token=%@", global.baseURL, global.accessToken];
URLString = [URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:URLString];
ConnectionManager *connectionManager = [[ConnectionManager alloc] init];
[connectionManager downloadDataFromURL:url withCompletionHandler:^(NSData *data) {
if (data != nil) {
// Convert the returned data into an array.
NSError *error;
NSDictionary *number = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (error != nil) {
CLS_LOG(@"Error: %@, Response:%@",error,[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
}
else{
[_mapView updateUIWithData:[number objectForKey:@"number"]];
}
}
}];
}
我在使用 Instruments 时发现,即使在从服务器获取数据并调用完成处理程序之后,所有 ConnectionManager 类型的对象都是持久的。
我试图将完成处理程序 属性 从复制更改为强,但我得到了相同的结果。将其更改为 weak 会导致崩溃并且永远不会被调用。
请有人指导我正确的方法。
经过大量研究,我发现 URL Session 对象仍然存在。
基于Apple Documentation Life Cycle of URL Session有两种情况:
使用系统提供的委托(通过不设置委托),这里系统将负责使 NSURLSession 对象无效。
使用自定义委托。当您不再需要会话时,通过调用 invalidateAndCancel(以取消未完成的任务)或 finishTasksAndInvalidate(以允许未完成的任务在使对象失效之前完成)来使其无效。
为了修复上面的代码,我只更改了委托方法 "didCompleteWithError",如下所示:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error) {
if (error.code == -1003 || error.code == -1009) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"Unable to connect to the server. Please check your internet connection and try again!" delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:@"retry",nil];
[alert showWithCompletion:^(UIAlertView *alertView, NSInteger buttonIndex) {
if (buttonIndex==1) {
// Retry
if (self.url) {
NSURLSessionDataTask *retryTask = [session dataTaskWithURL:self.url];
[retryTask resume];
}else{
NSURLSessionUploadTask *task = [session uploadTaskWithRequest:self.uploadRequest fromData:nil];
[task resume];
}
}else{
[session finishTasksAndInvalidate];
self.onCompletion(nil);
}
}];
}];
}else{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"An unkown error occured! Please try again later, thanks for your patience." delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:@"retry",nil];
[alert show];
[session finishTasksAndInvalidate];
self.onCompletion(nil);
}];
}
}
else {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[session finishTasksAndInvalidate];
self.onCompletion(self.receivedData);
}];
}
}