問: Category可否添加成員變量?若是能夠,如何給Category添加成員變量?面試
答:不能直接添加成員變量,可是能夠經過runtime的方式間接實現添加成員變量的效果。bash
使用RunTime給系統的類添加屬性,首先須要瞭解對象與屬性的關係。咱們經過以前的學習知道,對象一開始初始化的時候其屬性爲nil,給屬性賦值其實就是讓屬性指向一塊存儲內容的內存,使這個對象的屬性跟這塊內存產生一種關聯。函數
那麼若是想動態的添加屬性,其實就是動態的產生某種關聯就行了。而想要給系統的類添加屬性,只能經過分類。post
這裏給NSObject添加name屬性,建立NSObject的分類 咱們可使用@property給分類添加屬性學習
@property(nonatomic,strong)NSString *name;
複製代碼
經過探尋Category的本質咱們知道,雖然在分類中能夠寫@property 添加屬性,可是不會自動生成私有屬性,也不會生成set,get方法的實現,只會生成set,get的聲明,須要咱們本身去實現。ui
static NSString *_name;
-(void)setName:(NSString *)name
{
_name = name;
}
-(NSString *)name
{
return _name;
}
複製代碼
可是這樣_name靜態全局變量與類並無關聯,不管對象建立與銷燬,只要程序在運行_name變量就存在,並非真正意義上的屬性。atom
RunTime提供了動態添加屬性和得到屬性的方法。spa
-(void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name
{
return objc_getAssociatedObject(self, @"name");
}
複製代碼
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
複製代碼
參數一:id object
: 給哪一個對象添加屬性,這裏要給本身添加屬性,用self。 參數二:void * == id key
: 屬性名,根據key獲取關聯對象的屬性的值,在**objc_getAssociatedObject
中經過次key得到屬性的值並返回。 參數三:id value
** : 關聯的值,也就是set方法傳入的值給屬性去保存。 參數四:objc_AssociationPolicy policy
: 策略,屬性以什麼形式保存。 有如下幾種3d
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, // 指定一個弱引用相關聯的對象
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相關對象的強引用,非原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相關的對象被複制,非原子性
OBJC_ASSOCIATION_RETAIN = 01401, // 指定相關對象的強引用,原子性
OBJC_ASSOCIATION_COPY = 01403 // 指定相關的對象被複制,原子性
};
複製代碼
key值只要是一個指針便可,咱們能夠傳入@selector(name)指針
objc_getAssociatedObject(id object, const void *key);
複製代碼
參數一:id object
: 獲取哪一個對象裏面的關聯的屬性。 參數二:void * == id key
: 什麼屬性,與**objc_setAssociatedObject
**中的key相對應,即經過key值取出value。
- (void)removeAssociatedObjects
{
// 移除全部關聯對象
objc_removeAssociatedObjects(self);
}
複製代碼
此時已經成功給NSObject添加name屬性,而且NSObject對象能夠經過點語法爲屬性賦值。
NSObject *objc = [[NSObject alloc]init];
objc.name = @"xx_cc";
NSLog(@"%@",objc.name);
複製代碼
能夠看出關聯對象的使用很是簡單,接下來咱們來探尋關聯對象的底層原理
實現關聯對象技術的核心對象有
對關聯對象技術的核心對象有了一個大概的意識,咱們經過源碼來探尋這些對象的存在形式以及其做用
來到runtime源碼,首先找到objc_setAssociatedObject函數,看一下其實現
咱們看到其實內部調用的是_object_set_associative_reference函數,咱們來到_object_set_associative_reference函數中
經過AssociationsManager內部源碼發現,AssociationsManager內部有一個AssociationsHashMap對象。
咱們來看一下AssociationsHashMap內部的源碼。
經過AssociationsHashMap內部源碼咱們發現AssociationsHashMap繼承自unordered_map首先來看一下unordered_map內的源碼
從unordered_map源碼中咱們能夠看出_Key
和_Tp
也就是前兩個參數對應着map中的Key和Value,那麼對照上面AssociationsHashMap內源碼發現_Key
中傳入的是disguised_ptr_t
,_Tp
中傳入的值則爲ObjectAssociationMap*
。
緊接着咱們來到ObjectAssociationMap
中,上圖中ObjectAssociationMap已經標記出,咱們發現ObjectAssociationMap中一樣以key、Value的方式存儲着ObjcAssociation。
接着咱們來到ObjcAssociation中
咱們發現ObjcAssociation存儲着_policy
、_value
,而這兩個值咱們能夠發現正是咱們調用objc_setAssociatedObject函數傳入的值,也就是說咱們在調用objc_setAssociatedObject函數中傳入的value和policy這兩個值最終是存儲在ObjcAssociation中的。
如今咱們已經對AssociationsManager、 AssociationsHashMap、 ObjectAssociationMap、ObjcAssociation四個對象之間的關係有了簡單的認識,那麼接下來咱們來細讀源碼,看一下objc_setAssociatedObject函數中傳入的四個參數分別放在哪一個對象中充當什麼做用。
細讀上述源碼咱們能夠發現,首先根據咱們傳入的value通過acquireValue函數處理獲取new_value。acquireValue函數內部實際上是經過對策略的判斷返回不一樣的值
以後建立AssociationsManager manager;以及拿到manager內部的AssociationsHashMap即associations。 以後咱們看到了咱們傳入的第一個參數object object通過DISGUISE函數被轉化爲了disguised_ptr_t類型的disguised_object。
DISGUISE函數其實僅僅對object作了位運算
以後咱們看到被處理成new_value的value,同policy被存入了ObjcAssociation中。 而ObjcAssociation對應咱們傳入的key被存入了ObjectAssociationMap中。 disguised_object和ObjectAssociationMap則以key-value的形式對應存儲在associations中也就是AssociationsHashMap中。
若是咱們value設置爲nil的話那麼會執行下面的代碼
從上述代碼中能夠看出,若是咱們設置value爲nil時,就會將關聯對象從ObjectAssociationMap中移除。
最後咱們經過一張圖能夠很清晰的理清楚其中的關係
經過上圖咱們能夠總結爲:一個實例對象就對應一個ObjectAssociationMap,而ObjectAssociationMap中存儲着多個此實例對象的關聯對象的key以及ObjcAssociation,爲ObjcAssociation中存儲着關聯對象的value和policy策略。
由此咱們能夠知道關聯對象並非放在了原來的對象裏面,而是本身維護了一個全局的map用來存放每個對象及其對應關聯屬性表格。
objc_getAssociatedObject內部調用的是_object_get_associative_reference
從_object_get_associative_reference函數內部能夠看出,向set方法中那樣,反向將value一層一層取出最後return出去。
objc_removeAssociatedObjects用來刪除全部的關聯對象,objc_removeAssociatedObjects函數內部調用的是_object_remove_assocations函數
上述源碼能夠看出_object_remove_assocations函數將object對象向對應的全部關聯對象所有刪除。
關聯對象並非存儲在被關聯對象自己內存中,而是存儲在全局的統一的一個AssociationsManager中,若是設置關聯對象爲nil,就至關因而移除關聯對象。
此時咱們咱們在回過頭來看objc_AssociationPolicy policy 參數: 屬性以什麼形式保存的策略。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, // 指定一個弱引用相關聯的對象
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相關對象的強引用,非原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相關的對象被複制,非原子性
OBJC_ASSOCIATION_RETAIN = 01401, // 指定相關對象的強引用,原子性
OBJC_ASSOCIATION_COPY = 01403 // 指定相關的對象被複制,原子性
};
複製代碼
咱們會發現其中只有RETAIN和COPY而爲何沒有weak呢? 總過上面對源碼的分析咱們知道,object通過DISGUISE函數被轉化爲了disguised_ptr_t類型的disguised_object。
disguised_ptr_t disguised_object = DISGUISE(object);
複製代碼
而同時咱們知道,weak修飾的屬性,當沒有擁有對象以後就會被銷燬,而且指針置位nil,那麼在對象銷燬以後,雖然在map中既然存在值object對應的AssociationsHashMap,可是由於object地址已經被置位nil,會形成壞地址訪問而沒法根據object對象的地址轉化爲disguised_object了。
本文是對底層原理學習的總結,若是有不對的地方請指正,歡迎你們一塊兒交流學習 xx_cc 。須要這套視頻一塊兒交流學習的能夠加我Q:2336684744