web-dev-qa-db-ja.com

NSURLSessionメモリリークは、IOS

Webサービスを使用するアプリを構築しており、そのWebサービスから情報を取得するには、NSURLSessionNSURLSessionDataTaskを使用します。

現在、メモリテスト段階にあり、NSURLSessionがメモリリークを引き起こしていることがわかりました。

This is not all of the leaks. It is all I could fit in the picture.

これがすべてのリークではありません。私が写真に収めることができたのはそれだけです。

以下は、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)endSessionAndCancelTask​​sを参照)。

---(その他の情報:セッションが完了して「クリーンアップ」された後、メモリリークが発生します。時々、私がUIViewControllerをポップした後にも発生します。しかし、すべてのカスタム(GPPhotoRequestおよびGPSearch)オブジェクトとUIViewControllerの割り当てが解除されています(NSLogを追加して確認しました)。

多くのコードに投稿しないようにしましたが、ほとんどのコードを確認する必要があると感じました。

さらに情報が必要な場合はお知らせください。ヘルプは大歓迎です。

22
Jonathan

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];
    }
}
4
Jonathan

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を介して報告されているメモリリークが修正されました。

44
John Erck

同じ問題がありました。 @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];
  //...
}

アプリはメモリリークを停止しました。

1
Astoria

ここで私の答えを参照してください: https://stackoverflow.com/a/53428913/4437636

このリークは私が見たものと同じであり、プロキシを介してネットワークトラフィックを実行している場合にのみ発生すると思います。私のコードは問題ありませんでしたが、Apple APIの内部バグがリークの原因であることが判明しました。

1
CPR