BlocksKit(2)-DynamicDelegate

BlocksKit(2)-DynamicDelegate

動態代理能夠說是這個Block裏面最精彩的一部分了,能夠經過本身給一個類的的協議方法指定對應的block來實現讓這個協議的回調都直接在block裏面去執行,那麼爲何要這樣作呢?從功能的實現上來講,其實所實現的功能是同樣,可是在代碼的結構上倒是很是大的區別。在日常是delegate的時候,咱們把一個對象的delegate交給一個弱引用的對象,最多見的就是tableview的實現,而後在這個tableview裏面去實現這些協議,對比使用blockkit的方式,代碼明顯要集中,當一個對象要實現不少個委託的時候,這個時候就會寫不少的代碼。經過BlockKit的這種實現方式,可以讓代碼集中起來,並且在好比MVVM或者是函數式編程的風格中,很明顯都是更好的選擇。這是我我的對爲何要作這個動態代理功能實現的一點體會,知識有限,但願有大俠可以指點出更深層次的東西。那麼來看一下這個動態代理究竟是怎麼實現的呢?編程


A2DynamicDelegate

以UIAlertView爲例來看一下:數據結構

[dd implementMethod:@selector(alertView:willDismissWithButtonIndex:) withBlock:^(UIAlertView *alertView, NSInteger buttonIndex) {
    NSLog(@"You pushed button #%ld (%@)", buttonIndex, [alertView buttonTitleAtIndex:buttonIndex]);
}];

這個地方用用一個block實現了alertView:willDismissWithButtonIndex:這個協議方法的回調,這裏面有個implementMethod:withBlock:這個方法,看一下具體的實現:app

- (void)implementMethod:(SEL)selector withBlock:(id)block
{
    NSCAssert(selector, @"Attempt to implement or remove NULL selector");
    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. 先是判斷是否是類方法,接着判斷參數block是否爲空
  2. 而後根據協議裏面selector去取關於這個method的相關的參數信息
  3. 若是說名字存在,那麼根據這個method的types信息來生成一個方法簽名,若是不存在那麼就用另一個初始化方法來初始化一個A2BlockInvocation對象,兩個初始化方法的區別後面再進行比較
  4. 最後一步把A2BlockInvocation對象做爲object,selector做爲key的NSHashMap中去,這裏面爲何要要用NSHashMap呢,主要這個裏面有內存管理的條件選項,而且能夠指定一下描述、是否相等的方法,用起來比NSMutableSet要強大的多,再一點要說明額是selector做爲key實際是先被包裝成一個指針類型,而後再經過橋接成id的方式去生成一個對象,單純的selector不是對象,是不能做爲key來存在的
    上面的這段代碼就是爲了建立一個A2BlockInvocation對象,到這裏看到這個對象的名字,想到了爲何動態轉發的實際實現其實就是把根據Runtime裏面消息轉發的規則,而後去攔截,從新生成新的調用對象和方法簽名,那繼續看文件裏面有沒有實現消息轉發的兩個方法呢?果真存在:
- (void)forwardInvocation:(NSInvocation *)outerInv;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)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];
    }
}

- (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];
}

這兩個裏面都是根據Selector去到self.invocationsBySelectors裏面去拿A2BlockInvocation或者methodSignature,若是取不到A2BlockInvocation的話,就走self.realDelegate來進行處理,若是方法簽名拿不到就轉發給類方法,再不行就用NSObject來生成一個。函數

還有A2DynamicDelegate類是繼承自NSProxy,這個類自己就是一個處理消息轉發的東西而且遵照NSObject的協議,這裏面有實現了一些經常使用的方法:ui

- (BOOL)conformsToProtocol:(Protocol *)aProtocol
{
    return protocol_isEqual(aProtocol, self.protocol) || [super conformsToProtocol:aProtocol];
}
- (BOOL)respondsToSelector:(SEL)selector
{
    return [self.invocationsBySelectors bk_objectForSelector:selector] || class_respondsToSelector(object_getClass(self), selector) || [self.realDelegate respondsToSelector:selector];
}

- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    [NSException raise:NSInvalidArgumentException format:@"-[%s %@]: unrecognized selector sent to instance %p", object_getClassName(self), NSStringFromSelector(aSelector), (__bridge void *)self];
}

這個文件裏面,還有A2DynamicClassDelegate這個類,其實原理是同樣的,這裏就再也不說明,另外有一個獲取用戶協議的方法有意思:代理

static Protocol *a2_classProtocol(Class _cls, NSString *suffix, NSString *description)
{
    Class cls = _cls;
    while (cls) {
        NSString *className = NSStringFromClass(cls);
        NSString *protocolName = [className stringByAppendingString:suffix];
        Protocol *protocol = objc_getProtocol(protocolName.UTF8String);
        if (protocol) return protocol;

        cls = class_getSuperclass(cls);
    }

    NSCAssert(NO, @"Specify protocol explicitly: could not determine %@ protocol for class %@ (tried <%@>)", description, NSStringFromClass(_cls), [NSStringFromClass(_cls) stringByAppendingString:suffix]);
    return nil;
}

這裏面是一個while的循環,就是在本類上去找又沒有這個協議,若是沒有就繼續向上在父類的裏面去找,若是仍是是在找不到那就走斷言。
這差很少就把A2DynamicDelegate這個類講的差很少了。指針


