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