美文网首页
runtime常见问题

runtime常见问题

作者: 树根曰 | 来源:发表于2016-12-22 11:14 被阅读0次

    1 . iOS runtime 运行时,动态添加属性方法
    首先, 要明白为什么要动态给类添加方法?

    如果一个类方法很多,加载类到内存的时候也比较耗费资源,需要给每个方法生成一个映射表,可以使用动态给某个类添加方法。也就是说:"不去实现类的方法,采用动态的方式添加,通过不提前加入内存的方式来减少消耗"

        ivar表示成员变量
        class_addIvar
        class_addMethod
        class_addProperty
        class_addProtocol
        class_replaceProperty
    

    如果我们需要动态的加载一个属性,我们就要使用这个set方法首先我们分析一下,OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    参数含义
    • id object : 源对象 -指定我们需要绑定的对象,e.g ,给一个字符串添加一个内容
    • const void * key : 设置一个静态常亮,也就是Key 值,通过这个我们可以找到我们关联对象的那个数据值
    • id value 这个是我们打点调用属性的时候会自动调用set方法进行传值
    • objc_AssociationPolicy policy : 这个是关联策略,这几个管理策略,我们看下都有什么;

    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
        
        // 第一个关联策略是基于基本类型的,也就是我们常用的assign 属性
        OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
        //关联策略是基于对象类型的,如我们正常的是retain nonatomic (非原子操作)类型 ,retain  : 保留一个引用指针,内存地址不修改,指向同一块内存地址
        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
                                                *   The association is not made atomically. */
        //这个相当于属性中对一些对象或者字符串进行的copy 这个是拷贝一个新对象,内存地址不在一起,还是使用的非原子类型,非原子类型我们也称之为线程不安全的操作,但是对于OC里面的数据操作,我们尽量避开原子操作,原子操作是线程安全的,会影响我们对数据的写入操作
        OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied.
                                                *   The association is not made atomically. */
        // 简单的指针引用, retain 操作
        OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                                *   The association is made atomically. */
        //把简单的对象拷贝到一个新的内存地址
        OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                                *   The association is made atomically. */
    };
    

    以上就是我们动态添加属性索要用到的一些方法,简单理解就是
    : set(源对象,常亮key,数值,属性关联策略)

    下面这个想必都可以看明白,类似我们的getter 方法

    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);```
    
    同样 传入的参数有两个:
    
        id object :当前的属性是关联在那个对象上的,和set 保持一致
        const void * key : 静态关键key 要获取的值就是根据关联对象可以关键key 获取对应的数据
    
    使用方法我们介绍完毕了,下面直接代码:
    UIControl+RYButton.h 
    

    import <UIKit/UIKit.h>

    @interface UIControl (RYButton)
    // 声明一个时间间隔
    @property (assign,nonatomic)NSTimeInterval ry_time;

    @end

    UIControl+RYButton.m 
    

    import "UIControl+RYButton.h"

    import <objc/runtime.h>

    static const char * RY_CLICKKEY = "ry_clickkey";
    @implementation UIControl (RYButton)

    • (void)setRy_time:(NSTimeInterval)ry_time{
      objc_setAssociatedObject(self, RY_CLICKKEY, @(ry_time), OBJC_ASSOCIATION_ASSIGN);

    }

    • (NSTimeInterval)ry_time{
      return [objc_getAssociatedObject(self, RY_CLICKKEY) doubleValue];

    }
    @end

    使用方法
    
    • (void)viewDidLoad {
      [super viewDidLoad];
      // Do any additional setup after loading the view, typically from a nib.
      ThirdPartService * service = [ThirdPartService new];
      NSLog(@"%@ ---%@",NSStringFromSelector(_cmd),service);

      UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
      button.ry_time = 1.0f;

    }

    2 . runtime 如何实现 weak 属性
    
    >首先要搞清楚weak属性的特点
    weak策略表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似;然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)
    
    >那么runtime如何实现weak变量的自动置nil?
    
    >runtime对注册的类,会进行布局,会将 weak 对象放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法,假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为 nil。
    
    >weak属性需要在dealloc中置nil么
    在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理
    即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil
    在属性所指的对象遭到摧毁时,属性值也会清空
    
    >```objc// 模拟下weak的setter方法,大致如下- (void)setObject:(NSObject *)object{ objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN); [object cyl_runAtDealloc:^{ _object = nil; }];}```
    
    3 . runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
    >每一个类对象中都一个对象方法列表(对象方法缓存)
    类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)
    方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
    当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找
    当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找。
    
    4 . 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
    
    >无论在MRC下还是ARC下均不需要被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放**补充:对象的内存销毁时间表,分四个步骤**
    
    

    1、调用 -release :引用计数变为零

    • 对象正在被销毁,生命周期即将结束.
    • 不能再有新的 __weak 弱引用,否则将指向 nil.
    • 调用 [self dealloc]

    2、 父类调用 -dealloc

    • 继承关系中最直接继承的父类再调用 -dealloc
    • 如果是 MRC 代码 则会手动释放实例变量们(iVars)
    • 继承关系中每一层的父类 都再调用 -dealloc

    3、NSObject 调 -dealloc

    • 只做一件事:调用 Objective-C runtime 中object_dispose() 方法
    1. 调用 object_dispose()
    • 为 C++ 的实例变量们(iVars)调用 destructors
    • 为 ARC 状态下的 实例变量们(iVars) 调用 -release
    • 解除所有使用 runtime Associate方法关联的对象
    • 解除所有 __weak 引用
    • 调用 free()
    5 . _objc_msgForward函数是做什么的?直接调用它将会发生什么?
    >_objc_msgForward是IMP类型,用于消息转发的:
    当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发直接调用_objc_msgForward是非常危险的事,
    这是把双刃刀,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事
    [JSPatch](https://github.com/bang590/JSPatch)就是直接调用_objc_msgForward来实现其核心功能的
    [详细解说](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md)参见这里的第一个问题解答文
    
    6 . 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
    > · 不能向编译后得到的类中增加实例变量;
    · 能向运行时创建的类中添加实例变量;
    分析如下:
    >>1. 因为编译后的类已经注册在runtime中,类结构体中的objc_ivar_list 实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayout 或 class_setWeakIvarLayout来处理strong weak引用,所以不能向存在的类中添加实例变量
    >>2. 运行时创建的类是可以添加实例变量,调用 class_addIvar函数,但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。
    
    7 . 简述下Objective-C中调用方法的过程(runtime)
    >Objective-C是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector),整个过程介绍如下:
    
    >>1. objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类
    2. 然后在该类中的方法列表以及其父类方法列表中寻找方法运行
    3. 如果,在最顶层的父类(一般也就NSObject)中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX
    4. 但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会,这三次拯救程序奔溃的说明见问题[《什么时候会报unrecognized selector的异常》](http://www.jianshu.com/p/0ad2f1a9386d)中的说明
    
    >补充说明:Runtime 铸就了Objective-C 是动态语言的特性,使得C语言具备了面向对象的特性,在程序运行期创建,检查,修改类、对象及其对应的方法,这些操作都可以使用runtime中的对应方法实现。
    
    8 . 什么是method swizzling(俗称黑魔法)
    >1. 简单说就是进行方法交换
    2. 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的
    3. 每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP 
    ![](https://img.haomeiwen.com/i2790607/5e35e961f6e6521f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    4. 交换方法的几种实现方式
       a. 利用 method_exchangeImplementations 交换两个方法的实现
        b .利用 class_replaceMethod 替换方法的实现
        c. 利用 method_setImplementation 来直接设置某个方法的IMP 
    ![](https://img.haomeiwen.com/i2790607/799f646c89ed1d2c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    9. 消息机制中, 对象如何找到对应的方法去调用
    
    >// 方法保存到什么地方?对象方法保存到类中,类方法保存到元类(meta class),每一个类都有方法列表methodList
    //明确去哪个类中调用,通过isa指针
        1.根据对象的isa去对应的类查找方法,isa:判断去哪个类查找对应的方法 指向方法调用的类
        2.根据传入的方法编号SEL,里面有个哈希列表,在列表中找到对应方法Method(方法名)
        3.根据方法名(函数入口)找到函数实现,函数实现在方法区
    
    **注: 该博文并非原创, 但原文链接不可查, 若原文作者看到, 望留下原文链接, 谢谢. **

    相关文章

      网友评论

          本文标题:runtime常见问题

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