[iOS] Background 상태에서 네트워크 서버로 파일 업로드 하기

지난 iOS 포스팅에서는 iOS App State 에 대해 알아봤다. 이어서 Background 상태에서 코드를 수행시키는 방법에 대해 확인해보자.

지난 포스팅에서 알아봤듯이, iOS 4.0 부터 Multitasking 이 지원되며 앱의 필요에 따라 Background에서도 코드 수행이 가능하다. Multitasking에 관한 상세 정보는 다음을 참고하자. (Background Execution and Multitasking)

우선, 해결해야 할 상황을 가정하자. (Dropbox API 관련 포스팅은 별도로 할 계획이다. 여기선 단순히 파일을 전송하는 역할로만 인지하자)

  • Dropbox API 를 이용하여 iOS 디바이스의 파일을 Dropbox 서버에 전송한다.
  • 파일이 전송되는 중에 사용자는 다른 앱을 구동시킬 수 있다.
  • 사용자가 다른 앱을 사용 중이더라도 파일 전송은 계속되어야 한다.

앱이 Background 에 진입했을 때부터 파일 전송이 시작되는 것이 아니라, 파일 전송 중에 앱의 상태가 Background 로 바뀔 수 있고 이 경우에도 파일 전송은 계속되어야 한다.

일반적인 대부분의 앱은 사용자가 홈 버튼을 누르면 Background 상태에 잠시 머물렀다가 System에 의하여 Suspended 상태로 전환된다. 문제 상황의 경우 앱이 Suspended 상태로 변화되는 것을 지연시켜야 하며, 지연된 시간 동안 특정 코드가 계속 수행되어야 한다.

시간을 지연시킨다고 하더라도 무제한 시간동안 Background에서 앱이 실행되지는 않는다. 본인이 iOS Simulator (iOS 4.3)에서 로그를 남겨본 결과 대략 10분정도시간이 지연됨을 확인할 수 있었다. (UIApplication의 backgroundTimeRemaining 프로퍼티로 확인 가능, 실제 디바이스와 메모리 상황에 따라 지연되는 시간은 달라질 수 있을 것 같다)

앱이 Background에서 Suspended 상태로 넘어가는 시간을 지연시키기 위해서는 UIApplication의 beginBackgroundTaskWithExpirationHandler 를 호출하여 System에게 알려줘야 한다. 이 메소드는 UIApplication의 endBackgroundTask: 메소드와 짝을 이뤄야 하며 이를 통해 System에게 Background 작업이 종료되었음을 정상적으로 알릴 수 있다.

UIApplication의 beginBackgroundTaskWithExpirationHandler 는 UIBackgroundTaskIdentifier를 반환한다. 이는 다수의 Background 작업이 수행 가능하므로,  각 작업마다 부여된 ID 이다. 따라서 endBackgroundTask 메소드에도 UIBackgroundTaskIdentifier 를 인자로 넘겨줘 어떤 Background 작업이 끝났는지 알려준다.

이제 실제 코드와 콘솔 로그를 보면서 확인해보자.

우선 Background 작업을 추가하지 않은 상태에서, 앱이 Background 상태로 진입시 파일 업로드가 어떻게 되는지 로그를 통해 확인해보자. (파일 업로드 완료까지 경과 시간은 대략 20초이다. 20초 동안 사용자는 충분히 다른 앱을 실행시킬 수 있다.)

Upload button 을 누르고 파일이 업로드 되다가 홈버튼을 누른 시점에서 (그림 중 밑줄 부분, ApplicaitonDidEnterBackground) 파일 업로드가 멈춘다. 10초 정도 후에 iOS Simulator 에서 앱을 클릭하여 재개하면 앱이 Active 상태(ApplicaitonDidBecomeActive)로 전환되고 이 때부터 파일 업로드가 다시 시작되고 정상적으로 완료된다.

이제 Background 작업을 적용시켜보자.

-(IBAction)didPressUploadButton:(id)sender{
    NSLog(@"Upload Button Pressed");
    // backgroune execution 은 iOS 4 이후 지원 (특정 디바이스 미지원),  확인 작업 필요
    UIDevice* device = [UIDevice currentDevice];
    BOOL backgroundSupported = NO;
    if ([device respondsToSelector:@selector(isMultitaskingSupported)]){
        backgroundSupported = device.multitaskingSupported;
    }
    // background 작업을 지원하면
    if(backgroundSupported){
        // System 에 background 작업이 필요함을 알림. 작업의 id 반환
        taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
             NSLog(@"Backgrouund task ran out of time and was terminated");
             [UIApplication sharedApplication] endBackgroundTask:taskId];
        }];
    }

    // dropbox file upload api.
    [[self restClient] uploadFile:filename toPath:destDir
            withParentRev:nil fromPath:localPath];
}

위 메소드는 upload 버튼을 클릭했을 때 수행되는 메소드이다. 처음에 해당 디바이스가 Multitasking을 지원하는지 확인하고, 지원할 경우에만 UIApplication의beginBackgroundTaskWithExpirationHandler를 호출한다. taskId는 header 파일에서 정의한 필드로 UIBackgroundTaskIdentifier 타입이다.

