BlocksKit初見:一個支持將delegate轉換成block的Cocoa庫

jopen 8年前發布 | 12K 次閱讀 BlocksKit Objective-C開發

簡介

  • 項目主頁: https://github.com/zwaldowski/BlocksKit
  • </ul>

    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 任務池

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!