Runtime API01 - 类
- 获取isa指向的Class(类对象):
Class object_getClass(id obj)
- 设置isa指向的Class :
Class object_setClass(id obj, Class cls)
- 判断一个OC对象是否为Class:
Bool object_isClass(Class cls)
- 判断一个Class是否为元类:
Bool class_isMetaClass(Class cls)
- 获取父类:
Class class_getSuperclass(Class cls)
- 动态创建一个类(参数:父类,类名,额外的内存空间):
Class objc_allocateClassPair(Class superclass,const char *name,size_t extraBytes)
- 注册一个类(要在类注册之前添加成员变量):
void objc_registerClassPair(Class cls)
- 销毁一个类:
void objc_disposeClassPair(Class cls)
Person.h文件
@interface Person : NSObject
- (void)run;
@end
Person.m文件
#import "Person.h"
@implementation Person
- (void)run{
NSLog(@"%s",__func__);
}
@end
Car.h文件
#import <Foundation/Foundation.h>
@interface Car : NSObject
- (void)run;
@end
Car.m文件
#import "Car.h"
@implementation Car
- (void)run{
NSLog(@"%s",__func__);
}
@end
main文件
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person run];//打印结果:-[Person run]
NSLog(@"%p %p",object_getClass(person),[Person class]);//打印分别为类对象地址,类对象地址:0x100008230 0x100008230
NSLog(@"%p %p",object_getClass([Person class]),[Person class]);//打印分别为元类对象地址,类对象地址:0x100008208 0x100008230
object_setClass(person, [Car class]);//设置person的isa指向Car
[person run];//打印结果为-[Car run]
NSLog(@"%d %d %d",object_isClass(person),object_isClass([Person class]),object_isClass(object_getClass([Person class])));//object_isClass是否为类对象,元类对象是特殊的类对象,所以打印结果为:0 1 1
}
return 0;
}
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
#import <objc/runtime.h>
void run(id self,SEL _cmd){
NSLog(@"%@ %@",self,NSStringFromSelector(_cmd));
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//动态创建类
Class newClass = objc_allocateClassPair([NSObject class], "Dog", 0);
//添加成员变量
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
//注册类:类一旦注册完毕,类对象和元类对象里边的结构就已经创建好,所以添加成员变量要放在注册类前边。方法是可以放在注册类之后的。
objc_registerClassPair(newClass);
id dog = [[newClass alloc] init];
[dog setValue:@10 forKey:@"_age"];
[dog setValue:@20 forKey:@"_weight"];
[dog run];//打印结果为:<Dog: 0x108f2f070> run
NSLog(@"%zd",class_getInstanceSize(newClass));//打印结果为:16
NSLog(@"%@ %@",[dog valueForKey:@"_age"],[dog valueForKey:@"_weight"]);//打印结果为:10 20
Person *person = [[Person alloc] init];
object_setClass(person, newClass);
[person run];//打印结果为:<Dog: 0x108f0f050> run
//在不需要这个类时释放
objc_disposeClassPair(newClass);
}
return 0;
}
Runtime API02 - 成员变量
- 获取一个实例变量信息:
Ivar class_getInstanceVariable(Class cls,const char *name)
- 拷贝实例变量列表(最后需要调用free释放):
Ivar *class_copyIvarList(Class cls,unsigned int *outCount)
- 设置和获取成员变量的值:
void 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 alignment,const char *types)
- 获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
#import <objc/runtime.h>
void run(id self,SEL _cmd){
NSLog(@"%@ %@",self,NSStringFromSelector(_cmd));
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//获取成员变量信息
Ivar ageIvar = class_getInstanceVariable([Person class], "_age");
NSLog(@"%s %s",ivar_getName(ageIvar),ivar_getTypeEncoding(ageIvar));//打印结果为:_age i
//设置和获取成员变量的值
Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
Person *person = [[Person alloc] init];
object_setIvar(person, nameIvar, @"123");
object_getIvar(person, nameIvar);
NSLog(@"name = %@",person.name);//打印结果为:name = 123
//成员变量的数量
unsigned int count;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i=0; i<count; i++) {
//取出i位置的成员变量
Ivar ivar = ivars[i];
NSLog(@"%s %s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
}
//打印结果:
//_age i
//_name @"NSString"
free(ivars);
}
return 0;
}
Runtime API04 - 方法
- 获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls,SEL name) - 方法实现相关操作
IMP class_getMethodImplementation(Class cls,SEL name)
IMP method_setImplemention(Method m,IMP imp)
void method_exchangeImplementations(Method m1,Method m2) - 拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount) - 动态添加方法
BOOL class_addMethod(Class cls,SEL name,IMP imp,const char *types) - 动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types) - 获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method unsigned int index) - 选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str) - 用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
那么这些东西在实际项目中有什么作用呢?
Runtime的应用01 - 查看私有成员变量
- 用runtime方法获取并打印UITextField的成员变量,知道内部的很多细节
unsigned int count;
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i=0; i<count; i++) {
//取出i位置的成员变量
Ivar ivar = ivars[i];
NSLog(@"%s %s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
}
free(ivars);
Runtime的应用02 - 字典转模型
- 利用Runtime遍历所有的属性或者成员变量
- 利用KVC设值
Person.h文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,assign) int age;
@property(nonatomic,copy) NSString *name;
@property (nonatomic,assign) int weight;
@end
Person.m文件
#import "Person.h"
@implementation Person
@end
main文件
//字典转模型
NSDictionary *json = @{
@"age" : @20,
@"weight" : @60,
@"name" : @"Jack"
};
Person *person = [[Person alloc] init];
person.age = [json[@"age"] intValue];
person.weight = [json[@"weight"] intValue];
person.name = json[@"name"];
NSLog(@"----------");
但是如果模型里边有很多属性,就要写很多设置的代码,这里就可以给NSObject写一个分类,处理字典转模型的问题,用runtime的方法实现
NSObject+Json.h文件
#import <Foundation/Foundation.h>
@interface NSObject (Json)
+ (instancetype)ld_objectWithJson:(NSDictionary *)json;
@end
NSObject+Json.m文件
#import "NSObject+Json.h"
#import <objc/runtime.h>
@implementation NSObject (Json)
+(instancetype)mj_objectWithJson:(NSDictionary *)json{
id obj = [[self alloc] init];
unsigned int count;
Ivar *ivars = class_copyIvarList(self, &count);
for (int i=0; i<count; i++) {
//取出i位置的成员变量
Ivar ivar = ivars[i];
// NSLog(@"%s %s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
//带有下划线的成员变量的名字
NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
//删除最开始的下划线,就可以去字典取东西了
[name deleteCharactersInRange:NSMakeRange(0, 1)];
//设值
[obj setValue:json[name] forKey:name];
}
free(ivars);
return obj;
}
@end
这里只是一个很简单的字典转模型,没有考虑所有情况。不是完整的字典转模型的代码。只是举一个简单runtime的例子用
- (void)encodeWithCoder:(NSCoder *)coder{
[coder encodeObject:self.name forKey:@"name"];
}
- (instancetype)initWithCoder:(NSCoder *)coder{
if (self = [super init]) {
self.name = [coder decodeObjectForKey:@"name"];
}
return self;
}
这里也可以用字典转模型的思路实现归档、解档
Runtime的应用03 - 替换方法实现
- class_replaceMethod
- method_exchangeImplementations
void myrun(){
NSLog(@"-----myrun");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
//替换方法
class_replaceMethod([Person class], @selector(run), (IMP)myrun, "v");
[person run];
}
return 0;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
Method runMethod = class_getInstanceMethod([Person class], @selector(run));
Method testMethod = class_getInstanceMethod([Person class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);
[person run];//打印结果为:test ----
[person test];//打印结果为:run ----
}
return 0;
}
实现一个功能:拦截项目中所有按钮的点击事件
UIControl的分类
UIControl+ Extension.h文件
#import <UIKit/UIKit.h>
@interface UIControl (Extension)
@end
UIControl+ Extension.m文件
#import "UIControl+Extension.h"
#import <objc/runtime.h>
@implementation UIControl (Extension)
+(void)load{
Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method method2 = class_getInstanceMethod(self, @selector(ld_sendAction:to:forEvent:));
method_exchangeImplementations(method1, method2);
}
- (void)ld_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
NSLog(@"%@ - %@ - %@ ---",self,target,NSStringFromSelector(action));
//调用回系统原来的实现
[self ld_sendAction:action to:target forEvent:event];//这里为什么调用的是自己写的而不是系统之前的方法名呢?因为在上边的代码里已经交换了这两个方法。所以这里调用自己写的就是在调用系统自带的方法
if ([self isKindOfClass:[UIButton class]]) {
//拦截了所有按钮的事件
}
}
@end
#import "ViewController.h"
#import <objc/runtime.h>
@interface ViewController ()
@end
@implementation ViewController
- (IBAction)click1 {
NSLog(@"%s",__func__);
}
- (IBAction)click2 {
NSLog(@"%s",__func__);
}
- (IBAction)click3 {
NSLog(@"%s",__func__);
}
- (void)viewDidLoad {
[super viewDidLoad];
/*
UIButton继承自UIControl,UIControl中有如下一个方法,每个button点击的时候都会先走下边的方法。
- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;
那么想拦截所有按钮的点击,我们用runtime替换一下上边系统自带的方法就可以了
*/
}
@end
再举一个数组的例子,如下:
NSString *str = nil;
NSMutableArray *array = [NSMutableArray array];
[array addObject:@"jack"];
[array addObject:str];
//报错:-[__NSArrayM insertObject:atIndex:]: object cannot be nil'
NSLog(@"%zd",array.count);
如果我们不想因为数组添加了nil报错崩掉,那么我们就需要每次添加数据之前都要进行判断是否等于nil,太麻烦了,这个时候我们就可以用runtime的方法,hook 住insertObject:atInde这个函数,代码如下:
NSString *str = nil;
NSMutableArray *array = [NSMutableArray array];
[array addObject:@"jack"];
[array addObject:str];
//报错:-[__NSArrayM insertObject:atIndex:]: object cannot be nil'
NSLog(@"%@",array);
NSMutableArray+ Extensions.h文件
#import <Foundation/Foundation.h>
@interface NSMutableArray (Extensions)
@end
NSMutableArray+ Extensions.m文件
#import "NSMutableArray+Extensions.h"
#import <objc/runtime.h>
@implementation NSMutableArray (Extensions)
+(void)load{
//类簇:NSString、NSArray、NSDictionary
//也就是说在写交换的方法时,类对象那里一点要传对,虽然这里是要给NSMutableArray交换,但是他的底层依然是NSArray,所以直接写self的话,是交换不成功的。所以这里的类对象需要放__NSArrayM,从崩溃的提示能看出来:-[__NSArrayM insertObject:atIndex:]: object cannot be nil'
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(ld_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
}
- (void)ld_insertObject:(id)anObject atIndex:(NSUInteger)index{
if (anObject == nil) {
return;
}
[self ld_insertObject:anObject atIndex:index];
}
@end
这样就不会崩溃报错了。
这里需要注意一点的是:
交换方法放在dispatch_once里边,为了以防万一交换之后又交换一遍,
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//类簇:NSString、NSArray、NSDictionary
//也就是说在写交换的方法时,类对象那里一点要传对,虽然这里是要给NSMutableArray交换,但是他的底层依然是NSArray,所以直接写self的话,是交换不成功的。所以这里的类对象需要放__NSArrayM,从崩溃的提示能看出来:-[__NSArrayM insertObject:atIndex:]: object cannot be nil'
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(ld_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
}