当目前为止我们已经了解了runtime 是如何查找方法,以及如何缓存方法; 如何关联对象,给分类添加成员变量;Class 类的底层数据结构是怎样的,如何存储类信息的等等...
今天我们就了解一下runtime
常用的API
.
一: 类相关的 API
-
Class object_getClass(id _Nullable obj)
获取isa
指向的Class
-
Class object_setClass(id _Nullable obj, Class _Nonnull cls)
设置isa
的指向的Class
-
BOOL object_isClass(id _Nullable obj)
判断一个OC对象
是否为Class
-
BOOL class_isMetaClass(Class _Nullable cls)
判断一个Class
是否为元类 -
Class class_getSuperclass(Class _Nullable cls)
获取父类
superclass
实例对象和类对象都可以调用,他们的区别是:
- (Class)superclass {
return [self class]->superclass;
}
+ (Class)superclass {
return self->superclass;
}
-
Class objc_allocateClassPair(Class superclass, const char * name, size_t extraBytes)
动态创建一个类,(参数:父类,类名,额外的存储空间) -
objc_registerClassPair(Class cls)
注册一个类 (要在类注册之前添加成员变量)
void run(id self,SEL _cmd){
NSString *name = [self valueForKey:@"_name"];
int age = [[self valueForKey:@"_age"] intValue];
NSLog(@"my name is %@ age is %d",name,age);
}
int main(int argc, const char * argv[]) {
// 动态创建类
NSString *className = @"Cat";
const char * charClassName = [className UTF8String];
//动态创建一个类
Class newClass = objc_allocateClassPair([NSObject class], charClassName, 1);
//给这个类添加成员变量
class_addIvar(newClass, "_name", 16, 1, @encode(NSString *));
class_addIvar(newClass, "_age", 4, 1, @encode(int));
//给这个类添加方法
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
//注册类
objc_registerClassPair(newClass);
id cat = [[newClass alloc]init];
[cat setValue:@10 forKey:@"_age"];
[cat setValue:@"Tom" forKey:@"_name"];
[cat run];
}
// 打印:
2019-12-05 15:39:22.480797+0800 runtime常用API介绍[2213:384292] my name is Tom age is 10
二: 成员变量相关的 API
-
Ivar class_getInstanceVariable
获取一个实例变量的信息
-
object_setIvar(id obj, Ivar ivar, id value)
设置实例变量的值
-
id object_getIvar(id = obj, Ivar ivar)
获取成员变量的值
-
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
动态添加成员变量 (已经注册的类是不能添加成员变量的) -
const char *ivar_getName(Ivar v)
获取成员变量 name -
const char * ivar_getTypeEncoding(Ivar v)
获取成员变量字符串编码 -
Ivar * class_copyIvarList(Class cls, unsigned int * outCount)
拷贝实例变量列表,最后需要调用free
释放.
用途一: 获取系统类私有的成员变量 (这种方式在 iOS13 后 已经被禁用了,iOS13 后系统禁止访问一些私有的成员变量
)
unsigned int count;
Ivar *ivar = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i ++) {
Ivar iva = ivar[I];
NSLog(@"%s",ivar_getName(iva));
}
self.nameTF.placeholder = @"请输入姓名";
[self.nameTF setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
free(ivar);
用途二:字典转模型
+ (instancetype)json2Model:(NSDictionary *)json{
id obj = [[self alloc]init];
unsigned int count;
Ivar *ivars = class_copyIvarList([self class], &count);
//遍历所有的成员变量
for (int i = 0; i < count; i ++) {
Ivar iva = ivars[I];
NSMutableString *ivarStr = [NSMutableString stringWithUTF8String:ivar_getName(iva)];
//去掉成员变量前面的 _
[ivarStr deleteCharactersInRange:NSMakeRange(0, 1)];
[obj setValue:json[ivarStr] forKey:ivarStr];
}
return obj;
}
这样写会有很多问题,这只是个思路,仅供参考
用途三:归档,解档
- (instancetype)initWithCoder:(NSCoder *)coder{
if (self = [super init]) {
unsigned int count;
Ivar *ivars = class_copyIvarList([self class], &count);
//遍历所有的成员变量
for (int i = 0; i < count; i ++) {
Ivar iva = ivars[I];
NSMutableString *ivarStr = [NSMutableString stringWithUTF8String:ivar_getName(iva)];
//去掉成员变量前面的 _
[ivarStr deleteCharactersInRange:NSMakeRange(0, 1)];
//从文件中取出值
id value = [coder decodeObjectForKey:ivarStr];
//赋值到对象中
[self setValue:value forKey:ivarStr];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder{
unsigned int count;
Ivar *ivars = class_copyIvarList([self class], &count);
//遍历所有的成员变量
for (int i = 0; i < count; i ++) {
Ivar iva = ivars[I];
NSMutableString *ivarStr = [NSMutableString stringWithUTF8String:ivar_getName(iva)];
//去掉成员变量前面的 _
[ivarStr deleteCharactersInRange:NSMakeRange(0, 1)];
//从对象中取出对应的值
id value = [self valueForKey:ivarStr];
//归档到文件中
[coder encodeObject:value forKey:ivarStr];
}
}
三: 属性相关的 API
-
objc_property_t class_getProperty(Class cls, const char * name)
获取一个属性 -
objc_property_t * class_copyPropertyList(Class cls, unsigned int * outCount)
拷贝属性列表 (需要调用 free 释放) -
BOOL class_addProperty(Class cls, const char * name, const objc_property_attribute_t * attributes, unsigned int attributeCount)
动态添加属性 -
void class_replaceProperty(Class cls, const char * name, const objc_property_attribute_t * attributes, unsigned int attributeCount)
动态替换属性 -
const char * property_getName(objc_property_t property)
获取属性名字 -
const char * property_getAttributes(objc_property_t property)
获取属性真实类型
四: 方法相关的 API
Method class_getInstanceMethod(Class cls, SEL name)
获得一个实例方法Method class_getClassMethod(Class cls, SEL name)
获得一个类方法Method _Nonnull * class_copyMethodList(Class cls, unsigned int * outCount)
拷贝方法列表 (最后需要调用free
释放)BOOL class_addMethod(Class cls, SEL name, IMP imp, const char * types)
动态添加方法IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char * types)
动态替换方法获取方法相关信息:
SEL method_getName(Method m)
获取方法名称IMP method_getImplementation(Method m)
获取方法实现 IMPconst char * method_getTypeEncoding(Method m)
获取方法签名unsigned int method_getNumberOfArguments(Method m)
获取方法参数数量char * method_copyReturnType(Method m)
获取方法返回值类型char * method_copyArgumentType(Method m, unsigned int index)
获取方法返回值类型方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
获取方法的实现 IMPIMP method_setImplementation(Method m, IMP imp)
修改方法的实现 IMP-
void method_exchangeImplementations(Method m1, Method m2)
交换两个方法的实现
重点说一下最后3个方法实现相关的API.主要的用法就是交换系统方法的实现,往系统的方法里添加我们自己的逻辑.
void method_exchangeImplementations(Method m1, Method m2)
的交换原理如下图所示:
从上图可以看出来,method_exchangeImplementations
只是交换两个Method
中的IMP
.
用法一:交换UIControl
的- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;
方法,拦截所有的点击事件,给UIControl
添加一个分类:
+ (void)load{
Method systemSendAction = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
Method customSendAction = class_getInstanceMethod([self class], @selector(hook_sendAction:to:forEvent:));
method_exchangeImplementations(systemSendAction, customSendAction);
}
- (void)hook_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
//执行系统原来的方法
[self hook_sendAction:action to:target forEvent:event];
//TODO 添加自己的业务逻辑
}
用法二:拦截NSMutableDictionary
的设值方法setObject:forKeyedSubscript:
和NSDictionary
的取值方法objectForKeyedSubscript:
+ (void)load{
Class mCls = NSClassFromString(@"__NSDictionaryM");
Method old_set = class_getInstanceMethod(mCls, @selector(setObject:forKeyedSubscript:));
Method new_set = class_getInstanceMethod([self class], @selector(hook_setObject:forKeyedSubscript:));
method_exchangeImplementations(old_set, new_set);
Method old_setObj = class_getInstanceMethod(mCls, @selector(setObject:forKey:));
Method new_setObj = class_getInstanceMethod(mCls, @selector(hook_setObject:forKey:));
method_exchangeImplementations(old_setObj, new_setObj);
}
// mutaDIc[@"ok"] = nil 会触发这个方法
- (void)hook_setObject:(id)obj forKeyedSubscript:(id)key{
if (!obj){
NSLog(@"obj为nil");
return;
};
[self hook_setObject:obj forKeyedSubscript:key];
}
// [mutaDIc setObject:nil forKey:@"ok"] 会触发这个方法
- (void)hook_setObject:(id)anObject forKey:(id)aKey{
if (!anObject) {
return;
}
[self hook_setObject:anObject forKey:aKey];
}
用法三:拦截NSMutableArray
的insertObject:atIndex:
方法:
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(mj_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
}
- (void)mj_insertObject:(id)anObject atIndex:(NSUInteger)index
{
if (anObject == nil) return;
[self mj_insertObject:anObject atIndex:index];
}
这里只列举几个常用的防止崩溃的用法,大家可以根据具体业务需要拦截系统方法.