博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
runtime 如何实现 weak 属性
阅读量:6656 次
发布时间:2019-06-25

本文共 5668 字,大约阅读时间需要 18 分钟。

hot3.png

要实现 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引用的解除时间。)

先看下 runtime 里源码的实现:

/*** The internal structure stored in the weak references table. * It maintains and stores* a hash set of weak references pointing to an object.* If out_of_line==0, the set is instead a small inline array.*/#define WEAK_INLINE_COUNT 4struct weak_entry_t {   DisguisedPtr
referent; union { struct { weak_referrer_t *referrers; uintptr_t out_of_line : 1; uintptr_t num_refs : PTR_MINUS_1; uintptr_t mask; uintptr_t max_hash_displacement; }; struct { // out_of_line=0 is LSB of one of these (don't care which) weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; }; };};/*** The global weak references table. Stores object ids as keys,* and weak_entry_t structs as their values.*/struct weak_table_t { weak_entry_t *weak_entries; size_t num_entries; uintptr_t mask; uintptr_t max_hash_displacement;};

具体完整实现参照 。

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

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/ChenYilongid 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修饰的,拥有weak的效果。

我们从setter方法入手:

(注意以下的 cyl_runAtDealloc 方法实现仅仅用于模拟原理,如果想用于项目中,还需要考虑更复杂的场景,想在实际项目使用的话,可以使用我写的一个小库,可以使用 CocoaPods 在项目中使用: )

- (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);
  1. 在属性所指的对象遭到摧毁时,属性值也会清空(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 的实现原理有兴趣,可以看下我写的一个小库,可以使用 CocoaPods 在项目中使用:

参考博文:

转载于:https://my.oschina.net/jlongtian/blog/862154

你可能感兴趣的文章
chrome checkbox 偶尔不显示问题
查看>>
spring中 asm 和 jdk版本不兼容的问题
查看>>
homework-05
查看>>
dede数据库文件导入失败的可能原因是数据表前缀不同,这里的失败指的是mysql添加了数据,但后台不显示...
查看>>
bzoj3140: [Hnoi2013]消毒(二分图)
查看>>
VMware Workstation中安装linux系统(CentOS)超详细
查看>>
抓https包
查看>>
U-Boot在FL2440上移植(三)----支持NAND Flash
查看>>
防错笔记
查看>>
51nod1355 斐波那契的最小公倍数
查看>>
谷歌面试题求解.
查看>>
20135337——信息安全设计基础第八周学习笔记
查看>>
WordPress教程
查看>>
drf 多表
查看>>
损失函数
查看>>
对spring的理解是什么?
查看>>
Linux -- Ubuntu搭建java开发环境
查看>>
foreach和map
查看>>
angularjs封装bootstrap官网的时间插件datetimepicker
查看>>
java简单实现搜索指定后缀文件
查看>>