在我們的實(shí)際開發(fā)中Category
分類的使用必不可少岸军,那么我們通過(guò)以下幾個(gè)方面來(lái)探索一下分類
- 1.什么是分類
Category
- 2.
Category
的作用 - 3.
Category
和Exension
的區(qū)別 - 4.
Category
底層探究 - 5.關(guān)聯(lián)對(duì)象的探索
什么是分類(Category)
Category
是Ovjective-C 2.0
之后添加的語(yǔ)言特性蔽莱,Category
作用是為已經(jīng)存在的類添加方法
Category的作用
- 1.可以減少單個(gè)文件的體積
- 2.可以把不同的功能組織到不同的Category中
- 3.可以按需加載
- 4.聲明私有方法
- 5.把
framework
的私有方法公開
Category和Exension的區(qū)別
- 1.
Category
依托當(dāng)前類存在蛔添,Exension
則是類的一部分 - 2.
Category
在運(yùn)行時(shí)期確定袖迎,Exension
在編譯器確定 - 3.
Category
不能添加變量,Exension
可以添加變量(編譯時(shí)期對(duì)象的內(nèi)存布局已經(jīng)確定)
Category底層探究
4.1 Category
的數(shù)據(jù)結(jié)構(gòu)
Category
的數(shù)據(jù)結(jié)構(gòu)如下
struct category_t {
//當(dāng)前類的名稱
const char *name;
//需要擴(kuò)展的類對(duì)象史简,在編譯期沒(méi)有值
classref_t cls;
//實(shí)例方法
WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;
//對(duì)象方法
WrappedPtr<method_list_t, method_list_t::Ptrauth> classMethods;
//協(xié)議
struct protocol_list_t *protocols;
//實(shí)例屬性
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
//類屬性
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
const char *name;
: 當(dāng)前類的名稱classref_t cls;
:需要擴(kuò)展的類對(duì)象居暖,在編譯期沒(méi)有值WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;
:實(shí)例方法列表WrappedPtr<method_list_t, method_list_t::Ptrauth> classMethods;
:對(duì)象方法列表struct protocol_list_t *protocols;
:協(xié)議列表struct property_list_t *instanceProperties;
:實(shí)例屬性struct property_list_t *_classProperties;
:類屬性
4.2 Category
在編譯時(shí)期做了什么
#import "Person+A.h"
@implementation Person (A)
- (void)funcA {
NSLog(@"A");
}
@end
首先我們創(chuàng)建這樣一個(gè)分類顽频,下面我們通過(guò)clang
看下在編譯時(shí)期分類到底做了些什么
clang -rewrite-objc "文件名.m/h"
我們可以通過(guò)上面這個(gè)命令在終端
中獲取到.cpp
文件
static struct _category_t _OBJC_$_CATEGORY_Person_$_A __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_A,
0,
0,
0,
};
我們編譯后的東西都存放在Mach-o
的可執(zhí)行文件中,通過(guò)字段標(biāo)識(shí)當(dāng)前內(nèi)容太闺。上述內(nèi)容就是存在__DATA
中的__objc_const
的字段中
-
Person
->name
字段 類的名稱 -
0, // &OBJC_CLASS_$_Person,
->cls 需要擴(kuò)展的類對(duì)象糯景,在編譯期沒(méi)有值 -
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_A,
-> instanceMethods 在分類中聲明的實(shí)例方法
我們?cè)賮?lái)看下實(shí)例方法列表這個(gè)結(jié)構(gòu)體里有些啥
_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_A __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"funcA", "v16@0:8", (void *)_I_Person_A_funcA}}
};
-
funcA
方法編號(hào) -
v16@0:8
方法簽名 -
(void *)_I_Person_A_funcA
真實(shí)的函數(shù)地址
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_Person_$_A,
};
將編譯期生成的分類結(jié)構(gòu)體存放在__DATA
中的__objc_catlist
字段中
總結(jié)
Category
在編譯期做了什么
- 1.把分類編譯成結(jié)構(gòu)體
- 2.在對(duì)應(yīng)的字段中填充相應(yīng)的數(shù)據(jù)
- 3.把所有的分類存放在
__DATA
數(shù)據(jù)段中的__objc_catlist
字段中
4.3 Category
在運(yùn)行時(shí)期做了什么
要想探索Category
在運(yùn)行時(shí)期做了什么,首先我們來(lái)倒app的入口函數(shù)_objc_init
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
//環(huán)境變量初始化
environ_init();
//線程寄存器初始化
tls_init();
//靜態(tài)初始化
static_init();
//runtime初始化
runtime_init();
//異常初始化
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
首先進(jìn)行了一些初始化的操作省骂,environ_init ()
,tls_init ()
,static_init ()
,runtime_init ()
,exception_init
.
然后調(diào)用_dyld_objc_notify_register(&map_images, load_images, unmap_image);
函數(shù)蟀淮,通過(guò)dyld注冊(cè)三個(gè)函數(shù):map_images
(map映射,當(dāng)前dyld把images(鏡像)加載進(jìn)內(nèi)存時(shí)钞澳,會(huì)出發(fā)map_images
的回調(diào)), load_images
(當(dāng)dyld初始化images(鏡像)模塊時(shí)調(diào)用怠惶,+load
方法也會(huì)在這里被調(diào)用), unmap_image
(dyld把image移除內(nèi)存時(shí)調(diào)用)
這里我們重點(diǎn)關(guān)注map_images
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
//頭文件信息
header_info *hList[mhCount];
//頭文件數(shù)
uint32_t hCount;
size_t selrefCount = 0;
// Find all images with Objective-C metadata.
hCount = 0;
// Count classes. Size various table based on the total.
//類的數(shù)量
int totalClasses = 0;
if (hCount > 0) { //讀取鏡像文件,讀取OC相關(guān)的Section
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
由于map_images_nolock
函數(shù)中代碼太多轧粟,截取了部分關(guān)鍵代碼
_read_images
函數(shù)中做了什么
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
if (didInitialAttachCategories) {
//遍歷整個(gè)頭文件列表
for (EACH_HEADER) {
//加載分類
load_categories_nolock(hi);
}
}
}
由于_read_images
函數(shù)中代碼太多策治,截取了部分關(guān)鍵代碼
-
EACH_HEADER
是一個(gè)宏定義,用來(lái)遍歷整個(gè)頭文件列表 -
load_categories_nolock
加載分類
接著往下看 load_categories_nolock
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Ignore the category.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
if (cls->isStubClass()) {
// Stub classes are never realized. Stub classes
// don't know their metaclass until they're
// initialized, so we have to add categories with
// class methods or properties to the stub itself.
// methodizeClass() will find them and add them to
// the metaclass as appropriate.
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
if (cls->isRealized()) {
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {
//把分類中聲明的屬性兰吟,方法添加到累的屬性通惫,方法列表中
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
//把當(dāng)前分類加載到對(duì)應(yīng)的類中
objc::unattachedCategories.addForClass(lc, cls->ISA());
//addForClass底層數(shù)據(jù)結(jié)構(gòu)的映射
}
}
}
}
};
//讀取分類列表
processCatlist(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
}
在這個(gè)函數(shù)中做了三件事
- 1.
processCatlist(hi->catlist(&count));
,processCatlist(hi->catlist2(&count));
讀取分類列表 -
objc::unattachedCategories.addForClass(lc, cls->ISA());
當(dāng)前分類加載到對(duì)應(yīng)的類中
-
-
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
把分類中聲明的屬性,方法揽祥,協(xié)議添加到類的屬性讽膏,方法,協(xié)議列表中
-
attachCategories
函數(shù)的實(shí)現(xiàn)
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
//聲明 方法列表拄丰,屬性列表府树,協(xié)議列表所用的空間
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
if (mcount > 0) {
//準(zhǔn)備當(dāng)前的方法列表
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
//把分類方法追加進(jìn)類的方法列表中
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) {
flushCaches(cls, __func__, [](Class c){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
由于attachCategories
函數(shù)中代碼太多,截取了部分關(guān)鍵代碼
- 1.
prepareMethodLists
準(zhǔn)備當(dāng)前的方法列表料按,核心:把方法名稱放到方法列表中 - 2.
attachLists
分類和本類相應(yīng)的對(duì)象方法奄侠,屬性,和協(xié)議進(jìn)行了合并载矿。
attachLists
內(nèi)部實(shí)現(xiàn)
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
//多對(duì)多
uint32_t oldCount = array()->count; //獲取原來(lái)方法列表中的方法個(gè)數(shù)
uint32_t newCount = oldCount + addedCount; //得到添加后的方法個(gè)數(shù)
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount)); //開辟新的空間
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i]; //把原來(lái)的方法放在后面
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i]; //把新添加的方法放在數(shù)組的前面
free(array());
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<List> oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}
}
這里會(huì)分三種情況:多對(duì)多垄潮,0對(duì)1烹卒,1對(duì)多,這里就以多對(duì)多為例
-
uint32_t oldCount = array()->count;
獲取原來(lái)方法列表中的方法個(gè)數(shù) -
uint32_t newCount = oldCount + addedCount
得到添加后的方法個(gè)數(shù) -
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
開辟新的空間 -
newArray->lists[i + addedCount] = array()->lists[i];
把原來(lái)的方法放在后面 -
newArray->lists[i] = addedLists[i]
把新添加的方法放在數(shù)組的前面
總結(jié)
Category
在運(yùn)行時(shí)做了什么
1.通過(guò)catlist
,catlist2
(_getObjc2CategoryList
)讀取分類列表
2.通過(guò)addForClass
當(dāng)前分類加載到對(duì)應(yīng)的類中
3.attachCategories
把分類中聲明的屬性弯洗,方法旅急,協(xié)議添加到類的屬性,方法牡整,協(xié)議列表中
4.prepareMethodLists
準(zhǔn)備當(dāng)前的方法列表藐吮,核心:把方法名稱放到方法列表中
5.attachLists
分類和本類相應(yīng)的對(duì)象方法,屬性逃贝,和協(xié)議進(jìn)行了合并
6.合并的順序谣辞,先為分類中的方法,再是原類中的方法(這就是同名方法分類會(huì)優(yōu)先于本類調(diào)用的原因)
多個(gè)分類/類也有同名方法沐扳,方法查找泥从,本質(zhì)上調(diào)用Runtime遍歷方法列表,通過(guò)二分查找算法沪摄,不會(huì)立馬返回imp指針躯嫉,往前移動(dòng),找是否有同名方法杨拐。
關(guān)聯(lián)對(duì)象的探索
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
在類中聲明一個(gè)屬性和敬,系統(tǒng)會(huì)自動(dòng)幫我們生成getter
setter
ivar
.
當(dāng)我們使用分類想為原類添加屬性時(shí),我們則需要使用關(guān)聯(lián)對(duì)象的方式戏阅,那么關(guān)聯(lián)對(duì)象和直接聲明數(shù)據(jù)原理是否一樣呢,我們接下來(lái)分析下
分類中我們使用objc_setAssociatedObject
,objc_getAssociatedObject
這兩函數(shù)添加屬性
首先我們來(lái)看下關(guān)聯(lián)對(duì)象怎么存的objc_setAssociatedObject
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value}; //把外部傳入的value和策略存起來(lái)
// retain the new value (if any) outside the lock.
//根據(jù)上面存的policy啤它,設(shè)置內(nèi)存管理語(yǔ)義
association.acquireValue();
bool isFirstAssociation = false;
{
AssociationsManager manager; //聲明一個(gè)管理者奕筐,用來(lái)管理下面的AssociationsHashMap
AssociationsHashMap &associations(manager.get());
if (value) {
//<first, second> -> <disguised, ObjectAssociationMap{}>
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
auto &refs = refs_result.first->second;
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
-
DisguisedPtr<objc_object> disguised{(objc_object *)object}
按位取反 -
ObjcAssociation association{policy, value};
把外部傳入的value和策略存起來(lái) -
association.acquireValue();
根據(jù)上面存的policy,設(shè)置內(nèi)存管理語(yǔ)義,copy
,retain
-
AssociationsManager manager;
聲明一個(gè)管理者变骡,用來(lái)管理下面的AssociationsHashMap -
AssociationsHashMap &associations(manager.get());
初始化HashMap -
object->setHasAssociatedObjects();
與關(guān)聯(lián)對(duì)象做綁定离赫,方便釋放
根據(jù)上面的代碼我們就明白了關(guān)聯(lián)對(duì)象是怎么存對(duì)象的了
AssociationsManager -> <disguised , ObjectAssociationMap >
ObjectAssociationMap -> <key(外部傳入的), ObjcAssociation>
ObjcAssociation -> 有倆屬性 外部傳入的uintptr_t _policy
,id _value
objc_getAssociatedObject
獲取關(guān)聯(lián)對(duì)象實(shí)現(xiàn)
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
我們探索了objc_setAssociatedObject
的實(shí)現(xiàn),objc_getAssociatedObject
的實(shí)現(xiàn)就一幕了然了
通過(guò)AssociationsManager
獲取到AssociationsHashMap
,再通過(guò)獲取的AssociationsHashMap
得到association
塌碌。
下面我們?cè)賮?lái)看下關(guān)聯(lián)對(duì)像的釋放
- (void)dealloc {
_objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
delloac
-> _objc_rootDealloc
-> rootDealloc
(首先判斷是否有關(guān)聯(lián)對(duì)象等渊胸,如果沒(méi)有則直接釋放)如果沒(méi)有 -> object_dispose
-> objc_destructInstance
-> _object_remove_assocations
(釋放關(guān)聯(lián)對(duì)象)