转载

BlocksKit初见:一个支持将delegate转换成block的Cocoa库

简介

  • 项目主页: https://github.com/zwaldowski/BlocksKit

BlocksKit 是一个开源的框架,对 Cocoa 进行了扩展,将许多需要通过 delegate 调用的方法转换成了 block。在很多情况下,blocks 比 delegate 要方便简单,因为 block 是紧凑的,可以使代码简洁,提高代码可读性,另外 block 还可以进行异步处理。使用 block 要注意避免循环引用。

目录结构

BlocksKit 的所有方法都以 bk_ 开头,这样可以方便地列出所有 BlocksKit 的所有方法。BlocksKit 主要目录结构

  • Core:存放 Foundation 相关的 Block category,如 NSObject、NSTimer、NSarray、NSDictionary、NSSet、NSIndexSet、NSMutableArray等
  • DynamicDelegate:动态代理(消息转发机制)
  • UIKit:扩展了 UIAlertView,UIActionView,UIButton 等

最常用的是 UIKit Category,它为 UIAlertView,UIActionSheet,UIButton,UITapGestureRecognizer 等提供了 blocks。

用法实例

UIAlertView 和 UIActionSheet 用法示例:

UIAlertView *alertView = [[UIAlertView alloc] bk_initWithTitle:@"提示" message:@"提示信息"]; [alertView bk_setCancelButtonWithTitle:@"取消" handler:nil]; [alertView bk_addButtonWithTitle:@"确定" handler:nil]; [alertView bk_setDidDismissBlock:^(UIAlertView *alert, NSInteger index) {     if (index == 1) {         NSLog(@"%ld clicked",index);     } }]; [alertView show];
[[UIActionSheet bk_actionSheetCustomWithTitle:nil buttonTitles:@[@"查看", @"退出"] destructiveTitle:nil cancelTitle:@"取消" andDidDismissBlock:^(UIActionSheet *sheet, NSInteger index) {          }] showInView:self.view]; 

UIButton 和 UITapGestureRecognizer 用法示例:

UIButton *button = [[UIButton alloc] init]; [button bk_addEventHandler:^(id sender) {  } forControlEvents:UIControlEventTouchUpInside]; 
UITapGestureRecognizer *tapGestureRecognizer = [UITapGestureRecognizer bk_recognizerWithHandler:^(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location) {     if (state == UIGestureRecognizerStateRecognized) {         ...     } }];

UIButton 和 UIGesture 将 target-action 转换成 block,实现较简单:

- (id)bk_initWithHandler:(void (^)(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location))block delay:(NSTimeInterval)delay {     self = [self initWithTarget:self action:@selector(bk_handleAction:)];     if (!self) return nil;      self.bk_handler = block;     self.bk_handlerDelay = delay;      return self; }  - (void)bk_handleAction:(UIGestureRecognizer *)recognizer {     void (^handler)(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location) = recognizer.bk_handler;     if (!handler) return;          ...      if (!delay) {         block();         return;     }      ... }

delegate 转换成 block 实际上使用了消息转发机制,是 BlocksKit 源码中最难理解的部分。

原理分析: 消息转发机制

当一个对象收到它没实现的消息的时候,通常会发生如下的情况。

  1. 调用 +(BOOL)resolveInstanceMethod:(SEL)aSEL ,如果对象在这里动态添加了selector 的实现方法,则消息转发结束,否则执行步骤2
  2. 调用 - (id)forwardingTargetForSelector:(SEL)aSelector ,在这里你可以将消息转发给其他对象,如果实现则消息转发结束,否则执行步骤3
  3. 执行完整的消息转发机制,调用 -(void)forwardInvocation:(NSInvocation *)invocation 在这一步,你可以修改消息的任何内容,包括目标(target),selector,参数。如果没有实现在这里还未实现转发则程序将抛出异常。

原理实例分析

BlocksKit 动态代理实现方式是最后一步,即 -(void)forwardInvocation:(NSInvocation *)invocation ,使得动态代理能够接受任意消息。

以UIAlertView为例,UIAlertView在运行时动态关联了A2DynamicUIAlertViewDelegate

@implementation UIAlertView (BlocksKit)  @dynamic bk_willShowBlock, bk_didShowBlock, bk_willDismissBlock, bk_didDismissBlock, bk_shouldEnableFirstOtherButtonBlock;  + (void)load {     @autoreleasepool {         [self bk_registerDynamicDelegate];         [self bk_linkDelegateMethods:@{             @"bk_willShowBlock": @"willPresentAlertView:",             @"bk_didShowBlock": @"didPresentAlertView:",             @"bk_willDismissBlock": @"alertView:willDismissWithButtonIndex:",             @"bk_didDismissBlock": @"alertView:didDismissWithButtonIndex:",             @"bk_shouldEnableFirstOtherButtonBlock": @"alertViewShouldEnableFirstOtherButton:"         }];     } }

A2DynamicUIAlertViewDelegate 是 A2DynamicDelegate 的子类,并实现了UIAlertViewDelegate 的方法

BlocksKit初见:一个支持将delegate转换成block的Cocoa库

代理消息的转发由 A2DynamicDelegate 完成

- (void)forwardInvocation:(NSInvocation *)outerInv {     SEL selector = outerInv.selector;     A2BlockInvocation *innerInv = nil;     if ((innerInv = [self.invocationsBySelectors bk_objectForSelector:selector])) {         [innerInv invokeWithInvocation:outerInv];     } else if ([self.realDelegate respondsToSelector:selector]) {         [outerInv invokeWithTarget:self.realDelegate];     } }

注: 文章由我们 iOS122( http://www.ios122.com)的小伙伴 @鱼 整理,喜欢就一起参与: iOS122 任务池

正文到此结束
Loading...