2012-03-13 7 views
2

私たちは大きなファイルをサーバからバックグラウンドで 'ライブラリ'に同期させる必要があります。 NSOperationのサブクラス化は、iOSタスクのマルチスレッド化の最も柔軟な方法であり、それを試みました。したがって、この関数はダウンロードするURLのリストを受け取り、&保存し、同じNSOperationクラスのインスタンスを初期化し、NSOperationキューに一度に1つのファイルしかダウンロードしないようにします。NSURLConnectionでNSOperationの複数のインスタンスを実行しますか?

-(void) LibSyncOperation {  
    // Initialize download list. Download the homepage of some popular websites 
    downloadArray = [[NSArray alloc] initWithObjects:@"www.google.com", 
                @"www.stackoverflow.com", 
                @"www.reddit.com", 
                @"www.facebook.com", nil]; 

    operationQueue = [[[NSOperationQueue alloc]init]autorelease]; 
    [operationQueue setMaxConcurrentOperationCount:1]; // Only download 1 file at a time 
    [operationQueue waitUntilAllOperationsAreFinished]; 

    for (int i = 0; i < [downloadArray count]; i++) { 
     LibSyncOperation *libSyncOperation = [[[LibSyncOperation alloc] initWithURL:[downloadArray objectAtIndex:i]]autorelease]; 
     [operationQueue addOperation:libSyncOperation]; 
    } 
} 
これらのクラスインスタンスはすべて正しく作成され、すべてNSOperationQueueに追加され、実行が開始されます。しかし、問題はダウンロードを開始するときです。最初のファイルはダウンロードされません(デリゲートメソッドでNSURLConnectionを使用)。私は、別のスレッドで見たrunLoopトリックを使用しました。このトリックは、ダウンロードが完了するまで動作を継続できるようにする必要があります。 NSURLConnectionは確立されますが、NSMutableDataオブジェクトへのデータの追加が開始されません。

@synthesize downloadURL, downloadData, downloadPath; 
@synthesize downloadDone, executing, finished; 

/* Function to initialize the NSOperation with the URL to download */ 
- (id)initWithURL:(NSString *)downloadString { 

    if (![super init]) return nil; 

    // Construct the URL to be downloaded 
    downloadURL = [[[NSURL alloc]initWithString:downloadString]autorelease]; 
    downloadData = [[[NSMutableData alloc] init] autorelease]; 

    NSLog(@"downloadURL: %@",[downloadURL path]); 

    // Create the download path 
    downloadPath = [NSString stringWithFormat:@"%@.txt",downloadString]; 
    return self; 
} 

-(void)dealloc { 
    [super dealloc]; 
} 

-(void)main { 

    // Create ARC pool instance for this thread. 
    // NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init]; //--> COMMENTED OUT, MAY BE PART OF ISSUE 

    if (![self isCancelled]) { 

     [self willChangeValueForKey:@"isExecuting"]; 
     executing = YES; 

     NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:downloadURL]; 
     NSLog(@"%s: downloadRequest: %@",__FUNCTION__,downloadURL); 
     NSURLConnection *downloadConnection = [[NSURLConnection alloc] initWithRequest:downloadRequest delegate:self startImmediately:NO]; 

     // This block SHOULD keep the NSOperation from releasing before the download has been finished 
     if (downloadConnection) { 
      NSLog(@"connection established!"); 
      do { 
       [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 
      } while (!downloadDone); 

     } else { 
      NSLog(@"couldn't establish connection for: %@", downloadURL); 

      // Cleanup Operation so next one (if any) can run 
      [self terminateOperation]; 
     } 
      } 
    else { // Operation has been cancelled, clean up 
     [self terminateOperation]; 
    } 

// Release the ARC pool to clean out this thread 
//[pool release]; //--> COMMENTED OUT, MAY BE PART OF ISSUE 
} 

#pragma mark - 
#pragma mark NSURLConnection Delegate methods 
// NSURLConnectionDelegate method: handle the initial connection 
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse*)response { 
    NSLog(@"%s: Received response!", __FUNCTION__); 
} 

// NSURLConnectionDelegate method: handle data being received during connection 
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { 
    [downloadData appendData:data]; 
    NSLog(@"downloaded %d bytes", [data length]); 
} 