A2BlockInvocation

上面的屢次提到這個類,如今就這個類來討論一下,這個類主要是用來幹啥的呢?就是存儲方法簽名和block的方法簽名具體執行的block的一個類。剛剛說到兩個初始化方法的區別,就是差一個簽名參數沒傳,這個簽名參數是要被block代替的那個selector的方法。那若是是本身生成和穿進去的區別就是,這個block沒有攔截消息的事件,他自己就是他自己。如如下代碼:code

- (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]);
}

- (instancetype)initWithBlock:(id)block methodSignature:(NSMethodSignature *)methodSignature
{
    NSParameterAssert(block);
    NSMethodSignature *blockSignature = [[self class] typeSignatureForBlock:block];
    if (![[self class] isSignature:methodSignature compatibleWithSignature:blockSignature]) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Attempted to create block invocation with incompatible signatures" userInfo:@{A2IncompatibleMethodSignatureKey: methodSignature}];
    }
    return (self = [self initWithBlock:block methodSignature:methodSignature blockSignature:blockSignature]);
}

這裏面有幾個方法的實現是咱們不常見的,好比
typeSignatureForBlock:block和isSignature:compatibleWithSignature:blockSignature這個連個方法,一個是根據block來生成一個方法簽名,另外一個是判斷協議方法簽名和block的方法簽名是否兼容。要想明白這個兩個方法的實現,就須要瞭解block內部的數據結構究竟是什麼樣子的。看一下代碼:component

// block的內存結構,在蘋果官方的源碼中可以找到具體的數據結構,BK的做者只是寫了一個
// 同樣的結構體
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;

+ (NSMethodSignature *)typeSignatureForBlock:(id)block __attribute__((pure, nonnull(1)))
{
    BKBlockRef layout = (__bridge void *)block;
    // 經過flag標記來判斷是夠擁有簽名
    if (!(layout->flags & BKBlockFlagsHasSignature))
        return nil;
    // 存在的話就行就去到descriptor裏面去找
    void *desc = layout->descriptor;
    desc += 2 * sizeof(unsigned long int);

    if (layout->flags & BKBlockFlagsHasCopyDisposeHelpers)
        desc += 2 * sizeof(void *);

    if (!desc)
        return nil;
    //上面經過位移來找到signature的指針
    const char *signature = (*(const char **)desc);
    // 生成NSMethodSignature
    return [NSMethodSignature signatureWithObjCTypes:signature];
}

// 經過參數的數量和返回值判斷block的簽名和協議方法簽名事是否可以兼容
+ (BOOL)isSignature:(NSMethodSignature *)signatureA compatibleWithSignature:(NSMethodSignature *)signatureB __attribute__((pure))
{
    if (!signatureA || !signatureB) return NO;
    if ([signatureA isEqual:signatureB]) return YES;
    if (signatureA.methodReturnType[0] != signatureB.methodReturnType[0]) return NO;

    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;
    }

    NSUInteger numberOfArguments = methodSignature.numberOfArguments;
    for (NSUInteger i = 2; i < numberOfArguments; i++) {
        if ([methodSignature getArgumentTypeAtIndex:i][0] != [blockSignature getArgumentTypeAtIndex:i - 1][0])
            return NO;
    }

    return YES;
}

/// Creates a method signature compatible with a given block signature.
+ (NSMethodSignature *)methodSignatureForBlockSignature:(NSMethodSignature *)original
{
    if (!original) return nil;
    
    if (original.numberOfArguments < 1) {
        return nil;
    }

    if (original.numberOfArguments >= 2 && strcmp(@encode(SEL), [original getArgumentTypeAtIndex:1]) == 0) {
        return original;
    }

    // initial capacity is num. arguments - 1 (@? -> @) + 1 (:) + 1 (ret type)
    // optimistically assuming most signature components are char[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 = (返回類型)@:根據具體的類型繼續拼接如@"\TableView\"
    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];
    }

    return [NSMethodSignature signatureWithObjCTypes:signature.UTF8String];
}

接下來看一下一個消息收到以後是如何被轉發到block上的

- (BOOL)invokeWithInvocation:(NSInvocation *)outerInv returnValue:(out NSValue **)outReturnValue setOnInvocation:(BOOL)setOnInvocation
{
    NSParameterAssert(outerInv);

    NSMethodSignature *sig = self.methodSignature;

    if (![outerInv.methodSignature isEqual:sig]) {
        NSAssert(0, @"Attempted to invoke block invocation with incompatible frame");
        return NO;
    }

    NSInvocation *innerInv = [NSInvocation invocationWithMethodSignature:self.blockSignature];

    void *argBuf = NULL;
    // 因爲self.methodSignature的隱藏參數有兩個就從2開始
    // 而後循環參數傳遞給block的參數列列表
    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];
        //block的Signature簽名參數只有一個隱藏,因此要減一
        [innerInv setArgument:argBuf atIndex:i - 1];
    }
    
    // 調用的target設置爲block
    [innerInv invokeWithTarget:self.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;
        }
    }

    free(argBuf);

    return YES;
}

設置了參數和返回值,而後調用,咱們block就跑起來,完成了剛開開始的目的。

本站公眾號
   歡迎關注本站公眾號,獲取更多信息