iOS系统上有个“系统监控器”,如果程序在一定时间内无响应,就会被自动终止。所以,在处理用户界面的显示及触摸操作所用的线程(主线程)不能因为要执行I/O或者网络通信这类耗时的任务而阻塞。
异步方法在执行完任务后,需要以某种手段通知相关代码。常用的手段是设计委托代理。当相关事件发生时就可以通知对象执行相关操作了。
#import <Foundation/Foundation.h>
@class EOCNetworkFercher;
@protocol EOCNetworkFetcherDelegate <NSObject>
-(void)networkFetcher:(EOCNetworkFercher *)networkFetcher didFinishWithData:(NSDate *)data;
@end
@interface EOCNetworkFercher : NSObject
@property (nonatomic, weak) id <EOCNetworkFetcherDelegate>delegate;
-(id)initWithUrl:(NSURL *)url;
-(void)start;
@end
其他类可以使用此类的API
-(void)fetchFooData {
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/foo.dat"];
EOCNetworkFercher *fetcher = [[EOCNetworkFercher alloc] initWithUrl:url];
fetcher.delegate = self;
[fetcher start];
}
-(void)networkFetcher:(EOCNetworkFercher *)networkFetcher didFinishWithData:(NSDate *)data{
// get data
}
如果用块来改写的话,代码会更加清晰,开发者调用更加方便。
#import <Foundation/Foundation.h>
@class EOCNetworkFercher;
@protocol EOCNetworkFetcherDelegate <NSObject>
-(void)networkFetcher:(EOCNetworkFercher *)networkFetcher didFinishWithData:(NSDate *)data;
@end
typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);
@interface EOCNetworkFercher : NSObject
@property (nonatomic, weak) id <EOCNetworkFetcherDelegate>delegate;
-(id)initWithUrl:(NSURL *)url;
-(void)start;
-(void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)handler;
@end
这和委托协议很像,不过多了个好处,在调用start方法时可以以内联形式定义completion handler。
-(void)fetchFooDataTwo {
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/foo.dat"];
EOCNetworkFercher *fetcher = [[EOCNetworkFercher alloc] initWithUrl:url];
[fetcher startWithCompletionHandler:^(NSData *data) {
// get data
}];
}
委托模式有两个缺点,如果类要使用多个获取器下载不同数据。就需要在代理方法中判断获取器的类型以处理不同的数据。
-(void)networkFetcher:(EOCNetworkFercher *)networkFetcher didFinishWithData:(NSDate *)data{
if (networkFetcher == /*....*/) {
// data....
} else if (networkFetcher == /*....*/) {
// data....
}
}
这么写代码不稳定性很高,而且会很快使代码量激增。改用块来处理的好处是:无需监听获取器,也无需在监听方法中判断获取器。
-(void)fetchFooDataThree {
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/foo.dat"];
EOCNetworkFercher *fetcher = [[EOCNetworkFercher alloc] initWithUrl:url];
[fetcher startWithCompletionHandler:^(NSData *data) {
// get data
}];
NSURL *urlTwo = [[NSURL alloc] initWithString:@"http://www.example.com/foo.dat"];
EOCNetworkFercher *fetcherTwo = [[EOCNetworkFercher alloc] initWithUrl:urlTwo];
[fetcherTwo startWithCompletionHandler:^(NSData *data) {
// get data
}];
}
这种写法还有别的用处,现在很多基于块的API被用来处理错误。可以用两个块来分别处理成功和失败两种情况。也可以把成功和失败的处理都放在一个块中。
typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);
typedef void(^EOCNetworkFetcherErrorHandler)(NSError *error);
@interface EOCNetworkFercher : NSObject
@property (nonatomic, weak) id <EOCNetworkFetcherDelegate>delegate;
-(id)initWithUrl:(NSURL *)url;
-(void)start;
-(void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)handler;
-(void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)handler failureHandler:(EOCNetworkFetcherErrorHandler)failHandler;
@end
-(void)fetchFooDataFour {
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/foo.dat"];
EOCNetworkFercher *fetcher = [[EOCNetworkFercher alloc] initWithUrl:url];
[fetcher startWithCompletionHandler:^(NSData *data) {
// get data
} failureHandler:^(NSError *error) {
// get error
}];
}
总结:
1.在使用对象时,可以使用内联的handler块将相关业务逻辑一起声明。
2.在有多个对象需要监控时,代理模式的实现中需要判断对象。用块实现,可以直接将块与关联对象链接。
3.设计API时可以增加一个参数,调用者通过该参数来决定应该把块安排在那个队列上执行。