forked from draveness/analyze
-
Notifications
You must be signed in to change notification settings - Fork 0
/
神奇的 BlocksKit (一).md
640 lines (466 loc) · 25.6 KB
/
神奇的 BlocksKit (一).md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
# 神奇的 BlocksKit (一)
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/contents/BlocksKit/神奇的%20BlocksKit%20(一).md)
+ [神奇的 BlocksKit(二)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/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)