RunTime

作者: 我是C | 来源:发表于2017-12-22 18:59 被阅读39次
    runtime是什么?
    • runtime又称运行时,也就是运行时候的一些机制,其中最总要的就是消息机制.
    • 任何调用方法的本质都是发送消息

    oc 与c 有什么不同?
    • c语言函数,在编译阶段就确定调用哪个函数
    • oc方法,在编译的时候并不能决定调用哪个方法,只有在真正运行的时候才会根据方法名称找到与之对应的函数实现
      *注意:对于oc 来说是调用方法,对于c来说是调用函数.方法和函数是有区别的,对于oc根据方法名去找到函数名,相当于函数实现是方法的实现.函数名是函数实现的入口
    • 在编译阶段,oc 中的方法只要声明就不会报错,而c中的函数必须实现,才不会报错.

    runtime发消息
    NSObject *p = [NSObject alloc];
    p = [p init];
    

    clang -rewrite-objc ViewController.m
    如果报错看这里http://www.jianshu.com/p/43a09727eb2c
    这句话将ViewController.m代买转化成ViewController.cpp,oc代码转成c++代码
    然后我们去ViewController.cpp看看6万多行代码,
    搜索@implementation ViewController

     NSObject *p = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));
     p = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("init"));
    

    精简一下:

     NSObject *p = objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc"));
     p = objc_msgSend(p, sel_registerName("init"));
    

    从上面代码我们看到,我们创建对象的本质其实就是发消息
    objc_msgSend:发消息
    objc_getClass : 获取类
    sel_registerName:注册方法编号,相当于@selector()


    runtime几种用途
    • 方法交换

    举个例子,比如[UIImage imageNamed:@"1"],那么这张图片有没有也不知道,那么我们该怎么做呢?

    1. 可以建一个自定义的类,每次调用自己的类,但是这样每次都需要导入头文件,而且如果项目已经维护了好久,那么这种方法会改起来没麻烦
    2. 可以写个类别,但是问题同上

    因此,这时候我们需要想到runtime方法交换,因为我们不想改动之前的代码,但是之前的代码不符合我们需求,我们需要扩展,所以我们应该想到这种方法.
    那么需要将imageNamed系统方法实现IMP 和 自定义的 gl_ imageNamedIMP 交换

     //新建一个类别
    //值调用一次,在加载类的时候调用
    + (void)load{
        
        Method method1 = class_getClassMethod([self class], @selector(imageNamed:));
        Method method2 = class_getClassMethod([self class], @selector(gl_imageNamed:));
    
        
        method_exchangeImplementations(method1, method2);
        
    }
    
    //会调用多次,可以用单利来限制代码执行次数,因为swift里没有+load,所以只能用+initialize
    + (void)initialize{
        
    }
    
    + (UIImage *)gl_imageNamed:(NSString *)name{
    
        UIImage *image = [self gl_imageNamed:name];
        
        if (image) {
            NSLog(@"图片存在");
        } else {
            NSLog(@"图片不存在");
        }
        
        return image;
    }
    
    

    其中我说明了load方法和initialize 的区别
    这样我在调用UIImage *image = [UIImage imageNamed:@"qq"];会告诉我图片没有


    • 动态添加方法

    1.方法调用流程:
    1)通过isa 指针去对应的类中去找方法,对象方法去类对象的方法列表去找方法,类方法去元类的方法列表中去找方法.
    2)注册方法编号
    3)根据方法编号去查找方法
    4)找到最终函数实现地址

    2.运行时添加一个方法,我再举个🌰:
    我建一个Person类,接着调用

    Person *p = [[Person alloc] init];
    [p performSelector:@selector(eat)];
    

    为什么用performSelector,因为对于没有声明的方法,编译时无法编过去, performSelector是运行时执行,接着运行一下,crash!
    reason: '-[Person eat]: unrecognized selector sent to instance 0x604000002220'
    我们可以拦截这个崩溃,在Person.m里添加

    void eat(id self,SEL _cmd){
        NSLog(@"吃了");
    }
    
    //第一次拦截,其实有三次转发
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
            class_addMethod([self class], sel, (IMP)eat, "v@:");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    

    class_addMethod 方法最后一参数在苹果文档中查找
    v:代表返回值是void
    @:代表参数是id
    ::代表参数是 sel
    void eat(id self,SEL _cmd){ NSLog(@"吃了"); }:该函数的两个参数,是默认传的,其实苹果每个函数调用都会传self和_cmd,只是编译器帮我们做了.

    如果我们使用[p performSelector:@selector(eat) withObject:@1];带参数的,对应的写成下面这个样子的

    void eat(id self,SEL _cmd,NSNumber *n){
        NSLog(@"吃了%@",n);
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
            class_addMethod([self class], sel, (IMP)eat, "v@:@");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    

    • 动态添加属性

    本质也就是类别中添加一个属性,
    因为类别中只会set get 方法声明,不会实现,也不会生成对应的下划线成员变量.看看代码

    @interface Person : NSObject
    
    @property NSString *name;
    
    @end
    

    由于方法没有实现,所以给属性设置策略是无用的

    - (void)setName:(NSString *)name{
        objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (NSString *)name{
        return  objc_getAssociatedObject(self, @"name");
    }
    

    将该属性关联到Person对象上


    • 字典转模型

    重中之重!
    好多解析model 的框架底层原理都是基于runtime

    1.动态添加属性代码

    {
        "classNum": "3年二班",
        "user": [
                 {"name":"张三"
                 "age":22
                 },
                 {"name":"李四"
                 "age":34
                 },
                 ]
        "isHave":YES
        "totol":2
    }
    

    建一个NSDictionary 分类

    - (void)createPropertyWithDict{
        NSMutableString *string = [NSMutableString string];
    
        [self enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            
            
            if ([obj isKindOfClass:[NSString class]]) {
                NSString* str = [NSString stringWithFormat:@"@property (nonatomic,strong) NSString *%@;",key];
                [string appendFormat:@"\n%@\n",str];
            }else if ([obj isKindOfClass:[NSArray class]]){
                NSString* str = [NSString stringWithFormat:@"@property (nonatomic,strong) NSArray *%@;",key];
                [string appendFormat:@"\n%@\n",str];
    
            }else if ([obj isKindOfClass:[NSDictionary class]]){
                NSString* str = [NSString stringWithFormat:@"@property (nonatomic,strong) NSDictionary *%@;",key];
                [string appendFormat:@"\n%@\n",str];
    
            }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
                NSString* str = [NSString stringWithFormat:@"@property (nonatomic,assign) BOOL %@;",key];
                [string appendFormat:@"\n%@\n",str];
    
            }else if ([obj isKindOfClass:[NSNumber class]]){
                NSString* str = [NSString stringWithFormat:@"@property (nonatomic,assign) NSInteger %@;",key];
                [string appendFormat:@"\n%@\n",str];
            }
    
        }];
        
        NSLog(@"%@",string);
    
    }
    

    然后用字典直接调用createPropertyWithDict
    得到结果如下

    @property (nonatomic,assign) NSInteger totole;
    
    @property (nonatomic,strong) NSString * classNum;
    
    @property (nonatomic,assign) BOOL isHave;
    
    @property (nonatomic,strong) NSArray *user;
    
    

    2.字典转model

    - (id)modelWithDict:(NSDictionary*)dict{
        NSObject *obj = [[[self class] alloc] init];
        unsigned int count = 0;
        //之所以用class_copyIvarList 而不用 class_copyPropertyList,因为我们可能忽略成员变量,但不会忽略属性
        Ivar *IvalList = class_copyIvarList([self class], &count);
        for (int i = 0; i<count; i++) {
            Ivar ivar = IvalList[i];
            //ivar_getName(ivar) 得到成员变量的名字
            //c语言字符串转oc字符串转
            NSString *ivarStr = [NSString stringWithUTF8String:ivar_getName(ivar)];
            ivarStr = [ivarStr substringFromIndex:1];
            //ivar_getTypeEncoding(ivar) 得到成员变量的类型
            NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
    
            [obj setValue:dict[ivarStr] forKey:ivarStr];
        }
        
        return obj;
    }
    

    以上就是关于runtime 的一个总结,很多细节部分需要自己去动手才能发现,谢谢

    相关文章

      网友评论

          本文标题:RunTime

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