runtime

runtime 如何实现 weak 属性

2016-10-17  本文已影响113人  SessionSinGod

要实现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。

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

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属性”:

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);

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

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文件

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

typedef void (^voidBlock)(void);

@interface CYLBlockExecutor : NSObject

- (id)initWithBlock:(voidBlock)block;

@end

// .m文件

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

#import "CYLBlockExecutor.h"

@interface CYLBlockExecutor() {

voidBlock _block;

}

@implementation CYLBlockExecutor

- (id)initWithBlock:(voidBlock)aBlock

{

self = [super init];

if(self) {

_block = [aBlock copy];

}

returnself;

}

- (void)dealloc

{

_block ? _block() : nil;

}

@end

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

// CYLNSObject+RunAtDealloc.h文件

// 利用runtime实现cyl_runAtDealloc方法

#import "CYLBlockExecutor.h"

const void *runAtDeallocBlockKey = &runAtDeallocBlockKey;

@interface NSObject (CYLRunAtDealloc)

- (void)cyl_runAtDealloc:(voidBlock)block;

@end

// CYLNSObject+RunAtDealloc.m文件

// 利用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!");

}];

上一篇下一篇

猜你喜欢

热点阅读