神奇的 BlocksKit (一)

告别OFDM卡顿:用MATLAB手把手仿真AFDM波形,搞定高铁、无人机通信的时变信道难题 本文详细介绍了如何使用MATLAB仿真AFDM波形,解决高铁和无人机通信中的时变信道难题。通过仿射变换技术,AFDM在多普勒频移严重的场景下表现优异,显著提升通信质量。文章包含参数配置、波形生成、性能对比等实战内容,适合通信工程师和研究人员参考。 阅读详情


Blog: [Draveness](http://draveness.me)


<iframe src="http://ghbtns.com/github-btn.html?user=draveness&type=follow&size=large" height="30" width="240" frameborder="0" scrolling="0" style="width:240px; height: 30px;" allowTransparency="true"></iframe> 


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


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


****


## 概述


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


+ 为 `NSArray`、 `NSDictionary` 和 `NSSet` 等集合类型以及对应的可变集合类型 `NSMutableArray`、 `NSMutableDictionary` 和 `NSMutableSet` 添加 `bk_each:` 等方法完成对集合中元素的**快速遍历**

+ 使用 block 对 `NSObject` 对象 KVO

+ 为 `UIView` 对象添加 `bk_whenTapped:` 等方法快速添加手势

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


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


## 如何提供简洁的遍历方法


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


```objectivec

[@[@1,@2,@3] bk_each:^(id obj) {

    NSLog(@"%@",obj);

}];

```


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


```objectivec

[@[@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

```


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


```objectivec

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


```objectivec

// 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](http://nshipster.cn/associated-objects/) ,当我们想要为一个已经存在的类添加属性时,就需要用到 AssociatedObject 为类添加属性,而  BlocksKit 提供了更简单的方法来实现,不需要新建一个分类。


```objectivec

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`.


```objectivec

- (void)bk_associateValue:(id)value withKey:(const void *)key

{

objc_setAssociatedObject(self,key,value,OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

```


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


```objectivec

/**

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


```objectivec

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


```objectivec

@interface _BKWeakAssociatedObject : NSObject


@property (nonatomic,weak) id value;


@end


@implementation _BKWeakAssociatedObject


@end

```


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


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


```objectivec

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


```objectivec

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


```objectivec

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


```objectivec

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


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


```objectivec

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


```objectivec

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


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


```objectivec

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](https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html#//apple_ref/c/func/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` 作为参数


6. 在我们获得了新 `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 个属性:


```objectivec

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


```objectivec

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

[observer startObservingWithOptions:options];

```


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


```objectivec

- (void)startObservingWithOptions:(NSKeyValueObservingOptions)options

{

@synchronized(self) {

if (_isObserving) return;

#1:遍历 keyPaths 实现 KVO


_isObserving = YES;

}

}

```


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


```objectivec

[self.keyPaths bk_each:^(NSString *keyPath) {

[self.observee addObserver:self forKeyPath:keyPath options:options context:BKBlockObservationContext];

}];

```


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


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


```objectivec

[keyPaths bk_each:^(NSString *keyPath) {

[observee removeObserver:self forKeyPath:keyPath context:BKBlockObservationContext];

}];

```


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


```objectivec

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


```objectivec

 UITapGestureRecognizer *singleTap = [UITapGestureRecognizer bk_recognizerWithHandler:^(id sender) {

     NSLog(@"Single tap.");

 } delay:0.18];

 [self addGestureRecognizer:singleTap];

```


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


```objectivec

- (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;

}

```


它会在这个方法中传入 `target` 和 `selector`。 其中 `target` 就是 `self`,而 `selector` 也会在这个分类中实现:


```objectivec

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


```objectivec

CGPoint location = [self locationInView:self.view];

void (^block)(void) = ^{

if (!self.bk_shouldHandleAction) return;

handler(self,self.state,location);

};

```


====


同样 `UIBarButtonItem` 和 `UIControl` 也是用了几乎相同的机制,把 `target` 设置为 `self`,让后在分类的方法中调用指定的 block。


#### UIControlWrapper


稍微有些不同的是 `UIControl`。因为 `UIControl` 有多种 `UIControlEvents`,所以使用另一个类 `BKControlWrapper` 来封装 `handler` 和 `controlEvents`


```objectivec

@property (nonatomic) UIControlEvents controlEvents;

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

```


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


### 改造 UIView


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


```objectivec

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


+ [神奇的 BlocksKit(一)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/BlocksKit/神奇的%20BlocksKit%20(一).md)

+ [神奇的 BlocksKit(二)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/BlocksKit/神奇的%20BlocksKit%20(二).md)


<iframe src="http://ghbtns.com/github-btn.html?user=draveness&type=follow&size=large" height="30" width="240" frameborder="0" scrolling="0" style="width:240px; height: 30px;" allowTransparency="true"></iframe>


Follow: [@Draveness](https://github.com/Draveness)



OWASP 2025版AI大模型攻击安全风险汇总(攻击方法种类) 提示词注入是大模型最基础、最常见的核心漏洞,攻击者通过构造直接或间接的恶意自然语言输入,篡改大模型原本的正常行为与输出结果。哪怕这类恶意内容对人类是隐藏、不可见的,只要模型能够正常解析读取,就会触发漏洞。它和大家常说的模型“越狱”相关,但并不等同:越狱是提示词注入的极端形式,会直接让模型彻底无视所有内置安全协议,完全脱离管控;而普通提示词注入,更多是针对性篡改模型行为、诱导执行特定操作。 阅读详情

相关推荐

Ubuntu下全面解析硬盘健康:从基础检测到性能优化

本文详细介绍了在Ubuntu系统中如何全面监测硬盘健康状态。从安装smartmontools工具、解读SMART关键属性(如重新分配扇区计数、通电时间),到进行性能基准测试(使用dd和fio),最后根据检测结果提供针对性的优化与预警方案,帮助用户有效预防数据丢失并提升硬盘可靠性。

weixin_29223603的博客 287

BlocksKit源码分析(一)

1引言众所周知Block已被广泛用于iOS编程。它们通常被用作可并发执行的逻辑单元的封装,或者作为事件触发的回调。Block比传统回调函数有2点优势: 1. 允许在调用点上下文书写执行逻辑,不用分离函数 2. Block可以使用local variables.基于以上种种优点Cocoa Touch越发支持Block式编程,这点从UIView的各种动画效果可用Block实现就可见一斑。而Bloc

宿于松下的技术博客 3429

深入解析西门子S7通信协议:从抓包到实战应用

本文深入解析西门子S7通信协议,从环境搭建、抓包分析到实战应用,提供了一套完整的协议分析方法。通过Wireshark工具进行抓包分析,详细拆解了S7协议各层结构,并结合真实案例,展示了如何利用抓包技术解决通信故障、优化性能及进行安全审计,是掌握工业通信底层原理的实用指南。

weixin_29224529的博客 281

BlocksKit的使用

一、引言众所周知Block已被广泛用于iOS编程。它们通常被用作可并发执行的逻辑单元的封装,或者作为事件触发的回调。Block比传统回调函数有2点优势: 允许在调用点上下文书写执行逻辑,不用分离函数 Block可以使用local variables. 基于以上种种优点Cocoa Touch越发支持Block式编程,这点从UIView的各种动画效果可用Block实现就可见一斑。而BlocksKit

xiaoxiaobukuang的专栏 8690

【数字孪生工业应用实战】第2篇:数字孪生系统架构设计与技术选型——从“作秀”到“实干”的骨架构建

本文系统剖析了数字孪生系统从“作秀”到“实干”的架构设计关键。针对多数项目在POC转生产时因架构混乱、选型盲目而失败的问题,深入阐述了四层分层架构(感知层、传输层、模型层、应用层)的设计原则,重点解析了感知层的异构适配与边缘预处理、传输层的异步解耦与多模式通信等工程细节。结合OPC UA、MQTT、EdgeX Foundry等主流技术选型对比,以及汽车焊装车间边缘智能网关的实战代码,提供了从零搭建微型孪生系统的完整指导。旨在帮助工程师建立清晰的选型决策逻辑,构建稳定、可演进的数字孪生“骨架”。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 264

微信小程序 - 下单账号和支付账号不一致,请核实后再支付(微信小程序或微信H5页面/微信公众号网页支付时报错:“下单账号和支付账号不一致,请核实后再支付”,买家微信支付时提示错误)详细排查和解决方案!

微信支付出现 “下单账号与支付账号不一致,请核实后再支付”微信小程序下单账号与支付账号不一致不让支付,微信公众号网页 微信H5页面JS-SDK支付时出现错误:下单账号和支付账号不一致,请核实后再支付,openId导致的微信中“下单账号和支付账号不一致”原因及解决办法,调用统一下单所传openId与实际支付用户的openId不一致,微信支付jssdk报错,检查了没有不一致依然报错怎么办,微信支付下单账号和支付账号不一致,请核实后再支付,微信支付功能用JSAPI支付,H5页面负责下单并支付jsapi,微信调起支

🏆 前端领域优质创作者 5328

BlocksKit 开源项目教程

BlocksKit 是一个为 Objective-C 和 C 语言提供块(block)编程支持的框架。它旨在简化使用块进行编程的过程,移除一些在使用块时遇到的限制和障碍。BlocksKit 支持 OS X Mountain Lion 及更新版本,iOS 6 和 iOS 7 的静态库,以及 iOS 8 及更新版本的框架。该项目由 Zachary Waldowski 和 Alexsander Aker...

gitblog_00515的博客 494

YOLOv11改进 | 引入SFSConv空间频率选择卷积(含SFSC3k2二次创新),空间频率特征协同作用,检测性能全面提升

本文提出在YOLOv11中引入SFSConv空间频率选择卷积模块,通过显式分离和动态融合空间域与频率域特征,解决传统卷积对多尺度目标检测的局限性。SFSConv包含空间频率分离、动态权重融合和协同特征重建三个核心机制,并进一步创新提出SFSC3k2结构,通过双路频率分支增强特征协同作用。该改进显著提升了YOLOv11在遥感、医学等多尺度复杂场景下的检测性能,实验显示在微小目标检测任务中mAP提升5%-10%。代码实现展示了SFSConv模块的核心逻辑及其在PyTorch中的高效实现。

走向CTO的路上... 767

XiaoMusic:小爱音箱播放全网音乐部署指南

XiaoMusic 让小爱音箱播放任意音乐不再依赖会员,它接管你的语音指令,从网上把歌取回来。技术栈为 Python + FastAPI,核心机制是:拦截设备播放请求,用 yt-dlp 下载音频并缓存到本地,再把本地文件交还音箱播放。 ## 工作原理:XiaoMusic 从语音指令到播放的完整链路 你对着小爱说"播放歌曲 周杰伦 晴天",指令并不会走小米官方音乐服务。XiaoMusic 绑定同

gitblog_00069的博客 514

以太网MII、RMII、GMII、RGMII(三)

RMII接口收发的数据位宽为2bit,因此管脚数目大为减少,在高密端口PHY中应用较多,但是由于其是MAC和PHY共用一个参考时钟,接口之间的距离不能太远;一般的GMII接口都有两个发送参考时钟:TX_CLK和GTX_CLK,两者的方向是不一样的,前者有PHY提供(同MII),后者由MAC或外部参考时钟源提供,绝大多数GMII接口都是兼容MII接口的,在用作MII模式时,使用TX_CLK和8根数据线中的4根。MII接口中的接收时钟和发送时钟是分开的,而且都是由PHY芯片提供给MAC芯片的。

qq_42357877的博客 2330

Oracle EBS(R12+)通过Multi-Org 多组织架构、Multi-Ledger 多账簿、SLA 子分类账会计、多币种引擎、多会计政策并行五大核心能力,为跨国企业提供一体化、自动化、合规的

Oracle EBS(R12+)提供全球会计核算解决方案,通过Multi-Org多组织架构实现跨国集团数据隔离与共享,支持四层组织结构(业务组→法律实体→经营单位→库存组织)。Multi-Ledger多账簿功能允许一笔业务多准则自动核算,包含主账簿、辅助账簿和报告币种。系统内置多币种处理引擎,覆盖全流程货币业务,支持自动重估、再计量和折算。通过SLA子分类账会计实现多会计政策并行处理,满足不同国家会计准则和税制要求。该方案可同时支持本地法定、集团合并及管理分析需求,实现全球财务数据集中管理,提升合规性和运营

金牌架构师 553

神奇的 BlocksKit (二)

转自:http://draveness.me/blockskit-2/ 关注仓库,及时获得更新:iOS-Source-Code-Analyze 这篇文章『神奇的 BlocksKit』的第二部分,关于第一部分的内容在这里: 神奇的 BlocksKit(一)神奇的 BlocksKit(二) 动态代理 动态代理这部分可以说是 BlocksKit 的精华。它使用 block 属性替换

zhangjie1989的专栏 1174

AutoJs调用DeepSeek大模型接口实战教程 AutoJs接入DeepSeek、 DeepSeek API调用 、 手机脚本接入大模型

通过这篇文章,我们用 AutoJs 成功接入了 DeepSeek Chat API。使用发送请求在 Header 里正确携带 API Key从里拿到最终返回结果这样一来,你就可以在 AutoJs 里实现一个智能助手脚本,甚至做更复杂的 AI 自动化工具。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 465

神奇的 BlocksKit(1):源码分析(下)

神奇的 BlocksKit(1):源码分析(下)

qq_33777090的博客 543

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

神奇的 BlocksKit(1):源码分析(上)

神奇的 BlocksKit(1):源码分析(上)

qq_33777090的博客 512

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

BlocksKit源码分析

1引言 众所周知Block已被广泛用于iOS编程。它们通常被用作可并发执行的逻辑单元的封装,或者作为事件触发的回调。Block比传统回调函数有2点优势:  1. 允许在调用点上下文书写执行逻辑,不用分离函数  2. Block可以使用local variables. 基于以上种种优点Cocoa Touch越发支持Block式编程,这点从UIView的各种动画效果可用Block实现就

Rabe.W 335

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

iOS界面渲染性能

OHHTTPStubs iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求 如何进行 HTTP Mock(iOS) ProtocolKit 如何在 Objective-C 中实现协议扩展 FBRetainCycleDetector(Version 0.1.2) 如何在 iOS 中解决循环引用的问题 检测 NSObject 对象持有的强指针 如何实现 iOS 中的 Associat...

More reading and learning to become excellent 315

使用BlocksKit优雅编写你的代码

pod 'BlocksKit' #import <BlocksKit/BlocksKit.h> #import <BlocksKit/A2DynamicDelegate.h> - (void)initStyle { UICollectionView *collectionView = [[UICollectionView alloc] initWit...

MoonKingDon的博客 323

CBBlockKit:我只想用Block写东西

前言 这个工具集后续会持续维护,更希望的是初学者查看源码后,对于Block的使用,Category构建,Delegate和DataSourse代码分离的思想能有一定的了解。感谢Welkin Xie一直以来对于我的指导。?? 项目地址 Github: https://github.com/cbangchen/... 感谢大家的支持?? ...

weixin_34258838的博客 166
上一篇: 黑箱中的 retain 和 release
下一篇: 神奇的 BlocksKit (二)
appdeveloperxg
博客等级 码龄11年 38粉丝 30原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值