Webサービスを使用するアプリを構築しており、そのWebサービスから情報を取得するには、NSURLSession
とNSURLSessionDataTask
を使用します。
現在、メモリテスト段階にあり、NSURLSession
がメモリリークを引き起こしていることがわかりました。
これがすべてのリークではありません。私が写真に収めることができたのはそれだけです。
以下は、NSURLSession
を設定し、Webサービスに情報を要求する方法です。
#pragma mark - Getter Methods
- (NSURLSessionConfiguration *)sessionConfiguration
{
if (_sessionConfiguration == nil)
{
_sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
[_sessionConfiguration setHTTPAdditionalHeaders:@{@"Accept": @"application/json"}];
_sessionConfiguration.timeoutIntervalForRequest = 60.0;
_sessionConfiguration.timeoutIntervalForResource = 120.0;
_sessionConfiguration.HTTPMaximumConnectionsPerHost = 1;
}
return _sessionConfiguration;
}
- (NSURLSession *)session
{
if (_session == nil)
{
_session = [NSURLSession
sessionWithConfiguration:self.sessionConfiguration
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
}
return _session;
}
#pragma mark -
#pragma mark - Data Task
- (void)photoDataTaskWithRequest:(NSURLRequest *)theRequest
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Photo Request Data Task Set");
#endif
// Remove existing data, if any
if (_photoData)
{
[self setPhotoData:nil];
}
self.photoDataTask = [self.session dataTaskWithRequest:theRequest];
[self.photoDataTask resume];
}
#pragma mark -
#pragma mark - Session
- (void)beginPhotoRequestWithReference:(NSString *)aReference
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Fetching Photo Data...");
#endif
_photoReference = aReference;
NSString * serviceURLString = [[NSString alloc] initWithFormat:@"%@/json?photoreference=%@", PhotoRequestBaseAPIURL, self.photoReference];
NSString * encodedServiceURLString = [serviceURLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
serviceURLString = nil;
NSURL * serviceURL = [[NSURL alloc] initWithString:encodedServiceURLString];
encodedServiceURLString = nil;
NSURLRequest * request = [[NSURLRequest alloc] initWithURL:serviceURL];
[self photoDataTaskWithRequest:request];
serviceURL = nil;
request = nil;
}
- (void)cleanupSession
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Session Cleaned Up");
#endif
[self setPhotoData:nil];
[self setPhotoDataTask:nil];
[self setSession:nil];
}
- (void)endSessionAndCancelTasks
{
if (_session)
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Session Ended and Tasks Cancelled");
#endif
[self.session invalidateAndCancel];
[self cleanupSession];
}
}
#pragma mark -
#pragma mark - NSURLSession Delegate Methods
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Session Completed");
#endif
if (error)
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Photo Request Fetch: %@", [error description]);
#endif
[self endSessionAndCancelTasks];
switch (error.code)
{
case NSURLErrorTimedOut:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:@"RequestTimedOut" object:self];
}
break;
case NSURLErrorCancelled:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:@"RequestCancelled" object:self];
}
break;
case NSURLErrorNotConnectedToInternet:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:@"NotConnectedToInternet" object:self];
}
break;
case NSURLErrorNetworkConnectionLost:
{
// Post notification
[[NSNotificationCenter defaultCenter] postNotificationName:@"NetworkConnectionLost" object:self];
}
break;
default:
{
}
break;
}
}
else {
if ([task isEqual:_photoDataTask])
{
[self parseData:self.photoData fromTask:task];
}
}
}
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
if (error)
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Session Invalidation: %@", [error description]);
#endif
}
if ([session isEqual:_session])
{
[self endSessionAndCancelTasks];
}
}
#pragma mark -
#pragma mark - NSURLSessionDataTask Delegate Methods
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Received Data");
#endif
if ([dataTask isEqual:_photoDataTask])
{
[self.photoData appendData:data];
}
}
#pragma mark -
質問:NSURLSession
がこれらのメモリリークを引き起こしているのはなぜですか?終了したら、NSURLSession
を無効にします。また、不要なプロパティを解放し、セッションをnilに設定します(void)cleanupSession&-(void)endSessionAndCancelTasksを参照)。
---(その他の情報:セッションが完了して「クリーンアップ」された後、メモリリークが発生します。時々、私がUIViewController
をポップした後にも発生します。しかし、すべてのカスタム(GPPhotoRequestおよびGPSearch)オブジェクトとUIViewControllerの割り当てが解除されています(NSLogを追加して確認しました)。
多くのコードに投稿しないようにしましたが、ほとんどのコードを確認する必要があると感じました。
さらに情報が必要な場合はお知らせください。ヘルプは大歓迎です。
RLローディングシステムプログラミングガイド を読み直した後、NSURLSession
プロパティをnilに設定するのが早すぎたことがわかりました。
代わりに、デリゲートメッセージURLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
を受信した後、NSURLSession
プロパティをnilに設定する必要があります。これは理にかなっています。幸いなことに、それは小さな間違いでした。
例えば。
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
if (error)
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Session Invalidation: %@", [error description]);
#endif
}
if ([session isEqual:_session])
{
[self cleanupSession];
}
}
NSURLSessionに切り替えたときに、これと同じ「リーク」のメモリ管理の問題が発生しました。私にとって、セッションを作成し、dataTaskを再開/開始した後、セッション自体をクリーンアップする(つまり、割り当てられたメモリを解放する)ようにセッションに指示することはありませんでした。
// Ending my request method with only the following line causes memory leaks
[dataTask resume];
// Adding this line fixed my memory management issues
[session finishTasksAndInvalidate];
docs から:
最後のタスクが終了し、セッションが最後のデリゲート呼び出しを行うと、デリゲートオブジェクトとコールバックオブジェクトへの参照が切断されます。
セッションをクリーンアップすると、Instrumentsを介して報告されているメモリリークが修正されました。
同じ問題がありました。 @Jonathanの答えは意味がありませんでした-私のアプリはまだメモリリークを起こしていました。 URLSession:didBecomeInvalidWithError:
デリゲートメソッドでセッションプロパティをnilに設定すると、問題が発生することがわかりました。 RLローディングシステムプログラミングガイド を詳しく調べてみました。それは言う
セッションを無効にした後、すべての未処理のタスクがキャンセルまたは終了すると、セッションはデリゲートにURLSession:didBecomeInvalidWithError:メッセージを送信します。そのデリゲートメソッドが戻ると、セッションはその強力な参照をデリゲートに破棄します。
デリゲートメソッドは空白のままにしました。しかし、無効化されたsession
プロパティにはまだポインタがあります。いつnil
に設定する必要がありますか?このプロパティを弱く設定しました
// .h-file
@interface MyClass : NSObject <NSURLSessionDelegate>
{
__weak NSURLSession *_session;
}
// .m-file
- (NSURLSessionTask *)taskForRequest:(NSURLRequest *)request withCompletionHandler:(void(^)(NSData *,NSURLResponse *,NSError *))handler
{
if(!_session)
[self initSession];
//...
}
アプリはメモリリークを停止しました。
ここで私の答えを参照してください: https://stackoverflow.com/a/53428913/4437636
このリークは私が見たものと同じであり、プロキシを介してネットワークトラフィックを実行している場合にのみ発生すると思います。私のコードは問題ありませんでしたが、Apple APIの内部バグがリークの原因であることが判明しました。