美文网首页
理解使用runtime

理解使用runtime

作者: Maj_sunshine | 来源:发表于2017-07-14 14:59 被阅读17次

    前言

    作为一个runtime的初学着,决定去了解一下runtime的底层方法和数据结构,以方便加深我对于runtime的理解。

    什么是runtime ?

    runtime是一个C和汇编语言的的动态库,底层提供了大量的api。我们平常写的OC到最后都会转化为rumtime的C代码执行。OC是一个动态性的语言,它将工作尽可能的放在运行时而非编译时期处理,和其他语言不同,OC的动态性基础主要包括三种: 动态类型,动态绑定,动态加载。

    了解runtime的各种数据结构

    Class

    我将Class的定义取出为 typedef struct objc_class *Class;

    struct objc_class {
    //isa 指针,OC一切都可以看成对象,isa指向的是对象的类对象。即实例对象的isa指针指向该对象的类,类对象的isa指针指向其元类。
        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;
    

    以上列表我们可以通过runtime的方法来获取,方法为. class_copy...

    unsigned int count = 0 ;  
        objc_property_t *propertyList = class_copyPropertyList([UIButton class], &count);  //获取属性列表
        for (int i = 0; i <count; i++) {
            objc_property_t property = propertyList[I];  //获取其中一个属性
            const char *propertyName = property_getName(property); //获取属性名
            NSLog(@"propertyName = %s",propertyName);
        }
    
    unsigned int count = 0 ;  
    Ivar *ivarList = class_copyIvarList([UILabel class], &count); //获取实例变量列表
       for (int i = 0; i <count; i++) {
           Ivar ivar = ivarList[i];
           const char *ivarName = ivar_getName(ivar); //实例变量名
           const char *ivarType = ivar_getTypeEncoding(ivar); //实例变量类型
           ptrdiff_t offset = ivar_getOffset(ivar);  //实例变量指针偏移值
          NSLog(@"变量名 :%s  ,类型 : %s,指针偏移值 = %td",ivarName,ivarType,offset);
        }
    
    unsigned int count = 0 ;  
     Method *methodList = class_copyMethodList([self class], &count);
        for (unsigned int i; i<count; i++) {
            Method method = methodList[i];
            SEL sel = method_getName(method); //获取方法选择器
            const char *selName = sel_getName(sel);  //获取方法选择器名称
    //        NSString *Str = NSStringFromSelector(sel);  也可以利用OC
    来获取名称
            IMP imp = method_getImplementation(method); //方法的地址
             NSLog(@"方法地址为 : %p ,方法名称为 : %s" ,imp ,selName);
        }
    
    unsigned int count = 0 ;  
     __unsafe_unretained  Protocol **protocolList = class_copyProtocolList([UIView class], &count);
         for (int i = 0; i <count; i++) {
             __unsafe_unretained  Protocol *protocol = (protocolList[i]);
             const char *protocolname = protocol_getName(protocol);  //获取协议名
    //         NSString *protocolNameStr = [NSString stringWithUTF8String:protocolname];  转为oc字符串
             NSLog(@" 协议名为 : %s", protocolname);
        }
    

    Method

    typedef struct objc_method *Method;
    
    struct objc_method {
    //方法选择器 可以通过sel来获取方法选择器的名称 
        SEL method_name                                          OBJC2_UNAVAILABLE;
    //方法的参数类型 
        char *method_types                                       OBJC2_UNAVAILABLE;
    //存储方法的实现,类似函数指针。
        IMP method_imp                                           OBJC2_UNAVAILABLE;
    }
    

    IMP

    typedefine id (*IMP)(id, SEL, ...)
    

    这个结构体相当于在SEL和IMP之间作了一个绑定。这样有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。IMP本质是一个函数指针,在oc调用方法之后,只要不抛出异常,都会找到相应方法的实现,而IMP则存储着方法的实现地址,即IMP执行方法的实现地址。

    Cache

    struct objc_cache {
    // 缓存bucket的总数
        unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
     // 实际缓存bucket的总数
        unsigned int occupied                                    OBJC2_UNAVAILABLE;
    // 指向Method数据结构指针的数组
        Method buckets[1]                                        OBJC2_UNAVAILABLE;
    };
    

    方法的调用会先调用缓存中的方法列表,方法调用是懒调用。若没找到,其次才调用全部的方法类别,算是优化,因为类的方法列表频繁使用的方法其实是极少的,这样能提高系统的运行效率。

    Ivar

    struct objc_ivar {
    // 成员变量名
        char *ivar_name                                          OBJC2_UNAVAILABLE;
    // 获取成员变量类型
        char *ivar_type                                          OBJC2_UNAVAILABLE;
    //成员变量的偏移量
        int ivar_offset                                          OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
    }  
    

    Property

    objc_property_t是表示Objective-C声明的属性的类型,其实际是指向objc_property结构体的指针,其定义如下:

    typedef struct objc_property *objc_property_t; 
    
    

    消息转发机制

    我们都知道OC的代码在运行时会转化成为C语言的代码

        id objc_msgSend ( id self, SEL op, ... );
    

    当objc_msgSend函数调用时
    1 : 会检查当前selector是不是要忽略。
    2 : 检测这个 selector 的 target 是不是 nil,Objc 允许我们对一个 nil 对象执行任何方法不会 Crash,因为运行时会被忽略掉。
    3 : 如果上面都ok,则会开始查找IMP,先从缓存中查找,如果找到了就执行相应的方法。
    4 :如果没有找到,则从类的元类的方法列表中查找相应的方法,如果找到了就执行相应的方法。
    5 :如果还没找到,则从当前类的父类的元类方法列表中查找相应的方法,如果找到了,类对象就响应相应的方法。
    6 : 一直执行第五步,知道NSObject类,如果还没有找到,则会走消息转发。

    走消息转发

    第一步,如果方法列表中找不到相应的IMP时,runtime会给我们调用 resolveInstanceMethod: 或 resolveClassMethod: 的机会去添加相应的IMP。例

    void walkFunc(id self, SEL _cmd) {   // 这是IMP的实现
        //let the dog walk
        NSLog(@"走23步");
    }
    + (BOOL)resolveInstanceMethod:(SEL)sel {  //动态方法解析
        NSString * selString = NSStringFromSelector(sel);
        if ([selString isEqualToString:@"walk"]) {
            class_addMethod(self.class, @selector(walk), (IMP)walkFunc, "@:");   //为类动态添加方法
            
        }
        return [super resolveInstanceMethod:sel];
    }
    

    第二步,如果第一步返回NO,则走第二步将方法转发给另外一个对象

    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        NSString *selStr = NSStringFromSelector(aSelector);
        if ([selStr isEqualToString:@"walk" ]) {
            return person2;  //这里将消息转发给person2对象
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    

    第三步,如果第二步返回nil或者self ,则开始走完整的消息转发。参数是个 NSInvocation 类型的对象,该对象封装了原始的消息和消息的参数。我们可以实现 forwardInvocation: 方法来对不能处理的消息做一些处理。也可以将消息转发给其他对象处理,而不抛出错误

    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        if ([someOtherObject respondsToSelector:
                [anInvocation selector]])
            [anInvocation invokeWithTarget:someOtherObject];
        else
            [super forwardInvocation:anInvocation];
    }
    

    RunTime 经常使用的方法

    method_exchangeImplementations 交换方法的IMP 实现

    了解load方法和 initialize方法
    load 方法
    ·当一个类引入项目时,就会调用load方法。
    ·类和子类同时实现了load方法时,父类的执行顺势先于子类。
    ·类中的load方法执行顺序先与类别
    ·子类的load方法未实现时,不会调用父类的load方法。(不会)

    initialize:
    · 类或子类的第一个方法被调用前调用
    ·当子类实现了initialize方法,会覆盖父类的initialize方法。
    ·多个类目实现了initialize方法,执行最后一个引入的类目。
    ·当子类的initialize方法未实现,会调用父类的initialize方法(会)

    + (void)load
    {
        //获取系统方法
        Method Method1 =  class_getInstanceMethod([self class], @selector(fun1));
       
        
        // 获取fch_imageNamed
        Method Method2 =  class_getInstanceMethod([self class], @selector(fun2));
        
        // 交互方法:runtime
        method_exchangeImplementations(Method1, Method2);
    
    }
    - (void)fun1
    {
        NSLog(@"运行fun1");
    }
    
    - (void)fun2
    {
        NSLog(@"运行fun2");
    }
    

    运行[self fun1] ,控制台输出

    运行fun2
    

    使用runtime中objc_setAssociatedObject()和objc_getAssociatedObject()方法,为类新增属性(设置关联对象);

    /* 动态添加方法: 
      第一个参数表示Class cls 类型; 
      第二个参数表示待调用的方法名称; 
      第三个参数(IMP)myAddingFunction,IMP一个函数指针,这里表示指定具体实现方法myAddingFunction; 
      第四个参数表方法的参数,0代表没有参数; 
    */
    - (NSString *)addStr
    {
        
        return objc_getAssociatedObject(self, "addStrKey");
    }
    
    -(void)setAddStr:(NSString *)addStr
    {
        objc_setAssociatedObject(self,     //赋值的对象
                                 "addStrKey",  //属性的key值
                                 addStr,  //属性的value值
                                 OBJC_ASSOCIATION_RETAIN);  //修饰策略
    }
    

    后面看到人说可以通过runtime获取UIAlertController 和UIAlertAction的 实例变量ivar ,把实例变量当作key值,通过KVC来修改UIAlertController的样式。

    获取实例变量

    - (void)getAllVariable
    {
         unsigned int count = 0;
        Ivar *allIvar = class_copyIvarList([UIAlertAction class], &count);
        for (int i = 0; i <count; i++) { //对变量进行遍历
            @autoreleasepool {
                Ivar ivar = allIvar[I];
                const char *ivarName =  ivar_getName(ivar);
                NSLog(@"ivar_name = %s", ivarName);
            }
        }
        free(allIvar);
    }
    

    得到实例变量

    // UIAlertAction class
    //_title,
    //_titleTextAlignment,
    //_enabled,
    //_checked,
    //_isPreferred,
    //_imageTintColor,
    //_titleTextColor,
    //_style,
    //_handler,
    //_simpleHandler,
    //_image,
    //_shouldDismissHandler,
    //__descriptiveText,
    //_contentViewController,
    //_keyCommandInput,
    //_keyCommandModifierFlags,
    //__representer,
    //__interfaceActionRepresentation,
    //__alertController
    

    同理UIAlertController得实例变量 ,这个比较多,我拿了我用到的

    //     _attributedTitle,
    //     _attributedMessage,
    

    修改UIAlertController的显示

    - (void)showAlert
    {
       [self presentViewController:self.alert animated:YES completion:nil];
       NSMutableAttributedString *str = [[NSMutableAttributedString alloc]initWithString:self.alert.message];
       NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc]init];
       [style setAlignment:NSTextAlignmentRight];
       [str addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(0, str.length)];
       [str addAttribute:NSForegroundColorAttributeName value:[UIColor greenColor] range:NSMakeRange(0, str.length)];
       [self.alert setValue:str forKey:@"attributedMessage"];
       
       NSMutableAttributedString *str1 = [[NSMutableAttributedString alloc]initWithString:self.alert.title];
       [str1 addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, str1.length)];
       NSMutableParagraphStyle *style1 = [[NSMutableParagraphStyle alloc]init];
       [style1 setLineSpacing:2.0];
       [style1 setLineBreakMode:NSLineBreakByWordWrapping];
       [str1 addAttribute:NSParagraphStyleAttributeName value:style1 range:NSMakeRange(0, str1.length)];
       [self.alert setValue:str1 forKey:@"attributedTitle"];
       
       [self.alert.actions.firstObject setValue:[UIColor redColor] forKey:@"titleTextColor"]; //修改第一个action按钮颜色
         [self.alert.actions.lastObject setValue:[UIColor orangeColor] forKey:@"titleTextColor"]; //修改最后一个action按钮颜色
         [[self.alert.actions objectAtIndex:1] setValue:[UIColor cyanColor] forKey:@"titleTextColor"];//修改第二个action按钮颜色
       
    }
    
     - (UIAlertController *)alert
    {
        if (!_alert) {
            _alert = [UIAlertController alertControllerWithTitle:@"runtime标题" message:@"runtime留言" preferredStyle:UIAlertControllerStyleAlert];
            [_alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
                textField.placeholder = @"runtime输入框";
            }];
            [_alert addAction:[UIAlertAction actionWithTitle:@"runtime动作1" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
                NSLog(@"点击了按钮1");
            }]];
            [_alert addAction:[UIAlertAction actionWithTitle:@"runtime动作2" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
                NSLog(@"点击了按钮2");
            }]];
            [_alert addAction:[UIAlertAction actionWithTitle:@"runtime动作3" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
                NSLog(@"点击了按钮3");
            }]];
        }
        return _alert;
    }
    
    样子很丑........
    UIAlertController.png

    结束

    runtime runtime还有很多难点,接下来准备好好看看YYModel源码,估计也很多看不懂,加油勉励。

    相关文章

      网友评论

          本文标题:理解使用runtime

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