相关定义
// 类中的一个方法
typedef struct objc_method *Method;
// 实例变量
typedef struct objc_ivar *Ivar;
// 类别Category
typedef struct objc_category *Category;
// 类中声明的属性
typedef struct objc_property *objc_property_t;
// 类在runtime中的表示
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY; // 指针,顾名思义,表示是一个什么,
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE; // 指向父类
const char * _Nonnull name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; // 成员变量列表
struct objc_method_list * _Nullable * _Nullable methodLists // 方法列表 OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; // 缓存
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; // 协议列表
#endif
} OBJC2_UNAVAILABLE;
使用
获取方法/属性/协议列表*
我们可以通过runtime的一系列方法获取类的一些信息,如属性列表,方法列表,成员变量列表,和遵循的协议列表。
给当前的类导入头文件#import
unsigned int count;
//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"self.property---->%@", [NSString stringWithUTF8String:propertyName]);
}
//获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i=0; i < count; i++) {
Method method = methodList[i];
NSLog(@"self.method---->%@", NSStringFromSelector(method_getName(method)));
}
//获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i=0; i < count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"self.Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
//获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i=0; i < count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"self.protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
方法调用
如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。
如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。
1、首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行。
2、如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行
3、如果没找到,去父类指针所指向的对象中执行1,2.
4、以此类推,如果一直到根类还没找到,转向拦截调用。
5、如果没有重写拦截调用的方法,程序报错。
拦截调用
在方法调用中说到了,如果没有找到方法就会转向拦截调用。
那么什么是拦截调用呢。
拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后两个方法需要转发到其他的类处理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
第二个方法和第一个方法相似,只不过处理的是实例方法。
第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。
动态添加方法
重写了拦截调用的方法并且返回了YES,我们要怎么处理呢?
有一个办法是根据传进来的SEL类型的selector动态添加一个方法。
首先从外部隐式调用一个不存在的方法:
[model performSelector:@selector(resolveAdd:) withObject:@"test"];
然后,在model对象内部重写拦截调用的方法,动态添加方法。
void runAddMethod(id self, SEL _cmd, NSString *string){
NSLog(@"add C IMP %@", string);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
//给本类动态添加一个方法
if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) {
class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
}
return YES;
}
其中class_addMethod的四个参数分别是:
Class cls 给哪个类添加方法,本例中是self
SEL name 添加的方法,本例中是重写的拦截调用传进来的selector。
IMP imp 方法的实现,C方法的方法实现可以直接获得。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。
“v@:*”方法的签名,代表有一个参数的方法。