IT이야기

AF네트워킹 및 백그라운드 전송

cyworld 2021. 10. 16. 10:40
반응형

AF네트워킹 및 백그라운드 전송


새로운 iOS 7 NSURLSession백그라운드 전송 기능과 AFNetworking (버전 2 및 3) 을 활용하는 방법에 대해 약간 혼란 스럽습니다 .

나는 WWDC 705 - What’s New in Foundation Networking세션을 보았고 앱이 종료되거나 충돌 한 후에도 계속되는 백그라운드 다운로드를 시연했습니다.

이것은 새로운 API application:handleEventsForBackgroundURLSession:completionHandler:와 세션의 대리자가 결국 콜백을 받고 작업을 완료할 수 있다는 사실을 사용하여 수행됩니다.

그래서 AFNetworking(가능한 경우)과 함께 사용하여 백그라운드에서 계속 다운로드하는 방법이 궁금합니다.

문제는 AFNetworking이 블록 기반 API를 사용하여 모든 요청을 편리하게 수행하지만 앱이 종료되거나 충돌하면 해당 블록도 사라집니다. 그럼 어떻게 해야 임무를 완수할 수 있을까요?

아니면 내가 여기서 뭔가를 놓치고 있는건지...

내가 의미하는 바를 설명하겠습니다.

예를 들어 내 앱은 사진 메시징 앱 PhotoMessage입니다. 하나의 메시지를 나타내는 개체가 있고 이 개체에 다음과 같은 속성이 있다고 가정해 보겠습니다.

  • state - 사진 다운로드 상태를 설명합니다.
  • resourcePath - 최종 다운로드한 사진 파일의 경로.

따라서 서버에서 새 메시지를 PhotoMessage받으면 개체를 만들고 해당 사진 리소스를 다운로드하기 시작합니다.

PhotoMessage *newPhotoMsg = [[PhotoMessage alloc] initWithInfoFromServer:info];
newPhotoMsg.state = kStateDownloading;

self.photoDownloadTask = [[BGSessionManager sharedManager] downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
    NSURL *filePath = // some file url
    return filePath;
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
    if (!error) {
        // update the PhotoMessage Object
        newPhotoMsg.state = kStateDownloadFinished;
        newPhotoMsg.resourcePath = filePath;
    }
}];

[self.photoDownloadTask resume];   

보시다시피, 나는 PhotoMessage받은 응답에 따라 해당 객체 를 업데이트하기 위해 완료 블록을 사용합니다 .

백그라운드 전송으로 어떻게 할 수 있습니까? 이 완료 블록은 호출되지 않으며 결과적으로 newPhotoMsg.


