美文网首页编写高质量代码的52个有效方法
52个有效方法(39) - 用handler块降低代码分散程度

52个有效方法(39) - 用handler块降低代码分散程度

作者: SkyMing一C | 来源:发表于2018-09-14 16:51 被阅读18次

为用户界面编码时,一种常用的范式就是“异步执行任务”(perform task asynchro nously)。这种范式的好处在于:处理用户界面的显示及触摸操作所用的线程,不会因为要执行I/O或网络通信这类耗时的任务而阻塞。这个线程通常称为主线程(main thread)。某些情况下,如果应用程序在一定时间内无响应,那么就会自动终止。“系统监控器”(system watchdog)在发现某个应用程序的主线程已经阻塞了一段时间之后,就会令其终止。

异步方法在执行完任务之后,需要以某种手段通知相关代码。实现此功能有很多办法。常用的技巧是设计一个委托协议(参见第23条),令关注此事的对象遵从该协议。对象成为delegate之后,就可以在相关事件发生时(例如某个异步任务执行完毕时)得到通知了。

如果改用块来写的话,代码会更清晰。块可以令这种API变得更紧致,同时也令开发者调用起来更加方便。

typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);

@interface EOCNetworkFetcher : NSObject
- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler: (EOCNetworkFetcherCompletionHandler)handler;
@end

与使用委托模式的代码相比,用块写出来的代码闲的更为整洁。委托模式有个缺点:如果类要分别使用多个获取器下载不同的数据,那么就得在delegate回调方法里根据传入的获取器参数来切换。

异步执行任务完毕后所需运行的业务逻辑,和启动异步任务所用的代码放在了一起。无须保存获取器,也无需再回调方法里切换,每个completion handler的业务逻辑,都是和相关获取器对象一起来定义的。

NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/foo.dat"];
    EOCNetworkFercher *fetcher = [[EOCNetworkFercher alloc] initWithUrl:url];
    [fetcher startWithCompletionHandler:^(NSData *data) {
        //  get data
   }];

  • 采用两个独立的处理程序的API设计风格。由于成功和失败的情况要分别处理,所以调用此API的代码也就会按照逻辑,把应对成功和失败情况的代码分开来写,这将令代码更易读懂。
typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);
typedef void (^EOCNetworkFetcherErrorHandler)(NSError *error);

@interface EOCNetworkFetcher : NSObject
- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion failureHandler:(EOCNetworkFetcherErrorHandler)failure;
@end

  • 把处理成功情况和失败情况所用的代码全放在一个块里。
typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data, NSError *error);

@interface EOCNetworkFetcher : NSObject
- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;
@end
  • 此种API调用方式如下:
EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[fetcher startWithCompletionHandler:^(NSData *data, NSError *error) {
    if (error) {
        //Handler failure
    } else {
        //Handler success
    }
}];

这种方法需要在块代码中检测传入的error变量,并且要把所有逻辑代码都放在一处。这种写法的缺点是:由于全部逻辑都写在一起,所以会令块变得比较长,且比较复杂。然而只用一个块的写法也有好处,那就是更为灵活。

把成功情况和失败情况放在同一个块中,还有个优点:调用API的代码可能会在处理成功相应的过程中发现错误。比方说,返回的数据可能太短了。这种情况需要和网络数据获取器所认定的失败情况按同一方式处理。此时,如果采用单一块的写法,那么就能把这种情况和所认定的失败情况统一处理了。

总体来说,笔者建议使用同一块来处理成功与失败的情况,苹果公司似乎也是这样设计其API的。例如,Twitter框架中的TWRequest及MapKit框架中的MKLocalSearch都只是用一个handler块。

  • 有时需要在相关时间点执行回调操作,这种情况也可以使用handler块。
typedef void (^EOCNetworkFetcheProgressHandler)(float progress);

@property (nonatomic, copy) EOCNetworkFetcheProgressHandler progressHandler;
  • 基于handler来设计API还有个原因,就是某些代码必须运行在特定的线程上。
- (id <NSObject>)addObserverForName:(nullable NSString *)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;
要点
  1. 在创建对象时,可以使用内联的handler块将相关业务逻辑一并声明。

  2. 在有多个实例需要监控时,如果采用委托模式,那么经常需要根据传入的对象来切换,而若改用handler块来实现,则可直接将块与相关对象放在一起。

  3. 设计API时如果用到了handler块,那么可以增加一个参数,使调用者可通过此参数来决定应该把块安排在哪个队列上执行。

相关文章

网友评论

    本文标题:52个有效方法(39) - 用handler块降低代码分散程度

    本文链接:https://www.haomeiwen.com/subject/xwycgftx.html