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

簡介

BlocksKit 是一個開源的框架,對 Cocoa 進行了擴展,將許多須要經過 delegate 調用的方法轉換成了 block。在不少狀況下,blocks 比 delegate 要方便簡單,由於 block 是緊湊的,可使代碼簡潔,提升代碼可讀性,另外 block 還能夠進行異步處理。使用 block 要注意避免循環引用。ios

目錄結構

BlocksKit 的全部方法都以bk_開頭,這樣能夠方便地列出全部 BlocksKit 的全部方法。BlocksKit 主要目錄結構git

  • Core:存放 Foundation 相關的 Block category,如 NSObject、NSTimer、NSarray、NSDictionary、NSSet、NSIndexSet、NSMutableArray等
  • DynamicDelegate:動態代理(消息轉發機制)
  • UIKit:擴展了 UIAlertView,UIActionView,UIButton 等

最經常使用的是 UIKit Category,它爲 UIAlertView,UIActionSheet,UIButton,UITapGestureRecognizer 等提供了 blocks。github

用法實例

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,實現較簡單:ide

- (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 源碼中最難理解的部分。代理

原理分析: 消息轉發機制

當一個對象收到它沒實現的消息的時候,一般會發生以下的狀況。code

  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在運行時動態關聯了A2DynamicUIAlertViewDelegateget

@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 的方法

代理消息的轉發由 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 任務池

相關文章
相關標籤/搜索