몇 가지 생각:

  1. URL 로딩 시스템 프로그래밍 가이드iOS 백그라운드 활동 처리 섹션에 설명된 필요한 코딩을 수행했는지 확인해야 합니다 .

    NSURLSessioniOS에서 사용 하는 경우 다운로드가 완료되면 앱이 자동으로 다시 실행됩니다. 앱의 application:handleEventsForBackgroundURLSession:completionHandler:앱 대리자 메서드는 적절한 세션을 다시 만들고, 완료 핸들러를 저장하고, 세션이 세션 대리자의 URLSessionDidFinishEventsForBackgroundURLSession:메서드를 호출할 때 해당 핸들러를 호출하는 역할을 합니다 .

    그 가이드는 당신이 할 수 있는 몇 가지 예를 보여줍니다. 솔직히 WWDC 2013 비디오 What's New in Foundation Networking의 후반부에서 논의된 코드 샘플 이 훨씬 더 명확하다고 생각합니다.

  2. 의 기본 구현은 AFURLSessionManager앱이 일시 중단된 경우 백그라운드 세션과 함께 작동합니다(위의 작업을 수행했다고 가정하고 네트워크 작업이 완료되면 블록이 호출됨을 볼 수 있음). 그러나 예상대로 업로드 및 다운로드를 위해 AFURLSessionManager생성하는 메서드에 전달된 작업별 블록 매개변수 NSURLSessionTask는 "앱이 종료되거나 충돌하는 경우" 손실됩니다.

    백그라운드 업로드의 경우 이는 성가신 일입니다(작업 생성 시 지정한 작업 수준 정보 진행 및 완료 블록이 호출되지 않음). 그러나 세션 수준 변환(예: setTaskDidCompleteBlocksetTaskDidSendBodyDataBlock)을 사용하면 제대로 호출됩니다(세션 관리자를 다시 인스턴스화할 때 항상 이러한 블록을 설정한다고 가정).

    결과적으로 이 블록 손실 문제는 실제로 백그라운드 다운로드에서 더 문제가 되지만 솔루션은 매우 유사합니다(작업 기반 블록 매개변수를 사용하지 말고 대신 세션 기반 블록을 사용 setDownloadTaskDidFinishDownloadingBlock).

  3. 대안으로 기본(비백그라운드)을 NSURLSession유지할 수 있지만 작업이 진행되는 동안 사용자가 앱을 떠날 경우 앱에서 업로드를 완료하는 데 약간의 시간을 요청해야 합니다. 예를 들어 를 만들기 전에 다음을 만들 NSURLSessionTask수 있습니다 UIBackgroundTaskIdentifier.

    UIBackgroundTaskIdentifier __block taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) {
        // handle timeout gracefully if you can
    
        [[UIApplication sharedApplication] endBackgroundTask:taskId];
        taskId = UIBackgroundTaskInvalid;
    }];
    

    그러나 네트워크 작업의 완료 블록이 완료되었음을 iOS에 올바르게 알리는지 확인하십시오.

    if (taskId != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:taskId];
        taskId = UIBackgroundTaskInvalid;
    }
    

    이것은 배경만큼 강력하지 않지만 NSURLSession(예: 사용 가능한 시간이 제한됨) 경우에 따라 유용할 수 있습니다.


업데이트:

AFNetworking을 사용하여 백그라운드 다운로드를 수행하는 방법에 대한 실용적인 예를 추가할 생각입니다.

  1. 먼저 백그라운드 관리자를 정의합니다.

    //
    //  BackgroundSessionManager.h
    //
    //  Created by Robert Ryan on 10/11/14.
    //  Copyright (c) 2014 Robert Ryan. All rights reserved.
    //
    
    #import "AFHTTPSessionManager.h"
    
    @interface BackgroundSessionManager : AFHTTPSessionManager
    
    + (instancetype)sharedManager;
    
    @property (nonatomic, copy) void (^savedCompletionHandler)(void);
    
    @end
    

    그리고

    //
    //  BackgroundSessionManager.m
    //
    //  Created by Robert Ryan on 10/11/14.
    //  Copyright (c) 2014 Robert Ryan. All rights reserved.
    //
    
    #import "BackgroundSessionManager.h"
    
    static NSString * const kBackgroundSessionIdentifier = @"com.domain.backgroundsession";
    
    @implementation BackgroundSessionManager
    
    + (instancetype)sharedManager {
        static id sharedMyManager = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedMyManager = [[self alloc] init];
        });
        return sharedMyManager;
    }
    
    - (instancetype)init {
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:kBackgroundSessionIdentifier];
        self = [super initWithSessionConfiguration:configuration];
        if (self) {
            [self configureDownloadFinished];            // when download done, save file
            [self configureBackgroundSessionFinished];   // when entire background session done, call completion handler
            [self configureAuthentication];              // my server uses authentication, so let's handle that; if you don't use authentication challenges, you can remove this
        }
        return self;
    }
    
    - (void)configureDownloadFinished {
        // just save the downloaded file to documents folder using filename from URL
    
        [self setDownloadTaskDidFinishDownloadingBlock:^NSURL *(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location) {
            if ([downloadTask.response isKindOfClass:[NSHTTPURLResponse class]]) {
                NSInteger statusCode = [(NSHTTPURLResponse *)downloadTask.response statusCode];
                if (statusCode != 200) {
                    // handle error here, e.g.
    
                    NSLog(@"%@ failed (statusCode = %ld)", [downloadTask.originalRequest.URL lastPathComponent], statusCode);
                    return nil;
                }
            }
    
            NSString *filename      = [downloadTask.originalRequest.URL lastPathComponent];
            NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
            NSString *path          = [documentsPath stringByAppendingPathComponent:filename];
            return [NSURL fileURLWithPath:path];
        }];
    
        [self setTaskDidCompleteBlock:^(NSURLSession *session, NSURLSessionTask *task, NSError *error) {
            if (error) {
                // handle error here, e.g.,
    
                NSLog(@"%@: %@", [task.originalRequest.URL lastPathComponent], error);
            }
        }];
    }
    
    - (void)configureBackgroundSessionFinished {
        typeof(self) __weak weakSelf = self;
    
        [self setDidFinishEventsForBackgroundURLSessionBlock:^(NSURLSession *session) {
            if (weakSelf.savedCompletionHandler) {
                weakSelf.savedCompletionHandler();
                weakSelf.savedCompletionHandler = nil;
            }
        }];
    }
    
    - (void)configureAuthentication {
        NSURLCredential *myCredential = [NSURLCredential credentialWithUser:@"userid" password:@"password" persistence:NSURLCredentialPersistenceForSession];
    
        [self setTaskDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *credential) {
            if (challenge.previousFailureCount == 0) {
                *credential = myCredential;
                return NSURLSessionAuthChallengeUseCredential;
            } else {
                return NSURLSessionAuthChallengePerformDefaultHandling;
            }
        }];
    }
    
    @end
    
  2. 앱 대리자가 완료 처리기를 저장하는지 확인합니다(필요에 따라 백그라운드 세션 인스턴스화).

    - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
        NSAssert([[BackgroundSessionManager sharedManager].session.configuration.identifier isEqualToString:identifier], @"Identifiers didn't match");
        [BackgroundSessionManager sharedManager].savedCompletionHandler = completionHandler;
    }
    
  3. 그런 다음 다운로드를 시작합니다.

    for (NSString *filename in filenames) {
        NSURL *url = [baseURL URLByAppendingPathComponent:filename];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        [[[BackgroundSessionManager sharedManager] downloadTaskWithRequest:request progress:nil destination:nil completionHandler:nil] resume];
    }
    

    참고로, 백그라운드 세션에서 신뢰할 수 없는 작업 관련 블록은 제공하지 않습니다. (백그라운드 다운로드는 앱이 종료된 후에도 계속 진행되며 이러한 블록은 사라진 지 오래입니다.) 세션 수준에 의존해야 하며 쉽게 재생성 setDownloadTaskDidFinishDownloadingBlock만 가능합니다.

