TPreventUnrecognizedSEL
项目的 GitHub
地址: https://github.com/tobedefined/TPreventUnrecognizedSEL TPreventUnrecognizedSEL
的使用方法: LINK
关于方法的调用,ObjC对象以及类的结构,和unrecognized selector异常的产生流程的博文很多,这里只讲解和这个项目实现有关的重要部分,详细了解可以参考文末的参考文档。
objc_msgSend(id, SEL, ...)
在谈论这个之前,先写一段测试代码,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #import <Foundation/Foundation.h> #pragma mark - OBJ @interface TestOBJ : NSObject - (void )instanceMethod; + (void )classMethod; @end @implementation TestOBJ - (void )instanceMethod {} + (void )classMethod {} @end #pragma mark - Main int main(int argc, const char * argv[]) { TestOBJ *obj = [[TestOBJ alloc] init]; [obj instanceMethod]; [TestOBJ classMethod]; return 0 ; }
在命令行中运行clang -rewrite-objc main.m
,在文件夹下生成main.cpp
的C++文件,打开main.cpp
找到C++的main函数(我的在第96634行),可以看到如下代码
1 2 3 4 5 6 int main (int argc, const char * argv[]) { TestOBJ *obj = ((TestOBJ *(*)(id, SEL))(void *)objc_msgSend)((id)((TestOBJ *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestOBJ" ), sel_registerName("alloc" )), sel_registerName("init" )); ((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("instanceMethod" )); ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestOBJ" ), sel_registerName("classMethod" )); return 0 ; }
这段代码中包含了很多的类型转换,现在将类型转换去掉,转变后的代码更容易阅读,如下:
1 2 3 4 5 6 7 8 9 int main (int argc, const char * argv[]) { TestOBJ *obj = objc_msgSend(objc_msgSend(objc_getClass("TestOBJ" ), sel_registerName("alloc" )), sel_registerName("init" )); objc_msgSend(obj, sel_registerName("instanceMethod" )); objc_msgSend(objc_getClass("TestOBJ" ), sel_registerName("classMethod" )); return 0 ; }
首先看第二行代码objc_msgSend(obj, sel_registerName("instanceMethod"));
,这段代码将[obj instanceMethod];
rewrite 成了这种方式,也就是说ObjC中中括号调用方法实际上是调用了OBJC_EXPORT id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
的方法,第一个参数传的是调用方法的对象,第二个参数传的是方法选择器。
然后看第三行代码objc_msgSend(objc_getClass("TestOBJ"), sel_registerName("classMethod"));
,与上面类似,只是第一个含参数传的是调用方法的Class(他也就是调用类方法的对象)。
那么回头看第一行代码,就很容易理解了,TestOBJ *obj = objc_msgSend(objc_msgSend(objc_getClass("TestOBJ"), sel_registerName("alloc")), sel_registerName("init"));
可以拆分成两个部分,如下,就不在讨论了
1 2 id obj_alloc = objc_msgSend(objc_getClass("TestOBJ" ), sel_registerName("alloc" )) TestOBJ *obj = objc_msgSend(obj_alloc, sel_registerName("init" ))
类是什么? 网上很多的博文都写到了,类也是一个对象,关于这块有很多资料。可以从runtime源码中看到类就是一个对象。
runtime可运行修改版GitHub地址 git@github.com:RetVal/objc-runtime.git
, 下面代码摘自这个版本的runtime源码。
打开/Project Headers/objc-private.h
文件可以看到objc_object
的实现,我们用的oc对象其实就是一个objc_object
1 2 3 4 5 6 7 8 9 10 11 12 13 struct objc_object {private : isa_t isa; public : Class ISA () ; Class getIsa () ; ...... }
打开/Project Headers/objc-runtime-new.h
文件可以看到objc_class
的实现,oc中的类就是objc_class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct objc_class : objc_object { Class superclass; cache_t cache; class_data_bits_t bits; class_rw_t *data() { return bits.data(); } void setData (class_rw_t *newData) { bits.setData(newData); } ...... }
可以看到objc_class
是继承自objc_object
的,所以objc_class
也是一个对象是没错的。
这里我们关注下面这部分:(下面的代码进行了改写,比较容易理解混合了OBJC和OBJC2的代码,实际情况远比这复杂的多,详细了解参考文末的资料)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 struct objc_class ;struct objc_object ;typedef struct objc_class *Class ;typedef struct objc_object *id ;struct objc_object { isa_t isa; }; struct objc_class : objc_object { Class superclass; struct objc_method_list **methodLists ; }; union isa_t { Class cls; }
可以看到以下重要的几点内容:
id
是一个指向 objc_object
的指针
Class
是一个指向 objc_class
的指针
objc_object
中包含 isa_t isa;
isa
中包含 Class cls;
,cls 指向对应的Class
objc_class
继承自 objc_object
,所以也属于 objc_object
,同样也包含 isa
,也指向一个 Class
,这个 Class
我们称之为元类,即 meta class
meta class
也是 Class
,同样也包含 isa
,它指向 NSObject
的 meta class
,另外,NSObject meta class
的 isa
指向自身
这里有一张经典的图:
方法的调用过程 关于 objc_class
中的 methodLists
,很明显, methodLists
就是函数列表,他存储着这个类的所有对象方法 ,没错, objc_class
中 methodLists
存储的是对象方法;而类方法则是存储在 meta class
的 methodLists
中。通俗的说,就是我们写的 -
号开头的方法(对象方法),则加入 Class
的 methodLists
之中;+
写的方法(类方法),加入 meta class
的 methodLists
之中。
对象方法和类方法的调用运行走的是同一套流程。
对象方法
转换成 objc_msgSend(obj, sel, ...)
形式
对象调用方法时,首先通过 isa
找到所属的类,然后获取 methodLists
中对应的 sel
的函数的地址
运行函数
类方法
转换成 objc_msgSend(cls, sel, ...)
形式
类调用类方法时,首先通过该类的 isa
找到所属的元类,然后获取 methodLists
中对应的 sel
的函数的地址
运行函数
Unrecognized Selector 错误
对象方法:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[TestClass losted:instance:method:]: unrecognized selector sent to instance 0x102c....'
类方法:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[TestClass losted:class:method:]: unrecognized selector sent to class 0x10000....'
众所周知,产生 Unrecognized Selector 错误之前,系统会给出多次机会让用户进行添加方法或转发,一般分为两个步骤(Method Resolution
、 Forwarding
)或三个步骤(Method Resolution
、 Fast Forwarding
和 Normal Forwarding
)
Method Resolution
ObjC运行时会调用 + (BOOL)resolveInstanceMethod:(SEL)sel
或者 + (BOOL)resolveClassMethod:(SEL)sel
,让你有机会提供一个函数实现添加为对象方法或类方法。添加了函数之后,返回YES
,系统就会重新启动一次消息发送的过程,如果返回NO
,系统就会开始进行消息转发的过程。
Fast Forwarding
快速转发过程,- (id)forwardingTargetForSelector:(SEL)aSelector
,系统会调用这个方法对对象方法的调用进行转发,如果返回一个newObject
,则相当于转发后运行 [newObject performSelector:aSelector]
。如果返回的是nil,则进入默认转发的过程。
Normal Forwarding
这一步是runtime的最后一次转发过程。在这一个过程中,首先调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
获取SEL
的函数签名,这个时候你应该返回一个存在的已经函数签名,如果返回的是nil,此时runtime就会发出- (void)doesNotRecognizeSelector:(SEL)aSelector
消息,这个时候,程序就会crash崩溃掉了。如果返回了一个函数签名,就会根据这个函数签名创建一个NSInvocation
的对象并发送- (void)forwardInvocation:(NSInvocation *)anInvocation
消息给目标对象。
我们主要通过Forwarding的两步操作进行防止产生 Unrecognized Selector 错误(Method Resolution
过程在runtime中经常会进行主动去掉用,如果使用Resolution,可能产生死循环)。但是可以发现,-forwardingTargetForSelector:
、 -methodSignatureForSelector:
、 -forwardInvocation:
都只存在对象方法,而不存在类方法。这时候改如何解决?
回到前面,类其实也是一个对象,是元类(meta class)的对象,而元类(meta class)的结构是和Class的结构是相同的,都是objc_class
,而类方法的调用和对象方法的调用走的是同一个流程,则我们添加这几个方法到元类(meta class)中,就可以解决类方法不存在的流程;即将-forwardingTargetForSelector:
、 -methodSignatureForSelector:
、 -forwardInvocation:
写作 +forwardingTargetForSelector:
、 +methodSignatureForSelector:
、 +forwardInvocation:
既可以添加到元类(meta class)的 methodLists
之中
关于一些runtime的函数以及作用 class_getName
1 2 OBJC_EXPORT const char * _Nonnull class_getName(Class _Nullable cls);
class_getName
方法是获取Class
的 C String,相当于C语言版本的 NSString *NSStringFromClass(Class aClass)
objc_getClass
1 2 OBJC_EXPORT Class _Nullable objc_getClass(const char * _Nonnull name);
objc_getClass
方法是获取根据 C String 获取一个 Class,相当于C语言版本的 Class _Nullable NSClassFromString(NSString *aClassName)
1 2 OBJC_EXPORT Class _Nullable objc_getMetaClass(const char * _Nonnull name);
objc_getMetaClass
方法是获取根据 C string 获取这个 String 所表示的 Class 的 MetaClass 比如以下代码:
1 2 3 char myClassName[] = "MyClass" ;Class myClass = objc_getClass(myClassName); Class myMetaClass = objc_getMetaClass(myClassName);
其中 myClass
是 MyClass
这个类,myMetaClass
是 MyClass
这个类的元类 所以获取当前类的元类可以使用objc_getMetaClass(class_getName(self))
或者 objc_getMetaClass(class_getName([self class]))
class_getInstanceMethod
1 2 OBJC_EXPORT Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
返回Class
的对象方法SEL
的函数指针
class_getClassMethod
1 2 OBJC_EXPORT Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name);
返回Class
的类方法SEL
的函数指针,等同于class_getInstanceMethod(objc_getMetaClass(class_getName(self)), name)
method_exchangeImplementations
1 2 OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);
用于替换两个函数的实现。
比如以下代码中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @implementation MyOBJ + (void )load { method_exchangeImplementations(class_getInstanceMethod(self , @selector (instanceMethodOne)), class_getInstanceMethod(self , @selector (instanceMethodTwo))); } - (void )instanceMethodOne { } - (void )instanceMethodTwo { } @end
-instanceMethodOne
将与-instanceMethodTwo
将会指向对方的函数体,也就是说[obj instanceMethodOne]
其实是调用的函数体2,[obj instanceMethodTwo]
其实是调用的函数体1。
class_addMethod
1 2 3 OBJC_EXPORT BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);
对 Class
cls
添加名为 name
的 selector
,指向的函数实现为 imp
, 函数的 Type Encoding
为 types
Type Encoding 可以参考:Type Encoding
关于+load
load方法的调用规则:
调用顺序 父类 > 子类 > 分类
调用+load
之前先调用父类的+load
,这个过程自动实现,不需要手动添加[super load];
如果一个类没有实现+load
方法,不会沿用父类的实现
一个+load
函数只会执行一次
类和分类都执行中的+load
都会执行
调用时机:类被添加到 runtime 时(对于动态库和静态库中的class和category都有效)
实现 HandleUnrecognizedSELErrorBlock 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 typedef NS_ENUM (NSUInteger , UnrecognizedMethodType) { UnrecognizedMethodTypeClassMethod = 1 , UnrecognizedMethodTypeInstanceMethod = 2 , }; typedef void (^ __nullable HandleUnrecognizedSELErrorBlock)(Class cls, SEL selector, UnrecognizedMethodType methodType);@implementation NSObject (ADD )+ (void )setHandleUnrecognizedSELErrorBlock:(HandleUnrecognizedSELErrorBlock)handleBlock { objc_setAssociatedObject(self , @selector (handleUnrecognizedSELErrorBlock), handleBlock, OBJC_ASSOCIATION_RETAIN); } + (HandleUnrecognizedSELErrorBlock)handleUnrecognizedSELErrorBlock { return objc_getAssociatedObject(self , @selector (handleUnrecognizedSELErrorBlock)); } @end
用于向外部传出所丢失函数的具体错误信息,向 NSObject
的 Class对象
添加关联block,并在在适当的时候调用,具体参数的信息如下
cls
: Class
类型;为缺失方法的类或对象的Class,可使用NSStringFromClass(cls)
返回类名字符串
selector
: SEL
类型;为所缺失的方法名,可使用NSStringFromSelector(selector)
返回方法名的字符串
methodType
: UnrecognizedMethodType
类型;为所缺失的方法类型(类方法or对象方法)
TPUSELFastForwarding 核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 void __c_t_resolveLostedMethod(id self , SEL _cmd, ...) {}@implementation NSObject (TPUSELFastForwarding )#pragma mark - HandleUnrecognizedSELErrorBlock + (void )setJustForwardClassArray:(NSArray *)forwardClassArray handleUnrecognizedSELErrorBlock:(HandleUnrecognizedSELErrorBlock)handleBlock { objc_setAssociatedObject(self , @selector (handleUnrecognizedSELErrorBlock), handleBlock, OBJC_ASSOCIATION_RETAIN); objc_setAssociatedObject(self , @selector (justForwardClassArray), forwardClassArray, OBJC_ASSOCIATION_RETAIN); } + (HandleUnrecognizedSELErrorBlock)handleUnrecognizedSELErrorBlock { return objc_getAssociatedObject(self , _cmd); } + (NSArray *)justForwardClassArray { return (NSArray *)objc_getAssociatedObject(self , _cmd); } + (BOOL )isCanFowardingFor:(Class)cls { for (NSObject *element in [NSObject justForwardClassArray]) { Class justForwardCls; if ([element isKindOfClass:[NSString class ]]) { justForwardCls = NSClassFromString ((NSString *)element); } else { justForwardCls = (Class)element; } if ([cls isSubclassOfClass:justForwardCls]) { return YES ; } } return NO ; } + (Class)getProtectorClass { static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ Class protectorCls = objc_allocateClassPair([NSObject class ], "__TProtectorClass" , 0 ); objc_registerClassPair(protectorCls); }); Class protectorCls = NSClassFromString (@"__TProtectorClass" ); return protectorCls; } + (instancetype )getProtectorInstance { static id __t_protector_instance; static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ __t_protector_instance = [[[self getProtectorClass] alloc] init]; }); return __t_protector_instance; } #pragma mark - FastForwarding + (void )load { static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ method_exchangeImplementations(class_getInstanceMethod(self , @selector (forwardingTargetForSelector:)), class_getInstanceMethod(self , @selector (__t_forwardingTargetForSelector:))); method_exchangeImplementations(class_getClassMethod(self , @selector (forwardingTargetForSelector:)), class_getClassMethod(self , @selector (__t_forwardingTargetForSelector:))); }); } - (id )__t_forwardingTargetForSelector:(SEL)aSelector { if ([self respondsToSelector:aSelector]) { return [self __t_forwardingTargetForSelector:aSelector]; } if ([NSObject isCanFowardingFor:[self class ]]) { class_addMethod([NSObject getProtectorClass], aSelector, (IMP)__c_t_resolveLostedMethod, "v@:" ); HandleUnrecognizedSELErrorBlock handleBlock = [NSObject handleUnrecognizedSELErrorBlock]; if (handleBlock != nil ) { NSArray <NSString *>*callStackSymbols = @[@"The system version is too low." ]; if (@available(iOS 4.0 , tvOS 9.0 , macOS 10.6 , watchOS 2.0 , *)) { callStackSymbols = [NSThread callStackSymbols]; } handleBlock([self class ], aSelector, UnrecognizedMethodTypeInstanceMethod, callStackSymbols); } return [NSObject getProtectorInstance]; } return [self __t_forwardingTargetForSelector:aSelector]; } + (id )__t_forwardingTargetForSelector:(SEL)aSelector { if ([self respondsToSelector:aSelector]) { return [self __t_forwardingTargetForSelector:aSelector]; } if ([NSObject isCanFowardingFor:self ]) { class_addMethod(objc_getMetaClass(class_getName([NSObject getProtectorClass])), aSelector, (IMP)__c_t_resolveLostedMethod, "v@:" ); HandleUnrecognizedSELErrorBlock handleBlock = [NSObject handleUnrecognizedSELErrorBlock]; if (handleBlock != nil ) { NSArray <NSString *>*callStackSymbols = @[@"The system version is too low." ]; if (@available(iOS 4.0 , tvOS 9.0 , macOS 10.6 , watchOS 2.0 , *)) { callStackSymbols = [NSThread callStackSymbols]; } handleBlock([self class ], aSelector, UnrecognizedMethodTypeClassMethod, callStackSymbols); } return [NSObject getProtectorClass]; } return [self __t_forwardingTargetForSelector:aSelector]; } @end
思路:
在+load
中替换forwardingTargetForSelector:
方法(类中和元类中都进行替换)
[NSObject getProtectorClass]
和 [NSObject getProtectorInstance]
获得一个承载添加方法的类和对象,用于添加缺失的方法和方法调用
在__t_forwardingTargetForSelector:
中:
判断本类是否包含aSelector
,如果包含,返回自身;若不包含走下一步。
判断是否允许处理该类型的类,如果不允许,不进行处理,调用__t_forwardingTargetForSelector
去执行原函数;如果允许,走下一步
使用 class_addMethod
对 [NSObject getProtectorClass]
添加方法的实现(解决对象方法缺失问题)或对 [NSObject getProtectorClass]
的元类添加方法的实现(解决类方法缺失问题);
获取 handleBlock
,调用block将具体信息传出去。
返回 [NSObject getProtectorInstance]
(解决对象方法缺失问题)或者 [NSObject getProtectorClass]
(Class对象
, 解决类方法缺失问题)
TPUSELNormalForwarding 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 @implementation NSObject (TPUSELNormalForwarding )#pragma mark - HandleUnrecognizedSELErrorBlock + (void )setHandleUnrecognizedSELErrorBlock:(HandleUnrecognizedSELErrorBlock)handleBlock { objc_setAssociatedObject(self , @selector (handleUnrecognizedSELErrorBlock), handleBlock, OBJC_ASSOCIATION_RETAIN); } + (HandleUnrecognizedSELErrorBlock)handleUnrecognizedSELErrorBlock { return objc_getAssociatedObject(self , _cmd); } + (void )setJustForwardClassArray:(NSArray <NSString *> *)forwardClassArray; { objc_setAssociatedObject(self , @selector (justForwardClassArray), forwardClassArray, OBJC_ASSOCIATION_RETAIN); } + (NSArray *)justForwardClassArray { return (NSArray <NSString *> *)objc_getAssociatedObject(self , _cmd); } + (void )setIgnoreForwardNSNullClass:(BOOL )ignoreNSNull { objc_setAssociatedObject(self , @selector (ignoreForwardNSNullClass), [NSNumber numberWithBool:ignoreNSNull], OBJC_ASSOCIATION_RETAIN); } + (BOOL )ignoreForwardNSNullClass { return [(NSNumber *)objc_getAssociatedObject(self , _cmd) boolValue]; } + (void )setIgnoreForwardClassArray:(NSArray <NSString *> *)ignoreClassArray { objc_setAssociatedObject(self , @selector (ignoreForwardClassArray), ignoreClassArray, OBJC_ASSOCIATION_RETAIN); } + (NSArray *)ignoreForwardClassArray { return (NSArray *)objc_getAssociatedObject(self , _cmd); } + (BOOL )isCanFowardingFor:(Class)cls { if ([NSObject ignoreForwardNSNullClass] && [cls isSubclassOfClass:[NSNull class ]]) { return NO ; } for (NSObject *element in [NSObject ignoreForwardClassArray]) { Class ignoreCls; if ([element isKindOfClass:[NSString class ]]) { ignoreCls = NSClassFromString ((NSString *)element); } else { ignoreCls = (Class)element; } if ([cls isSubclassOfClass:ignoreCls]) { return NO ; } } NSArray *justForwardClassArray = [NSObject justForwardClassArray]; if (justForwardClassArray.count > 0 ) { for (NSObject *element in justForwardClassArray) { Class justForwardCls; if ([element isKindOfClass:[NSString class ]]) { justForwardCls = NSClassFromString ((NSString *)element); } else { justForwardCls = (Class)element; } if ([cls isSubclassOfClass:justForwardCls]) { return YES ; } } return NO ; } return YES ; } #pragma mark - ForwardInvocation + (void )load { static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ method_exchangeImplementations(class_getInstanceMethod(self , @selector (methodSignatureForSelector:)), class_getInstanceMethod(self , @selector (__t_methodSignatureForSelector:))); method_exchangeImplementations(class_getInstanceMethod(self , @selector (forwardInvocation:)), class_getInstanceMethod(self , @selector (__t_forwardInvocation:))); method_exchangeImplementations(class_getClassMethod(self , @selector (methodSignatureForSelector:)), class_getClassMethod(self , @selector (__t_methodSignatureForSelector:))); method_exchangeImplementations(class_getClassMethod(self , @selector (forwardInvocation:)), class_getClassMethod(self , @selector (__t_forwardInvocation:))); }); } - (NSMethodSignature *)__t_methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *signature = [self __t_methodSignatureForSelector:aSelector]; if (signature || [self respondsToSelector:aSelector]) { return signature; } if (![NSObject isCanFowardingFor:[self class ]]) { return signature; } HandleUnrecognizedSELErrorBlock handleBlock = [NSObject handleUnrecognizedSELErrorBlock]; if (handleBlock != nil ) { NSArray <NSString *>*callStackSymbols = @[@"The system version is too low." ]; if (@available(iOS 4.0 , tvOS 9.0 , macOS 10.6 , watchOS 2.0 , *)) { callStackSymbols = [NSThread callStackSymbols]; } handleBlock([self class ], aSelector, UnrecognizedMethodTypeInstanceMethod, callStackSymbols); } return [NSMethodSignature signatureWithObjCTypes:"v@:" ]; } - (void )__t_forwardInvocation:(NSInvocation *)anInvocation { if (![NSObject isCanFowardingFor:[self class ]]) { return [self __t_forwardInvocation:anInvocation]; } SEL selector = [anInvocation selector]; if ([self respondsToSelector:selector]) { [anInvocation invokeWithTarget:self ]; } } + (NSMethodSignature *)__t_methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *signature = [self __t_methodSignatureForSelector:aSelector]; if (signature || [self respondsToSelector:aSelector]) { return signature; } if (![NSObject isCanFowardingFor:self ]) { return signature; } HandleUnrecognizedSELErrorBlock handleBlock = [NSObject handleUnrecognizedSELErrorBlock]; if (handleBlock != nil ) { NSArray <NSString *>*callStackSymbols = @[@"The system version is too low." ]; if (@available(iOS 4.0 , tvOS 9.0 , macOS 10.6 , watchOS 2.0 , *)) { callStackSymbols = [NSThread callStackSymbols]; } handleBlock([self class ], aSelector, UnrecognizedMethodTypeClassMethod, callStackSymbols); } return [NSMethodSignature signatureWithObjCTypes:"v@:" ]; } + (void )__t_forwardInvocation:(NSInvocation *)anInvocation { if (![NSObject isCanFowardingFor:self ]) { return [self __t_forwardInvocation:anInvocation]; } SEL selector = [anInvocation selector]; if ([self respondsToSelector:selector]) { [anInvocation invokeWithTarget:self ]; } } @end
思路:
在+load
中替换methodSignatureForSelector:
、forwardInvocation:
方法(类中和元类中都进行替换)
在__t_methodSignatureForSelector:
中:
判断本类是否包含aSelector
,如果包含,不进行处理,调用__t_methodSignatureForSelector
去执行原函数;如果不包含,走下一步
判断是否允许处理该类型的类,如果不允许,不进行处理,调用__t_methodSignatureForSelector
去执行原函数;如果允许,走下一步
获取 handleBlock
,调用block将具体信息传出去。
创建 NSMethodSignature
并返回
在__t_forwardInvocation:
中
判断是否允许处理该类型的类,如果不允许,不进行处理,调用__t_forwardInvocation
去执行原函数;如果允许,走下一步
如果现在自己包含了对应的selector
,则执行[anInvocation invokeWithTarget:self];
;如果不包含不进行anInvocation
的执行
参考资料:
runtime可运行修改版GitHub地址
从 NSObject 的初始化了解 isa
NSObject 的 isa 和 Class(ObjC 1.0 和 ObjC 2.0)
深入解析 ObjC 中方法的结构
从源代码看 ObjC 中消息的发送
Type Encoding
Apple Doc: load
Objective-C +load vs +initialize
Objective-C Runtime