category是 Objective-C 2.0 以後添加的語言特性,主要做用是爲已經存在的類添加方法。除此以外,Apple 還推薦了category 的另外兩個使用場景。安全
不過除了apple推薦的使用場景,還衍生出了 category 的其餘幾個使用場景:bash
@property
只會生成 setter
和 getter
的聲明,不會生成 setter
和 getter
的實現以及成員變量這裏說的是覆蓋而不是替換,是由於後編譯的方法被放在了方法列表的前面而已,runtime機制先找到前面的方法來執行數據結構
Category
VS Extension
category 經常拿來與 extension 作比較,extension 同樣能夠添加屬性和方法,extension 看起來很像一個匿名的 category。但實際上二者幾乎徹底是兩個東西app
講了一堆category的做用和特色,咱們來看一下category的定義ide
typedef struct category_t *Category;
struct category_t {
const char *name; //category名稱
classref_t cls; //要拓展的類
struct method_list_t *instanceMethods; //給類添加的實例方法的列表
struct method_list_t *classMethods; //給類添加的類方法的列表
struct protocol_list_t *protocols; //給類添加的協議的列表
struct property_list_t *instanceProperties; //給類添加的屬性的列表
};
複製代碼
實際上 Category
是一個 category_t
的結構體,裏面維護着類的信息和category的名稱,以及類方法列表,實例方法列表,協議的列表和屬性的列表函數
那麼Category是怎麼加載的呢?佈局
咱們知道,Objective-C 的運行是依賴 OC 的 runtime 的, 而 OC 的 runtime 和其餘系統庫同樣,是OS X和iOS經過dyld動態加載的ui
咱們從OC運行時,入口方法出發atom
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
複製代碼
到真正完成綁定 category 的函數attachCategories
中間的函數調用棧是spa
void _objc_init(void);
└── void map_images(...);
└── void map_images_nolock(...);
└── void _read_images(...);
└── void _read_images(...);
└── static void remethodizeClass(Class cls);
└──attachCategories(Class cls, category_list *cats, bool flush_caches);
複製代碼
咱們來看一下 attachCategories
源碼的簡易版:
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));
int mcount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
}
複製代碼
從上面的代碼能夠看出,增長方法的操做實際是分配一個大的實例方法列表 method_list_t **mlists = (method_list_t **) malloc(cats->count * sizeof(*mlists));
再經過 for 循環將category中的方法列表填入這個大的列表,最後交給 rw->methods.attachLists(mlists, mcount);
將方法列表增長到類的方法列表上去。其餘的屬性添加與此相似
咱們再看一段 attachLists
的源碼
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (hasArray()) {
//舊的方法列表的長度
uint32_t oldCount = array()->count;
//新的方法列表的長度
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
//從addedCount的偏移量添加舊的方法列表
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//從開始添加新方法列表
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
複製代碼
從上面的代碼也驗證了咱們上面所說的,同名的方法是覆蓋而不是替換,category的方法被放到了新方法列表的前面
如上所述,category 的屬性不能生成成員變量和 getter
、setter
方法的實現,咱們要本身實現 getter
和 setter
方法,需藉助關聯對象來實現
關聯對象來實現提供三個接口 objc_setAssociatedObject
,objc_getAssociatedObject
,objc_removeAssociatedObjects
,他們分別調用的是
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
void objc_removeAssociatedObjects(id object) {
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
複製代碼
他們調用的接口都位於 objc-references.mm
文件中,
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 &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
複製代碼
這段代碼引用的類型有
AssociationsManager源碼
spinlock_t AssociationsManagerLock;
class AssociationsManager {
static AssociationsHashMap *_map;
public:
// 初始化時候
AssociationsManager() { AssociationsManagerLock.lock(); }
// 析構的時候
~AssociationsManager() { AssociationsManagerLock.unlock(); }
// associations 方法用於取得一個全局的 AssociationsHashMap 單例
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
複製代碼
AssociationsManager 初始化一個 AssociationsHashMap
的單例,用自旋鎖 AssociationsManagerLock
保證線程安全
AssociationsHashMap源碼
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
複製代碼
AssociationsHashMap
是一個map類型,用於保存對象的對象的 disguised_ptr_t
到 ObjectAssociationMap
的映射
ObjectAssociationMap源碼
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
複製代碼
ObjectAssociationMap
則保存了從 key 到關聯對象 ObjcAssociation
的映射,這個數據結構保存了當前對象對應的全部關聯對象
ObjcAssociation源碼
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
uintptr_t policy() const { return _policy; }
id value() const { return _value; }
bool hasValue() { return _value != nil; }
};
複製代碼
ObjcAssociation
就是真正的關聯對象的類,上面的全部數據結構只是爲了更好的存儲它。
最關鍵的 ObjcAssociation
包含了 policy
以及 value
用一張圖解釋他們的關係就是:
從上圖咱們不難看出 _object_get_associative_reference
獲取關聯對象的步驟是:
AssociationsHashMap &associations(manager.associations())
獲取 AssociationsHashMap
的單例對象 associations
disguised_ptr_t disguised_object = DISGUISE(object)
獲取對象的地址associations
中獲取 AssociationsHashMap
迭代器key
獲取到 ObjectAssociationMap
的迭代器ObjcAssociation
的實例 entry
,再獲取到 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.
uintptr_t old_policy = 0; // NOTE: old_policy is always assigned to when old_value is non-nil.
id new_value = value ? acquireValue(value, policy) : nil, old_value = nil; // 調用 acquireValue 對 value 進行 retain 或者 copy
{
// & 取地址 *是指針,就是地址的內容
AssociationsManager manager; // 初始化一個 AssociationsManager 類型的變量 manager
AssociationsHashMap &associations(manager.associations()); // 取得一個全局的 AssociationsHashMap 單例
if (new_value) {
// 若是new_value不爲空,開始遍歷associations指向的map,查找object對象是否存在保存聯合存儲數據的ObjectAssociationMap對象
// 查找map中是否包含某個關鍵字條目,用 find() 方法,傳入的參數是要查找的key(被關聯對象的內存地址),在這裏須要提到的是begin()和end()兩個成員,分別表明map對象中第一個條目和最後一個條目,這兩個數據的類型是iterator.
// 定義一個條目變量 i (實際是指針)
AssociationsHashMap::iterator i = associations.find(object); // AssociationsHashMap 是一個無序的哈希表,維護了從對象地址到 ObjectAssociationMap 的映射;
// iterator是 C++ 中的迭代器 , 這句話是定義一個 AssociationsHashMap::iterator 類型的變量 i,初始化爲 associations.find(object) , associations是AssociationsHashMap類型對象。
// 經過map對象的方法獲取的iterator數據類型 是一個std::pair對象
// 根據對象地址獲取起對應的 ObjectAssociationMap對象
if (i != associations.end()) {
// 存在
// object對象在associations指向的map中存在一個ObjectAssociationMap對象refs
// ObjectAssociationMap 是一個 C++ 中的 map ,維護了從 key(就是外界傳入的key) 到 ObjcAssociation 的映射,即關聯記錄
ObjectAssociationMap *refs = i->second; // 指針 調用方法 須要用 -> i 是 AssociationsHashMap i->second 表示ObjectAssociationMap i->first 表示對象的地址
ObjectAssociationMap::iterator j = refs->find(key); // 根據傳入的關聯對象的key(一個地址)獲取其對應的關聯對象 ObjectAssociationMap
// 關聯對象是否存在
if (j != refs->end()) {
// 使用過該key保存value,用新的value和policy替換掉原來的值
// 若是存在 持有舊的關聯對象
ObjcAssociation &old_entry = j->second;
old_policy = old_entry.policy;
old_value = old_entry.value;
// 存入新的關聯對象
old_entry.policy = policy;
old_entry.value = new_value;
} else {
// 沒用使用過該key保存value,將value和policy保存到key映射的map中
// 若是不存在 直接存入新的關聯對象
(*refs)[key] = ObjcAssociation(policy, new_value); // 對map 插入元素
}
}
else {
// 不存在
// 沒有object就建立
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
_class_setInstancesHaveAssociatedObjects(_object_getClass(object));
}
} else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &old_entry = j->second;
old_policy = old_entry.policy;
old_value = (id) old_entry.value;
// 從 map中刪除該項
refs->erase(j);
}
}
}
}
// 舊的關聯對象是否存在,若是存在,釋放舊的關聯對象。
// release the old value (outside of the lock).
if (old_value) releaseValue(old_value, old_policy);
}
複製代碼
_object_set_associative_reference
設置關聯對象的流程參照圖片:
在給一個對象添加關聯對象時有五種關聯策略可供選擇:
關聯策略 | 等價屬性 | 說明 |
---|---|---|
OBJC_ASSOCIATION_ASSIGN | @property (assign) or @property (unsafe_unretained) | 弱引用關聯對象 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (strong, nonatomic) | 強引用關聯對象,且爲非原子操做 |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (copy, nonatomic) | 複製關聯對象,且爲非原子操做 |
OBJC_ASSOCIATION_RETAIN | @property (strong, atomic) | 強引用關聯對象,且爲原子操做 |
OBJC_ASSOCIATION_COPY | @property (copy, atomic) | 複製關聯對象,且爲原子操做 |
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()) {
// 獲取到全部的關聯對象的associations實例
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
delete refs; //刪除ObjectAssociationMap
associations.erase(i);//刪除AssociationsHashMap
}
}
//刪除elements集合中的全部ObjcAssociation元素
for_each(elements.begin(), elements.end(), ReleaseValue());
}
複製代碼
刪除關聯對象的流程相對就比較簡單了,將獲取到的關聯對象ObjcAssociation的實例放入一個 vector
中,刪除對應的 ObjectAssociationMap
和 AssociationsHashMap
,最後對 vector
中每一個 ObjcAssociation
實例作release操做
Category在iOS開發中是比較常見的,用於給現有的類拓展新的方法和屬性。本文從底層分析了Category的原理,以及關聯對象實現,使你們對Category能有一個更深的認識,在之後的開發工做中能更好的使用這一特性。