Category原理、作用 深入理解Objective-C:Category (OC1.0) 結合 category 工作原理分析 OC2.0 中的 runtime
Objective-C Associated Objects 的實現原理 關聯物件 AssociatedObject 完全解析
結構基礎:
struct category_t {
const char *name; // category小括號裡寫的名字
classref_t cls; // cls要擴充套件的類物件,編譯期間這個值是不會有的,在app被runtime載入時才會根據name對應到類物件
struct method_list_t *instanceMethods; // 例項方法
struct method_list_t *classMethods; // 類方法
struct protocol_list_t *protocols; // category實現的protocol,比較不常用在category裡面實現協議,但是確實支援的
struct property_list_t *instanceProperties; // category所有的property,這也是category裡面可以定義屬性的原因,不過這個property不會@synthesize例項變數,一般有需求新增例項變數屬性時會採用objc_setAssociatedObject和objc_getAssociatedObject方法繫結方法繫結,不過這種方法生成的與一個普通的例項變數完全是兩碼事。
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties; // 類屬性(@property (class,nonatomic, strong) NSString *name; 然後還需要現實+的getter setter方法,然後就可以用.號代用
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
複製程式碼
最後的方法: 對 Category 中方法的解析並不複雜,首先來看一下 attachCategories 的簡化版程式碼(只有methods,properties與protocols的類似):
static void attachCategories(Class cls, category_list *cats, bool flush_caches) {
if (!cats) return;
bool isMeta = cls->isMetaClass();
method_list_t **mlists = (method_list_t **)malloc(cats->count * sizeof(*mlists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int i = cats->count;
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
}
複製程式碼
1、首先,通過 while 迴圈,我們遍歷所有的 category,也就是引數 cats 中的 list 屬性。對於每一個 category,得到它的方法列表 mlist 並存入 mlists 中。 2、最後:rw->methods.attachLists(mlists, mcount); 把mlists放在前面,主類方法放在後面。
######總結: 1、Categroy的實現,執行時先把Category中的方法遍歷出來mlists,rw-> methods.attachLists(mlists, mcount),把mlists放在前面,主類方法放在後面。
2、在主類和Category中,同名方法可以同時存在,且不不是覆蓋關係。執行時,當呼叫到同名方法,被呼叫的是在方法列表中排在前面的Category方法。
3、Category中,同名方法不是覆蓋關係,因此還是可以想辦法呼叫主類方法的。其途徑就是獲取當前物件的方法列表,從列表中找出最後一個同名方法,就是主類的方法。
4、在主類的+load方法中可以呼叫Category的方法,因為Category的載入是在load方法之前就完成的。對於主類和Category中都有+load的方法的時候,順序是先主類後Category。
5、associatedObject儲存變數是儲存在一個全域性的HashMap中,物件的地址是Key,value是另一個HashMap,這個HashMap裡儲存Key-Value對。
另外:
1、對系統提供的類做分類,分類是在執行時附加到相關類上的 2、對自己建立的類做分類,分類是在編譯期附加到相關類上的 Runtime原始碼 —— 關於category的一個問題
####2.關聯屬性的具體實現 基本用法
Category.h
@property(nonatomic,copy) NSString *name;
複製程式碼
Category.m
- (void)setName:(NSString *)name
{
// 各個引數簡單解析
// 1、self--關聯的源物件
// 2、@selector(name)--關聯的key
// 3、name--與物件的鍵鍵關聯的值。可以通過nil來清除現有的關聯。
// 4、OBJC_ASSOCIATION_COPY--關聯值的記憶體管理策略,有如下選擇:
/*
OBJC_ASSOCIATION_ASSIGN --@property(assign) / @property(unsafe_unretained)
OBJC_ASSOCIATION_RETAIN_NONATOMIC --@property(nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC --@property(nonatomic, copy)
OBJC_ASSOCIATION_RETAIN --@property(atomic, strong)
OBJC_ASSOCIATION_COPY --@property(atomic, copy)
*/
objc_setAssociatedObject(self,@selector(name),name,OBJC_ASSOCIATION_COPY);
}
- (NSString*)name
{
return objc_getAssociatedObject(self, @selector(name));
}
複製程式碼
系統原始碼
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
複製程式碼
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
// 根據傳進來的value與policy,acquireValue其實只是retain或者copy
id new_value = value ? acquireValue(value, policy) : nil;
{
// AssociationsManager管理者
AssociationsManager manager;
// 獲取AssociationsHashMap
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) { //
// 獲取關聯的源物件self對應的ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// ObjectAssociationMap *refs就是self對應的ObjectAssociationMap
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 查詢key對應ObjcAssociation存在,更新new_value
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
// 查詢key對應ObjcAssociation不存在,設定新的ObjcAssociation進去
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else { // ObjectAssociationMap不存在
// 設定新的ObjectAssociationMap、設定新的ObjcAssociation 儲存到associations
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
// 設定物件的has_assoc為true,標記這個物件有關聯屬性(物件釋放時候用到這個標記)
object->setHasAssociatedObjects();
}
} else { // 傳入的value是nil,就是移除這個key的關聯
// 獲取關聯的源物件self對應的ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// 查詢key對應ObjcAssociation
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 持有這個ObjcAssociation,最後釋放物件用
old_association = j->second;
// ObjectAssociationMap中移除ObjcAssociation
refs->erase(j);
}
}
}
}
// 如果原來的關聯物件有值的話,會呼叫 ReleaseValue() 釋放關聯物件的值
if (old_association.hasValue()) ReleaseValue()(old_association);
}
複製程式碼
在看這段程式碼前,我們需要先了解一下幾個資料結構以及它們之間的關係:
AssociationsManager:是頂級的物件,維護了一個從 spinlock_t 鎖到 AssociationsHashMap 雜湊表的單例鍵值對對映; AssociationsHashMap :是一個無序的雜湊表,(物件地址做key :ObjectAssociationMap); ObjectAssociationMap:是一個 C++ 中的 map ,(關聯 key : ObjcAssociation); ObjcAssociation: 是一個 C++ 的類,表示一個具體的關聯結構,主要包括兩個例項變數,_policy 表示關聯策略,_value 表示關聯物件。 每一個物件地址對應一個 ObjectAssociationMap 物件,而一個 ObjectAssociationMap 物件儲存著這個物件的若干個關聯記錄。
objc_getAssociatedObject
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
複製程式碼
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 取出對應的ObjcAssociation
ObjcAssociation &entry = j->second;
// 取出value
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
// 這裡判斷get的記憶體策略(暫時不知道get的記憶體策略怎麼使用,可能是系統私有使用的)
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
複製程式碼
objc_removeAssociatedObjects
void objc_removeAssociatedObjects(id object)
{
// 判斷物件、判斷是否已經標記為關聯物件
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
複製程式碼
_object_remove_assocations只是在Map裡面for 釋放關聯物件的值
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
複製程式碼
AssociatedObject 本身並不支援新增具備 weak 特性的 property, 但我們可以通過一個小技巧來完成:新增了一箇中間角色 block,再輔以 weak 關鍵字就實現了具備 weak 屬性的 associated object。
- (void)setContext:(NSObject*)object {
id __weak weakObject = object;
id (^block)() = ^{ return weakObject; };
objc_setAssociatedObject(self, @selector(context), block, OBJC_ASSOCIATION_COPY);
}
- (NSObject*)context {
id (^block)() = objc_getAssociatedObject(self, @selector(context));
id curContext = (block ? block() : nil);
return curContext;
}
複製程式碼