我需要异步加载和写入图像-但如果正在写入,则无法访问该文件。为此,我想使用barrier_async进行写入,并使用sync进行读取文件。这是我的代码:
执行gcd操作的block方法的一部分:
[NSURLConnection sendAsynchronousRequest: request queue: [NSOperationQueue mainQueue] completionHandler:
^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
[self.class writeData:data toFilePath:tileFilePathName completionHandler:^(NSError *error) {
if (!error) {
dispatch_sync(dispatch_get_main_queue(), ^{
[[AxPanoramaDataManager sharedInstance].tilesNamesArray addObject:tileFileName];
});
[self.class readImagefromFilePath:tileFilePathName
completionHandler:^(UIImage *image, NSError *error) {
if (!error)
dispatch_sync(dispatch_get_main_queue(), ^{
completion(tileCoordValue, side, image, error);
});
}];
}
}];
}];
和读/写方法:
+ (void) readImagefromFilePath: (NSString *) filePath
completionHandler:(void (^)(UIImage* image, NSError* error)) handler
{
dispatch_sync([AxPanoramaDataManager sharedInstance].dataManagerQueue, ^{
UIImage *tileImage = [UIImage imageWithContentsOfFile:filePath];
dispatch_sync(dispatch_get_main_queue(), ^{
handler(tileImage, nil);
NSLog(@"Image %@ loaded from cash", tileImage);
});
});
}
+ (void) writeData: (NSData *) data
toFilePath: (NSString *) filePath
completionHandler:(void (^)(NSError* error)) handler
{
dispatch_barrier_async([AxPanoramaDataManager sharedInstance].dataManagerQueue, ^{
[data writeToFile:filePath atomically:YES];
dispatch_sync(dispatch_get_main_queue(), ^{
handler(nil);
NSLog(@"Data %@ wrote to the disk", data);
});
});
}
现在,当我尝试执行此方法时,应用程序正在挂起。有什么帮助吗?
最佳答案
您陷入僵局。这是“展开”的代码,可以一次调用。 (我在下面将其分解。)
[NSURLConnection sendAsynchronousRequest: request queue: [NSOperationQueue mainQueue] completionHandler: ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// Unroll: [self.class writeData: data toFilePath: filePath completionHandler: writeDataCompletion];
dispatch_barrier_async(dataManagerQueue, ^{
[data writeToFile:filePath atomically:YES];
dispatch_sync(dispatch_get_main_queue(), ^{
// Unroll: writeDataCompletion(nil);
NSError* error = nil;
if (!error) {
dispatch_sync(dispatch_get_main_queue(), ^{
[[AxPanoramaDataManager sharedInstance].tilesNamesArray addObject:tileFileName];
});
// Unroll: [self.class readImagefromFilePath:tileFilePathName completionHandler:readCompletion];
dispatch_sync(dataManagerQueue, ^{
UIImage *tileImage = [UIImage imageWithContentsOfFile:filePath];
dispatch_sync(dispatch_get_main_queue(), ^{
// Unroll: readCompletion(tileImage, nil);
NSError* error = nil;
if (!error) {
dispatch_sync(dispatch_get_main_queue(), ^{
completion(tileCoordValue, side, tileImage, error);
});
}
NSLog(@"Image %@ loaded from cash", tileImage);
});
});
}
NSLog(@"Data %@ wrote to the disk", data);
});
});
}];
现在,让我们逐行进行跟踪,注意每个阶段我们所处的线程/队列:
[NSURLConnection sendAsynchronousRequest: request queue: [NSOperationQueue mainQueue] completionHandler: ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
好的,所以当您从
-[NSURLConnection sendAsync...]
进入回调时,您就在主线程上,因为您已将[NSOperationQueue mainQueue]
传递给了queue:
参数。 // Unroll: [self.class writeData: data toFilePath: filePath completionHandler: writeDataCompletion];
dispatch_barrier_async(dataManagerQueue, ^{
现在我们在障碍块中的
dataManagerQueue
上,这意味着除非我们从该块返回,否则其他任何内容都不能在dataManagerQueue
上运行。因为屏障调用是异步的,所以我们希望此时主线程/队列是空闲的。 [data writeToFile:filePath atomically:YES];
dispatch_sync(dispatch_get_main_queue(), ^{
现在我们回到了主队列。请注意,因为这是用
dispatch_sync
调用的,所以我们仍然位于dataManagerQueue
的障碍块中。 // Unroll: writeDataCompletion(nil);
NSError* error = nil;
if (!error) {
dispatch_sync(dispatch_get_main_queue(), ^{
我们已经在主队列中,所以
dispatch_sync(dispatch_get_main_queue()
在这里将陷入僵局。此时我们已经死在水里了,但是无论如何,让我们继续前进,并假设dispatch_sync
处理递归重入(不是,但是...) [[AxPanoramaDataManager sharedInstance].tilesNamesArray addObject:tileFileName];
});
// Unroll: [self.class readImagefromFilePath:tileFilePathName completionHandler:readCompletion];
dispatch_sync(dataManagerQueue, ^{
现在,请注意,我们仍处在您提交给
dataManagerQueue
的障碍块中,但是我们正尝试通过dispatch_sync(dataManagerQueue, ...)
提交另一个块。因此,如果我们还没有在上面的主队列上陷入僵局,那么现在我们将在dataManagerQueue
上陷入僵局。 UIImage *tileImage = [UIImage imageWithContentsOfFile:filePath];
dispatch_sync(dispatch_get_main_queue(), ^{
现在,我们再次同步重新进入主队列!
// Unroll: readCompletion(tileImage, nil);
NSError* error = nil;
if (!error) {
dispatch_sync(dispatch_get_main_queue(), ^{
然后再次!!!
completion(tileCoordValue, side, tileImage, error);
});
}
NSLog(@"Image %@ loaded from cash", tileImage);
});
});
}
NSLog(@"Data %@ wrote to the disk", data);
});
});
}];
简而言之,这里有许多僵局。您似乎在许多可能使用
dispatch_sync
的地方都使用了dispatch_async
,但是我可能不知道还有什么其他作用,使您认为所有这些完成都需要同步触发。根据您发布的代码,您可以先将每个_sync
调用都转换为_async
调用,而不会产生实质性的不良影响(即,此处发布的代码中唯一可见的效果是NSLog
会在不同的时间。)另一个一般经验法则是
dispatch_sync(dispatch_get_main_queue(), ...)
几乎总是一个坏主意。 (请参阅detailed explanation)即使它在“大多数时间”都有效,也是有问题的,因为无法控制的事物(例如,在OS中)可能会与该模式交互,从而导致死锁。您可能想在依赖dispatch_sync(dispatch_get_main_queue(), ...)
成为dispatch_async(dispatch_get_main_queue(), ... )
的任何地方进行重做,然后将剩余的后台工作嵌套重新调度到后台队列中。但是一般来说,大多数主线程完成应该能够异步发送而没有问题。关于ios - 无法对文件执行GCD I/O,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/25504981/