我正在通过互联网异步下载四个 plist 文件。我需要等到所有四个文件都下载完毕,直到我在第一次运行时推送 UIViewController,或者在所有后续运行中刷新数据并重新加载所有 UITableView。
第一次运行时,一切都很完美。不过,刷新时,所有四个 url 请求都会被调用并启动,但永远不会调用它们的完成或失败 block ,并且 UI 会卡住。这很奇怪,因为我在后台线程中执行所有操作。我一直无法弄清楚为什么会发生这种情况。
第一个加载和刷新方法以相同的方式调用四个“更新”方法,并以相同的方式使用 NSCondition。
第一次运行:
- (void)loadContentForProgram:(NSString *)programPath
{
NSLog(@"Start Load Program");
AppDelegate *myDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
hud = [[MBProgressHUD alloc] initWithView:myDelegate.window];
[myDelegate.window addSubview:hud];
hud.labelText = @"Loading...";
hud.detailsLabelText = @"Loading Data";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//Do stuff here to load data from files
//Update From online files
hud.detailsLabelText = @"Updating Live Data";
resultLock = NO;
progressLock = NO;
recallLock = NO;
stageLock = NO;
condition = [[NSCondition alloc] init];
[condition lock];
[self updateCurrentCompsText];
[self updateCompetitionResults];
[self updateCompetitionRecalls];
[self updateCompetitionProgress];
while (!resultLock) {
[condition wait];
}
NSLog(@"Unlock");
while (!stageLock) {
[condition wait];
}
NSLog(@"Unlock");
while (!recallLock) {
[condition wait];
}
NSLog(@"Unlock");
while (!progressLock) {
[condition wait];
}
NSLog(@"Unlock");
[condition unlock];
updateInProgress = NO;
//Reset Refresh controls and table views
self.refreshControlsArray = [[NSMutableArray alloc] init];
self.tableViewsArray = [[NSMutableArray alloc] init];
NSLog(@"Finished Loading Program");
[[NSNotificationCenter defaultCenter] postNotificationName:@"WMSOFinishedLoadingProgramData" object:nil]; //Pushes view controller
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:myDelegate.window animated:YES];
});
});
}
刷新数据时:
- (void)updateProgramContent
{
if (!updateInProgress) {
updateInProgress = YES;
for (int i = 0; i < self.refreshControlsArray.count; i++) {
if (!((UIRefreshControl *)self.refreshControlsArray[i]).refreshing) {
[self.refreshControlsArray[i] beginRefreshing];
[self.tableViewsArray[i] setContentOffset:CGPointMake(0.0, 0.0) animated:YES];
}
}
resultLock = NO;
stageLock = NO;
recallLock = NO;
progressLock = NO;
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
condition = [[NSCondition alloc] init];
[condition lock];
[self updateCompetitionProgress];
[self updateCompetitionRecalls];
[self updateCompetitionResults];
[self updateCurrentCompsText];
while (!resultLock) {
[condition wait];
}
NSLog(@"Unlock");
while (!stageLock) {
[condition wait];
}
NSLog(@"Unlock");
while (!recallLock) {
[condition wait];
}
NSLog(@"Unlock");
while (!progressLock) {
[condition wait];
}
NSLog(@"Unlock");
[condition unlock];
});
for (int i = 0; i < self.refreshControlsArray.count; i++) {
[self.refreshControlsArray[i] performSelector:@selector(endRefreshing) withObject:nil afterDelay:1.0];
[self.tableViewsArray[i] performSelector:@selector(reloadData) withObject:nil afterDelay:1.0];
}
updateInProgress = NO;
}
}
上面每个加载方法中出现的下面的 block 对应于下载和更新特定数据的方法。
[self updateCompetitionProgress];
[self updateCompetitionRecalls];
[self updateCompetitionResults];
[self updateCurrentCompsText];
运行:
- (void)updateCompetitionResults
{
__block NSDictionary *competitionResultsData = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"Some URL",[self.programName stringByReplacingOccurrencesOfString:@" " withString:@"%20"]]] cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:20.0];
AFPropertyListRequestOperation *operation = [AFPropertyListRequestOperation propertyListRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList) {
competitionResultsData = (NSDictionary *)propertyList;
[competitionResultsData writeToFile:[@"SOME LOCAL PATH"] atomically:NO];
[self updateCompetitionResultsWithDictionary:competitionResultsData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList) {
competitionResultsData = [NSDictionary dictionaryWithContentsOfFile:[@"SOME LOCAL PATH"]];
NSLog(@"Failed to retreive competition results: %@", error);
[self updateCompetitionResultsWithDictionary:competitionResultsData];
}];
[operation start];
}
并且完成和失败 block 调用相同的方法来更新数据
- (void)updateCompetitionResultsWithDictionary:(NSDictionary *)competitionResultsData
{
//Do Stuff with the data here
resultLock = YES;
[condition signal];
}
那么,为什么这在第一次运行时有效,但在后续运行中不起作用?
最佳答案
正如我在上面的评论中提到的,最明显的问题是您在初始化 condition
之前调用使用 condition
的方法。确保在开始调用 condition
等之前初始化 updateCompetitionResults
等。
就更彻底的改变而言,我可能建议完全淘汰 NSCondition
,并使用操作队列:
我可能会使用
NSOperationQueue
(或者如果您愿意的话,您也可以使用调度组,但我喜欢操作队列能够配置您可以操作的并发操作数......如果您达到某个点如果你想取消操作,我认为NSOperationQueue
也提供了一些不错的功能)。然后,您可以将每个下载和处理定义为单独的NSOperation
(每个下载应该同步发生,因为它们在操作队列中运行,您可以获得异步操作的好处,但您可以在下载后立即启动后处理下载完成)。然后,您只需将它们排队以异步运行,但定义一个最终操作,该操作依赖于其他四个操作,一旦四个下载完成,就会立即开始。 (顺便说一句,我使用NSBlockOperation
,它为NSOperation
对象提供 block 功能,但您可以按照自己的方式进行操作。)尽管您的
updateProgramContent
可能会异步下载,但它会依次处理四个下载的文件。因此,如果第一次下载需要一段时间才能下载,则会阻碍其他下载的后处理。相反,我喜欢将四个 plist 文件中每个文件的下载和后处理封装在一个NSOperation
中。因此,我们不仅可以享受下载的最大并发性,还可以享受后处理的最大并发性。我可能倾向于使用
AFNetworking
和NSDictionary
功能,而不是使用NSArray
(我通常非常喜欢)plist 相关方法,这些功能允许您从网络下载 plist 并加载它们到适当的结构中。这些dictionaryWithContentsOfURL
和arrayWithContentsOfURL
同步运行,但因为我们是在后台操作中执行此操作,所以一切都按照您想要的方式异步运行。这也绕过了将它们保存到文件的过程。如果您希望将它们保存到Documents
目录中的文件中,您也可以轻松做到这一点。显然,如果您在下载 plist 文件时执行一些复杂的操作(例如,您的服务器正在进行某些质询-响应身份验证),则无法使用方便的NSDictionary
和NSArray
方法。但如果您不需要所有这些,简单的NSDictionary
和NSArray
方法、___WithContentsOfURL
会让生活变得非常简单。
把所有这些放在一起,它可能看起来像:
@interface ViewController ()
@property (nonatomic, strong) NSArray *competitions;
@property (nonatomic, strong) NSDictionary *competitionResults;
@property (nonatomic, strong) NSDictionary *competitionRecalls;
@property (nonatomic, strong) NSDictionary *competitionProgress;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self transfer];
}
- (void)allTransfersComplete
{
BOOL success;
if (self.competitions == nil)
{
success = FALSE;
NSLog(@"Unable to download competitions");
}
if (self.competitionResults == nil)
{
success = FALSE;
NSLog(@"Unable to download results");
}
if (self.competitionRecalls == nil)
{
success = FALSE;
NSLog(@"Unable to download recalls");
}
if (self.competitionProgress == nil)
{
success = FALSE;
NSLog(@"Unable to download progress");
}
if (success)
{
NSLog(@"all done successfully");
}
else
{
NSLog(@"one or more failed");
}
}
- (void)transfer
{
NSURL *baseUrl = [NSURL URLWithString:@"http://insert.your.base.url.here/competitions"];
NSURL *competitionsUrl = [baseUrl URLByAppendingPathComponent:@"competitions.plist"];
NSURL *competitionResultsUrl = [baseUrl URLByAppendingPathComponent:@"competitionresults.plist"];
NSURL *competitionRecallsUrl = [baseUrl URLByAppendingPathComponent:@"competitionrecalls.plist"];
NSURL *competitionProgressUrl = [baseUrl URLByAppendingPathComponent:@"competitionprogress.plist"];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4; // if your server doesn't like four concurrent requests, you can ratchet this back to whatever you want
// create operation that will be called when we're all done
NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
// any stuff that can be done in background should be done here
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// any user interface stuff should be done here; I've just put this in a separate method so this method doesn't get too unwieldy
[self allTransfersComplete];
}];
}];
// a variable that we'll use as we create our four download/process operations
NSBlockOperation *operation;
// create competitions operation
operation = [NSBlockOperation blockOperationWithBlock:^{
// download the competitions and load it into the ivar
//
// note, if you *really* want to download this to a file, you can
// do that when the download is done
self.competitions = [NSArray arrayWithContentsOfURL:competitionsUrl];
// if you wanted to do any post-processing of the download
// you could do it here.
NSLog(@"competitions = %@", self.competitions);
}];
[completionOperation addDependency:operation];
// create results operation
operation = [NSBlockOperation blockOperationWithBlock:^{
self.competitionResults = [NSDictionary dictionaryWithContentsOfURL:competitionResultsUrl];
NSLog(@"competitionResults = %@", self.competitionResults);
}];
[completionOperation addDependency:operation];
// create recalls operation
operation = [NSBlockOperation blockOperationWithBlock:^{
self.competitionRecalls = [NSDictionary dictionaryWithContentsOfURL:competitionRecallsUrl];
NSLog(@"competitionRecalls = %@", self.competitionRecalls);
}];
[completionOperation addDependency:operation];
// create progress operation
operation = [NSBlockOperation blockOperationWithBlock:^{
self.competitionProgress = [NSDictionary dictionaryWithContentsOfURL:competitionProgressUrl];
NSLog(@"competitionProgress = %@", self.competitionProgress);
}];
[completionOperation addDependency:operation];
// queue the completion operation (which is dependent upon the other four)
[queue addOperation:completionOperation];
// now queue the four download and processing operations
[queue addOperations:completionOperation.dependencies waitUntilFinished:NO];
}
@end
现在,我不知道你的 plist 中哪些是数组,哪些是字典(在我的示例中,我将竞赛作为一个数组,其余的都是由竞赛 id 键控的字典),但希望你能了解什么我正在射击。最大化并发,消除 NSCondition
逻辑,真正充分利用 NSOperationQueue
等。
这可能太多了,但我只是将它作为 NSCondition
的替代品。如果您当前的技术有效,那就太好了。但上面概述了我将如何应对这样的挑战。
关于ios - 使用 NSCondition 等待异步方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14013947/