• OC-runtime 的温习


    -.runtime简介

    • runtime简称运行时,OC就是运行时机制,也就是运行时的一些机制,其中最主要的是消息机制;
    • 对于C语言,函数的调用在编辑的时候,会决定调用哪个函数;
    • 对于OC的函数,属于动态调用过程,在编译的时候,并不能决定真正调用哪个函数,只有真的运行的时候,才会根据函数的名称找到对应的函数来调用;
    • 事实证明: 

        在编译阶段,OC可以调用任何函数,即使这事函数并未实现,只要声明过就不会报错;

         在编译阶段,C语言调用为实现的函数就会报错。

    二、runtime作用

    1. 发送消息
    • 方法调用的本质,就是让对象发送消息
    • objc_msgSend, 只有对象才能发送消息,以objc开头
    • 使用消息机制前提,必须导入#import <objct/message.h>
    • 简单使用
    • clang 

       任何方法的调用本质,就是发送一个消息,用runtime发送消息。OC底层实现通过runtime实现。

    解决:运行时方法中参数不提醒的问题

       xcode6 之前,苹果运行使用objc_msgSend 是有参数提示的,之后就没有了。苹果不推荐我们使用runtime。如果方法,看:http://www.cnblogs.com/lyz0925/p/7365058.html。

    简单代码如下:

        //----runtime消息机制
        //id self : 类名
        //SEL op
       //...:表示该方法需要,传入对应的参数。 //objc_msgSend(<#id self#>, <#SEL op, ...#>) id object = objc_msgSend([NSObject class], @selector(alloc)); objc_msgSend(object, @selector(init)); NSLog(@"---%@----", object);

        以上的代码,类似于[[NSObject alloc]   init] 的功能。

    clang -rewrite-objc 文件名--使用编译器

       runtime:方法都是有前缀的,谁的事情谁开头,例如-消息机制 用objc_msgSend 开头一样。

       需要用到runtime的消息机制大致有:可以帮我们调用私有方法,另外就是装逼用,不要为了用runtime而用,而是再必要的时候才用。

       如果消息机制调用了多个参数的话,如下代码:

    //步骤:1.创建一个person类,2.声明一个run带NSInter类型的方法,3.实现该方法。之后再调用
        Person *pers = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
        objc_msgSend(pers, sel_registerName("init"));
        objc_msgSend(pers, @selector(run:), 30);   //30 就是runtime需要传入的参数
    

      方法调用流程:--面试

         例如:如果调用person类上的eat方法? 对象方法:类对象的方法列表, 类方法:元类中查找方法

        1.通过isa去对应的类中寻找对应的类方法

        2.把方法名转成方法编号(注册方法编号--用编号可以快速的找到它)

      3.根据方法编号去查找对应方法

      4.找到只是最终函数实现地址,根据地址去方法区调用应对函数

     

     内存5大区:栈 堆 静态区 常量区 方法区

      栈:不需要手动管理内存,自动管理

      2.交换方法(只要想修改系统方法实现时使用)

      来个需求吧:每次UIImage用系统方法加载图片时,告诉我是否图片加载成功。

        常规的方法,就是:

        //判读图片是否赋值成功
        UIImage *image = [UIImage imageNamed:@"111.png"];
        if (image == nil) {
            NSLog(@"---图片赋值失败------");
        }else {
            NSLog(@"---图片赋值成功------");
        }
    

      弊端:每次都要这么判断,太麻烦;

      另外一种方法就是,自定义一个继承UIImage的类YZImage,然后重写imageNamed:的方法:

    //重写方法:想给系统的方法添加额外功能
    + (UIImage *)imageNamed:(NSString *)name {
        UIImage *image = [super imageNamed:name];
        if (image) {
            NSLog(@"success");
        }else {
            NSLog(@"fail");
        }
        return image;
    }
    

       弊端:每次使用,都需要导入自定义的头文件;如果是突然修改,项目大了,不太好替换。

      因此,用运行时来实现这个需求是最好的选择!--给添加分类,用运行时交换方法

      注意:在分类中,最好不要重写系统方法,一旦重写,把系统方法实现给覆盖了。 

      步骤:1.给系统的方法添加一个分类;

         2.自己实现一个带有扩展功能的方法(命名时,给系统方法,加个前缀,例如yz_imageName:);

          3.只交换一次。在load方法中,实现--load方法把内存加载进内存的时候调用,只会调用一次,另外需要在方法被调用之前,就替换系统的方法。 而initialize 会调用多次。

          4.获取iamgeNamed方法、获取要替换的方法、交换方法:runtime

    代码实现如下:

    这里的代码是在UIImage 的自定义分类中实现的:
    
    + (void)load {
        Method imageName = class_getClassMethod(self, @selector(imageNamed:));
        Method yz_imageName = class_getClassMethod(self, @selector(yz_imageName:));
        //交换方法:
        method_exchangeImplementations(imageName, yz_imageName);
        //虽然走的imageName 但实际上是调用的yz_imageName
        //虽然走的yz_imageName 但实际上是调用的imageName
    }
    
    + (UIImage *)yz_imageName:(NSString *)name {
    //    UIImage *image = [UIImage imageNamed:name];  --如果这里调用imageName就会有死循环的问题
        UIImage *image = [UIImage yz_imageName:name];
        if (image) {
            NSLog(@"success");
        }else {
            NSLog(@"fail");
        }
        return image;
    }
    

     3.动态添加方法

      在动态添加方法的时候,performSelector就是一个典型的例子。

      为什么要动态添加方法?OC都是懒加载机制,只要一个方法实现了,就会马上添加到方法列表中。

      注意:只要一个对象调用了一个未实现的方法就会调用resolveInstanceMethod:这个方法,进行处理。该方法的作用时,动态添加方法,处理未实现。

       代码实现如下:

    在子类的分类中实现:
    
    #import "printName.h"
    #import <objc/message.h>
    
    @implementation printName
    
    //定义C函数
    void eat(id self, SEL sel, printName *print) {
        NSLog(@"-----%@ %@ name=%@-----", self, NSStringFromSelector(sel), print.name);
    }
    
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if (sel == @selector(eat)) {
            
            /**
             
             Class cls
             cls 参数表示需要添加新方法的类。
             
             SEL name
             name 参数表示 selector 的方法名称,可以根据喜好自己进行命名。
             
             IMP imp
             imp 即 implementation ,表示由编译器生成的、指向实现方法的指针。也就是说,这个指针指向的方法就是我们要添加的方法。
             
             const char *types
             最后一个参数 *types 表示我们要添加的方法的返回值和参数。
             */
            
            class_addMethod(self, @selector(eat), (IMP)eat, "v@:@");
        }
        return [super resolveInstanceMethod:sel];
    }
    
    @end
    

     代码调用如下:

        //动态添加方法--比较难理解
        printName *print = [[printName alloc] init];
        print.name = @"nameValue";
        [print performSelector:@selector(eat) withObject:print];
    

     4.给分类动态添加属性(成员变量)

      步骤:1.创建一个NSObject的分类:Property;

         2..h中声明属性 name;

            3..m中用runtime实现

    #import "NSObject+Property.h"
    #import <objc/message.h>
    
    static const char *key = "name";
    
    @implementation NSObject (Property)
    
    - (NSString *)name {
        return objc_getAssociatedObject(self, key);
    }
    
    - (void)setName:(NSString *)name {
        objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    

       代码调用如下:

        //给分类动态添加属性(成员变量)
        NSObject *objc = [[NSObject alloc] init];
        objc.name = @"珠珠测试";
        NSLog(@"-----%@-----", objc.name);
        
        //给对象动态添加关联对象
        objc_setAssociatedObject(objc, @"keyOne", @"keyValueOne", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        objc_setAssociatedObject(objc, @"keyTwo", @"keyValueTwo", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        NSLog(@"-----object keyOne = %@-----", objc_getAssociatedObject(objc, @"keyOne"));
        NSLog(@"-----object keyTwo = %@-----", objc_getAssociatedObject(objc, @"keyTwo"));
    

        

     

  • 相关阅读:
    【设计模式】策略模式
    【设计模式】模板方法模式
    【C++】《Effective C++》第五章
    【C++】《Effective C++》第四章
    free命令详解(内存)
    top命令详解(动态进程)
    ps命令详解(静态进程)
    SpringBoot_集成Redis
    SpringBoot_热部署插件
    SpringBoot_实现RESTfull API
  • 原文地址:https://www.cnblogs.com/lyz0925/p/7401185.html
Copyright © 2020-2023  润新知