runtime
在iOS
的作用和地位在此就無需多費口舌了.接下來我以添加屬性為例, 用
runtime
給Foundation
下的NSString
類添加兩種屬性, 對象屬性和非對象屬性. 初步了解一下runtime
的基本使用方法.
兩個重要的 API
首先來介紹 runtime
中的兩個 api
:
1. objc_setAssociatedObject
蘋果官方給出的解釋就是:
Sets an associated value for a given object using a given key and association policy.
稍微翻譯一下(btw, 英文不好, 請勿見笑):
用一個關(guān)鍵字和一個關(guān)聯(lián)策略, 給一個已存在的對象設(shè)置一個關(guān)聯(lián)值.
它的聲明是:
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
結(jié)合上面的解釋, 對應(yīng)著聲明中的參數(shù), 很容易知道四個參數(shù)的含義了:
object
: 給哪個對象關(guān)聯(lián)值;
key
: 給 object 關(guān)聯(lián)值, 肯定就是要其他地方使用這個關(guān)聯(lián)值, 不然干嘛關(guān)聯(lián), 自然聯(lián)想到 key-value
的取值方法;
value
: 不用說, 自然是關(guān)聯(lián)什么值;
policy
: 這個需要深究一下
接下來深究一下 policy
的類型 objc_AssociationPolicy
:
查看一下官方文檔就知道, objc_AssociationPolicy
是 枚舉
類型:
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
去掉前綴
OBJC_ASSOCIATION_
剩下的assign
,retain
,copy
,nonatomic
是不是有種熟悉的味道, 不就是我們在定義屬性@property
的時候使用的關(guān)鍵詞么~ 這下對objc_setAssociatedObject
就完全了解了.
2. objc_getAssociatedObject
Returns the value associated with a given object for a given key.
很明顯, 這個上面的 objc_setAssociatedObject
是一對方法, 上面是 setter
方法, objc_getAssociatedObject
是getter
方法.
再看看它的聲明:
id objc_getAssociatedObject(id object, void *key)
知道了 setter
里面的參數(shù), getter
里面參數(shù)就不用解釋了.
要注意一點,
getter
返回的id
類型, 要和setter
的value
的id
類型一致. 說的好拗口, 就是那個意思, 你懂的.
兩個關(guān)鍵的 api
介紹掌握清楚了, 接下來就回到之前的主題了, 給 NNString
添加對象屬性和非對象屬性, 畢竟上面的介紹屬于理論層次, 每一個 api
只有在實際中運用自如了, 才能算得上真正的掌握了.
對象屬性的添加
1.給 NSString
創(chuàng)建一個 Category
:
這時你可能已經(jīng)有疑問了,
Category
不是只能添加方法, 不能添加屬性嗎? 明明說的就是添加屬性, 這里怎么還是添加Category
呢 ? 帶著你的疑問繼續(xù)往下看...
File >> New >> File.. >> [select iOS, others are OK too] >> [select Objective-C File] >> File:[enter CategoryName] >> File Type:[select "Category"] >> Class:[enter "NSString"] >> Next >> ...
2.在 .h
文件中聲明一對 setter
和 getter
方法:
注意命名規(guī)則
- (void)setStrFlag:(NSString *)strFlag;
- (NSString *)strFlag;
3.在 .m
文件中引入 runtime
頭文件:
畢竟使用
runtime
機制, 頭文件自然不可缺少
#import <objc/runtime.h>
4.在 .m
文件中實現(xiàn) .h
中的一對方法:
- (void)setStrFlag:(NSString *)strFlag {
// void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
objc_setAssociatedObject(self, const void *key, strFlag, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)strFlag {
// id objc_getAssociatedObject(id object, const void *key)
return objc_getAssociatedObject(self, const void *key);
}
現(xiàn)在除了參數(shù) key
沒填入, 其他參數(shù)都準(zhǔn)備好了.
熟悉 C
語言的同學(xué)一定對 const void *
類型一定不陌生吧!
沒錯, void *
就是 C
語言中令人頭疼的指針類型, 指向的是某個變量在內(nèi)存中的首地址, const
是修飾這個指針的內(nèi)容是常量, 不能修改.
當(dāng)然, 這里我們沒必要思考指針那些頭疼的問題了, 只需要知道 key
其實是個指針類型就 OK 了.
那我們就隨便定義一個變量, 然后把它的指針獲取到, 作為 key 值傳進去就 OK 了:
@implementation NSString (Ass)
NSString *strKey;
- (void)setStrFlag:(NSString *)strFlag {
// void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// SEL key = @selector(strFlag);
objc_setAssociatedObject(self, &strKey, strFlag, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)strFlag {
// id objc_getAssociatedObject(id object, const void *key)
return objc_getAssociatedObject(self, &strKey);
}
@end
發(fā)現(xiàn)編譯器并沒有任何 error
或 warning
, 這也表明指針那些頭疼的問題, 在這里我們的確不需要去考慮的.
不知道你剛才的疑云還在不在? 不管在不在, 現(xiàn)在回到 .h
文件:
@interface NSString (Ass)
- (void)setStrFlag:(NSString *)strFlag;
- (NSString *)strFlag;
@end
有沒有發(fā)現(xiàn), 這對 存取器
(accessor) 就是我們在定義屬性的時候, 編譯器自動給我們創(chuàng)建的兩個方法, 既然我們在 .m
文件中都已經(jīng)實現(xiàn)了這兩個方法, 那么在 .h
中是否可以用一個屬性來代替這兩個方法呢?
@interface NSString (Ass)
/**
額外增加的屬性
*/
@property (nonatomic, copy) NSString *strFlag;
// 對象屬性的set和get
//- (void)setStrFlag:(NSString *)strFlag;
//- (NSString *)strFlag;
@end
替換之后, 編譯器并沒有任何 error
或 warning
, 這說明剛才的猜想是正確的.
至此, 一個 NSString *
類型的屬性 strFlag
是不是就冠冕堂皇地加到 NSString
類里面去了.
這時, 你一定會迫不及待的去驗證這個方法是不是可行:
引入上面創(chuàng)建類別的頭文件:
#import "NSString+Ass.h"
創(chuàng)建一個 NSString
對象:
NSString *str = [NSString new];
嘗試 .
出剛才添加的屬性 strFlag
:
str.str
突然發(fā)現(xiàn):
這說明
strFlag
屬性的確添加進去了!
如果你還不信, 你可以繼續(xù)復(fù)制, 然后打印看看...
至此, 對象屬性已經(jīng)添加成功了, 接下來添加非對象屬性了
非對象屬性的添加
基本和對象屬性的添加差不多
.h
/**
額外增加的屬性2
*/
@property (assign) int intFlag;
//// 非對象屬性的set和get
//- (void)setIntFlag:(int)intFlag;
//- (int)intFlag;
.m
int intKey;
- (void)setIntFlag:(int)flag {
// void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
objc_setAssociatedObject(self, &intKey, @(flag), OBJC_ASSOCIATION_ASSIGN);
}
- (int)intFlag {
// id objc_getAssociatedObject(id object, const void *key)
NSNumber *t = objc_getAssociatedObject(self,&intKey);
return (int)[t integerValue];
}
注意:
objc_setAssociatedObject
函數(shù)的第三個參數(shù)接受的是id
類型, 而int
不是id
類型, 所有將它轉(zhuǎn)為NSNumber
類型后再傳入.
同理,objc_getAssociatedObject
返回的是NSNumber
類型, 轉(zhuǎn)為int
后再返回.
至此, 兩種類型的屬性已經(jīng)添加完成了. 其實在 .m
文件中添加的那兩個 key
還是可以優(yōu)化的:
那兩個
api
只需要接受void *
類型就行了, 而剛才在.m
文件中 那兩個key
值我分別傳入的是NSString **
和int *
類型.
學(xué)習(xí)過C
語言的同學(xué)都知道在C
語言中,void *
可以指向任何指針.
在iOS
中, 給一個按鈕添加點擊事件的時候,action
接受的就是一個SEL
類型, 查看文檔不難發(fā)現(xiàn), 系統(tǒng)對SEL
的定義是:
typedef struct objc_selector *SEL;
不難發(fā)現(xiàn),
SEL
其實也是一種指針類型, 那么是不是可以用SEL
類型的值作為key
呢?
馬上修改 .m
文件:
- (void)setStrFlag:(NSString *)flag {
// void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
SEL key = @selector(strFlag);
NSLog(@"%p", key);
objc_setAssociatedObject(self, key, flag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)strFlag {
// id objc_getAssociatedObject(id object, const void *key)
return objc_getAssociatedObject(self, _cmd);
}
打兩個斷點查看一下:
在
strFlag
方法中就沒必要在獲取自身的函數(shù)指針了,SEL key = @selector(strFlag);,_cmd
是就是一個指向方法自身的宏 .
在非屬性的方法里如法炮制的替換一下就可以了, 注意要用SEL key = @selector(intFlag);
, 不能再用 SEL key = @selector(strFlag);
了.
至此, 給已有類添加屬性的方法就完美實現(xiàn)了.
如果代碼中有什么 bug 或者需要改進的地方, 還望海涵, 同時歡迎在下方留言~
不要吝嗇您那寶貴的??&★就好, 您的支持是我分享的動力~??