你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 神奇的 BlocksKit (二)

神奇的 BlocksKit (二)

編輯:IOS開發基礎

1832148_abec764a4e.jpg

關注倉庫,及時獲得更新: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 子類中實現代理方法

這裡是我對這部分代碼結構的理解:

2016-03-20-blockskit.jpg

這篇文成首先會從上到下對整個工作原理進行概述,然後再從底層到頂層詳細地解釋這個框架的機制和原理。

動態代理工作概述

在這裡我們要對這部分的實現進行一個簡單的概述,從上到下跟蹤 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,我們再來看一下整個框架的結構圖:

2016-03-20-blockskit-(1).jpg

我們將以下面的順序來依次介紹這些模塊,其中的 UITextField 可以換成其它的類:

  1. A2BlockInvocation

  2. A2DynamicDelegate

  3. NSObject+A2DynamicDelegate

  4. A2DynamicUITextFieldDelegate

  5. 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 的結構圖:

我寫這篇文章大約用了七天的時間,如果你對其中的內容有些疑問,可以發郵件或者在下面留言。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved