1 知識(shí)準(zhǔn)備
1.1 NSMethodSignature
NSMethodSignature-方法簽名類
方法簽名中保存了方法的名稱/參數(shù)名稱/參數(shù)個(gè)數(shù)/返回值類型,協(xié)同NSInvocation來進(jìn)行消息的轉(zhuǎn)發(fā)
方法簽名一般是用來設(shè)置參數(shù)和獲取返回值的, 和方法的調(diào)用沒有太大的關(guān)系
根據(jù)方法來初始化NSMethodSignature
NSMethodSignature *signature = [ViewController instanceMethodSignatureForSelector:@selector(XXX:)];
@interface NSMethodSignature : NSObject {
@private
void *_private;
void *_reserved[6];
}
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
@property (readonly) NSUInteger numberOfArguments;
- (const char *)getArgumentTypeAtIndex:(NSUInteger)idx NS_RETURNS_INNER_POINTER;
@property (readonly) NSUInteger frameLength;
- (BOOL)isOneway;
@property (readonly) const char *methodReturnType NS_RETURNS_INNER_POINTER;
@property (readonly) NSUInteger methodReturnLength;
@end
1.2 NSInvoation
NSInvocation-根據(jù)NSMethodSignature生成,保存了方法所屬的對(duì)象/方法名稱/參數(shù)/返回值。
其實(shí)NSInvocation就是將一個(gè)方法變成一個(gè)對(duì)象。設(shè)置NSInvocation的target、SEL、arguments,最后可用invoke執(zhí)行該方法。
可用getReturnValue獲取函數(shù)執(zhí)行后的返回值。
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;
@property (readonly, retain) NSMethodSignature *methodSignature;
- (void)retainArguments;
@property (readonly) BOOL argumentsRetained;
@property (nullable, assign) id target;
@property SEL selector;
- (void)getReturnValue:(void *)retLoc;
- (void)setReturnValue:(void *)retLoc;
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)invoke;
- (void)invokeWithTarget:(id)target;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
//設(shè)置方法調(diào)用者
invocation.target = self;
invocation.selector = @selector(XXX:); NSString *way = @"hh";
//這里的Index要從2開始,以為0跟1已經(jīng)被占據(jù)了,分別是self(target),selector(_cmd)
[invocation setArgument:&way atIndex:2];
//3、調(diào)用invoke方法
[invocation invoke];
1.3 self 、object_getClass、 [self class]的概念
- 當(dāng) self 為實(shí)例對(duì)象時(shí),[self class] 與 object_getClass(self) 等價(jià),因?yàn)榍罢邥?huì)調(diào)用后者。他們的返回值是類對(duì)象。object_getClass([self class]) 得到元類.
- 當(dāng) self 為類對(duì)象時(shí),[self class] 返回值為自身,還是 self。object_getClass(self) 與 object_getClass([self class]) 等價(jià),都是得到元類。
-(id)class
{
return (id)isa;
}
+ (id)class
{
return self;
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
2 消息轉(zhuǎn)發(fā)
Runtime 系統(tǒng)在Cache和方法分發(fā)表中(包括超類)找不到要執(zhí)行的方法時(shí),我們可以下述三種方式把消息轉(zhuǎn)發(fā)到其他對(duì)象,或者用本對(duì)象的其他方法替換。
基于這種原理可以實(shí)現(xiàn)一個(gè)doesNotRecognizeSelector的集中處理器,避免crash。
2.1 動(dòng)態(tài)方法解析
動(dòng)態(tài)方法解析會(huì)在消息轉(zhuǎn)發(fā)機(jī)制浸入前執(zhí)行。通過重寫resolveInstanceMethod:或resolveClassMethod:方法來實(shí)現(xiàn)。
@interface NSStudent : NSObject
+ (void)learnClass:(NSString *) string;
- (void)goToSchool:(NSString *) name;
@end
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(learnClass:)) {
class_addMethod(object_getClass([NSTeacher class]), sel, class_getMethodImplementation(object_getClass([NSTeacher class]), @selector(teacherCourse:)), "v@:");
return YES;
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(goToSchool:)) {
class_addMethod([self class], aSEL, class_getMethodImplementation([self class], @selector(myInstanceMethod:)), "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
定義一個(gè)NSStudent的類,申明了learnClass:和goToSchool:兩個(gè)方法,但是沒有提供實(shí)現(xiàn)。在.m文件中重寫了resolveClassMethod和resolveInstanceMethod兩個(gè)方法。在resolveClassMethod中給指定的SEL添加了IMP實(shí)現(xiàn),這個(gè)可以是當(dāng)前類也可以指定其他類,本例中指定的是當(dāng)前類的myClassMethod:。如果 respondsToSelector: 或 instancesRespondToSelector:方法被執(zhí)行,動(dòng)態(tài)方法解析器將會(huì)被首先給予一個(gè)提供該方法選擇器對(duì)應(yīng)的IMP的機(jī)會(huì)。如果你想讓該方法選擇器被傳送到轉(zhuǎn)發(fā)機(jī)制,那么就讓resolveInstanceMethod:返回NO。
2.2 重定向
在消息轉(zhuǎn)發(fā)機(jī)制執(zhí)行前,Runtime 系統(tǒng)會(huì)再給我們一次偷梁換柱的機(jī)會(huì),即通過重載
- (id)forwardingTargetForSelector:(SEL)aSelector或者
+ (id)forwardingTargetForSelector:(SEL)aSelector方法替換消息的接受者為其他對(duì)象。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
[self class];
if(aSelector == @selector(goToSchool:)){
return self;
return [[NSTeacher alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
通過這種方式只能返回了去執(zhí)行同名方法的類對(duì)象,所以這種方式的局限在于,接受者對(duì)象也應(yīng)該有一個(gè)同名并且參數(shù)相同的方法,即SEL一致。
2.3 轉(zhuǎn)發(fā)
如果一個(gè)selecter在2.1和2.2步驟都沒找到對(duì)應(yīng)的IMP,則進(jìn)入-(void)forwardInvocation:(NSInvocation *)anInvocation方法。
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSInvocation *methodInvocation = [NSInvocation invocationWithMethodSignature:anInvocation.methodSignature];
[methodInvocation setSelector:anInvocation.selector];
if ([NSTeacher instancesRespondToSelector:anInvocation.selector]) {
[methodInvocation invokeWithTarget:[[NSTeacher alloc] init]];
}
}
但是當(dāng)我們實(shí)現(xiàn)了forwardInvocation之后,發(fā)現(xiàn)還是會(huì)因?yàn)閐oesNotRecognizeSelector crash掉。這是為什么呢?原來在forwardInvocation:消息發(fā)送前,Runtime系統(tǒng)會(huì)向?qū)ο蟀l(fā)送methodSignatureForSelector:消息,并取到返回的方法簽名用于生成NSInvocation對(duì)象。所以我們?cè)谥貙慺orwardInvocation:的同時(shí)也要重寫methodSignatureForSelector:方法。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([NSTeacher instancesRespondToSelector:aSelector]) {
signature = [NSTeacher instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- 當(dāng)一個(gè)對(duì)象由于沒有相應(yīng)的方法實(shí)現(xiàn)而無法響應(yīng)某消息時(shí),運(yùn)行時(shí)系統(tǒng)將通過forwardInvocation:消息通知該對(duì)象。
- 每個(gè)對(duì)象都從NSObject類中繼承了forwardInvocation:方法。然而,NSObject中的方法實(shí)現(xiàn)只是簡(jiǎn)單地調(diào)用了doesNotRecognizeSelector:。通過實(shí)現(xiàn)我們自己的forwardInvocation:方法,我們可以在該方法實(shí)現(xiàn)中將消息轉(zhuǎn)發(fā)給其它對(duì)象。
- forwardInvocation:方法就像一個(gè)不能識(shí)別的消息的分發(fā)中心,將這些消息轉(zhuǎn)發(fā)給不同接收對(duì)象。或者它也可以象一個(gè)運(yùn)輸站將所有的消息都發(fā)送給同一個(gè)接收對(duì)象。它可以將一個(gè)消息翻譯成另外一個(gè)消息,或者簡(jiǎn)單的”吃掉“某些消息,因此沒有響應(yīng)也沒有錯(cuò)誤。
- forwardInvocation:方法也可以對(duì)不同的消息提供同樣的響應(yīng),這一切都取決于方法的具體實(shí)現(xiàn)。該方法所提供是將不同的對(duì)象鏈接到消息鏈的能力。
3 總結(jié)
從網(wǎng)上找了一張圖可以準(zhǔn)確描述上邊所述。