【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

神奇的 BlocksKit (一)

  • 2019-12-10
  • 本文字数:10585 字

    阅读完需:约 35 分钟

神奇的 BlocksKit (一)

关注仓库,及时获得更新:iOS-Source-Code-Analyze


高能预警:本篇文章非常长,因为 BlocksKit 的实现还是比较复杂和有意的。这篇文章不是为了剖析 iOS 开发中的 block 的实现以及它是如何组成甚至使用的,如果你想通过这篇文章来了解 block 的实现,它并不能帮到你。


Block 到底是什么?这可能是困扰很多 iOS 初学者的一个问题。如果你在 Google 上搜索类似的问题时,可以查找到几十万条结果,block 在 iOS 开发中有着非常重要的地位,而且它的作用也越来越重要。



概述

这篇文章仅对 BlocksKit v2.2.5 的源代码进行分析,从框架的内部理解下面的功能是如何实现的:


  • NSArrayNSDictionaryNSSet 等集合类型以及对应的可变集合类型 NSMutableArrayNSMutableDictionaryNSMutableSet 添加 bk_each: 等方法完成对集合中元素的快速遍历

  • 使用 block 对 NSObject 对象 KVO

  • UIView 对象添加 bk_whenTapped: 等方法快速添加手势

  • 使用 block 替换 UIKit 中的 delegate ,涉及到核心模块 DynamicDelegate


BlocksKit 框架中包括但不仅限于上述的功能,这篇文章是对 v2.2.5 版本源代码的分析,其它版本的功能不会在本篇文章中具体讨论。

如何提供简洁的遍历方法

BlocksKit 实现的最简单的功能就是为集合类型添加方法遍历集合中的元素。


Objective-C


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


这段代码非常简单,我们可以使用 enumerateObjectsUsingBlock: 方法替代 bk_each: 方法:


Objective-C


[@[@1,@2,@3] enumerateObjectsUsingBlock:^(id obj,NSUInteger idx,BOOL *stop) {    NSLog(@"%@",obj);}];
2016-03-05 16:02:57.295 Draveness[10725:453402] 12016-03-05 16:02:57.296 Draveness[10725:453402] 22016-03-05 16:02:57.297 Draveness[10725:453402] 3
复制代码


这部分代码的实现也没什么难度:


Objective-C


