runtime 如何實現 weak 屬性

要實現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!");

}];

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容