BlocksKit 能做什么
- 为集合类型添加 bk_each: 等函数方法对集合中元素快速遍历
- block 对 NSObject 的封装
- AssociatedObject:使用 block 封装关联对象
- BlockExecution:使用 block 封装块执行
- KVO:使用 block 封装 KVO,当 NSObject 销毁自动移除 KVO
- UIView 对象添加 bk_whenTapped: 等方法快速添加手势
- 动态代理:使用 block 替换 delegate method,不需要实现代理
block 封装集合操作
bk_each:
1 | [@[@1,@2,@3] bk_each:^(id obj) { |
类似的,对于集合函数式方法
1
2
3
4
5
6
7
8
9
10
11
12
13 - (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 相关封装
- AssociatedObject
- BlockExecution
- BlockObservation
AssociatedObject
NSObject+BKAssociatedObjects.h
OC 中给class 添加关联属性
associated object policy
1
2
3
4
5
6
7
8 // associated object 的策略中没有 weak
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. */
};
1 | void objc_setAssociatedObject(object, key, value, policy); |
BlocksKit 实现
1 | NSObject *test = [NSObject new]; |
BlocksKit针对每一种 policy都在 NSObject+BKAssociatedObjects.h
中实现了接口。
因为 policy – weak 没有。通过引入一个中间对象来实现『弱属性』
:
1 | @interface _BKWeakAssociatedObject : NSObject |
obj.weakObj.value
BlockExecution
NSObject+BKBlockExecution.h
在任意对象上执行 block
通过这个类提供的一些接口,可以在任意对象上快速执行线程安全、异步的 block,而且这些 block 也可以在执行之前取消。
1 | - (id <NSObject,NSCopying>)bk_performOnQueue:(dispatch_queue_t)queue afterDelay:(NSTimeInterval)delay usingBlock:(void (^)(id obj))block { |
这个方法中最关键的也就是它返回了一个可以取消的 block
,而这个 block
就是用静态函数 BKDispatchCancellableBlock 生成的。
1 | static id <NSObject,NSCopying> BKDispatchCancellableBlock(dispatch_queue_t queue,NSTimeInterval delay,void(^block)(void)) { |
BKSupportsDispatchCancellation
来判断当前平台和版本是否支持使用 GCD 取消 block,当然一般都是支持的:
- 函数返回的是 YES,那么在
block
被派发到指定队列之后就会返回这个dispatch_block_t
类型的block
- 函数返回的是 NO,那么就会就会手动包装一个可以取消的
wrapper
上面这部分代码就先创建一个 wrapper
block,然后派发到指定队列,派发到指定队列的这个 block 是一定会执行的,但是怎么取消这个 block 呢?
如果当前 block 没有执行,我们在外面调用一次 wrapper(YES)
时,block 内部的 cancelled
变量就会被设置为 YES,所以 block 就不会执行。
dispatch_after --- cancelled = NO
wrapper(YES) --- cancelled = YES
wrapper(NO) --- cancelled = YES
block 不会执行
这是实现取消的关键部分:
1 | + (void)bk_cancelBlock:(id <NSObject,NSCopying>)block { |
- GCD 支持取消 block,那么直接调用 dispatch_block_cancel 函数取消 block
- GCD 不支持取消 block 那么调用一次 wrapper(YES)
BlockObservation
使用 block 封装 的Observation,在添加 observer以后会自动remove。
NSObject+BKBlockObservation.h
BlocksKit
对 KVO 的封装由两部分组成:
NSObject+BKBlockObservation
的分类负责提供便利方法- 私有类
_BKObserver
具体实现原生的 KVO 功能
主要看点是,BlocksKit 是如何自动remove 掉observer的。
核心技巧:使用method swizzle 替换 dealloc
方法
在 NSObject+BKBlockObservation
分类中的主要接口:
1 | - (void)bk_addObserverForKeyPaths:(NSArray *)keyPaths identifier:(NSString *)identifier options:(NSKeyValueObservingOptions)options context:(BKObserverContext)context task:(id)task { |
根据上述代码BlocksKit 主要做了两件事 NSObject+BKBlockObservation.h:
- 重写class 的 dealloc 方法
- NSObject 分类对象管理更换 dealloc 的 classes
newDealloc
中处理 removeObserver
- 管理 _observer(封装KVO 上下文)
- 给 class 添加关联对象(一个 dict),用于管理注册的 _observer
_BKObserver
封装 KVO 上下文
改造 UIKit
- UIGestureRecongizer + UIBarButtonItem + UIControl
- UIView
改造 UIGestureRecongizer,UIBarButtonItem 和 UIControl
先来看一个 UITapGestureRecognizer 使用的例子
1 | UITapGestureRecognizer *singleTap = [UITapGestureRecognizer bk_recognizerWithHandler:^(id sender) { |
代码中的 bk_recognizerWithHandler:delay:
方法在最后都会调用初始化方法 bk_initWithHandler:delay:
生成一个 UIGestureRecongizer
的实例
1 | - (instancetype)bk_initWithHandler:(void (^)(UIGestureRecognizer *sender,UIGestureRecognizerState state,CGPoint location))block delay:(NSTimeInterval)delay { |
它会在这个方法中传入 target 和 selector。 其中 target 就是 self,而 selector 也会在这个分类中实现:
1 | - (void)bk_handleAction:(UIGestureRecognizer *)recognizer { |
因为在初始化方法 bk_initWithHandler:delay:
中保存了当前手势的 bk_handler,所以直接调用在 Block Execution 一节中提到过的 bk_performAfterDelay:usingBlock:
方法,将 block 派发到指定的队列中,最终完成对 block 的调用。
封装 block 并控制 block 是否可以执行
这部分代码和前面的部分有些相似,因为这里也用到了一个属性 bk_shouldHandleAction
来控制 block 是否会被执行:
1 | CGPoint location = [self locationInView:self.view]; |
同样 UIBarButtonItem
和 UIControl
也是用了几乎相同的机制,把 target 设置为 self,让后在分类的方法中调用指定的 block。
UIControlWrapper
稍微有些不同的是 UIControl。因为 UIControl
有多种 UIControlEvents,所以使用另一个类
BKControlWrapper
来封装 handler
和 controlEvents
1 | @property (nonatomic) UIControlEvents controlEvents; |
其中 UIControlWrapper 对象以 {controlEvents,wrapper}的形式作为 UIControl 的属性存入字典。
改造 UIView
1 | - (void)bk_whenTouches:(NSUInteger)numberOfTouches tapped:(NSUInteger)numberOfTaps handler:(void (^)(void))block { |
UIView 分类只有这一个核心方法,其它的方法都是向这个方法传入不同的参数,这里需要注意的就是。它会遍历所有的 gestureRecognizers
,然后把对所有有冲突的手势调用 requireGestureRecognizerToFail:
方法,保证添加的手势能够正常的执行。
动态代理
思考代理模式:
1 | class A -- protocol DelegateA |
BlocksKit 要改造成
1 | a.delegateBlock0 = ^{ } |
block:
- 不需要设置代理
- 不需要实现接口
- 让对象自己实现代理方法
BlocksKit 使用了什么方式实现?
这里分析 UIImagePickerController 如何实现的(代码少)
核心构建流程 NSObject+A2BlockDelegate
- 注册动态代理:bk_registerDynamicDelegate
- 关联block:bk_linkDelegateMethods
硬编码部分:
1 | // 1. 自定义delegate class 继承 A2DynamicDelegate,并遵从 UIImagePickerControllerDelegate 方法 |
自己以后自定义的组件可以依赖 BlocksKit
通过 block 实现 delegate
- 自定义 xxxDynamicDelegate class 继承 A2DynamicDelegate,并遵从 xxxDelegate 方法
- Class 分类
2.1 添加实现 xxxDelegateMethod 的 block
2.2 重写 load 方法,内部添加 { block : xxxDelegateMethod } 实现
关键的类
A2BlockInvocation
的主要作用是存储和转发 blockA2DynamicDelegate
用来实现类的代理和数据源,它是NSProxy
的子类NSObject+A2DynamicDelegate
负责为返回bk_dynamicDelegate
和bk_dynamicDataSource
等A2DynamicDelegate
类型的实例,为NSObject
提供主要的接口NSObject+A2BlockDelegate
提供了一系列接口将代理方法映射到block
上- 其他的
UIKit
的分类提供对应的属性,并在对应的A2DynamicDelegate
子类中实现代理方法
核心源码 regist,link
NSObject+A2BlockDelegate.m
regist
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 + (void)bk_registerDynamicDelegateNamed:(NSString *)delegateName forProtocol:(Protocol *)protocol {
NSMapTable *propertyMap = [self bk_delegateInfoByProtocol:YES];
A2BlockDelegateInfo *infoAsPtr = (__bridge void *)[propertyMap objectForKey:protocol];
if (infoAsPtr != NULL) { return; }
const char *name = delegateName.UTF8String;
objc_property_t property = class_getProperty(self, name);
SEL setter = setterForProperty(property, name);
SEL a2_setter = prefixedSelector(setter);
SEL getter = getterForProperty(property, name);
A2BlockDelegateInfo info = {
setter, a2_setter, getter
};
[propertyMap setObject:(__bridge id)&info forKey:protocol];
infoAsPtr = (__bridge void *)[propertyMap objectForKey:protocol];
IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject, id delegate) {
A2DynamicDelegate *dynamicDelegate = getDynamicDelegate(delegatingObject, protocol, infoAsPtr, YES);
if ([delegate isEqual:dynamicDelegate]) {
delegate = nil;
}
dynamicDelegate.realDelegate = delegate;
});
if (!swizzleWithIMP(self, setter, a2_setter, setterImplementation, "v@:@", YES)) {
bzero(infoAsPtr, sizeof(A2BlockDelegateInfo));
return;
}
if (![self instancesRespondToSelector:getter]) {
IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
return [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject, protocol)];
});
addMethodWithIMP(self, getter, NULL, getterImplementation, "@@:", NO);
}
}
link
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 + (void)bk_linkProtocol:(Protocol *)protocol methods:(NSDictionary *)dictionary{
[dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *selectorName, BOOL *stop) {
const char *name = propertyName.UTF8String;
objc_property_t property = class_getProperty(self, name);
NSCAssert(property, @"Property \"%@\" does not exist on class %s", propertyName, class_getName(self));
char *dynamic = property_copyAttributeValue(property, "D");
NSCAssert2(dynamic, @"Property \"%@\" on class %s must be backed with \"@dynamic\"", propertyName, class_getName(self));
free(dynamic);
char *copy = property_copyAttributeValue(property, "C");
NSCAssert2(copy, @"Property \"%@\" on class %s must be defined with the \"copy\" attribute", propertyName, class_getName(self));
free(copy);
SEL selector = NSSelectorFromString(selectorName);
SEL getter = getterForProperty(property, name);
SEL setter = setterForProperty(property, name);
if (class_respondsToSelector(self, setter) || class_respondsToSelector(self, getter)) { return; }
const A2BlockDelegateInfo *info = [self bk_delegateInfoForProtocol:protocol];
IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject, protocol, info, NO);
return [delegate blockImplementationForMethod:selector];
});
if (!class_addMethod(self, getter, getterImplementation, "@@:")) {
NSCAssert(NO, @"Could not implement getter for \"%@\" property.", propertyName);
}
IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject, id block) {
A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject, protocol, info, YES);
[delegate implementMethod:selector withBlock:block];
});
if (!class_addMethod(self, setter, setterImplementation, "v@:@")) {
NSCAssert(NO, @"Could not implement setter for \"%@\" property.", propertyName);
}
}];
}
核心流程图
使用技术:
- block 源码结构解析成 Signature
- runtime
- 动态添加方法
- 关联对象
- swizzle
- 消息转发