Ios 使用NSCondition等待异步方法
我正在通过internet异步下载四个plist文件。我需要等待所有四个文件下载完毕,直到我在第一次运行时按下UIViewController,或在所有后续运行时刷新数据,然后重新加载所有UITableView 在第一次运行时,一切都很完美。但在刷新时,会调用并启动所有四个url请求,但不会调用它们的完成或失败块,UI会冻结。这很奇怪,因为我在后台线程中执行了所有操作。我一直无法理解为什么会发生这种情况 第一个load和refresh方法以相同的方式调用四个“update”方法,并以相同的方式使用NSCondition 第一次运行:Ios 使用NSCondition等待异步方法,ios,multithreading,nsurlrequest,nscondition,Ios,Multithreading,Nsurlrequest,Nscondition,我正在通过internet异步下载四个plist文件。我需要等待所有四个文件下载完毕,直到我在第一次运行时按下UIViewController,或在所有后续运行时刷新数据,然后重新加载所有UITableView 在第一次运行时,一切都很完美。但在刷新时,会调用并启动所有四个url请求,但不会调用它们的完成或失败块,UI会冻结。这很奇怪,因为我在后台线程中执行了所有操作。我一直无法理解为什么会发生这种情况 第一个load和refresh方法以相同的方式调用四个“update”方法,并以相同的方式使
- (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;
}
}
它运行:
- (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];
}
完成块和失败块调用相同的方法来更新数据
- (void)updateCompetitionResultsWithDictionary:(NSDictionary *)competitionResultsData
{
//Do Stuff with the data here
resultLock = YES;
[condition signal];
}
那么,为什么这在第一次运行时起作用,而在随后的任何运行中都不起作用呢?正如我在上面的评论中提到的,最明显的问题是在初始化
条件之前调用使用条件的方法。在开始调用updateCompetitionResults
等之前,请确保初始化条件
就更彻底的变化而言,我可能建议完全退出NSCondition
,并使用操作队列:
我可能会使用(如果您愿意,您也可以使用调度组,但我喜欢操作队列配置您可以操作多少并发操作的功能……此外,如果您想要取消操作,我认为NSOperationQueue
也提供了一些不错的功能)。然后,您可以将每个下载和处理定义为一个单独的过程(每个下载应该同步进行,因为它们在操作队列中运行,您可以从异步操作中获得好处,但您可以在下载完成后立即启动后处理)。然后,您只需将它们排队以异步运行,但定义一个最终操作,该操作依赖于其他四个,并将在四次下载完成后立即启动。(顺便说一句,我使用它为NSOperation
对象提供块功能,但您可以按任何方式进行操作。)
您的updateProgramContent
可能会异步下载,但它会依次处理四个下载的文件。因此,如果第一次下载需要一段时间才能下载,则会阻碍其他下载的后期处理。相反,我喜欢将四个plist文件的下载和后处理都封装在一个NSOperation
中,每个文件都是。因此,我们不仅享受下载的最大并发性,还享受后期处理的最大并发性
我可能倾向于使用NSDictionary
和NSArray
功能,而不是使用AFNetworking
(我通常非常喜欢)plist相关方法,这些功能允许您从web下载plist并将其加载到适当的结构中。这些dictionaryWithContentsOfURL
和arrayWithContentsOfURL
同步运行,但由于我们是在后台操作中执行此操作,因此所有内容都会按照您的要求异步运行。这也绕过了将它们保存到文件的过程。如果您希望将它们保存到文档
目录中的文件中,您也可以轻松地执行此操作。显然,如果您在下载plist文件时做了一些复杂的工作(例如,您的服务器正在进行一些质询-响应身份验证),则不能使用方便的NSDictionary
和NSArray
方法。但是如果你不需要所有这些,简单的NSDictionary
和NSArray
方法,\uuuuuuu和url
内容使生活变得非常简单
把这些放在一起,它可能看起来像:
@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
的一种替代方案。如果你目前的技术有效,那就太好了。但是上面概述了我将如何应对这样的挑战。正如我在上面的评论中提到的,最明显的问题是,在初始化条件之前,您正在调用使用条件的方法。在开始调用updateCompetitionResults
等之前,请确保初始化条件
就更彻底的变化而言,我可能建议完全退出NSCondition
,并使用操作队列:
我可能会使用(如果您愿意,您也可以使用调度组,但我喜欢操作队列配置您可以操作多少并发操作的功能……此外,如果您想要取消操作,我认为NSOperationQueue
也提供了一些不错的功能)。然后,您可以将每个下载和处理定义为一个单独的过程(每个下载应该同步进行,因为它们在操作队列中运行,您可以从异步操作中获得好处,但您可以在下载完成后立即启动后处理)。然后,您只需将它们排队以异步运行,但定义一个最终操作,该操作依赖于其他四个,并将在四次下载完成后立即启动。(顺便说一句,我使用它为NSOperation
对象提供块功能,但您可以按任何方式进行操作。)
而你的updateProgramCo
@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