简而繁的BlocksKit
Posted ZeroOnet
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简而繁的BlocksKit相关的知识,希望对你有一定的参考价值。
前言 |
意如分类标题那样,笔者打算开始探究框架与源码。毫无疑问,这其中会遇到各种各样的挑战,但是我觉得我们应尽早走出这一步,不然就错过了很多的精彩。也许这精彩是更加开阔的程序视野,亦或是逻辑思维与编程能力的提升,这其中对耐心与意志的磨炼绝对会让人十分“酸爽”。
而这简而繁BlocksKit
就成为了第一道菜
,为什么呢?因为之前对它的认知就是对系统API
的block
方式调用的高度封装,这是它简单的使用特性。然而它的灵魂——动态代理让我深切感受到了框架的设计哲学:把简洁留给别人,把复杂留给自己
。不信?那就接着往下看!
简单的API使用与探究 |
你或许诟病过Target-Action
响应模式代码的编写,又或者对代理模式爱的深沉。BlocksKit
给你带来了福音,给按钮添加监听方法是这么写的:
[button bk_addEventHandler:^(id sender)
NSLog(@"点了我");
forControlEvents:UIControlEventTouchUpInside];
这是UIControl
添加的分类BlocksKit
中的一个方法,以下是它的实现:
- (void)bk_addEventHandler:(void (^)(id sender))handler forControlEvents:(UIControlEvents)controlEvents
//首先判断是否有事件的响应
NSParameterAssert(handler);
//动态获取对象绑定到的事件,对于只有一个事件的实例,此处的event自然是nil
NSMutableDictionary *events = objc_getAssociatedObject(self, BKControlHandlersKey);
if (!events)
events = [NSMutableDictionary dictionary];
//如果当前当前为首次给对象添加事件,则新建一个事件字典,并动态关联到对象
objc_setAssociatedObject(self, BKControlHandlersKey, events, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//使用事件在枚举中的值作为键,取出该事件的处理者
NSNumber *key = @(controlEvents);
NSMutableSet *handlers = events[key];
if (!handlers)
handlers = [NSMutableSet set];
events[key] = handlers;
//真正的目标对象,它有两个属性:handler和controlEvent,初始化方法就是对这两者赋值
BKControlWrapper *target = [[BKControlWrapper alloc] initWithHandler:handler forControlEvents:controlEvents];
//添加对应时间的响应者
[handlers addObject:target];
//target的invoke:方法就是调用自身的handler,并将本身作为参数传入
[self addTarget:target action:@selector(invoke:) forControlEvents:controlEvents];
这里之所以采用字典的形式来存储事件与响应者,是为了后面能够一次性移除某个事件的响应者和判断某个事件是否有响应者。具体可以看看bk_removeEventHandlersForControlEvents:
和bk_hasEventHandlersForControlEvents:
这两个方法,这里就不再赘述。
代理模式的实现,用到了本框架的核心模块:动态代理。后面会用相当篇幅解读,这里先看看其他有趣的东西,比方说延时手势。关于构造实际响应目标和按钮大同小异,这里主要看看如何让手势延时响应。手势响应时都会触发一个位于UIGestureRecognizer
分类中的bk_handleAction:
方法,以下仅贴出关于延时的实现:
- (void)bk_handleAction:(UIGestureRecognizer *)recognizer
//部分省略
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
//在指定时间后,将任务加入到主队列中等待执行,而非是任务在主队列中等待指定时间长度后执行
dispatch_after(popTime, dispatch_get_main_queue(), block);
特色菜 味道就是好 |
weak对象关联
如果你使用过分类(Category
)的话,你肯定知道它不能为对象直接新增属性。因为对象的本质是结构体,在编译时它的内存空间大小就已经确定了。它有一个成员变量是methodLists
,它是这样的一个类型:struct objc_method_list **
,即指向objc_method_list
类型的结构体二级指针。如果是系统固有的方法列表,那么它的大小就是固定的。当我们使用分类增加方法时,本质上就是通过修改*methodLists
值来指定新的内存地址以容下新增的方法(方法本身也是一个结构体)。
而Runtime
的对象关联(AssociateObject
)解决了使用分类不能新增属性的问题,其实就涉及到了两个函数,因此极易上手:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
id objc_getAssociatedObject(id object, const void *key)
系统提供的关联策略有如下几种:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy)
// 属性名key直接指向所关联的对象,不增加其引用计数,相当于__unsafe_unretained,相当于修饰属性的assign, atomic
OBJC_ASSOCIATION_ASSIGN = 0,
// 相当于用nonatomic、retain修饰属性
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
// 相当于copy、nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
// 相当于retain, atomic
OBJC_ASSOCIATION_RETAIN = 01401,
// 相当于copy, atomic
OBJC_ASSOCIATION_COPY = 01403
;
不难看出,没有weak
这一关联策略。那么我们来看看BlocksKit
是如何实现的:
- (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;
- (id)bk_associatedValueForKey:(const void *)key
id value = objc_getAssociatedObject(self, key);
if (value && [value isKindOfClass:[_BKWeakAssociatedObject class]])
return [(_BKWeakAssociatedObject *)value value];
return value;
@interface _BKWeakAssociatedObject : NSObject
@property (nonatomic, weak) id value;
@end
不难看出,作者使用了一个中间对象,它持有一个weak
类型的属性value
来存储实际所赋的值。在获取关联对象时就判断其是否为_BKWeakAssociatedObject
类型的对象,如果是就返回该对象value
的属性值。
取消block执行
有时我们需要在指定时间后追加一个block
到队列中,而且它只在条件满足时才会执行,也就是说这个block
的执行可以在未开始前取消。如果GCD
支持的话,用dispatch_block_cancel(dispatch_block_t block)
这个函数取消即可。但是它的使用声明是__OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0)
,而BlocksKit
出现是几年前的事了,所以作者完全应当考虑到GCD
不支持的情况,所以就有了这段条件编译的指令:
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1010)
// 两种情况下的宏值一样是一个奇怪的问题,在github这个框架的issue上也有人提出了这个疑问,但没人回复。
#define DISPATCH_CANCELLATION_SUPPORTED 1
#else
#define DISPATCH_CANCELLATION_SUPPORTED 1
#endif
不过这都是细枝末节,权当是作者手误,在这里姑且认为这个宏是有正确意义的。那么就来看看怎么使用一个可以取消的block
:
NSLog(@"start");
BKCancellationToken token = [self bk_performAfterDelay:10.0f usingBlock:^(id _Nonnull obj)
NSLog(@"haha");
];
//[UIViewController bk_cancelBlock:token];
//未取消注释的打印信息:
//2017-08-16 11:06:44.058 BlocksKit的block中途取消测试[1536:50956] start
//取消注释的打印信息:
//2017-08-16 11:07:54.038 BlocksKit的block中途取消测试[1565:51979] start
//2017-08-16 11:08:04.040 BlocksKit的block中途取消测试[1565:51979] haha
可以看到block
确实是可以被取消执行的。我们注意到这里有一个token
,嗯?进入方法内部一探究竟。第一个方法有这样的调用层级关系:
bk_performAfterDelay:usingBlock:
|__bk_performOnQueue:afterDelay:usingBlock:
...|__BKDispatchCancellableBlock()
最后这个BKCancellationToken
类型的token
,是这样返回的:
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;
// 另一个分支后面贴出
这里就可以知道BKCancellationToken
实际上就是id <NSObject, NSCopying>
,在NSObject+BKBlockExecution.h
文件中可以印证这一说法,它本质上就是接下来需要执行的block
。作者使用了双重保险来验证GCD
是否支持取消block
执行,函数BKSupportsDispatchCancellation()
的实现是这样的:
// NS_INLINE表示函数为内联函数,在编译时,函数的实现代码就会被拷贝到调用处,然后编译在同一个可执行文件中。这样可以提高函数的调用效率,但仅适用于体积小且逻辑简单的函数,否则可执行文件就可能变得很大。
NS_INLINE BOOL BKSupportsDispatchCancellation(void)
#if DISPATCH_CANCELLATION_SUPPORTED
return (&dispatch_block_cancel != NULL);
#else
return NO;
#endif
第二次验证实际上就是寻找函数dispatch_block_cancel
的入口地址,如果是空值NULL
(相当于OC
中的nil
),则表示不支持。
接着看看BlocksKit
如何自实现可取消执行的block
:
#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;
作者巧妙的用了一个__block
的cacelled
标记,标记无效时才执行block
。默认情况下就传入NO
,使其能正常工作。将要取消时传入YES
即可,以下就是bk_cancelBlock
的实现:
+ (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);
食髓知味,神奇的动态代理 |
看到这里相信你已经了解了BlocksKit
的一些基本使用的原理了,也肯定它们难不倒聪明的你。那么接下来的两个模块KVO
和动态代理
请你保持足够耐心(尤其是后者),期望你在阅读完之后依然能够轻松地呼出一口气,证明你确实理解了和笔者我真正的讲解到位了。
嗯,立个FLAG
:前方高能预警!
包装KVO
导入了这个框架,你就可以这么使用KVO
,十分简洁:
[self.view bk_addObserverForKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew task:^(id obj, NSDictionary *change)
NSLog(@"target:%@ change:%@", obj, change);
];
以下是NSObject+BKBlockObservation.h
中提供的所有关于KVO
的接口:
@interface NSObject (BlockObservation)
// 不指定标识符添加观察者,方法会返回一个标识符用作取消观察者的参数,默认是进程的标识信息。
- (NSString *)bk_addObserverForKeyPath:(NSString *)keyPath task:(void (^)(id target))task;
- (NSString *)bk_addObserverForKeyPaths:(NSArray *)keyPaths task:(void (^)(id obj, NSString *keyPath))task;
- (NSString *)bk_addObserverForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options task:(void (^)(id obj, NSDictionary *change))task;
- (NSString *)bk_addObserverForKeyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options task:(void (^)(id obj, NSString *keyPath, NSDictionary *change))task;
- (void)bk_addObserverForKeyPath:(NSString *)keyPath identifier:(NSString *)token options:(NSKeyValueObservingOptions)options task:(void (^)(id obj, NSDictionary *change))task;
// 添加指定键路径数组、标识和监听选项的观察者
- (void)bk_addObserverForKeyPaths:(NSArray *)keyPaths identifier:(NSString *)token options:(NSKeyValueObservingOptions)options task:(void (^)(id obj, NSString *keyPath, NSDictionary *change))task;
// 移除指定标识和键路径的观察者
- (void)bk_removeObserverForKeyPath:(NSString *)keyPath identifier:(NSString *)token;
- (void)bk_removeObserversWithIdentifier:(NSString *)token;
// 移除所有的观察者
- (void)bk_removeAllBlockObservers;
@end
大多框架都采用提供多个参数个数不一接口,而实质上接口的实现都是调用自身的一个参数最为完备的方法来克服objc
不支持默认参数的问题和满足类自身的封装性。在这里添加监听者的方法最终就进入了这样的一段代码:
- (void)bk_addObserverForKeyPaths:(NSArray *)keyPaths identifier:(NSString *)identifier options:(NSKeyValueObservingOptions)options context:(BKObserverContext)context task:(id)task
// 保证参数的完整性
NSParameterAssert(keyPaths.count);
NSParameterAssert(identifier.length);
// task就是block
NSParameterAssert(task);
// 这个方法实现较长,为了能够更加细致地分析,后面会慢慢贴出逻辑结构
Class classToSwizzle = self.class;
// 使用一个单例的集合来存放已经Swizzling过的类,具体就是拌和dealloc的实现。对于一个类来说,dealloc方法拌和一次就可以了。这里不关心集合中元素的顺序,故用NSMutableSet就可以了。
NSMutableSet *classes = self.class.bk_observedClassesHash;
// 使用互斥锁保证线程安全,防止同时对classes操作造成不可预期的结果
@synchronized (classes)
NSString *className = NSStringFromClass(classToSwizzle);
if (![classes containsObject:className])
下面进入Method Swizzling
关键部分:
SEL deallocSelector = sel_registerName("dealloc");
__block void (*originalDealloc)(__unsafe_unretained id, SEL) = NULL;
id newDealloc = ^(__unsafe_unretained id objSelf)
// 拌和dealloc方法的核心目的就是希望在dealloc之前移除所有的观察者
[objSelf bk_removeAllBlockObservers];
// 如果当前类中没有实现dealloc方法,就动态发送消息给父类。对于那些自定义的类,方法能够添加成功,originalDealloc就一直是NULL
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);
;
// 感觉这里的命名有点晕,上面定义的newDealloc实际上是用来构造IMP的block,这里就是定义新的dealloc实现
IMP newDeallocIMP = imp_implementationWithBlock(newDealloc);
// 对于系统类来说,肯定不能添加成功。因为NSObject已经有dealloc方法,默认实现就是调用父类。ARC中不允许出现[super dealloc],因为运行时会自动完成。
// 自定义类可以添加成功,因为自定义类中可以覆盖父类方法实现。从这个角度来看,这个操作是可以成功的。
// 由这个方法官方解释可知给类添加的方法必须接收两个参数,也就是self和_cmd两个隐式参数,故需要指定类型为v@:
//v@: 表示方法类型为无返回值接收两个参数:一个对象和一个selector。
if (!class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:"))
// 添加不成功,说明之前已经有方法的实现了,就把之前的方法取出来。
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是设置之前的最新的实现
originalDealloc = (void(*)(__unsafe_unretained id, SEL))method_setImplementation(deallocMethod, newDeallocIMP);
//添加成功后,就把这个类标记为已经拌合了dealloc方法
[classes addObject:className];
实例化实际的观察者:
_BKObserver *observer = [[_BKObserver alloc] initWithObservee:self keyPaths:keyPaths context:context task:task];
//实现如下:
- (id)initWithObservee:(id)observee keyPaths:(NSArray *)keyPaths context:(BKObserverContext)context task:(id)task
if ((self = [super init]))
_observee = observee;
_keyPaths = [keyPaths mutableCopy];
_context = context;
_task = [task copy];
return self;
[observer startObservingWithOptions:options];
// 实现如下:
- (void)startObservingWithOptions:(NSKeyValueObservingOptions)options
@synchronized(self)
// 防止同一个对象添加多个相同的观察者
if (_isObserving) return;
[self.keyPaths bk_each:^(NSString *keyPath)
// 调用系统的KVO
[self.observee addObserver:self forKeyPath:keyPath options:options context:BKBlockObservationContext];
];
_isObserving = YES;
NSMutableDictionary *dict;
@synchronized (self)
dict = [self bk_observerBlocks];
if (dict == nil)
dict = [NSMutableDictionary dictionary];
[self bk_setObserverBlocks:dict];
// 以标识符为键,存储观察者信息。这样移除指定标识符的观察者,把该键对应的值移除就可以了。
dict[identifier] = observer;
在观察的键值改变后,就根据监听选项来回调block
:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
// 保证只回调使用`startObservingWithOptions`方法的添加观察者,如此才能有正确的监听选项匹配,task的参数能够对应起来。
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;
// 移除观察者的代码相对简单,限于篇幅,这里就不过多唠叨了。
动态代理
作为BlocksKit
的灵魂所在,逻辑结构和代码的嵌套层次并不是三下五除二就能明白和理清的。既然你已经看到了这里,那么请相信你是一个很有耐心的人,为你鼓掌!那么我们开始本文的最后一个模块,也是干货最多的一个部分!
我们先思考静态的代理是如何实现的,无论是delegate
还是dataSource
!其实总共分三步,注意不是将大象装冰箱的那三步
,而是:
- 指定代理对象;
- 代理对象遵守协议;
- 代理对象实现协议。
当然第一步和第二步的顺序不是那么界限分明,不过这无关紧要,重要的是系统会在对象需要执行代理方法时做出怎样的响应。其实过程很简单:
- 查看该对象的代理对象,如果没有,则忽略这一系列的消息。如果有,就进入下一步;
- 查看代理对象是否遵守协议,对于有必选方法的协议,如果代理对象没有遵守协议,则程序运行直接崩溃;(如
UITableView
) - 查看代理对象是否实现协议中的必选方法,没有程序也会在运行时崩溃。(如
UITableView
)
在文章开篇的时候,我们就已经知道了在BlocksKit
的强力驱动下,我们只需要做什么就可以实现代理模式,这里以及后面的分析都以UIWebView
为例:
//调用属性的setter方法
[webView bk_setDidStartLoadBlock:^(UIWebView *web)
//..
];
[webView bk_setDidFinishLoadBlock:^(UIWebView *web)
//..
];
[webView bk_setDidFinishWithErrorBlock:^(UIWebView *web, NSError *error)
NSLog(@"error: %@", error.localizedDescription);
];
几乎可以说是零成本!然而这只是表面的简单而已,它的背后实则隐藏着各种复杂各种蒙圈各种……而文章到这里的目的就是希望将它们抽丝剥茧,化整为零,化繁为简,那么就来看看作者是怎样在背后完成动态代理的吧!(注:以下内容用UIWebView
作为分析实例,余者(系统对象)除了代理方法不同之外,实现过程是完全一样的。)
- - 找对象,找动态代理对象,上BlocksKit
如果你注意看了框架的源文件,就会发现UIWebView
、UITextField
和UIActionSheet
等原生视图对象都有BlocksKit
的分类,并且这里面还添加的是一系列属性。这是你可能就会想到属性的本质 = getter + setter + iVar
,然后进入实现文件中看看有木有熟悉的动态关联代码。于是你就发现了作者的目的是动态生成属性的存取方法,然后就是喜闻乐见的+ load
:
+ (void)load
// 以下是公用的注册动态代理和链接代理方法,自动释放池会让一个文件中调用它们产生的中间对象提前释放,防止对其他文件造成影响。
@autoreleasepool
[self bk_registerDynamicDelegate];
// 链接代理方法的调用省去,节约篇幅
众所周知,这里是进行Method Swizzling
最适合的地方。故作者自然顺遂
了我们的心意,在这里面完成delegate
的getter
和setter
的拌合:
+ (void)bk_registerDynamicDelegate
// 根据某一协议注册delegate
// 第二个参数就是获取当前对象的代理协议对象,代码不多,可自行查看
[self bk_registerDynamicDelegateNamed:@"delegate" forProtocol:a2_delegateProtocol(self)];
+ (void)bk_registerDynamicDelegateNamed:(NSString *)delegateName forProtocol:(Protocol *)protocol
//self = UIWebView类对象 protocol = UIWebViewDelegate delegateName = @"delegate"
// 取得当前协议的代理信息映射表(可将其看作字典),传入YES表示没有则创建,并绑定在self上,用来记录setter和getter的拌合状态,避免多次拌合。
NSMapTable *propertyMap = [self bk_delegateInfoByProtocol:YES];
//结构体:存储getter、setter、a2_setter
A2BlockDelegateInfo *infoAsPtr = (__bridge void *)[propertyMap objectForKey:protocol];
//正常情况下,一个类只会调用一次注册动态代理的方法。
if (infoAsPtr != NULL) return;
const char *name = delegateName.UTF8String;
objc_property_t property = class_getProperty(self, name);
//setter: setDelegate:
SEL setter = setterForProperty(property, name);
//a2_setter: a2_SetDelegate:
SEL a2_setter = prefixedSelector(setter);
//getter: delegate
SEL getter = getterForProperty(property, name);
//初始化block代理信息
A2BlockDelegateInfo info =
setter, a2_setter, getter
;
//将`delegate`的setter、getter、a2_Setter保存在对应的协议名下面,用mapTable存储
[propertyMap setObject:(__bridge id)&info forKey:protocol];
//将协议下的代理信息取出来
infoAsPtr = (__bridge void *)[propertyMap objectForKey:protocol];
// 以下的核心目的是拌合对象本身delegate属性的getter、setter
//构建新的setter的实现,block接收一个NSObject被代理对象和id类型的代理对象
// 当指定代理对象时,才会调用这个block,也就是你自己设置对象的delegate。
IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject, id delegate)
// realDelegate是让用户可以设置代理对象
A2DynamicDelegate *dynamicDelegate = getDynamicDelegate(delegatingObject, protocol, infoAsPtr, YES);
if ([delegate isEqual:dynamicDelegate])
// 如果设置realDelegate就是动态代理对象本身,就将realDelegate设置为nil,即依旧使用动态代理自身响应消息
delegate = nil;
dynamicDelegate.realDelegate = delegate;
);
// **重要**:将原有的setDelegate:实现替换成上面构建的新实现,而a2_setter即a2_SetDelegate:实现就变成了系统实现:xxx.delegate = xxx。正常情况下,能够交换成功。
if (!swizzleWithIMP(self, setter, a2_setter, setterImplementation, "v@:@", YES))
//bzero,置字节字符串s的前n个字节为零,即清除该协议下的代理信息
bzero(infoAsPtr, sizeof(A2BlockDelegateInfo));
return;
//对于UIWebView来说,是能够响应getter:delegate。以下情况是为了适配自定义代理模式。
if (![self instancesRespondToSelector:getter])
IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject)
return [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject, protocol)];
);
addMethodWithIMP(self, getter, NULL, getterImplementation, "@@:", NO);
到此,系统依然不知道代理对象是谁,这里的一系列操作是后续流程的大前提。
- - 不只牵线搭桥,还包装配到家 - -
贴上+load
中省略的那部分:
[self bk_linkDelegateMethods:@
@"bk_shouldStartLoadBlock": @"webView:shouldStartLoadWithRequest:navigationType:",
@"bk_didStartLoadBlock": @"webViewDidStartLoad:",
@"bk_didFinishLoadBlock": @"webViewDidFinishLoad:",
@"bk_didFinishWithErrorBlock": @"webView:didFailLoadWithError:"
];
在经过一步代理协议的获取后,进入:
+ (void)bk_linkProtocol:(Protocol *)protocol methods:(NSDictionary *)dictionary
//propertyName为新增的属性block名,selectorName对应于原有的delegate方法名
//目的是为所有属性生成getter和setter
[dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *selectorName, BOOL *stop)
// 省略参数检查
// property_copyAttributeValue()第二个参数表示属性的类型
// D: @dynamic N: nonatomic
// 以下就是验证属性的存储方式是否符合要求,即动态生成存取方法和Copy赋值对象。详见参考资料Declared Properties。省略实现代码
SEL selector = NSSelectorFromString(selectorName);
SEL getter = getterForProperty(property, name);
SEL setter = setterForProperty(property, name);
//使用分类添加的属性自然不能响应getter和setter
if (class_respondsToSelector(self, setter) || class_respondsToSelector(self, getter))
return;
//获取上一步注册的代理信息,对于UIWebView来说这里能够获取info
const A2BlockDelegateInfo *info = [self bk_delegateInfoForProtocol:protocol];
// 定义属性getter的实现,当在使用时显示访问才会调用。此处省略代码实现。
// **重要**:调用setter赋值时,进入实现体内部。在这里实现了动态代理的赋值。
IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject, id block)
A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject, protocol, info, YES);
// 生成对应selector的执行block
[delegate implementMethod:selector withBlock:block];
);
// 关于添加setter是否成功的判断,正常情况下均能成功。省略...
上面这段代码省略了很多是为了突出核心部分,也就是为属性setter
的实现,第一句代码内部是这样的:
// 内联函数,static表明是内部函数,不允许外部调用
static inline A2DynamicDelegate *getDynamicDelegate(NSObject *delegatingObject, Protocol *protocol, const A2BlockDelegateInfo *info, BOOL ensuring)
// 对于protocol:UIWebViewDelegate, info有效
A2DynamicDelegate *dynamicDelegate = [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject, protocol)];
// 后面贴出
// 调用方法实现依次贴出,方便阅读
- (id)bk_dynamicDelegateForProtocol:(Protocol *)protocol
// self是将要被代理的对象
Class class = [A2DynamicDelegate class];
NSString *protocolName = NSStringFromProtocol(protocol);
if ([protocolName hasSuffix:@"Delegate"])
// 根据协议名,生成动态代理对象所属类。对于UIWebView实例,它的类就是A2DynamicUIWebViewDelegate。实现细节,可自行查看。
class = a2_dynamicDelegateClass([self class], @"Delegate");
else if ([protocolName hasSuffix:@"DataSource"])
class = a2_dynamicDelegateClass([self class], @"DataSource");
// 返回一个动态代理实例
return [self bk_dynamicDelegateWithClass:class forProtocol:protocol];
// 懒加载
- (id)bk_dynamicDelegateWithClass:(Class)cls forProtocol:(Protocol *)protocol
__block A2DynamicDelegate *dynamicDelegate;
dispatch_sync(a2_backgroundQueue(), ^
dynamicDelegate = objc_getAssociatedObject(self, (__bridge const void *)protocol);
if (!dynamicDelegate)
// 动态代理绑定协议信息,例如cls = A2DynamicUIWebViewDelegate
dynamicDelegate = [[cls alloc] initWithProtocol:protocol];
// 给代理对象指定协议绑定动态代理对象
objc_setAssociatedObject(self, (__bridge const void *)protocol, dynamicDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
);
return dynamicDelegate;
以上就获取到了动态代理对象,下面就是将其关联:
// 续内联静态方法
// 以下两个判断的意义是保证info各项参数的完整,防止接下来的消息分发出错。(防御式编程)
if (!info || !info->setter || !info->getter)
return dynamicDelegate;
if (!info->a2_setter && !info->setter) return dynamicDelegate;
// 两个参数是方法的隐式参数
id (*getterDispatch)(id, SEL) = (id (*)(id, SEL)) objc_msgSend;
// 获取原始的代理对象,第一次进入自然为空
id originalDelegate = getterDispatch(delegatingObject, info->getter);
// 第一次自然条件不会满足,而后就直接返回取得的动态代理对象
if (bk_object_isKindOfClass(originalDelegate, A2DynamicDelegate.class)) return dynamicDelegate;
// 以下只在第一次设置动态代理对象时调用
void (*setterDispatch)(id, SEL, id) = (void (*)(id, SEL, id)) objc_msgSend;
// **重要**
// info->a2_setter ?:info->setter 是info->a2_setter ? info->a2_setter : info->setter偷懒的写法。前者条件成立是相当于是空操作,结果就为三目表达式的结果就为条件判断的结果。于是就调用a2_setter,设置真正的代理。
// 动态调用setter,在注册动态代理是已将系统的setter实现与a2_setter实现交换了。这里的逻辑就是delegatingObject.delegate = dynamicDelegate
setterDispatch(delegatingObject, info->a2_setter ?: info->setter, dynamicDelegate);
return dynamicDelegate;
终于在这步之后,系统能够获取到实际的代理对象了: A2DynamicUIWebViewDelegate
类型的实例。
属性的setter
实现的第二句就是链接block
实现与协议代理方法:
- (void)implementMethod:(SEL)selector withBlock:(id)block
// 将代理方法的用block实现,self就是现在的那个动态代理对象
BOOL isClassMethod = self.isClassProxy;
if (!block)
[self.invocationsBySelectors bk_removeObjectForSelector:selector];
return;
// 获取方法的描述:名字及类型。第三个参数指明该方法是否是协议中的必须方法
// 在必须方法中没找到就在可选方法中找
struct objc_method_description methodDescription = protocol_getMethodDescription(self.protocol, selector, YES, !isClassMethod);
if (!methodDescription.name) methodDescription = protocol_getMethodDescription(self.protocol, selector, NO, !isClassMethod);
A2BlockInvocation *inv = nil;
if (methodDescription.name)
NSMethodSignature *protoSig = [NSMethodSignature signatureWithObjCTypes:methodDescription.types];
// 使用自定义的block和协议方法签名初始化block调用者。这个构造其中还将验证函数签名和block签名是否一致,不一致将抛出异常,保证block能够正常接收参数并完成回调。详细验证过程请查看源码。
inv = [[A2BlockInvocation alloc] initWithBlock:block methodSignature:protoSig];
else
// 协议中没有这个方法,就用block的签名生成函数签名,然后实例化调用对象并返回。
inv = [[A2BlockInvocation alloc] initWithBlock:block];
// 以selector为键存储实际调用对象到动态代理的映射表中。
[self.invocationsBySelectors bk_setObject:inv forSelector:selector];
- (instancetype)initWithBlock:(id)block
NSParameterAssert(block);
NSMethodSignature *blockSignature = [[self class] typeSignatureForBlock:block];
// 后面贴出
这里需要获取到block
的签名,它与方法不同的是没有self
和_cmd
这两个默认参数。作者定义了一个BKBlockRef
结构体指针来反映其在内存中的数据结构,这里面各个分量可在运行时源码中找到:
typedef struct _BKBlock
__unused Class isa;
BKBlockFlags flags;
__unused int reserved;
void (__unused *invoke)(struct _BKBlock *block, ...);
struct
unsigned long int reserved;
unsigned long int size;
// requires BKBlockFlagsHasCopyDisposeHelpers
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
// requires BKBlockFlagsHasSignature
const char *signature;
const char *layout;
*descriptor;
// imported variables
*BKBlockRef;
然后使用指针偏移,获取signature
:
+ (NSMethodSignature *)typeSignatureForBlock:(id)block __attribute__((pure, nonnull(1)))
BKBlockRef layout = (__bridge void *)block;
// 验证block的标识:签名和Copy赋值的block对象
if (!(layout->flags & BKBlockFlagsHasSignature))
return nil;
void *desc = layout->descriptor;
desc += 2 * sizeof(unsigned long int);
if (layout->flags & BKBlockFlagsHasCopyDisposeHelpers)
desc += 2 * sizeof(void *);
if (!desc)
return nil;
const char *signature = (*(const char **)desc);
return [NSMethodSignature signatureWithObjCTypes:signature];
// 接initWithBlock:
// 将block签名转换成方法签名
NSMethodSignature *methodSignature = [[self class] methodSignatureForBlockSignature:blockSignature];
NSAssert(methodSignature, @"Incompatible block: %@", block);
// 实例化调用对象
return (self = [self initWithBlock:以上是关于简而繁的BlocksKit的主要内容,如果未能解决你的问题,请参考以下文章
BlocksKit block从配角到主角—oc通往函数式编程之路--oc rx化?