【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:53661

评论

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

如何在Mobpush中配置应用包名

MobTech袤博科技

大数据

操作系统迁移难?Alibaba Cloud Linux 支持跨版本升级 | 龙蜥技术

OpenAnolis小助手

开源 操作系统 迁移 阿里云服务器 龙蜥社区

位移贴图和法线贴图的区别

3D建模设计

材质修改 纹理贴图

EMQ 云边协同的 IIoT 解决方案架构,亮相 2023 工博会

新消费日报

解读亚马逊云服务器 EC2 预留实例与按需实例的区别,及其在成本节约的优势

亚马逊云科技 (Amazon Web Services)

sdk 云服务器 Amazon EC2

火山引擎DataLeap一站式数据治理解决方案及平台架构

字节跳动数据平台

大数据 数据中台 数据安全 数据研发 企业号10月PK榜

零售业:别让数据安全成为业务的绊脚石!

极盾科技

数据安全 零售行业

Python 元组完全指南1

小万哥

Python 程序员 软件 后端 开发

Rust语言中,const 和 static 的区别

0x5d0de9

​Rust

Moblink与深度链接:用户裂变的增长利器

MobTech袤博科技

大数据

用户案例合集 | 物联网平台的时序数据处理难点与优化实践

TDengine

时序数据库 ​TDengine 国产时序数据库

Mac电脑数据转换 EasyDataTransform激活最新

胖墩儿不胖y

数据处理 Mac软件 数据处理工具 编辑数据

ShareSDK:社会化分享是如何助力APP拉新促活的

MobTech袤博科技

大数据 前端

DCloud崔红保:云开发与跨端技术,构建企业降本增效新篇章

TRaaS

支付宝小程序 云开发 DCloud

体验华为云CodeArts Check IDE插件国际化展示效果

华为云PaaS服务小智

软件开发 代码质量 华为云 代码检查

火山引擎DataTester智能发布:助力产品降低功能迭代风险

字节跳动数据平台

大数据 A/B 测试 对比实验 数字化增长 企业号10月PK榜

从 Greenplum 到 YMatrix,某头部动力电池厂商核心业务数据的迁移实践

YMatrix 超融合数据库

greenplum 迁移数据 超融合数据库 YMatrix 电池厂商

大模型太贵?找找自己的原因好吧?

脑极体

AI

电力行业首个自主可控的大模型发布了!百度飞桨、文心大模型提供支持

飞桨PaddlePaddle

飞桨 大模型 文心大模型

要体验 AI 编程助手吗?

亚马逊云科技 (Amazon Web Services)

亚马逊云科技 AIGC

华为云API自然语言处理的魅力—AI情感分析、文本分析

华为云PaaS服务小智

人工智能 软件开发 API

OWASP Top 10漏洞解析(3)- A3:Injection 注入攻击

华为云PaaS服务小智

云计算 软件开发 华为云

3D孪生场景搭建:参数化模型

3D建模设计

模型 数字孪生 参数化模型

开发人员的首选:CodeWhisperer

亚马逊云科技 (Amazon Web Services)

#人工智能

socks5代理怎么帮助广告投放?

巨量HTTP

http代理

简单好用的网页设计:EverWeb 中文版最新

mac大玩家j

网页设计 Mac软件 网页制作

深入理解MySQL中的Join算法

Java随想录

Java MySQL

DevOps平台建设的关键点是什么?

laofo

DevOps cicd 研发效能 持续交付 效能度量

3D孪生场景搭建:模型区域摆放

3D建模设计

数字孪生 3D场景编辑器

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