- (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 等语言中也同样存在。如果你接触过上面的语言,理解这里方法的功能也就更容易了,不过这不是这篇文章关注的主要内容。


Objective-C


// 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 提供了更简单的方法来实现,不需要新建一个分类。


Objective-C


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.


Objective-C


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


这里的 OBJC_ASSOCIATION_RETAIN_NONATOMIC 表示当前属性为 retain nonatomic 的,还有其它的参数如下:


Objective-C


/** * 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 通过另一种方式实现了『弱属性』:


Objective-C


- (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 不能为对象添加弱属性,所以在这里添加了一个对象,然后让这个对象持有一个弱属性:


Objective-C


@interface _BKWeakAssociatedObject : NSObject
@property (nonatomic,weak) id value;
@end
@implementation _BKWeakAssociatedObject
@end
复制代码


这就是 BlocksKit 实现弱属性的方法,我觉得这个实现的方法还是比较简洁的。


getter 方法的实现也非常类似:


Objective-C


- (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 也可以在执行之前取消。


Objective-C


- (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 生成的。


Objective-C


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,具体实现的部分如下:


Objective-C


__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 不会执行


这是实现取消的关键部分:


Objective-C


+ (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 这个分类中的大部分接口都会调用这个方法:


Objective-C


- (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 以及之前的版本,如果某个对象被释放了,但是刚对象的注册的通知没有被移除,那么当事件再次发生,就会_向已经释放的对象发出通知_,整个程序就会崩溃。


这里解决的办法就十分的巧妙:


Objective-C


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 个属性:


Objective-C


@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: 太简单了也不说了。


Objective-C


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


上面的第一行代码生成一个 observer 实例之后立刻调用了 startObservingWithOptions: 方法开始观测对应的 keyPath


Objective-C


- (void)startObservingWithOptions:(NSKeyValueObservingOptions)options{  @synchronized(self) {    if (_isObserving) return;
#1:遍历 keyPaths 实现 KVO
_isObserving = YES; }}
复制代码


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


Objective-C


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


遍历自己的 keyPaths 然后让 _BKObserver 作观察者观察自己,然后传入对应的 keyPath


关于 _stopObservingLocked 方法的实现也十分的相似,这里就不说了。


Objective-C


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


到目前为止,我们还没有看到实现 KVO 所必须的方法 observeValueForKeyPath:ofObject:change:context,这个方法就是每次属性改变之后的回调:


Objective-C


- (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 使用的例子


Objective-C


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


代码中的 bk_recognizerWithHandler:delay: 方法在最后都会调用初始化方法 bk_initWithHandler:delay: 生成一个 UIGestureRecongizer 的实例


Objective-C


- (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 也会在这个分类中实现:


Objective-C


- (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 是否会被执行:


Objective-C


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


Objective-C


@property (nonatomic) UIControlEvents controlEvents;@property (nonatomic,copy) void (^handler)(id sender);
复制代码


其中 UIControlWrapper 对象以 {controlEvents,wrapper} 的形式作为 UIControl 的属性存入字典。

改造 UIView

因为在上面已经改造过了 UIGestureRecognizer,在这里改造 UIView 就变得很容易了:


Objective-C


- (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 中的最重要的部分动态代理:



本文转载自 Draveness 技术博客。


原文链接:https://draveness.me/blockskit-1


2019-12-10 17:53652

评论

发布
暂无评论
发现更多内容

将本地maven仓库的数据恢复到Nexus仓库

白粥

工作笔记

技术人如何调研和选型第三方 SDK?全文干货

融云 RongCloud

面试官:请说说什么是BFC?大白话讲清楚

蛙人

CSS 大前端

极智网络告警关联规则挖掘

鲸品堂

方法论 解决方案

装双系统?不需要!教你在iMac上流畅使用Windows

懒得勤快

Mac 虚拟机 苹果 crossover

OKR实践中的痛点(5):战略缺失怎么玩OKR?

大叔杨

团队管理 OKR 敏捷 敏捷绩效

css网页布局小结

Darren

CSS

进来看看是不是你想要的效果,Android吸顶效果,并有着ViewPager左右切换

第三女神程忆难

Java android kotlin 安卓 移动开发

企业如何做数字化转型?想要资产状况及时把控,它的作用至关重要!

一只数据鲸鱼

数字化 数据可视化 资产管理

Redis单线程已经很快,为何6.0要引入多线程?有啥优势?

Java架构师迁哥

systemctl的使用

箭上有毒

linux运维 4月日更

SpringBoot Admin2.0 集成 Java 诊断神器 Arthas 实践

阿里巴巴云原生

Java 运维 云原生 中间件 Arthas

云数据库时代的新思考,这位90后大咖想邀你聊聊

华为云开发者联盟

数据库 开源 opengauss GaussDB 华为云数据库

android开发面试题,字节跳动Android三面凉凉,手慢无

欢喜学安卓

android 程序员 面试 移动开发

交易所跟单软件搭建,合约跟单系统开发

Dubbo 学习笔记(三) Spring Boot 整合 Dubbo(官方版)

U2647

Spring Boot dubbo 4月日更

一文读懂容器存储接口 CSI

阿里巴巴云原生

容器 云原生 k8s 存储 调度

NA(Nirvana)公链“为应用而生” NAC公链领跑公链新格局!

区块链第一资讯

聪明人的训练(十二)

Changing Lin

4月日更

26天吃透算法笔记,面试字节,面试官朝我比了个“ok”

比伯

Java 编程 架构 算法 技术宅

“区块链+电子商务”,电商能否再创辉煌?

电微13828808271

区块链电子政务——不动产综合服务平台

电微13828808271

阿里高级架构师纯手打832页Java全栈知识点笔记,吃透后成功七面上岸滴滴!

Java架构追梦

Java 阿里巴巴 架构 面试 成长笔记

【LeetCode】子集二Java题解

Albert

算法 LeetCode 4月日更

程序员去大公司面试,我的头条面试经历分享,搞懂这些直接来阿里入职

欢喜学安卓

android 程序员 面试 移动开发

EGG Network阿凡提 公链EFTalk全球首创POTP二叉交叉共识机制

币圈那点事

大数据前置知识-服务器及磁盘

大数据技术指南

大数据 4月日更

攻击区块链网络的都有哪些方式方法

CECBC

区块链

你的数仓函数结果不稳定,可能是属性指定错了

华为云开发者联盟

函数 GaussDB(DWS) 函数属性 函数下推 易失性级别

MySQL 事务隔离

Sakura

4月日更

大意!6行代码,“报废”5片单片机!

不脱发的程序猿

程序人生 嵌入式软件 单片机 4月日更 国产MCU

神奇的 BlocksKit (一)_语言 & 开发_Draveness_InfoQ精选文章