神奇的 BlocksKit (一)

關注倉庫,及時得到更新:iOS-Source-Code-Analyze
Follow: Draveness · Githubhtml

高能預警:本篇文章很是長,由於 BlocksKit 的實現仍是比較複雜和有意的。這篇文章不是爲了剖析 iOS 開發中的 block 的實現以及它是如何組成甚至使用的,若是你想經過這篇文章來了解 block 的實現,它並不能幫到你。ios

Block 究竟是什麼?這多是困擾不少 iOS 初學者的一個問題。若是你在 Google 上搜索相似的問題時,能夠查找到幾十萬條結果,block 在 iOS 開發中有着很是重要的地位,並且它的做用也愈來愈重要。git


概述

這篇文章僅對 BlocksKit v2.2.5 的源代碼進行分析,從框架的內部理解下面的功能是如何實現的:github

  • NSArrayNSDictionaryNSSet 等集合類型以及對應的可變集合類型 NSMutableArrayNSMutableDictionaryNSMutableSet 添加 bk_each: 等方法完成對集合中元素的快速遍歷數組

  • 使用 block 對 NSObject 對象 KVO安全

  • UIView 對象添加 bk_whenTapped: 等方法快速添加手勢app

  • 使用 block 替換 UIKit 中的 delegate ,涉及到核心模塊 DynamicDelegate框架

BlocksKit 框架中包括但不只限於上述的功能,這篇文章是對 v2.2.5 版本源代碼的分析,其它版本的功能不會在本篇文章中具體討論。異步

如何提供簡潔的遍歷方法

BlocksKit 實現的最簡單的功能就是爲集合類型添加方法遍歷集合中的元素。ide

[@[@1,@2,@3] bk_each:^(id obj) {
    NSLog(@"%@",obj);
}];

這段代碼很是簡單,咱們可使用 enumerateObjectsUsingBlock: 方法替代 bk_each: 方法:

[@[@1,@2,@3] enumerateObjectsUsingBlock:^(id obj,NSUInteger idx,BOOL *stop) {
    NSLog(@"%@",obj);
}];

2016-03-05 16:02:57.295 Draveness[10725:453402] 1
2016-03-05 16:02:57.296 Draveness[10725:453402] 2
2016-03-05 16:02:57.297 Draveness[10725:453402] 3

這部分代碼的實現也沒什麼難度:

- (void)bk_each:(void (^)(id obj))block
{
    NSParameterAssert(block != nil);

    [self enumerateObjectsUsingBlock:^(id obj,NSUInteger idx,BOOL *stop) {
        block(obj);
    }];
}

它在 block 執行前會判斷傳進來的 block 是否爲空,而後就是調用遍歷方法,把數組中的每個 obj 傳給 block。

BlocksKit 在這些集合類中所添加的一些方法在 Ruby、Haskell 等語言中也一樣存在。若是你接觸過上面的語言,理解這裏方法的功能也就更容易了,不過這不是這篇文章關注的主要內容。

// NSArray+BlocksKit.h

- (void)bk_each:(void (^)(id obj))block;
- (void)bk_apply:(void (^)(id obj))block;
- (id)bk_match:(BOOL (^)(id obj))block;
- (NSArray *)bk_select:(BOOL (^)(id obj))block;
- (NSArray *)bk_reject:(BOOL (^)(id obj))block;
- (NSArray *)bk_map:(id (^)(id obj))block;
- (id)bk_reduce:(id)initial withBlock:(id (^)(id sum,id obj))block;
- (NSInteger)bk_reduceInteger:(NSInteger)initial withBlock:(NSInteger(^)(NSInteger result,id obj))block;
- (CGFloat)bk_reduceFloat:(CGFloat)inital withBlock:(CGFloat(^)(CGFloat result,id obj))block;
- (BOOL)bk_any:(BOOL (^)(id obj))block;
- (BOOL)bk_none:(BOOL (^)(id obj))block;
- (BOOL)bk_all:(BOOL (^)(id obj))block;
- (BOOL)bk_corresponds:(NSArray *)list withBlock:(BOOL (^)(id obj1,id obj2))block;

