1 知識準備
1.1 NSMethodSignature
NSMethodSignature-方法簽名類
方法簽名中保存了方法的名稱/參數(shù)名稱/參數(shù)個數(shù)/返回值類型分俯,協(xié)同NSInvocation來進行消息的轉(zhuǎn)發(fā)
方法簽名一般是用來設置參數(shù)和獲取返回值的, 和方法的調(diào)用沒有太大的關系
根據(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生成,保存了方法所屬的對象/方法名稱/參數(shù)/返回值。
其實NSInvocation就是將一個方法變成一個對象证鸥。設置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];
//設置方法調(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]的概念
- 當 self 為實例對象時踏施,[self class] 與 object_getClass(self) 等價石蔗,因為前者會調(diào)用后者罕邀。他們的返回值是類對象。object_getClass([self class]) 得到元類.
- 當 self 為類對象時养距,[self class] 返回值為自身诉探,還是 self。object_getClass(self) 與 object_getClass([self class]) 等價棍厌,都是得到元類肾胯。
-(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í)行的方法時竖席,我們可以下述三種方式把消息轉(zhuǎn)發(fā)到其他對象,或者用本對象的其他方法替換敬肚。
基于這種原理可以實現(xiàn)一個doesNotRecognizeSelector的集中處理器毕荐,避免crash。
2.1 動態(tài)方法解析
動態(tài)方法解析會在消息轉(zhuǎn)發(fā)機制浸入前執(zhí)行艳馒。通過重寫resolveInstanceMethod:或resolveClassMethod:方法來實現(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];
}
定義一個NSStudent的類,申明了learnClass:和goToSchool:兩個方法弄慰,但是沒有提供實現(xiàn)第美。在.m文件中重寫了resolveClassMethod和resolveInstanceMethod兩個方法。在resolveClassMethod中給指定的SEL添加了IMP實現(xiàn)陆爽,這個可以是當前類也可以指定其他類什往,本例中指定的是當前類的myClassMethod:。如果 respondsToSelector: 或 instancesRespondToSelector:方法被執(zhí)行慌闭,動態(tài)方法解析器將會被首先給予一個提供該方法選擇器對應的IMP的機會别威。如果你想讓該方法選擇器被傳送到轉(zhuǎn)發(fā)機制,那么就讓resolveInstanceMethod:返回NO驴剔。
2.2 重定向
在消息轉(zhuǎn)發(fā)機制執(zhí)行前兔港,Runtime 系統(tǒng)會再給我們一次偷梁換柱的機會,即通過重載
- (id)forwardingTargetForSelector:(SEL)aSelector或者
+ (id)forwardingTargetForSelector:(SEL)aSelector方法替換消息的接受者為其他對象仔拟。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
[self class];
if(aSelector == @selector(goToSchool:)){
return self;
return [[NSTeacher alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
通過這種方式只能返回了去執(zhí)行同名方法的類對象衫樊,所以這種方式的局限在于,接受者對象也應該有一個同名并且參數(shù)相同的方法利花,即SEL一致科侈。
2.3 轉(zhuǎn)發(fā)
如果一個selecter在2.1和2.2步驟都沒找到對應的IMP,則進入-(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]];
}
}
但是當我們實現(xiàn)了forwardInvocation之后臀栈,發(fā)現(xiàn)還是會因為doesNotRecognizeSelector crash掉。這是為什么呢挠乳?原來在forwardInvocation:消息發(fā)送前权薯,Runtime系統(tǒng)會向?qū)ο蟀l(fā)送methodSignatureForSelector:消息,并取到返回的方法簽名用于生成NSInvocation對象睡扬。所以我們在重寫forwardInvocation:的同時也要重寫methodSignatureForSelector:方法盟蚣。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([NSTeacher instancesRespondToSelector:aSelector]) {
signature = [NSTeacher instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- 當一個對象由于沒有相應的方法實現(xiàn)而無法響應某消息時,運行時系統(tǒng)將通過forwardInvocation:消息通知該對象卖怜。
- 每個對象都從NSObject類中繼承了forwardInvocation:方法屎开。然而,NSObject中的方法實現(xiàn)只是簡單地調(diào)用了doesNotRecognizeSelector:马靠。通過實現(xiàn)我們自己的forwardInvocation:方法奄抽,我們可以在該方法實現(xiàn)中將消息轉(zhuǎn)發(fā)給其它對象蔼两。
- forwardInvocation:方法就像一個不能識別的消息的分發(fā)中心,將這些消息轉(zhuǎn)發(fā)給不同接收對象逞度《罨或者它也可以象一個運輸站將所有的消息都發(fā)送給同一個接收對象。它可以將一個消息翻譯成另外一個消息档泽,或者簡單的”吃掉“某些消息锁孟,因此沒有響應也沒有錯誤。
- forwardInvocation:方法也可以對不同的消息提供同樣的響應茁瘦,這一切都取決于方法的具體實現(xiàn)品抽。該方法所提供是將不同的對象鏈接到消息鏈的能力。
3 總結(jié)
從網(wǎng)上找了一張圖可以準確描述上邊所述甜熔。