在開(kāi)發(fā)中曾經(jīng)遇到一個(gè)這樣的問(wèn)題:
在過(guò)渡到一個(gè)新的Controller之前,有一些數(shù)據(jù)是必須先傳遞過(guò)去的,但是這些數(shù)據(jù)必須從不同的接口中獲取。
也就是從接口A中獲取data1,從接口B中獲取data2,將data1和data2賦值給Controller的屬性,然后再push過(guò)去。push這個(gè)動(dòng)作依賴(lài)于data1和data2。
最簡(jiǎn)單的方法可以在接口A的請(qǐng)求的成功和失敗的回調(diào)中去執(zhí)行一個(gè)接口B的請(qǐng)求,然后在B的成功或者失敗的回調(diào)中去做push的動(dòng)作。但是這樣的做法代碼會(huì)很丑陋,而且B的請(qǐng)求變成依賴(lài)于A的請(qǐng)求的回調(diào)完成。
使用NSOperation解決
如果了解NSOperation的一些API(可見(jiàn)上一篇NSOperation),很容易想到通過(guò)給NSOperation設(shè)置依賴(lài)來(lái)解決:
- (void)orginalOp{
NSString *url1 = @"http://www.baidu.com";
NSString *url2 = @"http://www.itdecent.cn";
NSOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
[self getHtmlOfUrl:url1 success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable response) {
NSLog(@"get data of %@ success",url1);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"get html of %@ fail",url1);
}];
}];
NSOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
[self getHtmlOfUrl:url2 success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable response) {
NSLog(@"get data of %@ success",url2);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"get html of %@ fail",url2);
}];
}];
NSOperation *pushOp = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"push to new controller after all data is ready!");
}];
[pushOp addDependency:op1];
[pushOp addDependency:op2];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:pushOp];
}
- (void)getHtmlOfUrl:(NSString *)url
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure{
url = [url stringByRemovingPercentEncoding];
AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager manager];
sessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
sessionManager.responseSerializer.acceptableContentTypes = [sessionManager.responseSerializer.acceptableContentTypes setByAddingObject:@"text/html"];
[sessionManager GET:url parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
success(task,responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
failure(task,error);
}];
}
查看運(yùn)行結(jié)果:
2017-04-21 09:24:49.367 ConcurrentProgram[3733:210543] push to new controller after all data is ready!
2017-04-21 09:24:49.791 ConcurrentProgram[3733:195552] get data of http://www.itdecent.cn success
2017-04-21 09:24:49.802 ConcurrentProgram[3733:195552] get data of http://www.baidu.com success
顯然并沒(méi)有達(dá)到我們預(yù)期的效果。稍作分析即可理解:pushOp依賴(lài)于op1和op2的完成,那么op1和op2什么時(shí)候完成呢?在本例中,由于請(qǐng)求是異步的,并不是獲取到請(qǐng)求結(jié)果之后才完成,而是執(zhí)行完請(qǐng)求動(dòng)作之后該operation就已經(jīng)算是完成了。所以就會(huì)有上述的結(jié)果。
那么要怎么樣才能把operation的完成設(shè)置在請(qǐng)求成功或者失敗之后呢?NSOperation中有一個(gè)isFinished的方法,如果該方法返回YES那么就代表operation已經(jīng)完成了。所以,我們要新建一個(gè)類(lèi)繼承NSOperation來(lái)實(shí)現(xiàn)這個(gè)需求。代碼如下:
#import "AFRequestOperation.h"
@interface AFRequestOperation (){
BOOL finished;
BOOL executing;
}
@end
@implementation AFRequestOperation
- (instancetype)init{
self = [super init];
if (self) {
finished = NO;
executing = NO;
}
return self;
}
- (void)start{
// Always check for cancellation before launching the task.
if ([self isCancelled])
{
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:@"isExecuting"];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
[self performTask];
}
- (void)performTask{
AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager manager];
sessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
sessionManager.responseSerializer.acceptableContentTypes = [sessionManager.responseSerializer.acceptableContentTypes setByAddingObject:@"text/html"];
[sessionManager GET:_url parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (self.successHandler) {
self.successHandler(responseObject);
}
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
[self didChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (self.errorHandler) {
self.errorHandler(error);
}
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
[self didChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
}];
}
- (BOOL)isFinished {
return finished;
}
- (BOOL)isExecuting {
return executing;
}
@end
在調(diào)用的時(shí)候:
- (void)customOp{
NSString *url1 = @"http://www.baidu.com";
NSString *url2 = @"http://www.itdecent.cn";
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
AFRequestOperation *op1 = [[AFRequestOperation alloc] init];
op1.url = url1;
op1.successHandler = ^(id response) {
NSLog(@"get data of %@ success",url1);
};
op1.errorHandler = ^(NSError *error) {
NSLog(@"get data of %@ fail",url1);
};
[queue addOperation:op1];
AFRequestOperation *op2 = [[AFRequestOperation alloc] init];
op2.url = url2;
op2.successHandler = ^(id response) {
NSLog(@"get data of %@ success",url2);
};
op2.errorHandler = ^(NSError *error) {
NSLog(@"get data of %@ fail",url2);
};
[queue addOperation:op2];
NSOperation *pushOp = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"push to new controller after all data is ready!");
}];
[pushOp addDependency:op1];
[pushOp addDependency:op2];
[queue addOperation:pushOp];
}
運(yùn)行結(jié)果如下:
2017-04-21 09:37:54.833 ConcurrentProgram[4225:249946] get data of http://www.baidu.com success
2017-04-21 09:37:54.988 ConcurrentProgram[4225:249946] get data of http://www.itdecent.cn success
2017-04-21 09:37:54.988 ConcurrentProgram[4225:262299] push to new controller after all data is ready!
為了可以兼容不同的請(qǐng)求這里把一個(gè)請(qǐng)求的url以及成功和失敗的回調(diào)等傳入自定義的operation中。最關(guān)鍵的一點(diǎn)就在于完成請(qǐng)求的成功或者失敗的回調(diào)之后將isFinish設(shè)置為YES,此時(shí)operation才算是完成。
使用GCD解決
雖然這樣可以解決這個(gè)問(wèn)題,但是看起來(lái)比較復(fù)雜,要寫(xiě)一個(gè)類(lèi)繼承自NSOperation。之前也了解過(guò)GCD,那么能否考慮是使用GCD的方式來(lái)解決呢?
GCD中有一個(gè)dispatch_group_t的API,使用dispatch_group_t可以監(jiān)控一組block,dispatch_group_t會(huì)追蹤組內(nèi)的block的執(zhí)行狀態(tài),當(dāng)group中所有的block完成以后,可以使用dispatch_group_notify來(lái)執(zhí)行一個(gè)額外的block。增加dispatch_group_t中block的數(shù)量有兩種方式,一種是使用dispatch_group_async或者dispatch_group_sync后面添加block,另外一種是使用dispatch_group_enter和dispatch_group_leave組合來(lái)代表一個(gè)block的開(kāi)始和結(jié)束。在這里我們需要使用dispatch_group_enter和dispatch_group_leave組合。
代碼如下:
- (void)gcdGroup{
NSString *url1 = @"http://www.baidu.com";
NSString *url2 = @"http://www.itdecent.cn";
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self getHtmlOfUrl:url1 success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable response) {
NSLog(@"get data of %@ success",url1);
dispatch_group_leave(group);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"get html of %@ fail",url1);
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self getHtmlOfUrl:url2 success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable response) {
NSLog(@"get data of %@ success",url2);
dispatch_group_leave(group);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"get html of %@ fail",url2);
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"push to new controller after all data is ready!");
});
}
實(shí)際上dispatch_group_t代表了一個(gè)信號(hào)量,該信號(hào)量有一個(gè)初始值。當(dāng)調(diào)用dispatch_group_enter時(shí)該信號(hào)量的值-1,而掉用dispatch_group_leave的時(shí)候信號(hào)量的值加1.讓信號(hào)量的值等于初始值時(shí),就會(huì)執(zhí)行dispatch_group_notify中的block。有興趣的可以自己嘗試一下使用dispatch_group_async看能不能解決異步任務(wù)依賴(lài)的需求。我個(gè)人的實(shí)驗(yàn)結(jié)果是和第一個(gè)例子的結(jié)果是一樣的。