iOS 使用NSMethodSignature和 NSInvocation進行 method 或 block的調用

這篇博文是個人另外一篇 Aspects源碼剖析中的一部分,考慮到這部份內容相對獨立,單獨成篇以便查詢。html

使用NSMethodSignatureNSInvocation 不只能夠完成對method的調用,也能夠完成block的調用。在Aspect中,正是運用NSMethodSignature,NSInvocation 實現了對block的統一處理。這篇博文將演示NSMethodSignatureNSInvocation的使用方法及如何使用他們執行method 或 block。ios

##對象調用method代碼示例 一個實例對象能夠經過三種方式調用其方法。編程

- (void)test{
    
//type1
    [self printStr1:@"hello world 1"];
    
//type2
    [self performSelector:@selector(printStr1:) withObject:@"hello world 2"];
    
//type3
    //獲取方法簽名
    NSMethodSignature *sigOfPrintStr = [self methodSignatureForSelector:@selector(printStr1:)];
    
    //獲取方法簽名對應的invocation
    NSInvocation *invocationOfPrintStr = [NSInvocation invocationWithMethodSignature:sigOfPrintStr];
    
    /**
    設置消息接受者,與[invocationOfPrintStr setArgument:(__bridge void * _Nonnull)(self) atIndex:0]等價
    */
    [invocationOfPrintStr setTarget:self];
    
    /**設置要執行的selector。與[invocationOfPrintStr setArgument:@selector(printStr1:) atIndex:1] 等價*/
    [invocationOfPrintStr setSelector:@selector(printStr1:)];
    
    //設置參數 
    NSString *str = @"hello world 3";
    [invocationOfPrintStr setArgument:&str atIndex:2];
    
    //開始執行
    [invocationOfPrintStr invoke];
}

- (void)printStr1:(NSString*)str{
    NSLog(@"printStr1 %@",str);
}
複製代碼

在調用test方法時,會分別輸出:api

2017-01-11 15:20:21.642 AspectTest[2997:146594] printStr1  hello world 1
2017-01-11 15:20:21.643 AspectTest[2997:146594] printStr1  hello world 2
2017-01-11 15:20:21.643 AspectTest[2997:146594] printStr1  hello world 3
複製代碼

type1和type2是咱們經常使用的,這裏不在贅述,咱們來講說type3。 NSMethodSignatureNSInvocationFoundation框架爲咱們提供的一種調用方法的方式,常常用於消息轉發。數組

##NSMethodSignature概述bash

NSMethodSignature用於描述method的類型信息:返回值類型,及每一個參數的類型。 能夠經過下面的方式進行建立:數據結構

@interface NSObject 
//獲取實例方法的簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
//獲取類方法的簽名
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;
@end

-------------
//使用ObjCTypes建立方法簽名
@interface NSMethodSignature
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
@end
複製代碼

使用NSObject的實例方法和類方法建立NSMethodSignature很簡單,不說了。咱撩一撩signatureWithObjCTypes。 在OC中,每一種數據類型能夠經過一個字符編碼來表示(Objective-C type encodings)。例如字符‘@’表明一個object, 'i'表明int。 那麼,由這些字符組成的字符數組就能夠表示方法類型了。舉個例子:上面提到的printStr1:對應的ObjCTypes 爲 v@:@。app

  • ’v‘ : void類型,第一個字符表明返回值類型
  • ’@‘ : 一個id類型的對象,第一個參數類型
  • ’:‘ : 對應SEL,第二個參數類型
  • ’@‘ : 一個id類型的對象,第三個參數類型,也就是- (void)printStr1:(NSString*)str中的str。

printStr1:原本是一個參數,ObjCTypes怎麼成了三個參數?要理解這個還必須理解OC中的消息機制。一個method對應的結構體以下,ObjCTypes中的參數其實與IMP method_imp 函數指針指向的函數的參數相一致。相關內容有不少,不瞭解的能夠參考這篇文章方法與消息框架

typedef struct objc_method *Method;
struct objc_method {
    SEL method_name                 OBJC2_UNAVAILABLE;  // 方法名
    char *method_types                  OBJC2_UNAVAILABLE;
    IMP method_imp                      OBJC2_UNAVAILABLE;  // 方法實現
}
複製代碼

##NSInvocation概述ide

就像示例代碼所示,咱們能夠經過+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;建立出NSInvocation對象。接下來你設置各個參數信息, 而後調用invoke進行調用。執行結束後,經過- (void)getReturnValue:(void *)retLoc;獲取返回值。 這裏須要注意,對NSInvocation對象設置的參數個數及類型和獲取的返回值的類型要與建立對象時使用的NSMethodSignature對象表明的參數及返回值類型向一致,不然cresh。

##使用NSInvocation調用block 下面展現block 的兩種調用方式

- (void)test{

    void (^block1)(int) = ^(int a){
         NSLog(@"block1 %d",a);
    };
    
    //type1
    block1(1);
    
    //type2
    //獲取block類型對應的方法簽名。
    NSMethodSignature *signature = aspect_blockMethodSignature(block1);
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:block1];
    int a=2;
    [invocation setArgument:&a atIndex:1];
    [invocation invoke];
}
複製代碼

type1 就是經常使用的方法,再也不贅述。看一下type2。 type2和上面調用method的type3用的同樣的套路,只是參數不一樣:由block生成的NSInvocation對象的第一個參數是block自己,剩下的爲 block自身的參數。

因爲系統沒有提供獲取block的ObjCTypes的api,咱們必須想辦法找到這個ObjCTypes,只有這樣才能生成NSMethodSignature對象! ###block的數據結構 & 從數據結構中獲取 ObjCTypes oc是一門動態語言,經過編譯 oc能夠轉變爲c語言。通過編譯後block對應的數據結構是struct。(block中技術點仍是挺過的,推薦一本書「Objective-C 高級編程」)

//代碼來自 Aspect
// Block internals.
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;
複製代碼

在此結構體中 const char *signature 字段就是咱們想要的。經過下面的方法獲取signature並建立NSMethodSignature對象。

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];
}
複製代碼
相關文章
相關標籤/搜索