意如分类标题(这个博客主题貌似没有,😂)那样,笔者打算开始探究框架与源码。毫无疑问,这其中会遇到各种各样的挑战,但是我觉得我们应尽早走出这一步,不然就错过了很多的精彩。也许这精彩是更加开阔的程序视野,亦或是逻辑思维与编程能力的提升,这其中对耐心与意志的磨炼绝对会让人十分“酸爽”。
而这简而繁BlocksKit
就成为了第一道菜
,为什么呢?因为之前对它的认知就是对系统API
的block
方式调用的高度封装,这是它简单的使用特性。然而它的灵魂——动态代理让我深切感受到了框架的设计哲学:把简洁留给别人,把复杂留给自己
。不信?那就接着往下看!
简单的API使用与探究 你或许诟病过Target-Action
响应模式代码的编写,又或者对代理模式爱的深沉。BlocksKit
给你带来了福音,给按钮添加监听方法是这么写的:(原谅我这里把Objective-C
代码标记为C
,因为这个博客主题竟然识别不了Objective-C
,💔)
1 2 3 [button bk_addEventHandler:^(id sender) { NSLog(@"点了我" ); } forControlEvents:UIControlEventTouchUpInside];
这是UIControl
添加的分类BlocksKit
中的一个方法,以下是它的实现:
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 - (void )bk_addEventHandler:(void (^)(id sender))handler forControlEvents:(UIControlEvents)controlEvents { NSParameterAssert(handler); 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; } BKControlWrapper *target = [[BKControlWrapper alloc] initWithHandler:handler forControlEvents:controlEvents]; [handlers addObject:target]; [self addTarget:target action:@selector(invoke:) forControlEvents:controlEvents]; }
这里之所以采用字典的形式来存储事件与响应者,是为了后面能够一次性移除某个事件的响应者和判断某个事件是否有响应者。具体可以看看bk_removeEventHandlersForControlEvents:
和bk_hasEventHandlersForControlEvents:
这两个方法,这里就不再赘述。
代理模式的实现,用到了本框架的核心模块:动态代理 。后面会用相当篇幅解读,这里先看看其他有趣的东西,比方说延时手势。关于构造实际响应目标和按钮大同小异,这里主要看看如何让手势延时响应。手势响应时都会触发一个位于UIGestureRecognizer
分类中的bk_handleAction:
方法,以下仅贴出关于延时的实现:
1 2 3 4 5 6 7 - (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
)解决了使用分类不能新增属性的问题,其实就涉及到了两个函数,因此极易上手:
1 2 3 void objc_setAssociatedObject (id object, const void *key, id value, objc_AssociationPolicy policy) id objc_getAssociatedObject (id object, const void *key)
系统提供的关联策略有如下几种:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef OBJC_ENUM (uintptr_t , objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0 , OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1 , OBJC_ASSOCIATION_COPY_NONATOMIC = 3 , OBJC_ASSOCIATION_RETAIN = 01401 , OBJC_ASSOCIATION_COPY = 01403 };
不难看出,没有weak
这一关联策略。那么我们来看看BlocksKit
是如何实现的:
1 2 3 4 5 6 7 8 - (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; }
1 2 3 4 5 6 7 8 9 - (id)bk_associatedValueForKey:(const void *)key { id value = objc_getAssociatedObject(self, key); if (value && [value isKindOfClass:[_BKWeakAssociatedObject class]]) { return [(_BKWeakAssociatedObject *)value value]; } return value; }
1 2 3 4 5 @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
不支持的情况,所以就有了这段条件编译的指令:
1 2 3 4 5 6 #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) #define DISPATCH_CANCELLATION_SUPPORTED 1 #else #define DISPATCH_CANCELLATION_SUPPORTED 1 #endif
不过这都是细枝末节,权当是作者手误,在这里姑且认为这个宏是有正确意义的。那么就来看看怎么使用一个可以取消的block
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 NSLog(@"start" ); BKCancellationToken token = [self bk_performAfterDelay:10.0f usingBlock:^(id _Nonnull obj) { NSLog(@"haha" ); }];
可以看到block
确实是可以被取消执行的。我们注意到这里有一个token
,嗯?进入方法内部一探究竟。第一个方法有这样的调用层级关系:
bk_performAfterDelay:usingBlock:
|__bk_performOnQueue:afterDelay:usingBlock:
...|__BKDispatchCancellableBlock()
最后这个BKCancellationToken
类型的token
,是这样返回的:
1 2 3 4 5 6 7 8 9 10 11 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()
的实现是这样的:
1 2 3 4 5 6 7 8 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #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
的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 + (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
,十分简洁:
1 2 3 [self.view bk_addObserverForKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionNew task:^(id obj, NSDictionary *change) { NSLog(@"target:%@ change:%@" , obj, change); }];
以下是NSObject+BKBlockObservation.h
中提供的所有关于KVO
的接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @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
不支持默认参数的问题和满足类自身的封装性。 在这里添加监听者的方法最终就进入了这样的一段代码:
1 2 3 4 5 6 7 - (void )bk_addObserverForKeyPaths:(NSArray *)keyPaths identifier:(NSString *)identifier options:(NSKeyValueObservingOptions)options context:(BKObserverContext)context task:(id)task { NSParameterAssert(keyPaths.count); NSParameterAssert(identifier.length); NSParameterAssert(task);
1 2 3 4 5 6 7 8 9 Class classToSwizzle = self.class; NSMutableSet *classes = self.class.bk_observedClassesHash; @synchronized (classes) { NSString *className = NSStringFromClass(classToSwizzle); if (![classes containsObject:className]) {
下面进入
Method Swizzling
关键部分:
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 SEL deallocSelector = sel_registerName("dealloc" ); __block void (*originalDealloc) (__unsafe_unretained id, SEL) = NULL ; 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); if (!class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:" )) { Method deallocMethod = class_getInstanceMethod(classToSwizzle, deallocSelector); originalDealloc = (void (*)(__unsafe_unretained id, SEL))method_getImplementation(deallocMethod); originalDealloc = (void (*)(__unsafe_unretained id, SEL))method_setImplementation(deallocMethod, newDeallocIMP); } [classes addObject:className]; } }
实例化实际的观察者:
1 2 3 4 5 6 7 8 9 10 11 12 _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; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [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; } }
1 2 3 4 5 6 7 8 9 10 11 12 NSMutableDictionary *dict; @synchronized (self) { dict = [self bk_observerBlocks]; if (dict == nil) { dict = [NSMutableDictionary dictionary]; [self bk_setObserverBlocks:dict]; } } dict[identifier] = observer; }
在观察的键值改变后,就根据监听选项来回调
block
:
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 - (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 ; } } } }
动态代理
作为BlocksKit
的灵魂所在,逻辑结构和代码的嵌套层次并不是三下五除二就能明白和理清的。既然你已经看到了这里,那么请相信你是一个很有耐心的人,为你鼓掌!那么我们开始本文的最后一个模块,也是干货最多的一个部分!
我们先思考静态的代理是如何实现的,无论是delegate
还是dataSource
!其实总共分三步,注意不是将大象装冰箱的那三步
,而是:
指定代理对象;
代理对象遵守协议;
代理对象实现协议。
当然第一步和第二步的顺序不是那么界限分明,不过这无关紧要,重要的是系统会在对象需要执行代理方法时做出怎样的响应。其实过程很简单:
查看该对象的代理对象,如果没有,则忽略这一系列的消息。如果有,就进入下一步;
查看代理对象是否遵守协议,对于有必选方法的协议,如果代理对象没有遵守协议,则程序运行直接崩溃;(如UITableView
)
查看代理对象是否实现协议中的必选方法,没有程序也会在运行时崩溃。(如UITableView
)
在文章开篇的时候,我们就已经知道了在BlocksKit
的强力驱动下,我们只需要做什么就可以实现代理模式,这里以及后面的分析都以UIWebView
为例:
1 2 3 4 5 6 7 8 9 10 11 12 [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
:
1 2 3 4 5 6 7 + (void )load { @autoreleasepool { [self bk_registerDynamicDelegate]; } }
众所周知,这里是进行Method Swizzling
最适合的地方。故作者自然顺遂
了我们的心意,在这里面完成delegate
的getter
和setter
的拌合:
1 2 3 4 5 + (void )bk_registerDynamicDelegate { [self bk_registerDynamicDelegateNamed:@"delegate" forProtocol:a2_delegateProtocol(self)]; }
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 + (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); } }
到此,系统依然不知道代理对象是谁,这里的一系列操作是后续流程的大前提。
- - 不只牵线搭桥,还包装配到家 - -
贴上+load
中省略的那部分:
1 2 3 4 5 6 [self bk_linkDelegateMethods:@{ @"bk_shouldStartLoadBlock" : @"webView:shouldStartLoadWithRequest:navigationType:" , @"bk_didStartLoadBlock" : @"webViewDidStartLoad:" , @"bk_didFinishLoadBlock" : @"webViewDidFinishLoad:" , @"bk_didFinishWithErrorBlock" : @"webView:didFailLoadWithError:" }];
在经过一步代理协议的获取后,进入:
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 + (void )bk_linkProtocol:(Protocol *)protocol methods:(NSDictionary *)dictionary { [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *selectorName, BOOL *stop) { 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 setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject, id block) { A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject, protocol, info, YES); [delegate implementMethod:selector withBlock:block]; }); }
上面这段代码省略了很多是为了突出核心部分,也就是为属性setter
的实现,第一句代码内部是这样的:
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 static inline A2DynamicDelegate *getDynamicDelegate (NSObject *delegatingObject, Protocol *protocol, const A2BlockDelegateInfo *info, BOOL ensuring) { A2DynamicDelegate *dynamicDelegate = [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject, protocol)]; } - (id)bk_dynamicDelegateForProtocol:(Protocol *)protocol { Class class = [A2DynamicDelegate class ]; NSString *protocolName = NSStringFromProtocol(protocol); if ([protocolName hasSuffix:@"Delegate" ]) { 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) { dynamicDelegate = [[cls alloc] initWithProtocol:protocol]; objc_setAssociatedObject(self, (__bridge const void *)protocol, dynamicDelegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } }); return dynamicDelegate; }
以上就获取到了动态代理对象,下面就是将其关联:
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 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; setterDispatch(delegatingObject, info->a2_setter ?: info->setter, dynamicDelegate); return dynamicDelegate; }
终于在这步之后,系统能够获取到实际的代理对象了: A2DynamicUIWebViewDelegate
类型的实例。
属性的setter
实现的第二句就是链接block
实现与协议代理方法:
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 - (void )implementMethod:(SEL)selector withBlock:(id)block { 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]; inv = [[A2BlockInvocation alloc] initWithBlock:block methodSignature:protoSig]; } else { inv = [[A2BlockInvocation alloc] initWithBlock:block]; } [self.invocationsBySelectors bk_setObject:inv forSelector:selector]; }
1 2 3 4 5 - (instancetype)initWithBlock:(id)block { NSParameterAssert(block); NSMethodSignature *blockSignature = [[self class] typeSignatureForBlock:block]; }
这里需要获取到block
的签名,它与方法不同的是没有self
和_cmd
这两个默认参数。作者定义了一个BKBlockRef
结构体指针来反映其在内存中的数据结构,这里面各个分量可在运行时源码中找到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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; void (*copy)(void *dst, const void *src); void (*dispose)(const void *); const char *signature; const char *layout; } *descriptor; } *BKBlockRef;
然后使用指针偏移,获取signature
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + (NSMethodSignature *)typeSignatureForBlock:(id)block __attribute__((pure, nonnull(1 ))) { BKBlockRef layout = (__bridge void *)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]; }
1 2 3 4 5 6 7 NSMethodSignature *methodSignature = [[self class] methodSignatureForBlockSignature:blockSignature]; NSAssert(methodSignature, @"Incompatible block: %@" , block); return (self = [self initWithBlock:block methodSignature:methodSignature blockSignature:blockSignature]); }
现在动态代理对象就完成了自定义block
与代理方法的链接,当代理对象响应代理方法时,就会回调block
。
在UIWebView
的分类源文件中,可以看到关于它的代理所属类的定义:
1 2 3 4 5 6 7 8 @interface A2DynamicUIWebViewDelegate : A2DynamicDelegate <UIWebViewDelegate> @end @implementation A2DynamicUIWebViewDelegate @end
那么现在我们可以总结下整个动态代理流程了:
拌合delegate
的setter方法;
动态生成属性的存取方法,在delegate
的setter
中设置动态代理对象,并将代理方法映射为指定的block
实现;
系统获取对象的代理是实际上取得的是动态代理对象(不手动设置代理对象),这个对象遵循了相关协议并实现了协议方法;
在协议方法实现体中,回调block
以及readDelegate
的代理方法(如果有这个对象且其能够响应代理方法)。
例如webView:shouldStartLoadWithRequest:navigationType:
方法实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { BOOL ret = YES; id realDelegate = self.realDelegate; if (realDelegate && [realDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) ret = [realDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; BOOL (^block)(UIWebView *, NSURLRequest *, UIWebViewNavigationType) = [self blockImplementationForMethod:_cmd]; if (block) ret &= block(webView, request, navigationType); return ret; }
- - 这伙计还能个性化定制 - -
目前就UIWebView
的动态代理的整个流程大体上走了一遍了,你可以跟着以上的分析,结合实例,运用断点,try again!
然而这还不是终点,你自定义的代理协议也能完美的工作。这里你有两种选择:一是自定义对应的代理对象的实现类,命名方式为:A2Dynamic类名Delegate
,并动态注册代理和链接代理方法,也就是完全走上面的这条路。相信你不会这么做,于是乎,作者给了我们另外一条路:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #import <Foundation/Foundation.h> @protocol CustomeDelegate <NSObject> - (void )scanMe; - (void )giveMe:(id)any; @end @interface CustomeDelegate : NSObject @property (weak, nonatomic) id<CustomeDelegate> delegate; - (void )delegateTrigger; @end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import "CustomeDelegate.h" @implementation CustomeDelegate - (void )delegateTrigger { if ([self.delegate respondsToSelector:@selector(scanMe)]) { [self.delegate scanMe]; } if ([self.delegate respondsToSelector:@selector(giveMe:)]) { [self.delegate giveMe:@"给你" ]; } } @end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 _del = [[CustomeDelegate alloc] init]; A2DynamicDelegate<CustomeDelegate> *dd = [_del bk_dynamicDelegateForProtocol:@protocol(CustomeDelegate)]; [dd implementMethod:@selector(scanMe) withBlock:^ { NSLog(@"scan me" ); }]; [dd implementMethod:@selector(giveMe:) withBlock:^(id obj) { NSLog(@"obj:%@" , obj); }]; _del.delegate = dd; [_del delegateTrigger];
如果你注意到A2DynamicDelegate
是继承自NSProxy
,而且它本身就是根类。正如它的名字代理
,它的本质工作就是消息的转发:Normal forwarding
。为此它必须实现两个父类方法:
1 2 - (void )forwardInvocation:(NSInvocation *)invocation; - (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
到这里你可能就有些明悟了,当接收到代理消息时,就把消息转发给动态代理对象,然后它再根据具体的消息回调不同的block
。而事实上就是这样的过程,请继续往下看!
最开始我们需要通过代理协议对象创建一个动态代理对象,因为我们没有自定义实现类,最终它就是一个A2DynamicDelegate
实例。然后用指定block
实现指定的代理方法,这些实现前面已经贴出,下面重点内容是消息转发:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { A2BlockInvocation *invocation = nil; if ((invocation = [self.invocationsBySelectors bk_objectForSelector:aSelector])) return invocation.methodSignature; else if ([self.realDelegate methodSignatureForSelector:aSelector]) return [self.realDelegate methodSignatureForSelector:aSelector]; else if (class_respondsToSelector(object_getClass(self), aSelector)) return [object_getClass(self) methodSignatureForSelector:aSelector]; return [[NSObject class] methodSignatureForSelector:aSelector]; } - (void )forwardInvocation:(NSInvocation *)outerInv { SEL selector = outerInv.selector; A2BlockInvocation *innerInv = nil; if ((innerInv = [self.invocationsBySelectors bk_objectForSelector:selector])) { [innerInv invokeWithInvocation:outerInv]; } else if ([self.realDelegate respondsToSelector:selector]) { [outerInv invokeWithTarget:self.realDelegate]; } }
总结 花了近一周的时间,磕磕绊绊算是把这篇文章写完了。最初写这篇文章的动力来自看别人分析这个框架没怎么看懂的郁闷,抱着发挥主观能动性的理念,来自我理解框架的架构思想。不过这个框架大致有哪些功能,还是在那篇文章中知道的,因为之前没用过这个框架,文章链接会在参考资料中给出。
解读这个框架所花时间不可谓长也不可谓短,个人感觉在那茅塞顿开的一瞬间会有所感触:一切都值了。一个成熟的框架涉及到一些底层知识是必不可少的,像是这里面的block
内存中的数据结构之类的。早就听人言,阅读框架的代码不是要你每行每句都能够吃透,是希望你在这过程中开阔你的编程思想,提供你分析和理解能力。所以文章中一些省略的地方,希望各位能够按照自己的需求去阅读它,毕竟只有自己亲眼看到的才是最为真实的。
最后,发表一点关于这个框架的想法:它本质上是为了让我们能够更为简单的使用API,即用主观上更少的代码完成某项功能,但是背后就要付出更多的代价来支撑省下来的体力活。而我们应当在性能上投注更多的目光,而编写代码过程繁琐与否编程语言本身就已经决定了,要相信所有存在的东西都是有它的道理的。所以这个BlocksKit
不太推荐使用,但是学习它是完全有必要和有意义的!
参考资料 Draveness/analyze: 神奇的BlocksKit一 、二
Apple Documents: Declared Properties
勘误 框架作者在+load
方法中链接的代理方法被@autoreleasepool
包裹着的目的更切确来说是为了防止内存泄漏 ,因为这个方法是发生在main()
函数之前,所以自然这其中产生的对象没有被主函数里面的自动释放池管理到。而通过对源码的分析来看,其实在+load
方法的执行过程中前后是加了 objc_autoreleasePoolPush()
和objc_autoreleasePoolPop() 的!
源码如下:
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 void call_load_methods (void ) { static bool loading = NO; bool more_categories; loadMethodLock.assertLocked(); if (loading) return ; loading = YES; void *pool = objc_autoreleasePoolPush(); do { while (loadable_classes_used > 0 ) { call_class_loads(); } more_categories = call_category_loads(); } while (loadable_classes_used > 0 || more_categories); objc_autoreleasePoolPop(pool); loading = NO; }
以上在sunnyxx
的iOS程序main函数之前发生了什么 一文中得到印证。