2014-03-05 79 views
1

我開始developp iPhone應用程序,我需要對NSURLSession以及如何妥善管理自己的數據下載和解析一些建議。意見,以妥善管理NSURLSession

我只是完成校正在nsurlsession下載我的數據錯誤,但對於我發現怎麼難以理解這些asynchronus要求,我覺得我的解決方案是不是真的好... 還下載錯誤出現與2種不同的解決方案下載,這讓我覺得我忘了做點什麼...

在我的項目中,我下載不同的xml文件(和某些有圖片的zip文件),我需要解析之前顯示他們的信息。這些信息可以快速更改,所以如果我再次加載我的頁面,我想再次下載它們。 我正在尋找一種簡單的方法來以相同的方式管理所有的下載,就像我不必寫很多代碼一樣。

,我發現這個project第一。

就這樣,我不得不使用這些代碼來管理下載:

NSString *downloadUrl = @"https://www.url.com"; 

     NSURL *location = [NSURL URLWithString:downloadUrl]; 
     // DownloadManager is my version of the CTSessionOperation of the github project 
     DownloadManager *operation = [DownloadManager new]; 
     operation.downloadUrl = downloadUrl; 
     operation.completionAction = ^(NSURL *xmlUrl, BOOL success){ 
      dispatch_async(dispatch_get_main_queue(), ^{ 
       if (success){ 
        regions = [[TeamChoiceManager sharedManager] parseRegions:[NSData dataWithContentsOfURL:location]]; 
        [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:Nil waitUntilDone:YES]; 
       } 
      }); 
     }; 
     operation.isBackground = YES; 

     [operation enqueueOperation]; 

此代碼工作完美,我第一次下載。但是如果我嘗試再次啓動下載,則不會啓動下載(因此,沒有錯誤,只需下載一次代碼即可)。

我通過修改CTSessionOperation/DownloadManager中的metod (NSURLSession *)session來更正此錯誤。我把評論的「dispatch_once」,使其工作,但我不認爲這是很好的解決方案......

我嘗試導致了同樣的錯誤的其他解決方案至極。我管理的下載與此代碼:

 NSString *regionsUrl= @"url"; 

      NSURLSessionConfiguration *sessionConfig = 
      [NSURLSessionConfiguration defaultSessionConfiguration]; 
// My solution to the bug   
/*NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration 
                    backgroundSessionConfiguration:[NSString stringWithFormat:@"com.captech.mysupersession.BackgroundSession%d",numBGSession]]; */ 
      // numBGSession++; this is a static NSInteger 


NSURLSession *session = 
     [NSURLSession sessionWithConfiguration:backgroundConfiguration 
             delegate:teamChoiceDetailViewController 
           delegateQueue:nil]; 

     NSURLSessionDownloadTask *sessDLTask = 
     [session downloadTaskWithURL:[NSURL URLWithString:regionsUrl]]; 

     [sessDLTask resume]; 

,並在委託:

-(void)URLSession:(NSURLSession *)session 
    downloadTask:(NSURLSessionDownloadTask *)downloadTask 
didFinishDownloadingToURL:(NSURL *)location 
{ 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     self.regions = [[TeamChoiceManager sharedManager] parseRegions:[NSData dataWithContentsOfURL:location]]; 
     [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:Nil waitUntilDone:YES]; 
    }); 
} 

這個解決方案,我避免故障通過創建每次我嘗試下載時間自定義NSURLSessionConfiguration

所以在這裏我們去。我對這兩種解決方案非常混淆。我不知道他們是否來管理下載正確的方式,我不認爲我正確地糾正了這一錯誤,我一定錯過了與NSURLSession邏輯的東西。

你有什麼建議來改善這些解決方案,或者你看到哪一個比另一個好得多?

回答

0

