RunTime

作者: fjytqiu | 来源:发表于2016-10-09 18:47 被阅读69次

    一、runtime简介

    Objective-C是一种动态语言,所谓动态语言,是在程序执行时动态的确定变量类型,执行变量类型对应的方法的。因此,在Object-C中常用字符串映射类的技巧来动态创建类对象。因为OC的动态语言特性,我们可以通过一些手段,在程序运行时动态的更改对象的变量甚至方法,这就是我们所说的runtime机制。

    1. RunTime简称运行时。OC是动态语言,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会确定变量类型,根据函数的名称找到对应的函数来调用。 而C语言,函数的调用在编译的时候会决定调用哪个函数。
    2. 在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错 ,而C语言调用未实现的函数就会报错。

    二、runtime作用( 必须导入#import <objc/message.h> ;在build setting中搜objc_msg设置“NO”)

    1.发送消息(最主要的运行时机制)

    方法调用的本质,就是让对象发送消息。
    objc_msgSend,只有对象才能发送消息,因此以objc开头.
    消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现

        Dog *dog = [[Dog alloc] init];
        [dog bite];    // 对象方法
        //  底层是实现,对象发送消息
        objc_msgSend(dog, @selector(bite));
        
        
        [Dog bite];    // 类方法方法
        //  底层是实现,底层会自动把类名转换成类对象调用
        objc_msgSend([Dog class], @selector(bite));
    
    2.交换方法(黑魔法)

    需求: 在保持原有的功能的情况下,给系统自带的方法扩展一些功能。
    方案:
    一: 继承系统的类,重写方法.
    二: 使用runtime,交换方法.

    :给imageNamed方法扩展功能,每次加载图片后打印“输出图片”。

    
    @implementation UIImage (Extention)
    
    + (void)load {
        // 交换方法
        // 1. 获取imageWithName方法地址
        Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
        
        // 2. 获取imageWithName方法地址
        Method imageName = class_getClassMethod(self, @selector(imageNamed:));
        
        // 3. 交换方法地址,相当于交换实现方式
        method_exchangeImplementations(imageWithName, imageName);
        
    }
    
    + (UIImage *)imageWithName:(NSString *)icon {
        
        // load方法中方法交换后,这里调用imageWithName,就相当于调用imageName(并不会发生死循环)
        UIImage *image = [self imageWithName:icon];
        NSLog(@"输出图片");
        return image;
    }
    
    
    3.动态添加方法

    需求:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
    经典面试题:有没有使用performSelector,其实主要想问你有没有动态添加过方法。
    简单使用

    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        Dog *dog = [[Dog alloc] init];
        [dog performSelector:@selector(eat)];    // 对象方法,直接使用[dog eat;似乎也可以
    }
    
    @end
    
    
    
    @implementation Dog
    // void(*)()
    // 默认方法都有两个隐式参数,
    void eat(id self,SEL sel)
    {
        NSLog(@"%@ %@",self,NSStringFromSelector(sel));
    }
    
    // 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
    // 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
    
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        
        if (sel == @selector(eat)) {
            
            // 动态添加eat方法
            
            // 第一个参数:给哪个类添加方法
            // 第二个参数:添加方法的方法编号
            // 第三个参数:添加方法的函数实现(函数地址)
            // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
            class_addMethod(self, @selector(eat), eat, "v@:");
        }
        
        return [super resolveInstanceMethod:sel];
    }
    

    4.给分类添加属性
    原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。

    // 定义关联的key
    static const char *key = "name";
    
    @implementation NSObject (Property)
    
    - (NSString *)name
    {
        // 根据关联的key,获取关联的值。
        return objc_getAssociatedObject(self, key);
    }
    
    - (void)setName:(NSString *)name
    {
        // 第一个参数:给哪个对象添加关联
        // 第二个参数:关联的key,通过这个key获取
        // 第三个参数:关联的value
        // 第四个参数:关联的策略
        objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    @end
    
    5.获取对象的所有属性

    需求:修改.m文件中的私有属性

        Dog *dog = [[Dog alloc] init];
      
        //我们先声明一个unsigned int
        //调用runtime的方法
        //Ivar:方法返回的对象内容对象,这里将返回一个Ivar类型的指针
        //class_copyIvarList方法可以捕获到类的所有变量,将变量的数量存在一个unsigned int的指针中
        unsigned int count;
        Ivar * memList = class_copyIvarList([Dog class], &count);
        //进行遍历
        for (int i=0; i< count ; i++) {
            //通过移动指针进行遍历
            Ivar var = memList[i];
            //获取变量的名称
            NSString *name = [NSString stringWithUTF8String:ivar_getName(var)];
            //获取变量的类型
            NSLog(@"%@",name);
        }
        object_setIvar(dog, memList[0], @"pp");
    

    需求:调用.m文件中实现的方法

        unsigned int count;
        Method *memList = class_copyMethodList([Dog class], &count);
        //遍历
        for(int i=0; i<count; i++){
            SEL name = method_getName(memList[i]);
            NSString * method = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
            NSLog(@"%@\\n",method);
        }
        SEL name = method_getName(memList[0]);
    
        NSString *method = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
        [dog performSelector:name withObject:nil];
    
    6.用我们的函数替换掉类中的函数(黑魔法)

    这个太屌,可以将任何一个类的方法替换掉

    - (void)viewDidLoad {  
      [super viewDidLoad];    
     Dog *dog = [[Dog alloc]init];    //替换之前的方法  
     class_replaceMethod([Dog class], @selector(bite), (IMP)logHAHA, "q");   
     [dog bite];   
     }
    void logHAHA(){    
    NSLog(@"HAHA");}
    

    相关文章

      网友评论

          本文标题:RunTime

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