NSObject 上的魔法

NSObject 是 iOS 中的『上帝類』。

NSObject 上添加的方法幾乎會添加到 Cocoa Touch 中的全部類上,關於 NSObject 的討論和總共分爲如下三部分進行:

  1. AssociatedObject

  2. BlockExecution

  3. BlockObservation

添加 AssociatedObject

常常跟 runtime 打交道的人不可能不知道 AssociatedObject ,當咱們想要爲一個已經存在的類添加屬性時,就須要用到 AssociatedObject 爲類添加屬性,而 BlocksKit 提供了更簡單的方法來實現,不須要新建一個分類。

NSObject *test = [[NSObject alloc] init];
[test bk_associateValue:@"Draveness" withKey:@" name"];
NSLog(@"%@",[test bk_associatedValueForKey:@"name"]);

2016-03-05 16:02:25.761 Draveness[10699:452125] Draveness

這裏咱們使用了 bk_associateValue:withKey:bk_associatedValueForKey: 兩個方法設置和獲取 name 對應的值 Draveness.

- (void)bk_associateValue:(id)value withKey:(const void *)key
{
    objc_setAssociatedObject(self,key,value,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

這裏的 OBJC_ASSOCIATION_RETAIN_NONATOMIC 表示當前屬性爲 retain nonatomic 的,還有其它的參數以下:

/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t,objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,          /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,/**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,      /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

上面的這個 NS_ENUM 也沒什麼好說的,須要注意的是這裏沒有 weak 屬性。

BlocksKit 經過另外一種方式實現了『弱屬性』:

- (void)bk_weaklyAssociateValue:(__autoreleasing id)value withKey:(const void *)key
{
    _BKWeakAssociatedObject *assoc = objc_getAssociatedObject(self,key);
    if (!assoc) {
        assoc = [_BKWeakAssociatedObject new];
        [self bk_associateValue:assoc withKey:key];
    }
    assoc.value = value;
}

在這裏先獲取了一個 _BKWeakAssociatedObject 對象 assoc,而後更新這個對象的屬性 value

由於直接使用 AssociatedObject 不能爲對象添加弱屬性,因此在這裏添加了一個對象,而後讓這個對象持有一個弱屬性:

@interface _BKWeakAssociatedObject : NSObject

@property (nonatomic,weak) id value;

@end

@implementation _BKWeakAssociatedObject

@end

這就是 BlocksKit 實現弱屬性的方法,我以爲這個實現的方法仍是比較簡潔的。

getter 方法的實現也很是相似:

- (id)bk_associatedValueForKey:(const void *)key
{
    id value = objc_getAssociatedObject(self,key);
    if (value && [value isKindOfClass:[_BKWeakAssociatedObject class]]) {
        return [(_BKWeakAssociatedObject *)value value];
    }
    return value;
}

在任意對象上執行 block

經過這個類提供的一些接口,能夠在任意對象上快速執行線程安全、異步的 block,並且這些 block 也能夠在執行以前取消。

- (id <NSObject,NSCopying>)bk_performOnQueue:(dispatch_queue_t)queue afterDelay:(NSTimeInterval)delay usingBlock:(void (^)(id obj))block
{
    NSParameterAssert(block != nil);
    
    return BKDispatchCancellableBlock(queue,delay,^{
        block(self);
    });
}

判斷 block 是否爲空在這裏都是細枝末節,這個方法中最關鍵的也就是它返回了一個能夠取消的 block,而這個 block 就是用靜態函數 BKDispatchCancellableBlock 生成的。

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
    
    __block BOOL cancelled = NO;
    void (^wrapper)(BOOL) = ^(BOOL cancel) {
        if (cancel) {
            cancelled = YES;
            return;
        }
        if (!cancelled) block();
    };
    
    dispatch_after(time,queue,^{
        wrapper(NO);
    });
    
    return wrapper;
}

這個函數首先會執行 BKSupportsDispatchCancellation 來判斷當前平臺和版本是否支持使用 GCD 取消 block,固然通常都是支持的:

  • 函數返回的是 YES,那麼在 block 被派發到指定隊列以後就會返回這個 dispatch_block_t 類型的 block

  • 函數返回的是 NO,那麼就會就會手動包裝一個能夠取消的 block,具體實現的部分以下:

__block BOOL cancelled = NO;
void (^wrapper)(BOOL) = ^(BOOL cancel) {
    if (cancel) {
        cancelled = YES;
        return;
    }
    if (!cancelled) block();
};
    
dispatch_after(time,queue,^{
    wrapper(NO);
});
    
return wrapper;

上面這部分代碼就先建立一個 wrapper block,而後派發到指定隊列,派發到指定隊列的這個 block 是必定會執行的,可是怎麼取消這個 block 呢?

若是當前 block 沒有執行,咱們在外面調用一次 wrapper(YES) 時,block 內部的 cancelled 變量就會被設置爲 YES,因此 block 就不會執行。

  1. dispatch_after --- cancelled = NO

  2. wrapper(YES) --- cancelled = YES

  3. wrapper(NO) --- cancelled = YES block 不會執行

這是實現取消的關鍵部分:

+ (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
    
    void (^wrapper)(BOOL) = (void(^)(BOOL))block;
    wrapper(YES);
}
  • GCD 支持取消 block,那麼直接調用 dispatch_block_cancel 函數取消 block

  • GCD 不支持取消 block 那麼調用一次 wrapper(YES)

使用 Block 封裝 KVO

BlocksKit 對 KVO 的封裝由兩部分組成:

  1. NSObject 的分類負責提供便利方法

  2. 私有類 _BKObserver 具體實現原生的 KVO 功能

提供接口並在 dealloc 時中止 BlockObservation

NSObject+BKBlockObservation 這個分類中的大部分接口都會調用這個方法:

- (void)bk_addObserverForKeyPaths:(NSArray *)keyPaths identifier:(NSString *)identifier options:(NSKeyValueObservingOptions)options context:(BKObserverContext)context task:(id)task
{
    #1: 檢查參數,省略
    
    #2: 使用神奇的方法在分類中覆寫 dealloc

    NSMutableDictionary *dict;
    _BKObserver *observer = [[_BKObserver alloc] initWithObservee:self keyPaths:keyPaths context:context task:task];
    [observer startObservingWithOptions:options];
    
    #3: 惰性初始化 bk_observerBlocks 也就是下面的 dict,省略

    dict[identifier] = observer;
}

咱們不會在這裏討論 #1#3 部分,再詳細閱讀 #2 部分代碼以前,先來看一下這個省略了絕大部分細節的核心方法。

使用傳入方法的參數建立了一個 _BKObserver 對象,而後調用 startObservingWithOptions: 方法開始 KVO 觀測相應的屬性,而後以 {identifier,obeserver} 的形式存到字典中保存。

這裏實在沒什麼新意,咱們在下一小節中會介紹 startObservingWithOptions: 這一方法。

在分類中調劑 dealloc 方法

這個問題我以爲是很是值得討論的一個問題,也是我最近在寫框架時遇到很棘手的一個問題。

當咱們在分類中註冊一些通知或者使用 KVO 時,頗有可能會找不到註銷這些通知的時機。

由於在分類中是沒法直接實現 dealloc 方法的。 在 iOS8 以及以前的版本,若是某個對象被釋放了,可是剛對象的註冊的通知沒有被移除,那麼當事件再次發生,就會向已經釋放的對象發出通知,整個程序就會崩潰。

這裏解決的辦法就十分的巧妙:

Class classToSwizzle = self.class;
// 獲取全部修改過 dealloc 方法的類
NSMutableSet *classes = self.class.bk_observedClassesHash;

// 保證互斥避免 classes 出現難以預測的結果
@synchronized (classes) {

   // 獲取當前類名,並判斷是否修改過 dealloc 方法以減小這部分代碼的調用次數
   NSString *className = NSStringFromClass(classToSwizzle);
   if (![classes containsObject:className]) {
       // 這裏的 sel_registerName 方法會返回 dealloc 的 selector,由於 dealloc 已經註冊過
       SEL deallocSelector = sel_registerName("dealloc");
       
        __block void (*originalDealloc)(__unsafe_unretained id,SEL) = NULL;

       // 實現新的 dealloc 方法
        id newDealloc = ^(__unsafe_unretained id objSelf) {
           //在方法 dealloc 以前移除全部 observer
           [objSelf bk_removeAllBlockObservers];
           
           if (originalDealloc == NULL) {
               // 若是原有的 dealloc 方法沒有被找到就會查找父類的 dealloc 方法,調用父類的 dealloc 方法
               struct objc_super superInfo = {
                   .receiver = objSelf,
                   .super_class = class_getSuperclass(classToSwizzle)
               };
               
               void (*msgSend)(struct objc_super *,SEL) = (__typeof__(msgSend))objc_msgSendSuper;
               msgSend(&superInfo,deallocSelector);
           } else {
               // 若是 dealloc 方法被找到就會直接調用該方法,並傳入參數
               originalDealloc(objSelf,deallocSelector);
           }
       };

       // 構建選擇子實現 IMP
       IMP newDeallocIMP = imp_implementationWithBlock(newDealloc);

       // 向當前類添加方法,可是多半不會成功,由於類已經有 dealloc 方法
       if (!class_addMethod(classToSwizzle,deallocSelector,newDeallocIMP,"v@:")) {
           // 獲取原有 dealloc 實例方法
           Method deallocMethod = class_getInstanceMethod(classToSwizzle,deallocSelector);
           
           // 存儲 dealloc 方法實現防止在 set 的過程當中調用該方法
           originalDealloc = (void(*)(__unsafe_unretained id,SEL))method_getImplementation(deallocMethod);
           
           // 從新設置 dealloc 方法的實現,並存儲到 originalDealloc 防止方法實現改變
           originalDealloc = (void(*)(__unsafe_unretained id,SEL))method_setImplementation(deallocMethod,newDeallocIMP);
       }

       // 將當前類名添加到已經改變的類的集合中
       [classes addObject:className];
   }
}

這部分代碼的執行順序以下:

  1. 首先調用 bk_observedClassesHash 類方法獲取全部修改過 dealloc 方法的類的集合 classes

  2. 使用 @synchronized (classes) 保證互斥,避免同時修改 classes 集合的類過多出現意料以外的結果

  3. 判斷即將調劑方法的類 classToSwizzle 是否調劑過 dealloc 方法

  4. 若是 dealloc 方法沒有調劑過,就會經過 sel_registerName("dealloc") 方法獲取選擇子,這行代碼並不會真正註冊 dealloc 選擇子而是會獲取 dealloc 的選擇子,具體緣由能夠看這個方法的實現 sel_registerName

  5. 在新的 dealloc添加移除 Observer 的方法, 再調用原有的 dealloc

id newDealloc = ^(__unsafe_unretained id objSelf) {
        [objSelf bk_removeAllBlockObservers];
  
       if (originalDealloc == NULL) {
            struct objc_super superInfo = {
                 .receiver = objSelf,
                .super_class = class_getSuperclass(classToSwizzle)
            };
            void (*msgSend)(struct objc_super *,SEL) = (__typeof__(msgSend))objc_msgSendSuper;
            msgSend(&superInfo,deallocSelector);
        } else {
            originalDealloc(objSelf,deallocSelector);
        }
    };
    IMP newDeallocIMP = imp_implementationWithBlock(newDealloc);
    1. 調用 bk_removeAllBlockObservers 方法移除全部觀察者,也就是這段代碼的最終目的

    2. 根據 originalDealloc 是否爲空,決定是向父類發送消息,仍是直接調用 originalDealloc 並傳入 objSelf,deallocSelector 做爲參數

    1. 在咱們得到了新 dealloc 方法的選擇子和 IMP 時,就要改變原有的 dealloc 的實現了

      if (!class_addMethod(classToSwizzle,deallocSelector,newDeallocIMP,"v@:")) {
             // The class already contains a method implementation.
             Method deallocMethod = class_getInstanceMethod(classToSwizzle,deallocSelector);
      
            // We need to store original implementation before setting new implementation
             // in case method is called at the time of setting.
             originalDealloc = (void(*)(__unsafe_unretained id,SEL))method_getImplementation(deallocMethod);
         
            // We need to store original implementation again,in case it just changed.
             originalDealloc = (void(*)(__unsafe_unretained id,SEL))method_setImplementation(deallocMethod,newDeallocIMP);
         }
      1. 調用 class_addMethod 方法爲當前類添加選擇子爲 dealloc 的方法(固然 99.99% 的可能不會成功)

      2. 獲取原有的 dealloc 實例方法

      3. 將原有的實現保存到 originalDealloc 中,防止使用 method_setImplementation 從新設置該方法的過程當中調用 dealloc 致使無方法可用

      4. 從新設置 dealloc 方法的實現。一樣,將實現存儲到 originalDealloc 中防止實現改變

    關於在分類中調劑 dealloc 方法的這部分到這裏就結束了,下一節將繼續分析私有類 _BKObserver

    私有類 _BKObserver

    _BKObserver 是用來觀測屬性的對象,它在接口中定義了 4 個屬性:

    @property (nonatomic,readonly,unsafe_unretained) id observee;
    @property (nonatomic,readonly) NSMutableArray *keyPaths;
    @property (nonatomic,readonly) id task;
    @property (nonatomic,readonly) BKObserverContext context;

    上面四個屬性的具體做用在這裏不說了,上面的 bk_addObserverForKeyPaths:identifier:options:context: 方法中調用 _BKObserver 的初始化方法 initWithObservee:keyPaths:context:task: 太簡單了也不說了。

    _BKObserver *observer = [[_BKObserver alloc] initWithObservee:self keyPaths:keyPaths context:context task:task];
    [observer startObservingWithOptions:options];

    上面的第一行代碼生成一個 observer 實例以後馬上調用了 startObservingWithOptions: 方法開始觀測對應的 keyPath

    - (void)startObservingWithOptions:(NSKeyValueObservingOptions)options
    {
        @synchronized(self) {
            if (_isObserving) return;
            
            #1:遍歷 keyPaths 實現 KVO
    
            _isObserving = YES;
        }
    }

    startObservingWithOptions: 方法最重要的就是第 #1 部分:

    [self.keyPaths bk_each:^(NSString *keyPath) {
        [self.observee addObserver:self forKeyPath:keyPath options:options context:BKBlockObservationContext];
    }];

    遍歷本身的 keyPaths 而後讓 _BKObserver 做觀察者觀察本身,而後傳入對應的 keyPath

    關於 _stopObservingLocked 方法的實現也十分的類似,這裏就不說了。

    [keyPaths bk_each:^(NSString *keyPath) {
        [observee removeObserver:self forKeyPath:keyPath context:BKBlockObservationContext];
    }];

    到目前爲止,咱們尚未看到實現 KVO 所必須的方法 observeValueForKeyPath:ofObject:change:context,這個方法就是每次屬性改變以後的回調:

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
        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;
                }
            }
        }
    }

    這個方法的實現也很簡單,根據傳入的 context 值,對 task 類型轉換,並傳入具體的值。

    這個模塊倒着就介紹完了,在下一節會介紹 BlocksKit 對 UIKit 組件一些簡單的改造。

    改造 UIKit

    在這個小結會具體介紹 BlocksKit 是如何對一些簡單的控件進行改造的,本節大約有兩部份內容:

    • UIGestureRecongizer + UIBarButtonItem + UIControl

    • UIView

    改造 UIGestureRecongizer,UIBarButtonItem 和 UIControl

    先來看一個 UITapGestureRecognizer 使用的例子

    UITapGestureRecognizer *singleTap = [UITapGestureRecognizer bk_recognizerWithHandler:^(id sender) {
         NSLog(@"Single tap.");
     } delay:0.18];
     [self addGestureRecognizer:singleTap];

    代碼中的 bk_recognizerWithHandler:delay: 方法在最後都會調用初始化方法 bk_initWithHandler:delay: 生成一個 UIGestureRecongizer 的實例

    - (instancetype)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;
    }

    它會在這個方法中傳入 targetselector。 其中 target 就是 self,而 selector 也會在這個分類中實現:

    - (void)bk_handleAction:(UIGestureRecognizer *)recognizer
    {
        void (^handler)(UIGestureRecognizer *sender,UIGestureRecognizerState state,CGPoint location) = recognizer.bk_handler;
        if (!handler) return;
        
        NSTimeInterval delay = self.bk_handlerDelay;
        
        #1: 封裝 block 並控制 block 是否能夠執行
    
        self.bk_shouldHandleAction = YES;
    
        [NSObject bk_performAfterDelay:delay usingBlock:block];
    }

    由於在初始化方法 bk_initWithHandler:delay: 中保存了當前手勢的 bk_handler,因此直接調用在 Block Execution 一節中提到過的 bk_performAfterDelay:usingBlock: 方法,將 block 派發到指定的隊列中,最終完成對 block 的調用。

    封裝 block 並控制 block 是否能夠執行

    這部分代碼和前面的部分有些類似,由於這裏也用到了一個屬性 bk_shouldHandleAction 來控制 block 是否會被執行:

    CGPoint location = [self locationInView:self.view];
    void (^block)(void) = ^{
        if (!self.bk_shouldHandleAction) return;
        handler(self,self.state,location);
    };

    ====

    一樣 UIBarButtonItemUIControl 也是用了幾乎相同的機制,把 target 設置爲 self,讓後在分類的方法中調用指定的 block。

    UIControlWrapper

    稍微有些不一樣的是 UIControl。由於 UIControl 有多種 UIControlEvents,因此使用另外一個類 BKControlWrapper 來封裝 handlercontrolEvents

    @property (nonatomic) UIControlEvents controlEvents;
    @property (nonatomic,copy) void (^handler)(id sender);

    其中 UIControlWrapper 對象以 {controlEvents,wrapper} 的形式做爲 UIControl 的屬性存入字典。

    改造 UIView

    由於在上面已經改造過了 UIGestureRecognizer,在這裏改造 UIView 就變得很容易了:

    - (void)bk_whenTouches:(NSUInteger)numberOfTouches tapped:(NSUInteger)numberOfTaps handler:(void (^)(void))block
    {
        if (!block) return;
        
        UITapGestureRecognizer *gesture = [UITapGestureRecognizer bk_recognizerWithHandler:^(UIGestureRecognizer *sender,UIGestureRecognizerState state,CGPoint location) {
            if (state == UIGestureRecognizerStateRecognized) block();
        }];
        
        gesture.numberOfTouchesRequired = numberOfTouches;
        gesture.numberOfTapsRequired = numberOfTaps;
    
        [self.gestureRecognizers enumerateObjectsUsingBlock:^(id obj,NSUInteger idx,BOOL *stop) {
            if (![obj isKindOfClass:[UITapGestureRecognizer class]]) return;
    
            UITapGestureRecognizer *tap = obj;
            BOOL rightTouches = (tap.numberOfTouchesRequired == numberOfTouches);
            BOOL rightTaps = (tap.numberOfTapsRequired == numberOfTaps);
            if (rightTouches && rightTaps) {
                [gesture requireGestureRecognizerToFail:tap];
            }
        }];
    
        [self addGestureRecognizer:gesture];
    }

    UIView 分類只有這一個核心方法,其它的方法都是向這個方法傳入不一樣的參數,這裏須要注意的就是。它會遍歷全部的 gestureRecognizers,而後把對全部有衝突的手勢調用 requireGestureRecognizerToFail: 方法,保證添加的手勢可以正常的執行。

    因爲這篇文章中的內容較多,因此內容分紅了兩個部分,下一部分介紹的是 BlocksKit 中的最重要的部分動態代理:

    關注倉庫,及時得到更新:iOS-Source-Code-Analyze
    Follow: Draveness · Github

    相關文章
    相關標籤/搜索