美文网首页iOS Developer
#iOS Runtime 从初识到实践

#iOS Runtime 从初识到实践

作者: 叫我余温好了 | 来源:发表于2016-05-25 16:54 被阅读158次
    xcode.png

    ** 本文适用于没有揭开runtime神秘面纱的读者参考, 大神看到这里的话还希望向小弟甩点墨水, 增长学识! **

    Runtime简介:


    它是个运行时的库,基本是C和汇编写的。可以把一些工作从编译推迟到运行时处理, 也就是说是运行时系统来执行编译后的代码. 因为其动态性, 所以我们可以在程序运行的时候为类添加,修改,匹配方法等.

    初识Runtime:


    我们所创建的对象或者说类在runtime中是结构体的形式存在的, 进入runtime的头文件即可获悉

    #if !OBJC_TYPES_DEFINED
    
    /// An opaque type that represents a method in a class definition.
    typedef struct objc_method *Method; // 描述一个方法
    
    /// An opaque type that represents an instance variable.
    typedef struct objc_ivar *Ivar; // 实例变量
    
    /// An opaque type that represents a category.
    typedef struct objc_category *Category; // 分类
    
    /// An opaque type that represents an Objective-C declared property.
    typedef struct objc_property *objc_property_t; // 类中属性
    
    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY; //指针, 实例的指针指向类对象象,类对象的指针指向元类
    
    #if !__OBJC2__
        Class super_class //指向父类                              OBJC2_UNAVAILABLE;
        const char *name      //类的名称                                   OBJC2_UNAVAILABLE;
        long version           //类的版本信息                                  OBJC2_UNAVAILABLE;
        long info                   // 类的信息                             OBJC2_UNAVAILABLE;
        long instance_size                //该类实例变量的大小                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars        //成员变量列表                     OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists     //方法列表               OBJC2_UNAVAILABLE;
        struct objc_cache *cache            //缓存列表(对于已经调用的方法会存入其中,下次调用优先从缓存找)                     OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols       //协议列表              OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    

    相信看到这如果我问你category为什么不能添加属性?(不能在.h中写成员变量,却可以写属性,但调用的时候会crash) 你应该可以知道了吧! 但可以通过其它方式间接实现,详情一会会写到**

    Runtime作用


    1. 发送消息

    方法的调用即是让对象发送消息, 运行时会转换成objc_msgSend, 使用消息机制须#import <objc/message.h>
    消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现

    Cat *cat = [Cat new];
    // 直接调用
    [self eatFish];
    
    // 运行时会转成
    objc_msgSend(cat, @selector(eatFish));
    

    2. 动态添加方法

    当我们调用[cat performSelector:@selector(play)];的时候程序会carsh, 因为没有实现play方法, 怎么办呢? 可以通过下面的方式解决

    
    
    @implementation Cat
    
    void c_play(id self,SEL sel) // 如果不写括号里的self和_cmd会隐式添加 self为方法调用者 _cmd为方法编号
    {
      NSLog(@"自己玩");
    }
     
    //消息接收者没有找到对应的方法时候,会先调用此方法,我们拦截没实现的方法
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
     
      if (sel == @selector(play)) {
         //这里我们添加play方法
        // 参数1:哪个类添加方法 2:方法编号 3:方法实现的函数地址 4:函数类型
        //函数类型解释:v代表没有返回值void, @:代表对象, :代表SEL
        class_addMethod(self, @selector(play), c_play, "v@:");
     
      }
     
      return [super resolveInstanceMethod:sel];
    }
    @end
    

    这样调用[cat performSelector:@selector(play)]程序跑起来的时候就不会crash了, 因为我们已经动态添加了;若添加类方法为resolveClassMethod, 同理的.

    3. 方法的交换

    **若有这样一个需求, 觉得系统的功能不太受用, 在无法改变系统方法的前提下, 为这个方法添加一些功能, 比如即使数组中插入nil时也不让其crash, 用imageNamed:这个方法时候知道图片到底加载成功了没 **

    你有可能想到的是继承或者重写该方法(但分类中无法调用super,重写会覆盖之前功能), 下面我们用runtime试试看

    @implementation UIImage (Image)
    // 分类在内存中的时候很早就被调用的一个方法,一般都在它中实现方法的交换
    + (void)load
    {
     // 获取类方法imageNamed:
      Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
     // // 获取类方法rt_imageNamed:
      Method m2 = class_getClassMethod([UIImage class], @selector(rt_imageNamed:));
     
      // 交换彼此方法的实现
      method_exchangeImplementations(m1, m2);
    }
     
    // 需要交换的方法实现
    + (UIImage *)rt_imageNamed:(NSString *)imageName
    {
      // 可能有读者会问,这不是死循环了吗, no, 我们在load里已经交换了方法实现了哦, 所以看似调用自己而已
      UIImage *image = [UIImage rt_imageNamed:imageName];
      //功能
      if (image == nil) {
        // do something
      }
     
      return image;
    }
    
    @end
    
    

    4. 添加属性

    我们之前说过如何变相的给category添加属性, 答案是objc_getAssociatedObject, 也是大家常用的一个函数, 即关联.
    值得注意的是关联对象不是为类\对象添加属性或者成员变量(因为在设置关联后也无法通过ivarList或者propertyList取得, 至于后面这两个函数我们稍后实践)
    使用场景: 例如给NSArray添加一个name属性,我们这里不用继承实现

    
    @interface NSArray (TestAssociated)
    
    @property (nonatomic, copy)NSString *name;
    @end
    #import "NSArray+TestAssociated.h"
    
    @implementation NSArray (TestAssociated)
    
    static char associatedKey;
    
    - (void)setName:(NSString *)name{
    
    //参数1: 给谁添加关联 2: 关联的key(通过key获取关联的对象) 3: 被关联的对象 4: 关联策略(点进入看一下就知道了)
        objc_setAssociatedObject(self, &associatedKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)name{
    
    // 同上
        return objc_getAssociatedObject(self, &associatedKey);
    }
    @end
    
    

    这样我们就不仅能用tag值了, 使用就这样

    NSArray *arr = @[];
        arr.name = @"111";
        
        NSLog(@"%@", arr.name);//打印的值为111
    

    5. 打印对象的属性等

    unsigned int i = 0;
        // 打印当前对象的属性
        objc_property_t *pros = class_copyPropertyList([self class], &i);
        
        for (int j = 0; j < i; j++) {
            
            NSString *name = @(property_getName(pros[j]));
            NSLog(@":%@", name);
               
        }
    
    // 同理我们可以打印更多的东西从头文件可知
    objc_ivar_list //成员变量列表
    objc_method_list //方法列表
    ...
    
    

    如果你看到了这里, 你现在一定恍然大悟, 原来那些大神们写的json转model就是这么实现的啊!

    使用总结: 对对象进行操作的方法一般以object_开头

    对类进行操作的方法一般以class_开头

    对类或对象的方法进行操作的方法一般以method_开头

    对成员变量进行操作的方法一般以ivar_开头

    对属性进行操作的方法一般以property_开头开头

    对协议进行操作的方法一般以protocol_开头


    总结:关于runtime的使用还有更多更多, 我只介绍了冰山一角,或者说助你看清了轮廓. 实际做项目中我们很少时候用到它, 但实现强大的功能时,往往却离不开它, 例如: json转model功能, 调用私有函数并且绕过苹果审核等!

    相关文章

      网友评论

        本文标题:#iOS Runtime 从初识到实践

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