美文网首页
Runtime应用

Runtime应用

作者: 冰棍儿好烫嘴 | 来源:发表于2023-04-08 01:01 被阅读0次
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);
    });
}

相关文章

网友评论

      本文标题:Runtime应用

      本文链接:https://www.haomeiwen.com/subject/odmpddtx.html