분명히 이것은 간단한 예입니다(단 하나의 백그라운드 세션 개체, URL의 마지막 구성 요소를 파일 이름으로 사용하여 문서 폴더에 파일을 저장하는 것 등). 그러나 패턴을 보여주기를 바랍니다.


콜백이 블록인지 아닌지는 아무런 차이가 없어야 합니다. 를 인스턴스화할 때 AFURLSessionManager로 인스턴스화해야 합니다 NSURLSessionConfiguration backgroundSessionConfiguration:. 또한 setDidFinishEventsForBackgroundURLSessionBlock콜백 블록으로 관리자를 호출 해야 합니다. 여기에서 일반적으로 NSURLSessionDelegate의 메서드에 정의된 코드를 작성해야 합니다 URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session. 이 코드는 앱 대리자의 백그라운드 다운로드 완료 핸들러를 호출해야 합니다.

백그라운드 다운로드 작업에 관한 한 가지 조언 - 포그라운드에서 실행 중일 때도 시간 초과는 무시됩니다. 즉, 응답하지 않는 다운로드에서 "멈춤" 상태가 될 수 있습니다. 이것은 어디에도 문서화되지 않았고 한동안 나를 미치게 만들었습니다. 첫 번째 용의자는 AFNetworking이었지만 NSURLSession을 직접 호출한 후에도 동작은 동일하게 유지되었습니다.

행운을 빕니다!


AFURL세션 관리자

AFURLSessionManager생성하고 관리 NSURLSession규정에 기초하여 대상 NSURLSessionConfiguration객체에 준거 <NSURLSessionTaskDelegate>, <NSURLSessionDataDelegate>, <NSURLSessionDownloadDelegate>, 및 <NSURLSessionDelegate>.

여기 문서에 대한 링크 문서

참조URL : https://stackoverflow.com/questions/21350125/afnetworking-and-background-transfers

반응형