我们有一个大型项目,需要将大型文件从服务器同步到后台的“库”中。我读过子类化 NSOperation 是多线程 iOS 任务的最灵活方式,并尝试过。因此,该函数接收要下载和保存的 URL 列表,初始化同一 NSOperation 类的实例并将每个实例添加到 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 并开始执行。但问题是何时开始下载,第一个文件永远不会开始下载(使用带有委托(delegate)方法的 NSURLConnection )。我使用了我在另一个线程中看到的 runLoop 技巧,它应该允许操作继续运行,直到下载完成。 NSURLConnection 已建立,但它从未开始将数据附加到 NSMutableData 对象!
@synthesize downloadURL, downloadData, downloadPath;
@synthesize downloadDone, executing, finished;
/* Function to initialize the NSOperation with the URL to download */
- (id)initWithURLNSString *)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)connectionNSURLConnection *)connection didReceiveResponseNSHTTPURLResponse*)response {
NSLog(@"%s: Received response!", __FUNCTION__);
}
// NSURLConnectionDelegate method: handle data being received during connection
-(void)connectionNSURLConnection *)connection didReceiveDataNSData *)data {
[downloadData appendData:data];
NSLog(@"downloaded %d bytes", [data length]);
}
// NSURLConnectionDelegate method: What to do once request is completed
-(void)connectionDidFinishLoadingNSURLConnection *)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)connectionNSURLConnection *)connection didFailWithErrorNSError *)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与工作代码。希望如果您来这里是为了寻找同样的东西,它会有所帮助!
Best Answer-推荐答案 strong>
出于良好的社区实践和帮助可能遇到同样问题的其他人的利益,我最终解决了这个问题并更新了 GitHub sample project here现在可以正常工作,即使是多个并发的 NSOperations!
最好查看 GitHub 代码,因为我进行了大量更改,但我必须进行的关键修复是:
[downloadConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
这在 NSURLConnection 初始化之后调用,就在它启动之前。它将连接的执行附加到当前的主运行循环,以便 NSOperation 在下载完成之前不会过早终止。我很想感谢第一次发布这个聪明的修复的地方,但是太久了,我忘记了在哪里,道歉。希望这对某人有帮助!
关于ios - 使用 NSURLConnection 运行多个 NSOperation 实例?,我们在Stack Overflow上找到一个类似的问题:
https://stackoverflow.com/questions/9684770/
|