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