Dropbox api 는 대부분 비동기로 호출이 되고 완료가 되면 callback 함수가 불려진다. 이 callback 함수에서 endBackgroundTask 를 호출하기 위해 UIBackgroundTaskIdentifier 를 헤더파일에서 선언하였다.

우리가 Background 에서도 작업이 진행되길 바라는 것은 uploadFile 코드이다. -상황에 따라 자신의 앱에 맞는 코드가 들어가면 된다. – 따라서 이 코드를 Background에서도 진행시키기 위해 코드 실행 이전에 UIApplication의 beginBackgroundTaskWithExpirationHandler를 호출한다.

앞에서도 언급했듯이 endBackgroundTask 가 쌍으로 호출되어져야 한다. 이제 endBackgroundTask가 호출되어지는 구문을 살펴보자.

// 파일 업로드가 정상적으로 되었을 때 불려지는 callback 함수
- (void)restClient:(DBRestClient*)client uploadedFile:(NSString*)destPath
from:(NSString*)srcPath metadata:(DBMetadata*)metadata {
NSLog(@"File uploaded successfully to path: %@", metadata.path);
    // background 작업의 종료를 알린다.
    [[UIApplication sharedApplication] endBackgroundTask:taskId];
}
// 파일 업로드가 실패했을 경우 불려지는 callback 함수
- (void)restClient:(DBRestClient*)client uploadFileFailedWithError:(NSError*)error {
NSLog(@"File upload failed with error - %@", error);
    // Background 작업 종료를 알린다.
    [[UIApplication sharedApplication] endBackgroundTask:taskId];
}

Dropbox api 의 특성 때문에 두 callback 메소드에서 [[UIApplication sharedApplication] endBackgroundTask:taskId]; 구문을 호출했다. 각자 자신이 Background 에서 유지되길 원하는 작업의 종료 시점에 위 구문을 호출하면 된다.

이렇게 구현이 되면 파일 업로드가 Foreground 에서만 수행되는 경우, 업로드 중 Background로 전환되고 머무는 경우, 업로드 중 Background 로 전환되었다가 다시 Foreground 로 넘어와서 업로드가 종료되는 경우 모두 다 정상적으로 동작하게 된다. 이 중 마지막 경우의 로그를 살펴보자.

위 로그를 보면 앱이 Background 상태로 진입을 하더라도 정상적으로 파일 업로드가 진행이 됨을 알 수 있다.

실제로 구현해야 할 코드는 그리 많지 않다. 그러나 메모리 관리를 유의해야 한다. System은 언제든 Background에 있는 앱을 Suspended 상태를 만들 수 있고 메모리가 부족하면 Suspended 상태의 앱을 강제 종료 시킬 수 있다.

Background 작업을 하더라도 메모리를 최소한으로 유지하도록 관리해야 한다. 그래야 System에 의해 종료될 확률이 줄어든다.

위와 같은 방법 말고 UIApplication의 – (void)applicationDidEnterBackground:(UIApplication *)application 메소드에서 직접 구현하는 방법이 있다. 이 경우는 실제로 Background 상태에 진입했을 때 작업을 시작해야 되는 경우에 적합한 것 같다. 이 메소드를 이용할 경우 UIApplication Delegate 의 상태 전환 관련 메소드를 이용하여 메모리 제거,재생성 등이 적절히 구현되어야 한다. (상세 정보는 다음을 참고하라 : Background Execution and Multitasking)

이 경우는 아직 본인이 경험하지 못해서 상세하게 기술하지 못하지만, 기회가 되어 접하게 되면 포스팅을 통해 다루도록 하겠다.

Advertisements
Leave a comment

5 Comments

  1. kjs

     /  February 3, 2013

    매우감사합니다

    Reply
  2. 김동진

     /  February 20, 2014

    좋은 내용 잘 봤습니다.
    BACKGROUND로 어느 정도 파일을 전송할 수 있으니,.. ^^
    그런데, 사이즈가 큰 파일들도 모두 보낼때까지 백그라운드로 돌릴 수 있나요?
    시간 및 데이터의 제한인지? 아니면 요청건수(1건)의 제한인지? 혹시 아시면 답변 부탁드립니다.

    Reply
    • 파일 사이즈나 시간 제한이 아닌 OS단에서 디바이스의 상태(메모리 등)에 따라 제한하는것으로 알고 있습니다. UIApplication에 백그라운드 남은 시간정보가 담긴 프로퍼티가 있는데 그 값이 좋은 정보가 될 것 같습니다.

      걱정돨 만큼 큰 사이즈라면 Local Notification으로 성공/실패 여부를 알려주는 것도 방법인것 같네요. 실패하면 포그라운드에서 재시도할 수 있도록 말이죠.

      몇가지 특정상황(위치,오디오,음악,뉴스스탠드 다운로드 등)에서 긴 시간을 보장하긴 하는데 그 쪽을 보시면 혹 뭔가 꼼수가 있을지도 모르겠네요

      일단 직접 구현해보시고 적절한 대책을 찾는게 좋은 방법일 것 같습니다. 생각보다 괜찮을지도 모르잖아요 ㅎ

      Reply
  3. 김동진

     /  February 25, 2014

    감사합니다. 여러가지 방법이 있군요. 그럼 수고하세요.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: