廢話不多說,直接進入主題。阅爽。路幸。
下面我們由幾個問題展開:
1、什么是Category付翁?
2简肴、Category有什么特點?
3百侧、Category中能添加哪些內容砰识?
什么是Category?
Category(分類)是在Objective-C 2.0之后出現(xiàn)的,主要用于給現(xiàn)有類添加方法佣渴,以便對原有類的一個補充和完善辫狼,因為我們任何類都不是天生完美的。我們看下蘋果的官方文檔是如何解釋的:
You use categories to define additional methods of an existing class—even one whose source code is unavailable to you—without subclassing. You typically use a category to add methods to an existing class, such as one defined in the Cocoa frameworks. The added methods are inherited by subclasses and are indistinguishable at runtime from the original methods of the class. You can also use categories of your own classes to:
- Distribute the implementation of your own classes into separate source files—for example, you could group the methods of a large class into several categories and put each category in a different file.
- Declare private methods.
總結一下就是:
1辛润、分解體積龐大的自定義類文件
2膨处、聲明私有方法
Category有什么特點?
我們先看這句解釋
The added methods are inherited by subclasses and are indistinguishable at runtime from the original methods of the class.
可知:
1砂竖、分類是在運行時期決議的真椿。
2、分類中添加的方法子類是可繼承的乎澄。
那么除此之外分類還可以添加什么呢突硝?
Category中可以添加哪些內容?
我們先看下蘋果源碼的實現(xiàn):
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta) {
if (isMeta) return nil; // classProperties;
else return instanceProperties;
}
};
在category_t的結構體中我們可以看到如下:
name:類名
cls:類
instanceMethods:實例方法列表
classMethods:類方法列表
protocols:協(xié)議列表
instanceProperties:屬性列表
現(xiàn)在就很明確了:
分類中可以添加:實例方法置济、類方法解恰、協(xié)議、屬性
所以也由此可知:category是不能添加成員變量的舟肉,從源代碼層面上解釋就是category_t的結構體里面沒有ivars成員變量列表。
那么查库,問題又來了路媚。。樊销。整慎。。有人該會問啦围苫,上面不是說可以添加屬性么裤园,按照正常情況來說,我在類文件里面使用@property聲明一個屬性剂府,編譯器就會自動生成一個帶下劃線的成員變量呀拧揽,這樣不就是能添加成員變量嘛,NONONO,在category里面這樣的想法是不對的淤袜,下面我解釋一下原因:
第一痒谴、在分類里使用@property聲明屬性,只是將該屬性添加到該類的屬性列表(instanceProperties)铡羡,聲明的getter积蔚、setter方法添加到實例方法列表,但是不會生成相應的成員變量烦周,也沒有實現(xiàn)setter尽爆、getter方法。
第二读慎、前面有講到category是在運行時期決議的漱贱,因為在運行期也就是編譯完成之后,對象的內存布局已經(jīng)確定贪壳,如果添加實例變量就需要重新內存對齊饱亿,就會破壞類的內部布局,這對編譯型語言來說是不可取的闰靴。
這里說明一點彪笼,其實有很多開發(fā)者包括我自己以前都會誤認為category是不能添加屬性的,其實這種理解是錯誤的蚂且,屬性是可以添加的配猫,在category中@property聲明一個屬性編譯器并不會報錯,但是在運行時期調用setter杏死、getter方法的時候就會報錯泵肄,錯誤原因是找不到該方法,所以也證明了category能添加屬性淑翼,但是不能添加成員變量腐巢。
但是我們在開發(fā)過程中需要往分類中添加成員變量怎么辦呢?
這個問題其實大家都知道改如何解決玄括,那就是runtime的關聯(lián)對象冯丙,說到這,可能有人又會有疑問啦遭京,既然說category中添加成員變量會遇到內存對齊問題胃惜,為什么通過runtime就可以添加呢,所以請帶著疑問繼續(xù)往下繼續(xù)看:
先看一下關聯(lián)對象的實現(xiàn)源碼
void objc_setAssociatedObject_non_gc(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);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
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()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
class AssociationsManager {
static spinlock_t _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { _lock.lock(); }
~AssociationsManager() { _lock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
通過源代碼我們可以看見哪雕,關聯(lián)對象是由AssociationsManager進行管理船殉,AssociationsManager結構體里面是由一個靜態(tài)AssociationsHashMap來存儲所有的關聯(lián)對象的,
disguised_ptr_t disguised_object = DISGUISE(object);
這里是獲取要關聯(lián)屬性的對象的指針地址斯嚎,作為map的key利虫,value對應的是ObjectAssociationMap挨厚,看下ObjectAssociationMap里面都有什么
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator>
其中void *
就是我們在objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy)
中傳入的key,
然后看下ObjcAssociation
class ObjcAssociation {
uintptr_t _policy;
id _value;
};
這里面的value
就是我們關聯(lián)的對象成員變量的值列吼。
到這里幽崩,關聯(lián)對象的解釋基本就結束了,所以看得出來寞钥,通過關聯(lián)對象給category添加成員變量由于是存儲在一個全局的AssociationsManager里面慌申,所以并不會對現(xiàn)有類的內存造成影響。
objc_getAssociatedObject_gc
就不做詳細介紹了理郑,大家可以靜下心來閱讀蘋果的源代碼蹄溉。
注:
本文章解釋的比較淺顯,也易懂您炉,如有解釋不當?shù)牡胤綒g迎留言交流柒爵,謝謝!W簟棉胀!