一對夫婦的意見:

  1. 如果你使用的背景NSURLSession,要知道,這可能是比標準NSURLSession慢。而且,如果你做了很多下載的,您的會話可能有一些以前的請求的積壓,所以當你回來的時候,它似乎沒有做任何事情(而事實上,它很可能只是忙於完成先前的任務請求正嘗試更新不存在或不再可見的表視圖實例)。這可以解釋爲什麼創建一個新會話看起來可以正常工作(因爲新會話不會將新任務排在之前排入隊列的任務之後,而是並行運行)。

    我建議:

    • 恢復dispatch_once邏輯(因爲你的直覺這裏是對的,你絕對不希望被做了一堆會話,而舊的在那裏運行);並且

    • 當您返回到此視圖控制器時,請在啓動新控制器之前取消所有待處理的請求。

  2. 您最初的嘗試,使用completionAction,存在問題,因爲:

    • 您保留視圖控制器。因此,而不是:

      operation.completionAction = ^(NSURL *xmlUrl, BOOL success){ 
          dispatch_async(dispatch_get_main_queue(), ^{ 
           if (success){ 
            regions = [[TeamChoiceManager sharedManager] parseRegions:[NSData dataWithContentsOfURL:location]]; 
            [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:Nil waitUntilDone:YES]; 
           } 
          }); 
      }; 
      

      你可能想:

      typeof(self) __weak weakSelf = self; 
      operation.completionAction = ^(NSURL *xmlUrl, BOOL success){ 
          dispatch_async(dispatch_get_main_queue(), ^{ 
           if (success){ 
            regions = [[TeamChoiceManager sharedManager] parseRegions:[NSData dataWithContentsOfURL:location]]; 
            [weakSelf.tableView reloadData]; // don't need that `performSelectorOnMainThread` call 
           } 
          }); 
      }; 
      
    • 你還試圖一起使用這completionAction與背景NSURLSession。請注意,如果應用程序被終止,而下載將在後臺完成,則此完成塊將丟失(破壞進行後臺會話的目的)。

      如果您確實需要使用後臺會話,則應將此completionAction中的任何邏輯移入下載委託方法本身。您不能將此completionAction與後臺會話結合使用,並希望它能在應用程序終止後繼續運行(即使NSURLSessionDownloadTask對象也會)。


下面是我原來的答案,在我猛烈抨擊CTSessionOperation類,因爲(a)存在採用完工塊連同背景NSURLSession(因爲這些塊認知失調即使下載將繼續,如果應用程序被終止,它將會丟失);和(b)它將一個非併發的NSOperation用於異步任務,擊敗了使用基於NSOperation的實現的許多關鍵好處。雖然這兩點都是有問題的,但我認爲這些問題僅次於我上面的觀點,但我會將其留在這裏作爲參考。

原來的答覆:

你說的意思是「完美的作品,我第一次下載,但如果我嘗試[不]再次啓動」?你說的應用程序被終止,下載仍在後臺發生?原來的CTSessionOperation似乎無法正確處理,因爲它試圖使用NSOperation和完成塊的標準技巧,但所有這些都與NSURLSession背景會話不兼容,即使在應用程序終止後,它們也會繼續進行。一旦應用程序終止,NSURLSession後臺請求繼續進行,但所有操作和完成塊將完全丟失。我認爲這CTSessionOperation已經錯過了這裏的標誌。即使在應用程序終止後,您仍然無法享受後臺會話的豐富性,並期望保留您在首次啓動後臺下載任務時創建的操作和完成塊。您必須堅持使用NSURLSessionDownloadTask對象並僅使用委託方法;沒有完成塊或操作。

儘管我的東西似乎是CTSessionOperation結構性缺陷的批評,註釋掉dispatch_once,創造獨特的背景會話或創建標準的前景會議的企圖補救肯定是如果你需要後臺下載正確的方法。如果有的話,如果你正在嘗試補救在應用終止之後CTSessionOperation沒有處理後臺會話的問題,那麼這實際上是朝着錯誤的方向發展的。

所以,問題是你是否真的想享受背景NSURLSession的全部功能(在這種情況下,你必須放棄的CTSessionOperation完成模塊和操作模式),或者你是否確定與非背景NSURLSession當應用程序自身終止時,下載被終止,而您只希望能夠在用戶再次啓動應用程序時從頭開始重新啓動它們。

