1、前言
我們?cè)诜治鯞lock的動(dòng)態(tài)調(diào)用之前劫瞳,先簡(jiǎn)單了解一下消息的轉(zhuǎn)發(fā)機(jī)制倘潜。作為一個(gè)iOS開(kāi)發(fā)者,消息的轉(zhuǎn)發(fā)機(jī)制應(yīng)該都是我們耳熟能詳?shù)闹R(shí)點(diǎn)了志于,這里貼一個(gè)轉(zhuǎn)發(fā)流程圖涮因,不再占用篇幅:
NSMethodSignature
和NSInvocation
來(lái)分析Block的動(dòng)態(tài)調(diào)用。
2伺绽、實(shí)例探究
先解釋一些概念
方法的構(gòu)成:在runtime源碼中养泡,可以清晰的看到,方法method_t
是由SEL
+types
+IMP
組成奈应。
1澜掩、SEL:方法的編號(hào)。
2杖挣、types:方法的簽名肩榕。簡(jiǎn)單的說(shuō)方法簽名包含:返回值、參數(shù)惩妇、參數(shù)類型株汉,一個(gè)簡(jiǎn)單的例子v@:
,v
對(duì)應(yīng)返回值為void歌殃,@
對(duì)應(yīng)一個(gè)id類型的對(duì)象乔妈,:
對(duì)應(yīng)SEL
。具體對(duì)比可以參照Type Encodings氓皱。
3路召、IMP:一個(gè)函數(shù)指針勃刨,保存的是方法的地址。
接下來(lái)我們來(lái)看下面這段代碼:
- (void)viewDidLoad {
[super viewDidLoad];
// 方法的簽名
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
// NSInvocation對(duì)象
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:self];
[invocation setSelector:@selector(testInvocation)];
[invocation invoke];
}
- (void)testInvocation {
NSLog(@"testInvocation");
}
顯然股淡,我們的testInvocation
方法會(huì)被調(diào)用身隐。由我的 上篇文章 可以知道Block的本身也是對(duì)象,那Block如何進(jìn)行動(dòng)態(tài)調(diào)用呢揣非?
我們?cè)倏聪旅鍮lock動(dòng)態(tài)調(diào)用的簡(jiǎn)單寫(xiě)法:
- (void)viewDidLoad {
[super viewDidLoad];
void (^testBlock)(NSString *, double) = ^(NSString *test, double a) {
NSLog(@"block test %@ -- %.1f", test, a);
};
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@?@d"];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:testBlock];
NSString *str = @"invocation block";
[invocation setArgument:&str atIndex:1];
double dou = 0.1;
[invocation setArgument:&dou atIndex:2];
[invocation invoke];
}
這是一段帶有參數(shù)Block的動(dòng)態(tài)調(diào)用代碼抡医,值得說(shuō)的是,在一般方法的NSInvocation 中setArgument : atIndex :
的index傳的值是從2開(kāi)始的早敬,而B(niǎo)lock的動(dòng)態(tài)調(diào)用是從1開(kāi)始的忌傻。原因是跟我們的方法簽名有關(guān),在越界信息中我們是可以看到所有types
的遍歷是從-1開(kāi)始的-[NSInvocation setArgument:atIndex:]: index (3) out of bounds [-1, 2]
搞监,一般方法的v@:
分別是3個(gè)獨(dú)立的代表水孩,而在Blockv@?
中@?是綁定一起的。(沒(méi)有相關(guān)解釋文檔)
首先看調(diào)用的API:
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
來(lái)看源碼中定義了AspectBlockRef
的結(jié)構(gòu)體:
AspectIdentifier
類就可以了馍乙。首先初始化的時(shí)候布近,在這個(gè)aspect_blockMethodSignature
方法中,生成Block的NSMethodSignature丝格,并且包裝在AspectIdentifier對(duì)象中撑瞧。后續(xù)再- (BOOL)invokeWithInfo:(id<AspectInfo>)info
中,生成hook方法的NSInvocation中显蝌,觸發(fā)block的調(diào)用预伺。下面貼上
AspectIdentifier
類中Block動(dòng)態(tài)調(diào)用過(guò)程的注釋。
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
NSCParameterAssert(block);
NSCParameterAssert(selector);
NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
return nil;
}
AspectIdentifier *identifier = nil;
if (blockSignature) {
identifier = [AspectIdentifier new];
// 方法的聲明
identifier.selector = selector;
// block對(duì)象
identifier.block = block;
// block的簽名信息
identifier.blockSignature = blockSignature;
identifier.options = options;
identifier.object = object; // weak
}
return identifier;
}
- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
// 生成NSInvocation對(duì)象
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
// 交換之前的 invocation
NSInvocation *originalInvocation = info.originalInvocation;
// 參數(shù)個(gè)數(shù)
NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
// Be extra paranoid. We already check that on hook registration.
// 檢查block中寫(xiě)了幾個(gè)參數(shù)曼尊,如果比原方法個(gè)數(shù)還多扭屁,那么是有問(wèn)題的
if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
AspectLogError(@"Block has too many arguments. Not calling %@", info);
return NO;
}
// The `self` of the block will be the AspectInfo. Optional.
// 如果block中有參數(shù),那么先把info放到1位置上
if (numberOfArguments > 1) {
[blockInvocation setArgument:&info atIndex:1];
}
// 根據(jù)block中的參數(shù)個(gè)數(shù)涩禀,從原方法中逐個(gè)賦值過(guò)來(lái)
void *argBuf = NULL;
for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
NSUInteger argSize;
NSGetSizeAndAlignment(type, &argSize, NULL);
if (!(argBuf = reallocf(argBuf, argSize))) {
AspectLogError(@"Failed to allocate memory for block invocation.");
return NO;
}
[originalInvocation getArgument:argBuf atIndex:idx];
[blockInvocation setArgument:argBuf atIndex:idx];
}
// 執(zhí)行block
[blockInvocation invokeWithTarget:self.block];
if (argBuf != NULL) {
free(argBuf);
}
return YES;
}
3、最后
學(xué)無(wú)止境然眼。在源碼分析中艾船,還是要主要學(xué)思想,知識(shí)點(diǎn),架構(gòu)屿岂。歡迎各位大佬點(diǎn)評(píng)践宴、斧正。