在iOS開發(fā)中,Block是常用的數(shù)據(jù)類型,Block的源碼是開放的,對于Blcok的其他探究可以查看這篇文章深入研究Block捕獲外部變量和__block實現(xiàn)原理.先來簡單介紹一般MethodSignature的獲取和配合Invocation的使用.
方法簽名
以Person
類為例,該類只有一個類方法和對象方法:
@interface Person : NSObject
- (void)say:(NSString *)name;
+ (void)setAge:(int)age;
@end
@implementation Person
- (void)say:(NSString *)name
{
NSLog(@"say : %@",name);
}
+ (void)setAge:(int)age{
}
@end
一般我們獲取對象方法和類方法的簽名可以這樣:
/// 根據(jù)方法簽名獲取TypeEncoding
NSString *getMethodSignatureTypeEncoding(NSMethodSignature *methodSignature){
NSMutableString *str = @"".mutableCopy;
const char *rtvType = methodSignature.methodReturnType;
[str appendString:[NSString stringWithUTF8String:rtvType]];
for (int i = 0; i < methodSignature.numberOfArguments; i ++) {
const char *type = [methodSignature getArgumentTypeAtIndex:i];
[str appendString:[NSString stringWithUTF8String:type]];
}
return [str copy];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 對象方法簽名
NSMethodSignature *instanceMethodSignature = [Person instanceMethodSignatureForSelector:@selector(say:)];
// 類方法簽名
NSMethodSignature *classMethodSignature = [Person methodSignatureForSelector:@selector(setAge:)];
NSLog(@"instanceMethodSignature : %@ \n classMethodSignature : %@",getMethodSignatureTypeEncoding(instanceMethodSignature),getMethodSignatureTypeEncoding (classMethodSignature));
}
return 0;
}
/*
打印結(jié)果:
instanceMethodSignature : v@:@
classMethodSignature : v@:i
*/
方法簽名記錄著一個方法的返回值類型編碼(TypeEncoding)、形參個數(shù)期揪、每一個形參的類型編碼.有了方法簽名之后,可以通過類型編碼來反解出真實類型,類型的映射關(guān)系可以查看官方資料.
根據(jù)Person
的say:
方法獲得的方法簽名結(jié)果為v@:@
,v
代表void
類型,@
代表對象類型,對應(yīng)的是隱藏參數(shù)self
,:
代表SEL
類型,對應(yīng)的是隱藏參數(shù)_cmd
,@
對應(yīng)的是第一個形參.每一個OC的類方法或?qū)ο蠓椒ǘ紩瑑蓚€隱藏參數(shù)self
和_cmd
,并且它們的位置是固定的,在invocation
對象中的索引分別是0和1,返回值是不占索引的,需要額外獲取.
配合NSInvocation使用
有了方法簽名之后,我們可以組裝一個NSInvocation對象并調(diào)用對應(yīng)方法,這里就簡單直接設(shè)置了索引2為name
了,一般都會動態(tài)循環(huán)設(shè)置設(shè)置,例如JSPatch.
Person *p = [Person new];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:instanceMethodSignature];
invocation.selector = @selector(say:);
NSString *name = @"abc";
[invocation setArgument:&name atIndex:2];
[invocation invokeWithTarget:p];
/*
打印結(jié)果: say : abc
*/
如果不是block對象,invocation必須要設(shè)置selector
,原因跟類的方法結(jié)構(gòu)有關(guān),關(guān)于類的方法結(jié)構(gòu)<<深入解析 ObjC 中方法的結(jié)構(gòu)>>這篇文章分析的非常深入.Aspects也對NSInvocation做了分類來獲取方法簽名并獲取對應(yīng)實參.
Block的方法簽名
Aspects 源碼中,有一段獲取Blcok方法簽名的代碼比較有意思:
typedef NS_OPTIONS(int, AspectBlockFlags) {
AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
AspectBlockFlagsHasSignature = (1 << 30)
};
typedef struct _AspectBlock {
__unused Class isa;
AspectBlockFlags flags;
__unused int reserved;
void (__unused *invoke)(struct _AspectBlock *block, ...);
struct {
unsigned long int reserved;
unsigned long int size;
// requires AspectBlockFlagsHasCopyDisposeHelpers
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
// requires AspectBlockFlagsHasSignature
const char *signature;
const char *layout;
} *descriptor;
// imported variables
} *AspectBlockRef;
static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
AspectBlockRef layout = (__bridge void *)block;
if (!(layout->flags & AspectBlockFlagsHasSignature)) {
NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
AspectError(AspectErrorMissingBlockSignature, description);
return nil;
}
void *desc = layout->descriptor;
desc += 2 * sizeof(unsigned long int);
if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
desc += 2 * sizeof(void *);
}
if (!desc) {
NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
AspectError(AspectErrorMissingBlockSignature, description);
return nil;
}
const char *signature = (*(const char **)desc);
return [NSMethodSignature signatureWithObjCTypes:signature];
}
_AspectBlock
結(jié)構(gòu)體是不是很熟悉? 對,就是跟Block指向的結(jié)構(gòu)體是基本一樣的!為什么說基本一樣的呢?因為官方文檔里面block是長這樣子的:
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
先來分析一下這段代碼,AspectBlockFlags
枚舉是block的標(biāo)記.aspect_blockMethodSignature
方法先強制將Blcok轉(zhuǎn)換成了AspectBlockRef
類型指針,然后利用flags
判斷是否存在方法簽名,再獲取block的descriptor
,descriptor
結(jié)構(gòu)體的copy
或者signature
存在著方法簽名.但是由于全局block和堆block存在的位置不同,所以需要判斷.如果是全局block,則方法簽名存在于signature
上,如果是堆block,則存在于copy
上.我們可以來驗證一下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// _NSConcreteMallocBlock 堆block
__block int a = 0;
id mallocBlock = ^(int b){
a ++;
NSLog(@"mallocBlock: a = %d ,b = %d",a,b);
};
// _NSConcreteGlobalBlock 全局block
id globalBlock = ^(NSString *name){
NSLog(@"globalBlock : name = %@",name);
};
NSMethodSignature *mallocBlockSignature = aspect_blockMethodSignature(mallocBlock, NULL);
NSMethodSignature *globalBlockSignature = aspect_blockMethodSignature(globalBlock, NULL);
NSLog(@"mallocBlockSignature : %@ \n globalBlockSignature : %@",getMethodSignatureTypeEncoding(mallocBlockSignature),getMethodSignatureTypeEncoding (globalBlockSignature));
}
return 0;
}
/*
打印結(jié)果:
mallocBlockSignature : v@?i
globalBlockSignature : v@?@"NSString"
*/
在aspect_blockMethodSignature
方法的desc += 2 * sizeof(void *);
處打斷點,然后發(fā)現(xiàn)全局block執(zhí)行了這句,而堆block沒有執(zhí)行這句.
棧block由于在ARC下無法驗證,所以目前不知道.
我們發(fā)現(xiàn)block的方法簽名和對象的方法簽名不一樣,第二位和第三位不再是@
和:
了,而是被@?
這個block標(biāo)記代替了.再看看類型編碼,基本數(shù)據(jù)類型在兩者情況下是一樣的,但是對象類型在block的方法簽名對應(yīng)的@
后面會標(biāo)明對象類型.
調(diào)用block
既然獲取到了block的方法簽名,這樣就可以組裝一個NSInvocation對象來調(diào)用block.
__block int a = 0;
id mallocBlock = ^(int b){
a ++;
NSLog(@"mallocBlock: a = %d ,b = %d",a,b);
};
NSMethodSignature *methodSignature = aspect_blockMethodSignature(mallocBlock, NULL);
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
int b = 1;
[invocation setArgument:&b atIndex:1];
[invocation invokeWithTarget:mallocBlock];
/*
打印結(jié)果: mallocBlock: a = 1 ,b = 1
*/
由于block的方法簽名最前面除了返回值以外只有@?
,所以所以需要從1開始設(shè)置參數(shù).
總結(jié):我們可以利用block來表示一個方法的實現(xiàn),block的參數(shù)類型和個數(shù)可以根據(jù)實際的方法來動態(tài)一一對應(yīng).這樣做的好處是如果方法的參數(shù)個數(shù)和類型是不確定的,利用block可以將整個方法的實現(xiàn)替換成一個block,可以達到替換任意方法的目的,這也是Aspects這個AOP實現(xiàn)框架的重要一部分.