要實(shí)現(xiàn)weak屬性,首先要搞清楚weak屬性的特點(diǎn):
weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)榜掌。為這種屬性設(shè)置新值時(shí)果正,設(shè)置方法既不保留新值影兽,也不釋放舊值炫乓。此特質(zhì)同assign類(lèi)似膏燕, 然而在屬性所指的對(duì)象遭到摧毀時(shí)鸠儿,屬性值也會(huì)清空(nil out)屹蚊。
那么runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil?
runtime 對(duì)注冊(cè)的類(lèi)进每, 會(huì)進(jìn)行布局汹粤,對(duì)于 weak 對(duì)象會(huì)放入一個(gè) hash 表中。 用 weak 指向的對(duì)象內(nèi)存地址作為 key田晚,當(dāng)此對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì) dealloc嘱兼,假如 weak 指向的對(duì)象內(nèi)存地址是a,那么就會(huì)以a為鍵贤徒, 在這個(gè) weak 表中搜索芹壕,找到所有以a為鍵的 weak 對(duì)象,從而設(shè)置為 nil接奈。
我們可以設(shè)計(jì)一個(gè)函數(shù)(偽代碼)來(lái)表示上述機(jī)制:
objc_storeWeak(&a, b)函數(shù):
objc_storeWeak函數(shù)把第二個(gè)參數(shù)--賦值對(duì)象(b)的內(nèi)存地址作為鍵值key踢涌,將第一個(gè)參數(shù)--weak修飾的屬性變量(a)的內(nèi)存地址(&a)作為value,注冊(cè)到 weak 表中序宦。如果第二個(gè)參數(shù)(b)為0(nil)睁壁,那么把變量(a)的內(nèi)存地址(&a)從weak表中刪除,
你可以把objc_storeWeak(&a, b)理解為:objc_storeWeak(value, key),并且當(dāng)key變nil潘明,將value置nil行剂。
在b非nil時(shí),a和b指向同一個(gè)內(nèi)存地址钳降,在b變nil時(shí)厚宰,a變nil。此時(shí)向a發(fā)送消息不會(huì)崩潰:在Objective-C中向nil發(fā)送消息是安全的牲阁。
而如果a是由assign修飾的固阁,則: 在b非nil時(shí),a和b指向同一個(gè)內(nèi)存地址城菊,在b變nil時(shí)备燃,a還是指向該內(nèi)存地址,變野指針凌唬。此時(shí)向a發(fā)送消息極易崩潰并齐。
下面我們將基于objc_storeWeak(&a, b)函數(shù),使用偽代碼模擬“runtime如何實(shí)現(xiàn)weak屬性”:
id obj1;
objc_initWeak(&obj1,?obj);
/*obj引用計(jì)數(shù)變?yōu)?客税,變量作用域結(jié)束*/
objc_destroyWeak(&obj1);
下面對(duì)用到的兩個(gè)方法objc_initWeak和objc_destroyWeak做下解釋?zhuān)?br>
總體說(shuō)來(lái)况褪,作用是: 通過(guò)objc_initWeak函數(shù)初始化“附有weak修飾符的變量(obj1)”,在變量作用域結(jié)束時(shí)通過(guò)objc_destoryWeak函數(shù)釋放該變量(obj1)更耻。
下面分別介紹下方法的內(nèi)部實(shí)現(xiàn):
objc_initWeak函數(shù)的實(shí)現(xiàn)是這樣的:在將“附有weak修飾符的變量(obj1)”初始化為0(nil)后测垛,會(huì)將“賦值對(duì)象”(obj)作為參數(shù),調(diào)用objc_storeWeak函數(shù)
obj1 = 0秧均;
obj_storeWeak(&obj1,?obj);
也就是說(shuō):
weak 修飾的指針默認(rèn)值是 nil (在Objective-C中向nil發(fā)送消息是安全的)
然后obj_destroyWeak函數(shù)將0(nil)作為參數(shù)食侮,調(diào)用objc_storeWeak函數(shù)。
objc_storeWeak(&obj1, 0);
前面的源代碼與下列源代碼相同目胡。
id obj1;
obj1?=?0;
objc_storeWeak(&obj1,?obj);
/*?...?obj的引用計(jì)數(shù)變?yōu)?锯七,被置nil?...?*/
objc_storeWeak(&obj1,?0);
objc_storeWeak函數(shù)把第二個(gè)參數(shù)--賦值對(duì)象(obj)的內(nèi)存地址作為鍵值,將第一個(gè)參數(shù)--weak修飾的屬性變量(obj1)的內(nèi)存地址注冊(cè)到 weak 表中誉己。如果第二個(gè)參數(shù)(obj)為0(nil)眉尸,那么把變量(obj1)的地址從weak表中刪除
使用偽代碼是為了方便理解,下面我們“真槍實(shí)彈”地實(shí)現(xiàn)下:
如何讓不使用weak修飾的@property巨双,擁有weak的效果噪猾。
我們從setter方法入手:
- (void)setObject:(NSObject *)object
{
objc_setAssociatedObject(self,"object",?object,?OBJC_ASSOCIATION_ASSIGN);
[object?cyl_runAtDealloc:^{
_object?=?nil;
}];
}
也就是有兩個(gè)步驟:
1)在setter方法中做如下設(shè)置:
objc_setAssociatedObject(self,"object",?object,?OBJC_ASSOCIATION_ASSIGN);
2)在屬性所指的對(duì)象遭到摧毀時(shí),屬性值也會(huì)清空(nil out)筑累。做到這點(diǎn)袱蜡,同樣要借助runtime:
//要銷(xiāo)毀的目標(biāo)對(duì)象
id?objectToBeDeallocated;
//可以理解為一個(gè)“事件”:當(dāng)上面的目標(biāo)對(duì)象銷(xiāo)毀時(shí)拥知,同時(shí)要發(fā)生的“事件”赚瘦。
id?objectWeWantToBeReleasedWhenThatHappens;
objc_setAssociatedObject(objectToBeDeallocted,
someUniqueKey,
objectWeWantToBeReleasedWhenThatHappens,
OBJC_ASSOCIATION_RETAIN);
知道了思路泪幌,我們就開(kāi)始實(shí)現(xiàn)cyl_runAtDealloc方法,實(shí)現(xiàn)過(guò)程分兩部分:
第一部分:創(chuàng)建一個(gè)類(lèi)异吻,可以理解為一個(gè)“事件”:當(dāng)目標(biāo)對(duì)象銷(xiāo)毀時(shí)极颓,同時(shí)要發(fā)生的“事件”衅谷。借助block執(zhí)行“事件”慕爬。
//?.h文件
//?這個(gè)類(lèi),可以理解為一個(gè)“事件”:當(dāng)目標(biāo)對(duì)象銷(xiāo)毀時(shí)茵典,同時(shí)要發(fā)生的“事件”湘换。借助block執(zhí)行“事件”。
typedef?void?(^voidBlock)(void);
@interface?CYLBlockExecutor?:?NSObject
-?(id)initWithBlock:(voidBlock)block;
@end
//?.m文件
//?這個(gè)類(lèi)统阿,可以理解為一個(gè)“事件”:當(dāng)目標(biāo)對(duì)象銷(xiāo)毀時(shí)彩倚,同時(shí)要發(fā)生的“事件”。借助block執(zhí)行“事件”扶平。
#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實(shí)現(xiàn)cyl_runAtDealloc方法
//?CYLNSObject+RunAtDealloc.h文件
//?利用runtime實(shí)現(xiàn)cyl_runAtDealloc方法
#import?"CYLBlockExecutor.h"
const?void?*runAtDeallocBlockKey?=?&runAtDeallocBlockKey;
@interface?NSObject?(CYLRunAtDealloc)
-?(void)cyl_runAtDealloc:(voidBlock)block;
@end
//?CYLNSObject+RunAtDealloc.m文件
//?利用runtime實(shí)現(xiàn)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
使用方法: 導(dǎo)入
#import?"CYLNSObject+RunAtDealloc.h"
然后就可以使用了:
NSObject?*foo?=?[[NSObject?alloc]?init];
[foo?cyl_runAtDealloc:^{
NSLog(@"正在釋放foo!");
}];