神奇的 BlocksKit (二)
這篇文章『神奇的 BlocksKit』的第二部分,關于第一部分的內容在這里:
動態代理
動態代理這部分可以說是 BlocksKit 的精華。它使用 block 屬性替換 UIKit 中的所有能夠通過代理完成的事件,省略了設置代理和實現方法的過程,讓對象自己實現代理方法,而且這個功能的實現是極其動態的。
其實不是對象自己實現的代理方法,只是框架為我們提供的便捷方法,不需要構造其它對象就能完成代理方法的實現,具體我們會在后面詳細地解釋。
下面是這部分幾個關鍵的類:
A2BlockInvocation
的主要作用是存儲和轉發 blockA2DynamicDelegate
用來實現類的代理和數據源,它是NSProxy
的子類NSObject+A2DynamicDelegate
負責為返回bk_dynamicDelegate
和bk_dynamicDataSource
等A2DynamicDelegate
類型的實例,為NSObject
提供主要的接口NSObject+A2BlockDelegate
提供了一系列接口將代理方法映射到 block 上- 其他的 UIKit 的分類提供對應的屬性,并在對應的
A2DynamicDelegate
子類中實現代理方法
這里是我對這部分代碼結構的理解:
這篇文成首先會從上到下對整個工作原理進行概述,然后再從底層到頂層詳細地解釋這個框架的機制和原理。
動態代理工作概述
在這里我們要對這部分的實現進行一個簡單的概述,從上到下跟蹤 BlocksKit 的調用過程。
以 UIImagePickerController
為例,因為這個文件中的代碼較少,能省去很多不必要的實現細節。
在頭文件中聲明了兩個屬性,也就是 UIImagePickerController
代理方法的對應 block 屬性:
@property (nonatomic,copy) void(^bk_didFinishPickingMediaBlock)(UIImagePickerController *,NSDictionary *);
@property (nonatomic,copy) void(^bk_didCancelBlock)(UIImagePickerController *);
然后在實現文件中動態生成這兩個方法的存取方法
@dynamic bk_didFinishPickingMediaBlock;
@dynamic bk_didCancelBlock;
你可以看到在這個名為 BlocksKit
的分類中只添加了一個方法:
+ (void)load
{
@autoreleasepool {
[self bk_registerDynamicDelegate];
[self bk_linkDelegateMethods:@{ @"bk_didFinishPickingMediaBlock": @"imagePickerController:didFinishPickingMediaWithInfo:",
@"bk_didCancelBlock": @"imagePickerControllerDidCancel:" }];
}
}
在 load
中實現這個方法,能夠減少其中兩個方法的調用次數,在 autoreleasepool
塊中調用方法,使得其它地方的代碼不會受到這里注冊代理,鏈接代理方法中產生的對象的影響。
bk_registerDynamicDelegate
方法是 NSObject+A2BlockDelegate
分類中添加的方法,用于修改原有屬性 delegate
方法的實現(動態替換 delegate 方法實現)。在這里就是與 UIImagePickerController+BlocksKit
處于同一文件下的 A2DynamicUIImagePickerControllerDelegate
,先不說這個文件的功能,會在之后介紹。
在 NSObject+A2DynamicDelegate
分類中的 bk_registerDynamicDelegateNamed:forProtocol:
修改 @selector(delegate)
和 @selector(setDelegate:)
的實現,使用 A2DynamicUIImagePickerControllerDelegate
替換原有的 delegate
IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject,id delegate) {
A2DynamicDelegate *dynamicDelegate = getDynamicDelegate(delegatingObject,protocol,infoAsPtr,YES);
if ([delegate isEqual:dynamicDelegate]) {
delegate = nil;
}
dynamicDelegate.realDelegate = delegate;
});
IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
return [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject,protocol)];
});
在獲取 delegate
屬性時,就會獲取 A2DynamicUIImagePickerControllerDelegate
, realDelegate
相當于原有的 delegate
屬性,會在下面的小節中具體分析。
在 load
方法中調用下一個方法是 bk_linkDelegateMethods:
這個方法會把代理方法和對應的 block 屬性鏈接起來,這樣可以通過代理方法的選擇子查找對應的 block。
IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject,protocol,info,NO);
return [delegate blockImplementationForMethod:selector];
});
IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject,id block) {
A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject,protocol,info,YES);
[delegate implementMethod:selector withBlock:block];
});
通過調用 A2DynamicDelegate
的實例方法 blockImplementationForMethod:
和 implementMethod:withBlock:
動態實現 block 的存取方法。
當代理方法 imagePickerController:didFinishPickingMediaWithInfo:
被調用時,因為 A2DynamicUIImagePickerControllerDelegate
是 UIImagePickerController
的代理,所以會調用它的方法:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
id realDelegate = self.realDelegate;
if (realDelegate && [realDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingMediaWithInfo:)])
[realDelegate imagePickerController:picker didFinishPickingMediaWithInfo:info];
void (^block)(UIImagePickerController *,NSDictionary *) = [self blockImplementationForMethod:_cmd];
if (block) block(picker,info);
}
通過 blockImplementationForMethod:
方法獲取在上面存儲的 block,然后傳入參數執行該代碼塊。
- 在
load
方法注冊動態代理并鏈接代理方法 - 在運行時修改原有的
delegate
屬性的存取方法,使用A2DynamicDelegate
替換原有的delegate
,原有的delegate
換為realDelegate
- 為 block 屬性動態實現存取方法,返回對應
A2DynamicDelegate
子類中存儲的 block - 在代理方法真正被調用時,查找
realDelegate
中是否對代理方法做出響應,無論是否響應,都通過選擇子查找對應的block
,然后傳入相應參數執行 block
自底向上分析動態代理的工作
我們已經自頂向下分析了 BlocksKit 的工作過程,也對這個部分有一個基本的了解,接下來我們將從底層到頂層分析整個 BlocksKit,我們再來看一下整個框架的結構圖:
我們將以下面的順序來依次介紹這些模塊,其中的 UITextField
可以換成其它的類:
- A2BlockInvocation
- A2DynamicDelegate
- NSObject+A2DynamicDelegate
- A2DynamicUITextFieldDelegate
- UITextField+BlocksKit
A2BlockInvocation
A2BlockInvocation
使用來對閉包,也就是 block 進行存儲和轉發的類。
先介紹這個的是因為 A2BlockInvocation
的功能比較底層,涉及的內容也都比較奇葩,所以想先簡單介紹一下,避免之后一個類分幾部分介紹。
在 Objective-C 中,每一個方法甚至 block 都是有類型簽名的:
@interface NSMethodSignature : NSObject {
...
@property (readonly) NSUInteger numberOfArguments;
...
@property (readonly) const char *methodReturnType NS_RETURNS_INNER_POINTER;
...
@end
它們有返回類型、參數數字和參數類型等等。
Block 結構體
block 的簽名沒有哪個函數能夠直接獲取,它存儲在 block 的結構體中,就像這樣:
typedef NS_OPTIONS(int,BKBlockFlags) {
BKBlockFlagsHasCopyDisposeHelpers = (1 << 25),
BKBlockFlagsHasSignature = (1 << 30)
};
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;
這部分其實就是 block 實際存儲在內存中的數據接口,可以在 runtime 中的源代碼中看到這里的代碼。
typeSignatureForBlock
上面的 signature
就是 block 的簽名,下面實現方法來獲取這個簽名
+ (NSMethodSignature *)typeSignatureForBlock:(id)block __attribute__((pure,nonnull(1)))
{
BKBlockRef layout = (__bridge void *)block;
// 如果 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];
}
知道了這個方法的作用再理解它的實現就非常簡單了,根據flag
來移動指針,最終 signature
所在的內存空間。
Unlike a typical method signature,a block type signature has no
self
('@'
) or_cmd
(':'
) parameter,but instead just one parameter for the block itself ('@?'
)。
在這里所涉及的 @
、:
和@?
可以看這里的文檔 類型編碼
在一般的方法簽名中 block 的類型簽名是沒有 self
('@'
) 或者 _cmd
(':'
) 的,只有一個參數代表 block 自己 ('@?'
).
^(UIActionSheet *) {}
- 參數類型:
@?(@"UIActionSheet")
- 返回類型:
v
- 參數類型:
- (void)willPresentActionSheet:(UIActionSheet *)actionSheet
- 參數類型:
@:@
- 返回類型:
v
- 參數類型:
為什么要把 @"UIActionSheet"
標記上括號?因為它們屬于同一個參數。
同時因為 UIActionSheet
也是 id
類型,所以它的類型編碼也是 @
。
當調用 initWithBlock:
方法時,會先調用上面說的方法 typeSignatureForBlock:
獲取 block 的類型簽名:
- (instancetype)initWithBlock:(id)block
{
NSParameterAssert(block);
NSMethodSignature *blockSignature = [[self class] typeSignatureForBlock:block];
NSMethodSignature *methodSignature = [[self class] methodSignatureForBlockSignature:blockSignature];
NSAssert(methodSignature,@"Incompatible block: %@",block);
return (self = [self initWithBlock:block methodSignature:methodSignature blockSignature:blockSignature]);
}
methodSignatureForBlockSignature
然后調用 methodSignatureForBlockSignature:
方法構造一個可以兼容的方法簽名:
+ (NSMethodSignature *)methodSignatureForBlockSignature:(NSMethodSignature *)original
{
#1: 檢查方法簽名的參數,省略
NSMutableString *signature = [[NSMutableString alloc] initWithCapacity:original.numberOfArguments + 1];
const char *retTypeStr = original.methodReturnType;
// 返回類型,id 類型(self @),選擇子類型(SEL :)
[signature appendFormat:@"%s%s%s",retTypeStr,@encode(id),@encode(SEL)];
// signature = (返回類型)@:
for (NSUInteger i = 1; i < original.numberOfArguments; i++) {
const char *typeStr = [original getArgumentTypeAtIndex:i];
NSString *type = [[NSString alloc] initWithBytesNoCopy:(void *)typeStr length:strlen(typeStr) encoding:NSUTF8StringEncoding freeWhenDone:NO];
[signature appendString:type];
}
// signature = (返回類型)@:(參數類型)
return [NSMethodSignature signatureWithObjCTypes:signature.UTF8String];
}
具體的實現細節我們就省略了,它的工作原理是把 @?(@"UIActionSheet")
類型簽名轉換成 @:@
,然后返回方法簽名。
關于代碼中的 @encode
可以看這里 聲明方法的屬性
isSignature:compatibleWithSignature:
在這個類中最后一個重要的方法就是 isSignature:compatibleWithSignature:
,這個方法是判斷傳入的 block 和方法的類型簽名是否兼容。
+ (BOOL)isSignature:(NSMethodSignature *)signatureA compatibleWithSignature:(NSMethodSignature *)signatureB __attribute__((pure))
{
#1: 參數檢查,省略
...
#2: 判斷返回值是否相同,省略
if (signatureA.methodReturnType[0] != signatureB.methodReturnType[0]) return NO;
#3: 設置 methodSignature 和 blockSignature
...
#4: 比較 methodSignature 和 blockSignature
return YES;
}
第 #3
部分設置 methodSignature
和 blockSignature
。
因為方法簽名會比 block 類型簽名多一個默認參數,所以,這里會將參數多的設置為 methodSignature
,如果把為 block 類型簽名的設置給了 methodSignature
也不會有問題,在 #4
部分會判斷出來并返回 NO
。
方法默認參數:
self,SEL
,block 默認類型參數:block
NSMethodSignature *methodSignature = nil,*blockSignature = nil;
if (signatureA.numberOfArguments > signatureB.numberOfArguments) {
methodSignature = signatureA;
blockSignature = signatureB;
} else if (signatureB.numberOfArguments > signatureA.numberOfArguments) {
methodSignature = signatureB;
blockSignature = signatureA;
} else {
return NO;
}
第 #4
部分就是一次比較各個類型簽名,也沒什么復雜的,需要注意的就是選擇正確的 index
NSUInteger numberOfArguments = methodSignature.numberOfArguments;
for (NSUInteger i = 2; i < numberOfArguments; i++) {
if ([methodSignature getArgumentTypeAtIndex:i][0] != [blockSignature getArgumentTypeAtIndex:i - 1][0])
return NO;
}
invokeWithInvocation:returnValue:outReturnValue:
這一節主要介紹的是,當 A2BlockInvocation
對象具體需要執行某一個 NSInvocation
時是如何工作的,其實這個方法還是很容易理解的。
- (BOOL)invokeWithInvocation:(NSInvocation *)outerInv returnValue:(out NSValue **)outReturnValue setOnInvocation:(BOOL)setOnInvocation
{
#1: 參數以及類型簽名是否匹配的檢查,省略
NSInvocation *innerInv = [NSInvocation invocationWithMethodSignature:self.blockSignature];
#2: 設置 innerInv 參數
...
[innerInv invokeWithTarget:self.block];
#3: 獲取返回值
free(argBuf);
return YES;
}
第 #2
、#3
部分的代碼是為了設置 innerInv
的參數,獲取返回值:
void *argBuf = NULL;
for (NSUInteger i = 2; i < sig.numberOfArguments; i++) {
const char *type = [sig getArgumentTypeAtIndex:i];
NSUInteger argSize;
NSGetSizeAndAlignment(type,&argSize,NULL);
if (!(argBuf = reallocf(argBuf,argSize))) {
return NO;
}
[outerInv getArgument:argBuf atIndex:i];
[innerInv setArgument:argBuf atIndex:i - 1];
}
// 執行 block
NSUInteger retSize = sig.methodReturnLength;
if (retSize) {
if (outReturnValue || setOnInvocation) {
if (!(argBuf = reallocf(argBuf,retSize))) {
return NO;
}
[innerInv getReturnValue:argBuf];
if (setOnInvocation) {
[outerInv setReturnValue:argBuf];
}
if (outReturnValue) {
*outReturnValue = [NSValue valueWithBytes:argBuf objCType:sig.methodReturnType];
}
}
} else {
if (outReturnValue) {
*outReturnValue = nil;
}
}
A2BlockInvocation
這一節就到這里了,接下來要說一下 A2DynamicDelegate
。
A2DynamicDelegate
A2DynamicDelegate
可以說是 BlocksKit 實現動態代理的關鍵,是這個框架中很重要的類,它通過 block 實現了類的代理和數據源等協議。
A2DynamicDelegate
它的父類是 NSProxy
,而 NSProxy
出現的目的就是為了代理一個對象的。
@interface NSProxy <NSObject>
我們不具體解釋這里的 NSProxy
,如果想要更詳細的信息,請看這里。
A2DynamicDelegate
作為 NSProxy
的子類,必須實現 forwardInvocation:
methodSignatureForSelector:
方法進行對象轉發,這是在蘋果官方文檔中說明的。
覆寫必要的方法 methodSignatureForSelector: 和 forwardInvocation:
我們首先來看一下 methodSignatureForSelector:
,它為一個選擇子返回合適的方法簽名:
- (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];
}
這里的邏輯如下:
- 判斷
invocationsBySelectors
屬性中是否存儲了該選擇子對應的A2BlockInvocation
,直接返回這個invocation
對象的類型簽名,也就是說自己實現了該選擇子對應的方法 -
在真正的
realDelegate
中查找原有的代理(不是當前的動態代理A2DynamicDelegate
)是否實現了該選擇子,并返回方法簽名在這里的
realDelegate
是對象真正的代理,例如objectivec self.tableView.delegate = [[UIViewController alloc] init];
其中
realDelegate
是視圖控制器,但是在我們設置時,不需要這么設置objectivec self.tableView.realDelegate = [[UIViewController alloc] init];
因為在
NSObject+A2BlockDelegate
中會進行方法調劑,修改原有方法的實現,每次在設置delegate
時,會將這個值設置傳到realDelegate
中。 -
在自己的類中查找該方法的選擇子
- 如果上面三個步驟都沒有得到相應,那么調用
NSObject
對象的methodSignatureForSelector:
方法獲取方法簽名,當然可能返回空值
====
forwardInvocation:
的實現其實跟上面的方法的思路差不多
- (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];
}
}
-
判斷
invocationsBySelectors
屬性中是否存儲了該選擇子對應的A2BlockInvocation
,然后調用invokeWithInvocation:
傳入outerInv
轉發這個方法,最終會調用- [A2BlockInvocation invokeWithInvocation:returnValue:setOnInvocation:]
-
判斷
realDelegate
是否實現了該方法,如果真正的代理能做出響應,將方法轉發給realDelegate
Block 實現方法 blockImplementationForMethod: 和 implementMethod:withBlock:
這部分的代碼其實相當于平時的 Getter/Setter
- (id)blockImplementationForMethod:(SEL)selector
{
A2BlockInvocation *invocation = nil;
if ((invocation = [self.invocationsBySelectors bk_objectForSelector:selector]))
return invocation.block;
return NULL;
}
因為 block 都是在 A2BlockInvocation
中封裝的,所以在通過選擇子查找 block 的時候,實際上是查找對應的 A2BlockInvocation
,然后返回它的 block。
- (void)implementMethod:(SEL)selector withBlock:(id)block
{
#1: 參數檢查,省略
if (!block) {
[self.invocationsBySelectors bk_removeObjectForSelector:selector];
return;
}
#2: 實例化 A2BlockInvocation
[self.invocationsBySelectors bk_setObject:inv forSelector:selector];
}
如果能獲取到方法的描述,那么就可以得到對應的方法簽名,然后調用不同的初始化方法實例一個 A2Blockinvocation
對象。
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];
}
這兩個方法的實現,主要目的是為子類實現代理方法提供支持。
NSObject+A2DynamicDelegate 為對象添加動態代理
這個分類是為所有的對象提供簡單快捷的接口找到對應的動態代理:
@property (readonly,strong) id bk_dynamicDataSource;
@property (readonly,strong) id bk_dynamicDelegate;
- (id)bk_dynamicDelegateForProtocol:(Protocol *)protocol;
以 UITableView
為例:
- 訪問
tableView.bk_dynamicDataSource
那么它就會尋找A2DynamicUITableViewDataSource
的對象 - 訪問
tableView.bk_dynamicDelegate
那么它就會尋找A2DynamicUITableViewDelegate
的對象
這些對象都是在后臺進程中惰性初始化的:
- (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;
}
NSObject+A2BlockDelegate
我們在概述的一部分實際上已經接觸過這個分類里面的重要方法 bk_linkProtocol:methods:
,它動態實現所有添加的 block 屬性的存取方法,比如說 bk_didFinishPickingMediaBlock
bk_didCancelBlock
IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject,protocol,info,NO);
return [delegate blockImplementationForMethod:selector];
});
IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject,id block) {
A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject,protocol,info,YES);
[delegate implementMethod:selector withBlock:block];
});
方法調劑之后的存取方法如下
- getter: 以 selector 為鍵在動態代理中查找對應的 block
- setter: 以 selector 也就是代理方法為鍵,通過
implementMethod:withBlock:
方法以A2BlockInvocation
的形式存儲 block
另一個方法 bk_registerDynamicDelegateNamed:forProtocol:
,它主要功能就是修改 getter 和 setter 方法,將原有的 delegate
轉發到 realDelegate
,修改原有的 delegate
的實現,實現的方法就是喜聞樂見的方法調節:
IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject,id delegate) {
A2DynamicDelegate *dynamicDelegate = getDynamicDelegate(delegatingObject,protocol,infoAsPtr,YES);
if ([delegate isEqual:dynamicDelegate]) {
delegate = nil;
}
dynamicDelegate.realDelegate = delegate;
});
IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
return [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject,protocol)];
});
注意,在這里省略了一些與脈絡無關的實現細節,在調劑過后 delegate
的存取方法如下:
- getter:返回一個動態代理對象
- setter:設置代理并不會改變
delegate
中存儲的動態代理,只會修改realDelegate
我們現在有了通過 runtime 實現 block 的 getter/setter,修改原有的 delegate
屬性的方法將對象的代理設置為動態代理,接下來要在子類化動態代理,使用動態代理的子類實現所有的代理方法。
A2DynamicUITextFieldDelegate
A2DynamicUITextFieldDelegate
和 UITextField+BlocksKit
位于統一文件下,它是一個私有類,我們選取其中一個簡單的代理方法:
- (void)textFieldDidEndEditing:(UITextField *)textField
{
id realDelegate = self.realDelegate;
if (realDelegate && [realDelegate respondsToSelector:@selector(textFieldDidEndEditing:)])
[realDelegate textFieldDidEndEditing:textField];
void (^block)(UITextField *) = [self blockImplementationForMethod:_cmd];
if (block)
block(textField);
}
- 當
realDelegate
實現了該代理方法時,首先調用代理的方法 - 當該代理方法對應的 block 存在的話,也會調用該 block
UITextField+BlocksKit 分類和 load 方法
在最后就是對 NSObject+A2BlockDelegate
分類中方法的調用
+ (void)load {
[self bk_registerDynamicDelegate];
[self bk_linkDelegateMethods: @{
@"bk_shouldBeginEditingBlock": @"textFieldShouldBeginEditing:",
@"bk_didBeginEditingBlock": @"textFieldDidBeginEditing:",
@"bk_shouldEndEditingBlock": @"textFieldShouldEndEditing:",
@"bk_didEndEditingBlock" : @"textFieldDidEndEditing:",
@"bk_shouldChangeCharactersInRangeWithReplacementStringBlock" : @"textField:shouldChangeCharactersInRange:replacementString:",
@"bk_shouldClearBlock" : @"textFieldShouldClear:",
@"bk_shouldReturnBlock" : @"textFieldShouldReturn:",
}];
}
為什么在 load
方法中調用這兩個方法?原因有兩個:
- 該方法只會調用一次,減少了調用的次數
- 該方法只會在文件被引用的時候調用,減少了不必要的動態代理注冊等一系列步驟
其中的 autoreleasepool
的作用在上面已經介紹過了,它使得其它地方的代碼不會受到這里注冊代理,鏈接代理方法中產生的對象的影響。
UIKit+BlocksKit
這些分類的另一作用就是提供 block 回調接口,聲明屬性,然后使用 @dynamic
表明屬性是動態生成的。
@property (nonatomic,copy,nullable) BOOL(^bk_shouldBeginEditingBlock)(UITextField *textField);
@property (nonatomic,copy,nullable) void(^bk_didBeginEditingBlock)(UITextField *textField);
...
@dynamic bk_shouldBeginEditingBlock,bk_didBeginEditingBlock ...;
End
到這里對于 BlocksKit 的實現機制就基本上已經看完了。我們在來看一下 整個 BlocksKit 的結構圖:
我寫這篇文章大約用了七天的時間,如果你對其中的內容有些疑問,可以發郵件或者在下面留言。
來自:http://draveness.me/blockskit-2/