TPreventUnrecognizedSEL项目的GitHub地址: https://github.com/tobedefined/TPreventUnrecognizedSELTPreventUnrecognizedSEL的使用方法: LINK
objc_msgSend(id, SEL, ...)
在谈论这个之前,先写一段测试代码,如下:
// main.m
#import #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行),可以看到如下代码
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;
}这段代码中包含了很多的类型转换,现在将类型转换去掉,转变后的代码更容易阅读,如下:
int main(int argc, const char * argv[]) {
// 1
TestOBJ *obj = objc_msgSend(objc_msgSend(objc_getClass("TestOBJ"), sel_registerName("alloc")), sel_registerName("init"));
// 2
objc_msgSend(obj, sel_registerName("instanceMethod"));
// 3
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"));可以拆分成两个部分,如下,就不在讨论了
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
......
}网上很多的博文都写到了,类也是一个对象,关于这块有很多资料。可以从runtime源码中看到类就是一个对象。
runtime可运行修改版GitHub地址 git@github.com:RetVal/objc-runtime.git, 下面代码摘自这个版本的runtime源码。
打开/Project Headers/objc-private.h文件可以看到objc_object的实现,我们用的oc对象其实就是一个objc_object
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
......
}打开/Project Headers/objc-runtime-new.h文件可以看到objc_class的实现,oc中的类就是objc_class
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
......
}可以看到objc_class是继承自objc_object的,所以objc_class也是一个对象是没错的。
这里我们关注下面这部分:(下面的代码进行了改写,比较容易理解混合了OBJC和OBJC2的代码,实际情况远比这复杂的多,详细了解参考文末的资料)
// 上述的runtime源码
// File: "/Project Headers/objc-private.h"
// Line: 48
struct objc_class;
struct objc_object;
typedef struct objc_class *Class;
typedef struct objc_object *id;
// 上述的runtime源码
// File: "/Project Headers/objc-private.h"
// Line: 168
struct objc_object {
isa_t isa;
};
// macOS 的runtime.h头文件和上述的runtime源码
// File: "/Project Headers/objc-runtime-new.h"
// Line: 1064
struct objc_class : objc_object {
Class superclass;
struct objc_method_list **methodLists;
};
// 上述的runtime源码
// File: "/Project Headers/objc-private.h"
// Line: 68
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 的函数的地址
运行函数
对象方法:
*** 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之中
class_getNameOBJC_EXPORT const char * _Nonnull class_getName(Class _Nullable cls);
class_getName方法是获取Class的 C String,相当于C语言版本的 NSString *NSStringFromClass(Class aClass)
objc_getClassOBJC_EXPORT Class _Nullable objc_getClass(const char * _Nonnull name);
objc_getClass方法是获取根据 C String 获取一个 Class,相当于C语言版本的 Class _Nullable NSClassFromString(NSString *aClassName)
objc_getMetaClassOBJC_EXPORT Class _Nullable objc_getMetaClass(const char * _Nonnull name);
objc_getMetaClass方法是获取根据 C string 获取这个 String 所表示的 Class 的 MetaClass
比如以下代码:
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_getInstanceMethodOBJC_EXPORT Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
返回Class的对象方法SEL的函数指针
class_getClassMethodOBJC_EXPORT Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name);
返回Class的类方法SEL的函数指针,等同于class_getInstanceMethod(objc_getMetaClass(class_getName(self)), name)
method_exchangeImplementationsOBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);
用于替换两个函数的实现。
比如以下代码中
@implementation MyOBJ
+ (void)load {
method_exchangeImplementations(class_getInstanceMethod(self, @selector(instanceMethodOne)),
class_getInstanceMethod(self, @selector(instanceMethodTwo)));
}
- (void)instanceMethodOne {
// 函数体1
}
- (void)instanceMethodTwo {
// 函数体2
}
@end-instanceMethodOne将与-instanceMethodTwo将会指向对方的函数体,也就是说[obj instanceMethodOne]其实是调用的函数体2,[obj instanceMethodTwo]其实是调用的函数体1。
class_addMethodOBJC_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
+loadload方法的调用规则:
调用顺序 父类 > 子类 > 分类
调用+load之前先调用父类的+load,这个过程自动实现,不需要手动添加[super load];
如果一个类没有实现+load方法,不会沿用父类的实现
一个+load函数只会执行一次
类和分类都执行中的+load都会执行
调用时机:类被添加到 runtime 时(对于动态库和静态库中的class和category都有效)
// NSObject+TPUSELFastForwarding.h 或 NSObject+TPUSELNormalForwarding.h
typedef NS_ENUM(NSUInteger, UnrecognizedMethodType) {
UnrecognizedMethodTypeClassMethod = 1,
UnrecognizedMethodTypeInstanceMethod = 2,
};
typedef void (^ __nullable HandleUnrecognizedSELErrorBlock)(Class cls, SEL selector, UnrecognizedMethodType methodType);
// NSObject+TPUSELFastForwarding.m 或 NSObject+TPUSELNormalForwarding.m
@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对象方法)
核心代码如下:
// NSObject+TPUSELFastForwarding.m
void __c_t_resolveLostedMethod(id self, SEL _cmd, ...) {}
@implementation NSObject (TPUSELFastForwarding)
#pragma mark - FastForwarding
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// exchange instance function in class
method_exchangeImplementations(class_getInstanceMethod(self, @selector(forwardingTargetForSelector:)),
class_getInstanceMethod(self, @selector(__t_forwardingTargetForSelector:)));
// exchange class function in meta class
method_exchangeImplementations(class_getClassMethod(self, @selector(forwardingTargetForSelector:)),
class_getClassMethod(self, @selector(__t_forwardingTargetForSelector:)));
// 后一部分等同于下面这种方式
// Class metaClass = objc_getMetaClass(class_getName(self));
// method_exchangeImplementations(class_getInstanceMethod(metaClass, @selector(forwardingTargetForSelector:)),
// class_getInstanceMethod(metaClass, @selector(__t_forwardingTargetForSelector:)));
});
}
- (id)__t_forwardingTargetForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {
return self;
}
if (![[TUndertakeObject sharedInstance] respondsToSelector:aSelector]) {
class_addMethod([TUndertakeObject class],
aSelector,
(IMP)__c_t_resolveLostedMethod,
"v@:");
}
HandleUnrecognizedSELErrorBlock handleBlock = [NSObject handleUnrecognizedSELErrorBlock];
if (handleBlock != nil) {
handleBlock([self class], aSelector, UnrecognizedMethodTypeInstanceMethod);
}
return [TUndertakeObject sharedInstance];
}
+ (id)__t_forwardingTargetForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {
return self;
}
if (![TUndertakeObject respondsToSelector:aSelector]) {
class_addMethod(objc_getMetaClass(class_getName([TUndertakeObject class])),
aSelector,
(IMP)__c_t_resolveLostedMethod,
"v@:");
}
HandleUnrecognizedSELErrorBlock handleBlock = [NSObject handleUnrecognizedSELErrorBlock];
if (handleBlock != nil) {
handleBlock([self class], aSelector, UnrecognizedMethodTypeClassMethod);
}
return [TUndertakeObject class];
}
@end思路:
在+load中替换forwardingTargetForSelector:方法(类中和元类中都进行替换)
TUndertakeObject为一个承载添加方法的类,使用单例,只用于添加缺失的方法
在__t_forwardingTargetForSelector:中:
判断本类是否包含aSelector,如果包含,返回自身;若不包含走下一步。
使用 class_addMethod 对 TUndertakeObject 添加方法的实现(解决对象方法缺失问题)或对 TUndertakeObject 的元类添加方法的实现(解决类方法缺失问题);
获取 handleBlock,调用block将具体信息传出去。
返回 TUndertakeObject 的单例对象(解决对象方法缺失问题);或者 TUndertakeObject 的 Class对象(解决类方法缺失问题)
// NSObject+TPUSELNormalForwarding.m
void __c_t_resolveLostedMethod(id self, SEL _cmd, ...) {}
@implementation NSObject (TPUSELNormalForwarding)
#pragma mark - ForwardInvocation
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// exchange instance function in class
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:)));
// exchange class function in meta class
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:)));
// 后一部分等同于下面这种方式
// Class metaClass = objc_getMetaClass(class_getName(self));
// method_exchangeImplementations(class_getInstanceMethod(metaClass, @selector(methodSignatureForSelector:)),
// class_getInstanceMethod(metaClass, @selector(__t_methodSignatureForSelector:)));
// method_exchangeImplementations(class_getInstanceMethod(metaClass, @selector(forwardInvocation:)),
// class_getInstanceMethod(metaClass, @selector(__t_forwardInvocation:)));
});
}
- (NSMethodSignature *)__t_methodSignatureForSelector:(SEL)aSelector {
if (![self respondsToSelector:aSelector]) {
class_addMethod([self class],
aSelector,
(IMP)__c_t_resolveLostedMethod,
"v@:");
HandleUnrecognizedSELErrorBlock handleBlock = [NSObject handleUnrecognizedSELErrorBlock];
if (handleBlock != nil) {
handleBlock([self class], aSelector, UnrecognizedMethodTypeInstanceMethod);
}
}
return [self __t_methodSignatureForSelector:aSelector];
}
- (void)__t_forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
if ([self respondsToSelector:selector]) {
[anInvocation invokeWithTarget:self];
} else {
[self __t_forwardInvocation:anInvocation];
}
}
+ (NSMethodSignature *)__t_methodSignatureForSelector:(SEL)aSelector {
if (![self respondsToSelector:aSelector]) {
class_addMethod(objc_getMetaClass(class_getName(self)),
aSelector,
(IMP)__c_t_resolveLostedMethod,
"v@:");
HandleUnrecognizedSELErrorBlock handleBlock = [NSObject handleUnrecognizedSELErrorBlock];
if (handleBlock != nil) {
handleBlock([self class], aSelector, UnrecognizedMethodTypeClassMethod);
}
}
return [self __t_methodSignatureForSelector:aSelector];
}
+ (void)__t_forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
if ([self respondsToSelector:selector]) {
[anInvocation invokeWithTarget:self];
} else {
[self __t_forwardInvocation:anInvocation];
}
}思路:
在+load中替换methodSignatureForSelector:、forwardInvocation:方法(类中和元类中都进行替换)
在__t_methodSignatureForSelector:中:
判断本类是否包含aSelector,如果包含,不进行处理,调用__t_methodSignatureForSelector去执行原函数;如果不包含,走下一步
使用 class_addMethod 对自己的Class添加方法的实现(解决对象方法缺失问题)或对自己的的元类添加方法的实现(解决类方法缺失问题);
获取 handleBlock,调用block将具体信息传出去。
调用 __t_methodSignatureForSelector 去执行原函数获得添加后的aSelector的NSMethodSignature
在__t_forwardInvocation:中,如果现在自己包含了对应的selector,则执行[anInvocation invokeWithTarget:self];;如果还不包含(应该不会有这一步),则调用__t_forwardInvocation去执行原函数
原文地址:LINK
项目地址:GitHub
参考资料:
runtime可运行修改版GitHub地址
从 NSObject 的初始化了解 isa
深入解析 ObjC 中方法的结构
从源代码看 ObjC 中消息的发送
Type Encoding
Apple Doc: load
Objective-C +load vs +initialize
Objective-C Runtime