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_initWeak
和objc_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;
}];
}
也就是有两个步骤:
在setter方法中做如下设置:
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
在属性所指的对象遭到摧毁时,属性值也会清空(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
runtime 如何实现 weak 属性的更多相关文章
- runtime如何实现weak属性
首先了解weak是一种非拥有关系,属性所值对象销毁时,属性值会清空(nil). Runtime对注册的类会进行布局,对于weak对象会放入hash表中,用weak指向的内存地址作为key,当对象引用计 ...
- weak属性需要在dealloc中置nil么?
出题者简介: 孙源(sunnyxx),目前就职于百度 整理者简介:陈奕龙(子循),目前就职于滴滴出行. 转载者:豆电雨(starain)微信:doudianyu 不需要. 在ARC环境无论是强指针还是 ...
- OC 中的 weak 属性是怎么实现的?
OC 中的 weak 属性是怎么实现的,为什么在对象释放后会自动变成 nil?本文对这个问题进行了一点探讨.环境 mac OS Sierra 10.12.4 objc709参考答案 搜索后发现runt ...
- Runtime获取类的属性列表和方法列表
Runtime获取类的属性列表和方法列表 Runtime很强大,他使得OC中没有真正意义上的私有属性和私有方法,我们可以利用OC的运行时拿到一个类的任何方法和任何属性,然后动态的去调用方法,objc_ ...
- runtime实现weak属性
我们可以自己创建一个 A 类,然后在“宿主对象”和“值对象”建立 weak 关系的时候,偷偷地创建一个 A 类的实例 a,绑定在 “值对象” 上. 当“值对象”销毁后,这个 a 也会被销毁.而 A 类 ...
- Runtime 实现 动态添加属性
利用动态加载为对象添加一个 block 点击属性; .h 文件 #import <UIKit/UIKit.h> @interface UIView (Tap) /** * 动态添加手势 * ...
- RunTime 给类添加属性
RunTime网上有很多人都不知道Runtime到底是干嘛的?有很多博主都是长篇大论给他们讲这个讲那个,我感觉还不如实例来的实在.很简单的一个例子:我们都知道会有这样的需求,未读消息列表的图片上要有一 ...
- Runtime之成员变量&属性&关联对象
上篇介绍了Runtime类和对象的相关知识点,在4.5和4.6小节,也介绍了成员变量和属性的一些方法应用.本篇将讨论实现细节的相关内容. 在讨论之前,我们先来介绍一个很冷僻但又很有用的一个关键字:@e ...
- delegate 为什么用 weak属性
weak指针主要用于“父-子”关系,父亲拥有一个儿子的strong指针,因此是儿子的所有者:但是为了阻止所有权回环,儿子需要使用weak指针指向父亲:你的viewcontroller通过strong指 ...
随机推荐
- .NET中的三种Timer的区别和用法(转)
最近正好做一个WEB中定期执行的程序,而.NET中有3个不同的定时器.所以正好研究研究.这3个定时器分别是: //1.实现按用户定义的时间间隔引发事件的计时器.此计时器最宜用于 Windows 窗 ...
- Web性能压力测试之Webbench使用详解
Webbench是知名的网站压力测试工具,它是由Lionbridge公司(http://www.lionbridge.com)开发.Webbench能测试处在相同硬件上,不同服务的性能以及不同硬件上同 ...
- 百度ios 开发面试题
百度移动云可穿戴部门的面试经历,面试官都非常热情友好,一上来到弄的我挺不好意思的.下面记录一下自己的面试过程,因为我真的没啥面试经验,需要总结下. 1面 Objective C runtime lib ...
- c#中设置按钮Button为透明
方法一:代码实现 /// <summary> /// 设置透明按钮样式 /// </summary> private void SetBtnStyle(Button btn) ...
- How to handle the DbEntityValidationException in C#
When I want to use db.SaveChanges(), if some of the columns got validation error and throw DbEntityV ...
- Java如何连接到MySQL数据库的
下载:mysql-connector-java-5.1.38.tar.gz http://dev.mysql.com/downloads/connector/j/ tar zxvf mysql-con ...
- .animate动画
.animate(params, [duration], [easing], [callback]) params: 结果样式属性 duration: 动画时长 也可以用 slow normal fa ...
- Ajax基础--JavaScript实现
ajax原理 1.ajax 即“Asynchronous JavaScript and XML”(异步 JavaScript 和 XML),也就是无刷新数据读取. 通俗地讲就是:AJAX 通过在后台与 ...
- Magento 编译 php5.6.21 命令
./configure '--prefix=/alidata/server/php' '--enable-opcache' '--with-config-file-path=/alidata/ser ...
- Ruby自学笔记(二)— Ruby的一些基础知识
Ruby安装好之后,我们就可以来实践Ruby语言了. 以下是一些学习到的简单基础知识: 1. 如何执行Ruby文件? 我们编写的Ruby文件是以rb为后缀名的,例如:XXX.rb.当要执行ruby文件 ...