衆所周知Block已被普遍用於iOS編程。它們一般被用做可併發執行的邏輯單元的封裝,或者做爲事件觸發的回調。Block比傳統回調函數有2點優點:git
基於以上種種優勢Cocoa Touch愈加支持Block式編程,這點從UIView的各類動畫效果可用Block實現就可見一斑。而BlocksKit是對Cocoa Touch Block編程更進一步的支持,它簡化了Block編程,發揮Block的相關優點,讓更多UIKit類支持Block式編程。BlocksKit是一個block的大雜燴,它給Fundation和UIKit框架裏不少的類都作了擴展,能夠經過調用相關類的擴展的方法簡單的實現一下幾個功能:github
block能夠幫助咱們組織獨立的代碼段,並提升複用性和可讀性。而BlocksKit能夠很簡單的實現block,實現回調,和通訊,能夠大大減小工做量。編程
在瀏覽器輸入https://github.com/zwaldowski/BlocksKit這個鏈接,進入下面這個頁面: 數組
點擊Clone or download,下載最新的BlocksKit-master;瀏覽器
下載下來的文件結構以下圖:
v2.2.5 數據結構
BlocksKit代碼存放在4個目錄中分別是Core、DynamicDelegate、MessageUI、UIKit。其中:併發
Core文件夾下面的代碼能夠分爲以下幾個部分:app
導入工程有兩種方式:框架
第一種
按照官方文檔描述,編譯成靜態庫,添加到本身的文件工程中。ide
第二種
把文件BlocksKit添加到本身工程文件中,而後修改部分.h文件;
修改規則以下:
把全部的
#import <BlocksKit/BlocksKit.h>
修改爲
#import "BlocksKit.h"
#import <BlocksKit/BlocksKit.h> #import <BlocksKit/UIActionSheet+BlocksKit.h> #import <BlocksKit/UIAlertView+BlocksKit.h> #import <BlocksKit/UIBarButtonItem+BlocksKit.h> #import <BlocksKit/UIControl+BlocksKit.h> #import <BlocksKit/UIGestureRecognizer+BlocksKit.h> #import <BlocksKit/UIImage+BlocksKit.h> #import <BlocksKit/UIImagePickerController+BlocksKit.h> #import <BlocksKit/UIPopoverController+BlocksKit.h> #import <BlocksKit/UITextField+BlocksKit.h> #import <BlocksKit/UIView+BlocksKit.h> #import <BlocksKit/UIWebView+BlocksKit.h> #import <BlocksKit/UITextField+BlocksKit.h> #import <BlocksKit/UITextView+BlocksKit.h> #import <BlocksKit/UIImagePickerController+BlocksKit.h>
#import "BlocksKit.h" #import "UIActionSheet+BlocksKit.h" #import "UIAlertView+BlocksKit.h" #import "UIBarButtonItem+BlocksKit.h" #import "UIControl+BlocksKit.h" #import "UIGestureRecognizer+BlocksKit.h" #import "UIImage+BlocksKit.h" #import "UIImagePickerController+BlocksKit.h" #import "UIPopoverController+BlocksKit.h" #import "UITextField+BlocksKit.h" #import "UIView+BlocksKit.h" #import "UIWebView+BlocksKit.h" #import "UITextField+BlocksKit.h" #import "UITextView+BlocksKit.h" #import "UIImagePickerController+BlocksKit.h"
作好上面的步驟以後,在代碼中使用就更簡單了,使用以前導入相應的頭文件
//Foundation框架: #import "BlocksKit.h" //UIKit框架: #import "BlocksKit+UIKit.h" //QuickLook框架: #import "BlocksKit+QuickLook.h" //MessageUI框架: #import "BlocksKit+MessageUI.h"
而後根據本身的須要調用相關類的擴展方法便可
無論是可變容器仍是不可變容器,容器相關的BlocksKit代碼整體上說是對容器原生block相關函數的封裝。容器相關的BlocksKit函數更加接近天然語義,有一種函數式編程和語義編程的感受。
//串行遍歷容器中全部元素 - (void)bk_each:(void (^)(id obj))block; //併發遍歷容器中全部元素(不要求容器中元素順次遍歷的時候可使用此種遍歷方式來提升遍歷速度) - (void)bk_apply:(void (^)(id obj))block; //返回第一個符合block條件(讓block返回YES)的對象 - (id)bk_match:(BOOL (^)(id obj))block; //返回全部符合block條件(讓block返回YES)的對象 - (NSArray *)bk_select:(BOOL (^)(id obj))block; //返回全部!!!不符合block條件(讓block返回YES)的對象 - (NSArray *)bk_reject:(BOOL (^)(id obj))block; //返回對象的block映射數組 - (NSArray *)bk_map:(id (^)(id obj))block; //查看容器是否有符合block條件的對象 //判斷是否容器中至少有一個元素符合block條件 - (BOOL)bk_any:(BOOL (^)(id obj))block; //判斷是否容器中全部元素都!!!不符合block條件 - (BOOL)bk_none:(BOOL (^)(id obj))block; //判斷是否容器中全部元素都符合block條件 - (BOOL)bk_all:(BOOL (^)(id obj))block;
NSString* str = [arr bk_match:^BOOL(id _Nonnull obj) { return ((NSString *)obj).length == 1; }]; NSArray* arr_01 = [arr bk_select:^BOOL(id _Nonnull obj) { return ((NSString *)obj).length == 1; }]; NSArray* arr_02 = [arr bk_reject:^BOOL(id _Nonnull obj) { return ((NSString *)obj).length == 1; }]; NSLog(@"str = %@",str); NSLog(@"arr_01 = %@",arr_01); NSLog(@"arr_02 = %@",arr_02);
2016-06-24 14:54:12.085 BlocksKitDemoTwo[12443:438922] str = 1 2016-06-24 14:54:12.085 BlocksKitDemoTwo[12443:438922] arr_01 = ( 222, 433 ) 2016-06-24 14:54:12.086 BlocksKitDemoTwo[12443:438922] arr_02 = ( 222, 433 )
/** Filters a mutable array to the objects matching the block. @param block A single-argument, BOOL-returning code block. @see <NSArray(BlocksKit)>bk_reject: */ //刪除容器中!!!不符合block條件的對象,即只保留符合block條件的對象 - (void)bk_performSelect:(BOOL (^)(id obj))block; //刪除容器中符合block條件的對象 - (void)bk_performReject:(BOOL (^)(id obj))block; //容器中的對象變換爲本身的block映射對象 - (void)bk_performMap:(id (^)(id obj))block;
關聯對象的做用以下:
在類的定義以外爲類增長額外的存儲空間。使用關聯,咱們能夠不用修改類的定義而爲其對象增長存儲空間。這在咱們沒法訪問到類的源碼的時候或者是考慮到二進制兼容性的時候是很是有用。關聯是基於關鍵字的,所以,咱們能夠爲任何對象增長任意多的關聯,每一個都使用不一樣的關鍵字便可。關聯是能夠保證被關聯的對象在關聯對象的整個生命週期都是可用的(ARC下也不會致使資源不可回收)。
關聯對象的例子,在咱們的實際項目中的常見用法通常有category中用關聯對象定義property,或者使用關聯對象綁定一個block。
關聯對象相關的BlocksKit是對objc_setAssociatedObject、objc_getAssociatedObject、objc_removeAssociatedObjects這幾個原生關聯對象函數的封裝。主要是封裝其其內存管理語義。
部分函數聲明以下
//@interface NSObject (BKAssociatedObjects) //以OBJC_ASSOCIATION_RETAIN_NONATOMIC方式綁定關聯對象 - (void)bk_associateValue:(id)value withKey:(const void *)key; //以OBJC_ASSOCIATION_COPY_NONATOMIC方式綁定關聯對象 - (void)bk_associateCopyOfValue:(id)value withKey:(const void *)key; //以OBJC_ASSOCIATION_RETAIN方式綁定關聯對象 - (void)bk_atomicallyAssociateValue:(id)value withKey:(const void *)key; //以OBJC_ASSOCIATION_COPY方式綁定關聯對象 - (void)bk_atomicallyAssociateCopyOfValue:(id)value withKey:(const void *)key; //弱綁定 - (void)bk_weaklyAssociateValue:(__autoreleasing id)value withKey:(const void *)key; //刪除全部綁定的關聯對象 - (void)bk_removeAllAssociatedObjects;
所謂邏輯執行,就是Block塊執行。邏輯執行相關的BlocksKit是對dispatch_after函數的封裝。使其更加符合語義。
主要函數以下
//@interface NSObject (BKBlockExecution) //主線程執行block方法,延遲時間可選 - (BKCancellationToken)bk_performAfterDelay:(NSTimeInterval)delay usingBlock:(void (^)(id obj))block; //後臺線程執行block方法,延遲時間可選 - (BKCancellationToken)bk_performInBackgroundAfterDelay:(NSTimeInterval)delay usingBlock:(void (^)(id obj))block; //全部執行block相關的方法都是此方法的簡化版,該函數在指定的block隊列上以指定的時間延遲執行block - (BKCancellationToken)bk_performOnQueue:(dispatch_queue_t)queue afterDelay:(NSTimeInterval)delay usingBlock:(void (^)(id obj))block; //取消block,很是牛逼!!!通常來講一個block加到block queue上是無法取消的,此方法實現了block的取消操做(必須是用BlocksKit投放的block) + (void)bk_cancelBlock:(id <NSObject, NSCopying>)block;
static id <NSObject, NSCopying> BKDispatchCancellableBlock(dispatch_queue_t queue, NSTimeInterval delay, void(^block)(void)) { dispatch_time_t time = BKTimeDelay(delay); #if DISPATCH_CANCELLATION_SUPPORTED if (BKSupportsDispatchCancellation()) { dispatch_block_t ret = dispatch_block_create(0, block); dispatch_after(time, queue, ret); return ret; } #endif //cancelled是個__block變量,使得該block在加入queue後可以邏輯上取消。注意,僅僅是邏輯上取消,不能把block從queue中剔除。 __block BOOL cancelled = NO; //在外部block之上加一層可以邏輯取消的代碼,使其變爲一個wrapper block //當調用wrapper(YES)的時候就讓__block BOOL cancelled = YES,使得之後每次block主體都被跳過。 void (^wrapper)(BOOL) = ^(BOOL cancel) { //cancel參數是爲了在外部可以控制cancelled _block變量 if (cancel) { cancelled = YES; return; } if (!cancelled) block(); }; //每一個投入queue中的block其實是wraper版的block dispatch_after(time, queue, ^{ //把cancel設置爲NO,block可以邏輯執行 wrapper(NO); }); //返回wraper block,以便bk_cancelBlock的時候使用 return wrapper; } + (void)bk_cancelBlock:(id <NSObject, NSCopying>)block { NSParameterAssert(block != nil); #if DISPATCH_CANCELLATION_SUPPORTED if (BKSupportsDispatchCancellation()) { dispatch_block_cancel((dispatch_block_t)block); return; } #endif //把cancel設置爲YES,修改block中_block cancelled變量,若是此時block未執行則,block在執行的時候其邏輯主體會被跳過 void (^wrapper)(BOOL) = (void(^)(BOOL))block; wrapper(YES); }
KVO主要涉及兩類對象,即「被觀察對象「和「觀察者「。
與「被觀察對象」相關的函數主要有以下兩個:
//添加觀察者 - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context; //刪除觀察者 - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context; //與「觀察者「相關的函數以下: //觀察到對象發生變化後的回調函數(觀察回調) - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
一般的KVO作法是先對「被觀察對象」添加「觀察者」,同時在「觀察者」中實現觀察回調。這樣每當「被觀察對象」的指定property改變時,「觀察者」就會調用觀察回調。
KVO相關BlocksKit弱化了「觀察者」這種對象,使得每當「被觀察對象」的指定property改變時,就會調起一個block。具體實現方式是定義一個_BKObserver類,讓該類實現觀察回調、對被觀察對象添加觀察者和刪除觀察者。
_BKObserver類定義以下:
@interface _BKObserver : NSObject { BOOL _isObserving; } //存儲「被觀察的對象」 @property (nonatomic, readonly, unsafe_unretained) id observee; @property (nonatomic, readonly) NSMutableArray *keyPaths; //存儲回調block @property (nonatomic, readonly) id task; @property (nonatomic, readonly) BKObserverContext context; - (id)initWithObservee:(id)observee keyPaths:(NSArray *)keyPaths context:(BKObserverContext)context task:(id)task; @end
static void *BKObserverBlocksKey = &BKObserverBlocksKey; static void *BKBlockObservationContext = &BKBlockObservationContext; @implementation _BKObserver - (id)initWithObservee:(id)observee keyPaths:(NSArray *)keyPaths context:(BKObserverContext)context task:(id)task { if ((self = [super init])) { _observee = observee; _keyPaths = [keyPaths mutableCopy]; _context = context; _task = [task copy]; } return self; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { //觀察者回調,在KV改變的時候調用相關block if (context != BKBlockObservationContext) return; @synchronized(self) { switch (self.context) { case BKObserverContextKey: { void (^task)(id) = self.task; task(object); break; } case BKObserverContextKeyWithChange: { void (^task)(id, NSDictionary *) = self.task; task(object, change); break; } case BKObserverContextManyKeys: { void (^task)(id, NSString *) = self.task; task(object, keyPath); break; } case BKObserverContextManyKeysWithChange: { void (^task)(id, NSString *, NSDictionary *) = self.task; task(object, keyPath, change); break; } } } } //開啓KV觀察 - (void)startObservingWithOptions:(NSKeyValueObservingOptions)options { @synchronized(self) { if (_isObserving) return; [self.keyPaths bk_each:^(NSString *keyPath) { //observee的被觀察對象,observer是本身, [self.observee addObserver:self forKeyPath:keyPath options:options context:BKBlockObservationContext]; }]; _isObserving = YES; } } //中止KV觀察 - (void)stopObservingKeyPath:(NSString *)keyPath { NSParameterAssert(keyPath); @synchronized (self) { if (!_isObserving) return; if (![self.keyPaths containsObject:keyPath]) return; NSObject *observee = self.observee; if (!observee) return; [self.keyPaths removeObject: keyPath]; keyPath = [keyPath copy]; if (!self.keyPaths.count) { _task = nil; _observee = nil; _keyPaths = nil; } [observee removeObserver:self forKeyPath:keyPath context:BKBlockObservationContext]; } } @end
使用BlocksKit
- (void)bk_addObserverForKeyPaths:(NSArray *)keyPaths identifier:(NSString *)token options:(NSKeyValueObservingOptions)options task:(void (^)(id obj, NSString *keyPath, NSDictionary *change))task; - (void)bk_removeObserverForKeyPath:(NSString *)keyPath identifier:(NSString *)token;
NSTimer有個比較噁心的特性,它會持有它的target。好比在一個controller中使用了timer,而且timer的target設置爲該controller自己,那麼想在controller的dealloc中fire掉timer是作不到的,必需要在其餘的地方fire。這會讓編碼很難受。具體參考《Effective Objective C》的最後一條。 BlocksKit解除這種噁心,其方式是把timer的target設置爲timer 的class對象。把要執行的block保存在timer的userInfo中執行。由於timer 的class對象一直存在,因此是否被持有其實無所謂。
實現代碼以下:
//"Replaced with -bk_performAfterDelay:usingBlock:" + (id)bk_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats { NSParameterAssert(block != nil); return [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats]; } + (id)bk_timerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats { NSParameterAssert(block != nil); return [self timerWithTimeInterval:inTimeInterval target:self selector:@selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats]; } + (void)bk_executeBlockFromTimer:(NSTimer *)aTimer { void (^block)(NSTimer *) = [aTimer userInfo]; if (block) block(aTimer); }
代理是objective c裏經常使用的模式,主要用來作邏輯切分,一個類作一類事情,讓代碼的耦合度減小。但他不方便的地方在於,要建立一個代理,就要定義一個類,聲明這個類遵循那些接口,而後實現這些接口對應的函數。動態代理(Dynamic delegate)則讓咱們可以在code裏,on the fly的建立這樣一個代理,經過block定義要實現的方法。
例如:
- (void)annoyUser { // 建立一個alert view UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Hello World!" message:@"This alert's delegate is implemented using blocks. That's so cool!" delegate:nil cancelButtonTitle:@"Meh." otherButtonTitles:@"Woo!", nil]; // 獲取該alert view的動態代理對象(什麼是動態代理對象稍後會說) A2DynamicDelegate *dd = alertView.bk_dynamicDelegate; // 調用動態代理對象的 - (void)implementMethod:(SEL)selector withBlock:(id)block;方法,使得SEL映射一個block對象(假設叫作block1) [dd implementMethod:@selector(alertViewShouldEnableFirstOtherButton:) withBlock:^(UIAlertView *alertView) { NSLog(@"Message: %@", alertView.message); return YES; }]; // 同上,讓映射-alertView:willDismissWithButtonIndex:的SEL到另一個block對象(假設叫作block2) [dd implementMethod:@selector(alertView:willDismissWithButtonIndex:) withBlock:^(UIAlertView *alertView, NSInteger buttonIndex) { NSLog(@"You pushed button #%d (%@)", buttonIndex, [alertView buttonTitleAtIndex:buttonIndex]); }]; // 把alertView的delegate設置爲動態代理 alertView.delegate = dd; [alertView show]; } // 那麼,alert view在顯示的時候收到alertViewShouldEnableFirstOtherButton:消息調用block1;alert view在消失的時候收到alertView:willDismissWithButtonIndex:消息,調用block2
拿UIControl打比方,要想處理一個事件:
- (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;
須要經過上述方法將某一個對象的某一個selector傳入,通常的作法是在viewcontroller裏定義一個方法專門處理某一個按鈕的點擊事件。
- (void)bk_addEventHandler:(void (^)(id sender))handler forControlEvents:(UIControlEvents)controlEvents;
經過上述方法將一個block註冊上去,不須要單獨定義方法。
例如:
[btn bk_addEventHandler:^(id _Nonnull sender) { NSLog(@"111"); } forControlEvents:UIControlEventTouchUpInside];