1. 程式人生 > >runtime學習之- 關聯(association),在分類中新增屬性!

runtime學習之- 關聯(association),在分類中新增屬性!

一提到runtime,很多人都會產生莫名的恐懼(比如我。。。)

但事實上,runtime有相當一部分內容很簡單、很好用,比如今天要講的關聯。

在<objc/runtime.h>中,有三個和它有關的方法(是的,一共就三個):

objc_setAssociatedObject

objc_getAssociatedObject

objc_removeAssociatedObjects

顧名思義,三個分別是設定關聯、獲取關聯、移除關聯。下面我們就分別來說一下。

1. objc_setAssociatedObject

來看一下函式原型:void objc_setAssociatedObject(

id object,constvoid *key,id value,objc_AssociationPolicy policy)

它沒有返回值,共有四個引數:object 是關聯的源物件,key 是關聯的關鍵字,value 是關聯的值,policy 是關聯策略。

需要注意的是,key 是一個 const 的常量,而且每一個關聯的 key 必須是唯一的。

關聯策略 objc_AssociationPolicy 是一個列舉型別,一共有五種:


意思很好懂,看名字就知道了,就不多說了。

2. objc_getAssociatedObject

來看一下函式原型:id objc_getAssociatedObject(

id object,constvoid *key)

返回型別為 id,返回的就是關聯的那個值。有兩個引數,和上面的一樣,object 是關聯的源物件,key 是關聯的關鍵字。

注意,這個 key 必須和剛才的 key 一樣才能獲取到正確的值。

3. objc_removeAssociatedObject

來看一下函式原型:void objc_removeAssociatedObjects(id object)

它沒有返回值,只有一個引數 object,是關聯的源物件。意思就是移除 object 的所有關聯。

注意,是移除所有關聯。這個函式的本意是使一個物件回到“原始狀態”,通常不會用這個方法,因為它會把你在別的地方加入的關聯也一起移除。那怎麼辦呢?

還記得怎麼設定關聯嗎?void objc_setAssociatedObject(id object, constvoid *key, id value, objc_AssociationPolicy policy)

第三個引數 value 傳 nil 就是移除關聯了,這隻會移除指定 key 的關聯而不是移除所有關聯。

下面我們舉個栗子,為了體現出關聯的強大,我們在MRC下實踐:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
static char overviewKey;
NSArray *array = [[NSArray alloc] initWithObjects:@"lala", nil];
NSString *overview = [[NSString alloc] initWithString:@"I'm associatedObject."];

// 1
objc_setAssociatedObject(array, &overviewKey, overview, OBJC_ASSOCIATION_COPY);
NSString *associatedObject = objc_getAssociatedObject(array, &overviewKey);
NSLog(@"1 - associatedObject: %@", associatedObject);

// 2
[overview release];
associatedObject = objc_getAssociatedObject(array, &overviewKey);
NSLog(@"2 - associatedObject: %@", associatedObject);

// 3
objc_setAssociatedObject(array, &overviewKey, nil, OBJC_ASSOCIATION_ASSIGN);
associatedObject = objc_getAssociatedObject(array, &overviewKey);
NSLog(@"3 - associatedObject: %@", associatedObject);

[array release];
[pool drain];

我們先定義一個 key。

然後建立一個 array,它就是關聯的源物件。

然後我們建立一個字串,它就是要關聯到源物件上的那個值。

然後我們分三步來說:

1. 設定關聯。輸出結果為 “1 - associatedObject: I'm associatedObject.”

這說明我們設定關聯成功了,我們通過overviewKey這個關鍵字就在array處取到了關聯物件。

2. release 掉 overview(關聯值)。輸出結果為 “2 - associatedObject: I'm associatedObject.” 

咦?!為什麼我們還能獲取到關聯物件呢?

這正是關聯的優點之一。它可以保證關聯物件在整個生命週期中都可用。即使關聯值(overview)被 release 掉了,我們還能使用關聯物件。
3. 移除關聯,設定關聯值為nil。輸出結果為 “3 - associatedObject: (null)“

此時關聯物件才為空。

那麼,什麼時候我們會用到關聯呢?

比如說,在類別中新增屬性。

是的,你沒有聽錯,就是在分類中新增屬性!!!

以前我們看書看視訊看部落格,都知道 “分類中只能新增方法,不能新增屬性”。但其實這麼說不準確。

不能新增屬性指的是,分類不會生成setter和getter方法,所以你新增的屬性不能通過self.xxx的方式呼叫。

事實上在分類中不能新增屬性是指編譯時不能新增屬性,而在執行時是可以動態新增的

我們再來舉個栗子

我們定義了一個NSObject的分類叫CategoryWithProperty,在這個分類中我們添加了一個屬性叫property。

正常情況下我們寫setter方法和getter方法應該是這樣的:

@interface NSObject (CategoryWithProperty)
@property (nonatomic, strong) NSObject *property;
@end

@implementation NSObject (CategoryWithProperty)
- (void)setProperty:(NSObject *)value {
    _property = value;
}
- (NSObject *)property {
    return _property;
}
@end
_property是與property屬性對應的成員變數,但是在分類中是沒有這個成員變數的。所以我們只能用關聯來解決它:
@interface NSObject (CategoryWithProperty)
@property (nonatomic, strong) NSObject *property;
@end

@implementation NSObject (CategoryWithProperty)
- (void)setProperty:(NSObject *)value {
    objc_setAssociatedObject(self, @selector(property), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSObject *)property {
    return objc_getAssociatedObject(self, @selector(property));
}
@end

兩段對照著來看會好理解一些。

setter方法無非就是把引數value的值賦給property,這不正好是objc_setAssociatedObject做的事嗎?

同理,getter方法無非就是獲取property的值,這不也正好是objc_getAssociatedObject做的事嗎?!

我們在setter方法中把value這個值通過一個key關聯到self這個源物件上,然後在getter方法中通過這個同樣的key在源物件self中獲取到這個值。一切都是如此的美妙。


也許你會對這個key有疑惑。(沒有疑惑才不正常好吧?)

是的,通常情況下key推薦使用static char型別。但是在setter和getter這裡有一個更簡單的做法,那就是直接使用選擇器(selector)。

因為SEL生成的時候就是一個唯一的常量,所以可以用_cmd作為objc_setAssociatedObject()的key。

我們來思考幾個問題:

1.關聯物件被儲存在什麼地方,是不是存放在被關聯物件本身的記憶體中?

答:關聯物件與被關聯物件本身的儲存並沒有直接的關係,它是儲存在單獨的雜湊表中的。

2.關聯物件的生命週期是怎樣的,什麼時候被釋放,什麼時候被移除?

答:關聯物件的釋放時機與移除時機並不總是一致。

關於這些問題,可以參考點選開啟連結