zoukankan      html  css  js  c++  java
  • runtime 如何实现 weak 属性

    出题者简介: 孙源(sunnyxx),目前就职于百度

    整理者简介:陈奕龙(子循),目前就职于滴滴出行。

    转载者:豆电雨(starain)微信:doudianyu

    要实现 weak 属性,首先要搞清楚 weak 属性的特点:

    weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。

    那么 runtime 如何实现 weak 变量的自动置nil?

    runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

    (注:在下文的《使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?》里给出的“对象的内存销毁时间表”也提到__weak引用的解除时间。)

    我们可以设计一个函数(伪代码)来表示上述机制:

    objc_storeWeak(&a, b)函数:

    objc_storeWeak函数把第二个参数--赋值对象(b)的内存地址作为键值key,将第一个参数--weak修饰的属性变量(a)的内存地址(&a)作为value,注册到 weak 表中。如果第二个参数(b)为0(nil),那么把变量(a)的内存地址(&a)从weak表中删除,

    你可以把objc_storeWeak(&a, b)理解为:objc_storeWeak(value, key),并且当key变nil,将value置nil。

    在b非nil时,a和b指向同一个内存地址,在b变nil时,a变nil。此时向a发送消息不会崩溃:在Objective-C中向nil发送消息是安全的。

    而如果a是由 assign 修饰的,则: 在 b 非 nil 时,a 和 b 指向同一个内存地址,在 b 变 nil 时,a 还是指向该内存地址,变野指针。此时向 a 发送消息极易崩溃。

    下面我们将基于objc_storeWeak(&a, b)函数,使用伪代码模拟“runtime如何实现weak属性”:

    // 使用伪代码模拟:runtime如何实现weak属性
    // http://weibo.com/luohanchenyilong/
    // https://github.com/ChenYilong
    
     id obj1;
     objc_initWeak(&obj1, obj);
    /*obj引用计数变为0,变量作用域结束*/
     objc_destroyWeak(&obj1);

    下面对用到的两个方法objc_initWeakobjc_destroyWeak做下解释:

    总体说来,作用是: 通过objc_initWeak函数初始化“附有weak修饰符的变量(obj1)”,在变量作用域结束时通过objc_destoryWeak函数释放该变量(obj1)。

    下面分别介绍下方法的内部实现:

    objc_initWeak函数的实现是这样的:在将“附有weak修饰符的变量(obj1)”初始化为0(nil)后,会将“赋值对象”(obj)作为参数,调用objc_storeWeak函数。

    obj1 = 0;
    obj_storeWeak(&obj1, obj);

    也就是说:

    weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)

    然后obj_destroyWeak函数将0(nil)作为参数,调用objc_storeWeak函数。

    objc_storeWeak(&obj1, 0);

    前面的源代码与下列源代码相同。

    // 使用伪代码模拟:runtime如何实现weak属性
    // http://weibo.com/luohanchenyilong/
    // https://github.com/ChenYilong
    
    id obj1;
    obj1 = 0;
    objc_storeWeak(&obj1, obj);
    /* ... obj的引用计数变为0,被置nil ... */
    objc_storeWeak(&obj1, 0);

    objc_storeWeak 函数把第二个参数--赋值对象(obj)的内存地址作为键值,将第一个参数--weak修饰的属性变量(obj1)的内存地址注册到 weak 表中。如果第二个参数(obj)为0(nil),那么把变量(obj1)的地址从 weak 表中删除,在后面的相关一题会详解。

    使用伪代码是为了方便理解,下面我们“真枪实弹”地实现下:

    如何让不使用weak修饰的@property,拥有weak的效果。

    我们从setter方法入手:

    - (void)setObject:(NSObject *)object
    {
        objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
        [object cyl_runAtDealloc:^{
            _object = nil;
        }];
    }

    也就是有两个步骤:

    1. 在setter方法中做如下设置:

            objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
    2. 在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。做到这点,同样要借助 runtime:

      //要销毁的目标对象
      id objectToBeDeallocated;
      //可以理解为一个“事件”:当上面的目标对象销毁时,同时要发生的“事件”。
      id objectWeWantToBeReleasedWhenThatHappens;
      objc_setAssociatedObject(objectToBeDeallocted,
                           someUniqueKey,
                           objectWeWantToBeReleasedWhenThatHappens,
                           OBJC_ASSOCIATION_RETAIN);

    知道了思路,我们就开始实现 cyl_runAtDealloc 方法,实现过程分两部分:

    第一部分:创建一个类,可以理解为一个“事件”:当目标对象销毁时,同时要发生的“事件”。借助 block 执行“事件”。

    // .h文件

    // .h文件
    // http://weibo.com/luohanchenyilong/
    // https://github.com/ChenYilong
    // 这个类,可以理解为一个“事件”:当目标对象销毁时,同时要发生的“事件”。借助block执行“事件”。
    
    typedef void (^voidBlock)(void);
    
    @interface CYLBlockExecutor : NSObject
    
    - (id)initWithBlock:(voidBlock)block;
    
    @end

    // .m文件

    // .m文件
    // http://weibo.com/luohanchenyilong/
    // https://github.com/ChenYilong
    // 这个类,可以理解为一个“事件”:当目标对象销毁时,同时要发生的“事件”。借助block执行“事件”。
    
    #import "CYLBlockExecutor.h"
    
    @interface CYLBlockExecutor() {
        voidBlock _block;
    }
    @implementation CYLBlockExecutor
    
    - (id)initWithBlock:(voidBlock)aBlock
    {
        self = [super init];
    
        if (self) {
            _block = [aBlock copy];
        }
    
        return self;
    }
    
    - (void)dealloc
    {
        _block ? _block() : nil;
    }
    
    @end

    第二部分:核心代码:利用runtime实现cyl_runAtDealloc方法

    // CYLNSObject+RunAtDealloc.h文件
    // http://weibo.com/luohanchenyilong/
    // https://github.com/ChenYilong
    // 利用runtime实现cyl_runAtDealloc方法
    
    #import "CYLBlockExecutor.h"
    
    const void *runAtDeallocBlockKey = &runAtDeallocBlockKey;
    
    @interface NSObject (CYLRunAtDealloc)
    
    - (void)cyl_runAtDealloc:(voidBlock)block;
    
    @end
    
    
    // CYLNSObject+RunAtDealloc.m文件
    // http://weibo.com/luohanchenyilong/
    // https://github.com/ChenYilong
    // 利用runtime实现cyl_runAtDealloc方法
    
    #import "CYLNSObject+RunAtDealloc.h"
    #import "CYLBlockExecutor.h"
    
    @implementation NSObject (CYLRunAtDealloc)
    
    - (void)cyl_runAtDealloc:(voidBlock)block
    {
        if (block) {
            CYLBlockExecutor *executor = [[CYLBlockExecutor alloc] initWithBlock:block];
    
            objc_setAssociatedObject(self,
                                     runAtDeallocBlockKey,
                                     executor,
                                     OBJC_ASSOCIATION_RETAIN);
        }
    }
    
    @end

    使用方法: 导入

        #import "CYLNSObject+RunAtDealloc.h"

    然后就可以使用了:

    NSObject *foo = [[NSObject alloc] init];
    
    [foo cyl_runAtDealloc:^{
        NSLog(@"正在释放foo!");
    }];

    如果对 cyl_runAtDealloc 的实现原理有兴趣,可以看下这篇博文 Fun With the Objective-C Runtime: Run Code at Deallocation of Any Object

     

  • 相关阅读:
    cloud-api-service和cloud-iopm-web提交merge方法
    Java知识点-判断null、空字符串和空格
    Windows本机搭建Redis
    api-gateway-engine知识点(2)
    能够提高开发效率的Eclipse实用操作
    IOP知识点(2)
    获取分辨率及dp/px换算
    Android软件自动更新(自定义处理,不使用第三方)
    友盟自动更新参数详解
    [Android]ping -c 1 -w 100 sina.cn的解析
  • 原文地址:https://www.cnblogs.com/starainDou/p/5253421.html
Copyright © 2011-2022 走看看