+0

感謝您的回答。通過工作非常好,我的意思是例如,如果我在tableViewController的viewDidLoad中使用此代碼,我將第一次有我的tableView充滿數據,但如果我在其他視圖中再次加載tableView,它將爲空(當enqueueOperation再次啓動時,它不會啓動下載)。 –

+0

我使用NSURLSession只是因爲我不想在下載時凍結應用程序。所以我想第二個解決方案對我來說應該夠了。但我仍然覺得奇怪的是,創建一個不同的NSURLSessionConfiguration來糾正錯誤,這對我來說似乎不合邏輯。 –

+0

@ user3282167我同意它的好奇,創建一個新的會話的作品。我可以想象的是,通過重新使用舊會話,新請求可能排在最初嘗試未完成的下載之後。所以我懷疑正確的答案是取消先前的請求,而不是創建一個新的會話與其他一整套請求。看到我修改後的答案。 – Rob

1

編輯:如果有人正在尋找一個更通用的解決方案來輕鬆正確地管理網絡交易,你可以檢查AFNetworking哪些做了一些魔術(加上你可以找到很多教程)。

我放棄了開發一些適合每種情況的工作(特別是如果它是後臺會話,因爲我不需要它)。 最後,我剛剛創建了一個靜態會話的類,管理下載的代理,以及我在didFinishDownloadingToURL中使用的塊來管理下載的數據。 當然不完美,但足夠好的時刻。

typedef void (^CTCompletionBlock)(NSURL *location, NSError* err); 

    @interface DownloadManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate> 

    @property (nonatomic, retain) NSURLSessionDownloadTask *dlTask; 
    @property (nonatomic, retain) NSString *location; 
    @property (strong) CTCompletionBlock afterDLBlock; 

    + (DownloadManager *)sharedManager; 
    -(void)downloadTask; 
    @end 


    // 
    // DownloadManager.m 
    // MVCTest 
    // 
    // Created by 
    // 

    #import "DownloadManager.h" 
    #import "AppDelegate.h" 


    static DownloadManager *instance = nil; 
    static NSURLSession *session = nil; 

    @implementation DownloadManager 

    + (DownloadManager *)sharedManager { 
     if (instance == nil) { 
      //session = [DownloadManager sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil]; 
      instance = [DownloadManager new]; 
     } 
     return instance; 
    } 

    + (id)new 
    { 
     return [[self alloc] init]; 
    } 
    - (id)init{ 
     self = [super init]; 
     session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] 
     return self; 
    } 

    -(void)downloadTask{ 
     self.dlTask = [session downloadTaskWithURL:[NSURL URLWithString:self.location]]; 
     [self.dlTask resume]; 
    } 

    #pragma mark - NSURLSessionDownloadDelegate 
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { 

NSError *error; 
     if (self.afterDLBlock){ 
      self.afterDLBlock(location, error); 
     } 

    } 

//i still have to manage the delegate... 
    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {} 

    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {} 

    #pragma mark - NSURLSessionTaskDelegate 

    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {} 

    -(void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error{} 

    #pragma mark - NSURLSessionDelegate 

    - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {} 

    @end 

這一類,我只需要編寫代碼來管理數據下載:

typeof(self) __weak weakSelf = self; 
NSString *downloadUrl = @"http://www.whatyouwant.com"; 
[DownloadManager sharedManager].location = downloadUrl; 
[DownloadManager sharedManager].afterDLBlock = ^(NSURL *location, NSError *error) { 
     weakSelf.regions = [[TeamChoiceManager sharedManager] parseRegions:[NSData dataWithContentsOfURL:location]]; 

     dispatch_sync(dispatch_get_main_queue(), ^{ 
      [weakSelf.activityViewIndicator stopAnimating]; 
      [weakSelf.tableView reloadData]; 
     }); 
}; 
[[DownloadManager sharedManager] downloadTask]; 

我仍然不得不管理錯誤和委託,但與taht解決方案,我會不會有很多代碼寫下載一些數據