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 源代碼中最難理解的部分。
原理分析: 消息轉發機制
當一個對象收到它沒實現的消息的時候。一般會發生例如以下的情況。
- 調用
+(BOOL)resolveInstanceMethod:(SEL)aSEL
,假設對象在這裏動態加入了selector 的實現方法,則消息轉發結束,否則運行步驟2 - 調用
- (id)forwardingTargetForSelector:(SEL)aSelector
,在這裏你能夠將消息轉發給其它對象。假設實現則消息轉發結束,否則運行步驟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 的方法
代理消息的轉發由 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 任務池
BlocksKit初見:一個支持將delegate轉換成block的Cocoa庫