ios - 使用 NSURLConnection 运行 NSOperation 的多个实例?

标签 ios concurrency nsurlconnection nsoperation

我们有一个大型项目,需要将大文件从服务器同步到后台的“库”中。我读到子类化 NSOperation 是多线程 iOS 任务最灵活的方式,并尝试了这一点。因此,该函数接收要下载和保存的 URL 列表,初始化同一 NSOperation 类的实例,并将每个实例添加到 NSOperation 队列中(一次应该只下载 1 个文件)。

-(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 中并开始执行。但问题是,当开始下载时,第一个文件永远不会开始下载(使用带有委托(delegate)方法的 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与工作代码。希望如果您来这里寻找同样的东西,它会有所帮助!

最佳答案

为了良好的社区实践并帮助其他可能遇到同样问题的人,我最终解决了这个问题并更新了 GitHub sample project here现在即使对于多个并发 NSOperations 也可以正常工作!

最好查看 GitHub 代码,因为我进行了大量更改,但要使其正常工作,我必须进行的关键修复是:

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

这在 NSURLConnection 初始化之后、启动之前调用。它将连接的执行附加到当前的主运行循环,以便 NSOperation 在下载完成之前不会提前终止。我很想感谢第一次发布这个巧妙修复的地方,但时间太久了,我已经忘记在哪里了,抱歉。希望这对某人有帮助!

关于ios - 使用 NSURLConnection 运行 NSOperation 的多个实例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9684770/

相关文章:

ios - UIView不可见时截图

ios - 函数签名特化 <Arg[0] = Owned To Guaranteed>

c++ - 等待多个线程通知的条件变量的正确方法

ios - POST header 和正文之间的随机延迟

ios - NSURLConnection 在另一个线程中启动。未调用委托(delegate)方法

iphone - 使用 NSPredicate 过滤带有字典的数组

ios - 无法从 CoreData 中删除 NSOrderedSet

amazon-s3 - 从Amazon S3(HTTPS)加载图像时在iPad上获取 “The certificate for this server is invalid.”,但在模拟器上没有错误

linux - mongodb 和非常高的锁定百分比和低吞吐量

java - ArrayBlockingQueue - 它真的是并发的吗?