// NSURLConnectionDelegate method: What to do once request is completed 
-(void)connectionDidFinishLoading:(NSURLConnection *)connection { 
    NSLog(@"%s: Download finished! File: %@", __FUNCTION__, downloadURL); 
    NSFileManager *fileManager = [NSFileManager defaultManager]; 
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *docDir = [paths objectAtIndex:0]; 
    NSString *targetPath = [docDir stringByAppendingPathComponent:downloadPath]; 
    BOOL isDir; 

    // If target folder path doesn't exist, create it 
    if (![fileManager fileExistsAtPath:[targetPath stringByDeletingLastPathComponent] isDirectory:&isDir]) { 
     NSError *makeDirError = nil; 
     [fileManager createDirectoryAtPath:[targetPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:&makeDirError]; 
     if (makeDirError != nil) { 
      NSLog(@"MAKE DIR ERROR: %@", [makeDirError description]); 
      [self terminateOperation]; 
     } 
    } 

    NSError *saveError = nil; 
    //NSLog(@"downloadData: %@",downloadData); 
    [downloadData writeToFile:targetPath options:NSDataWritingAtomic error:&saveError]; 
    if (saveError != nil) { 
     NSLog(@"Download save failed! Error: %@", [saveError description]); 
     [self terminateOperation]; 
    } 
    else { 
     NSLog(@"file has been saved!: %@", targetPath); 
    } 
    downloadDone = true; 
} 

// NSURLConnectionDelegate method: Handle the connection failing 
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { 
    NSLog(@"%s: File download failed! Error: %@", __FUNCTION__, [error description]); 
    [self terminateOperation]; 
} 

// Function to clean up the variables and mark Operation as finished 
-(void) terminateOperation { 
    [self willChangeValueForKey:@"isFinished"]; 
    [self willChangeValueForKey:@"isExecuting"]; 
    finished = YES; 
    executing = NO; 
    downloadDone = YES; 
    [self didChangeValueForKey:@"isExecuting"]; 
    [self didChangeValueForKey:@"isFinished"]; 
} 

#pragma mark - 
#pragma mark NSOperation state Delegate methods 
// NSOperation state methods 
- (BOOL)isConcurrent { 
    return YES; 
} 
- (BOOL)isExecuting { 
    return executing; 
} 
- (BOOL)isFinished { 
    return finished; 
} 

注:それはあまりにも読めないだった場合、私はあなたを介して見ることができるQUICK GITHUB PROJECT HEREを設定します。ご迷惑をおかけして申し訳ありません。

私はそれがクラス変数の保持/解放と関係があると思われますが、クラスをインスタンス化すると各インスタンスにクラス変数のセットが与えられると思っていたので、それを確認することはできません。私はすべてを試してみましたが、私は答えを見つけることができません、どんな助け/提案も大いに感謝されるでしょう!

更新:私の答えは以下の通りですが、この問題を少し前に解決し、GitHub projectをワーキングコードで更新しました。うまくいけば、あなたがここに来て同じことを探しているなら、それは助けになる!

+1

あなたの問題の最も直接的な解決策ではないかもしれませんが、あなたの操作はバックグラウンドで実行されるので、NSURLConnnectionを使う必要はないでしょう。単に+ [NSData dataWithContentsOfURL:] 'これは実行ループを必要としません。 – jtbandes

+0

確かに可能ですが、その同期メソッドを使用する際の主な問題は、NSURLConnectionデリゲートメソッドのような確実なエラー処理を許可しないことです。私は誰も私のための答えがない場合は恐れている私はそれと一緒に行かなければならない:/ – andycam

+1

それは価値があることについては、私はこれが可能であると確信しています - 今はそれを探す時間があります。おそらく、より深刻なグーグルが助けになるだろう。運が良かった! – jtbandes

答えて

4

ここで同じ問題が発生する可能性のあるコミュニティの慣行と助けになるよう、この問題を解決して、GitHub sample project hereが正しく動作するように更新しました。複数のNSOperationsを同時に実行しました。

それは私が変更を大量に作られたので、GitHubのコードに目を通すのがベストですが、私はそれが動作を取得するためにしなければならなかったキー修正はした:

[downloadConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; 

NSURLConnectionが初期化された後に呼び出されます、そしてそれが始まる直前。現在のメイン実行ループに接続の実行をアタッチするので、ダウンロードが完了する前にNSOperationが途中で終了することはありません。私はこの巧妙な修正を最初に投稿したどこにでも信用を与えたいと思っていますが、それはずっとずっとずっとずっと謝罪しています。これが誰かを助けることを願って!

関連する問題