關注倉庫,及時獲得更新:iOS-Source-Code-Analyze
這篇文章『神奇的 BlocksKit』的第二部分,關於第一部分的內容在這裡:神奇的 BlocksKit(一)
動態代理
動態代理這部分可以說是 BlocksKit 的精華。它使用 block 屬性替換 UIKit 中的所有能夠通過代理完成的事件,省略了設置代理和實現方法的過程,讓對象自己實現代理方法(其實不是對象自己實現的代理方法,只是框架為我們提供的便捷方法,不需要構造其它對象就能完成代理方法的實現,具體我們會在後面詳細地解釋),而且這個功能的實現是極其動態的。
下面是這部分幾個關鍵的類:
A2BlockInvocation 的主要作用是存儲和轉發 block
A2DynamicDelegate 用來實現類的代理和數據源,它是 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
我們不具體解釋這裡的 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 的結構圖:
我寫這篇文章大約用了七天的時間,如果你對其中的內容有些疑問,可以發郵件或者在下面留言。