參考篇:iOS-分類(lèi)(Category)
前言:本文簡(jiǎn)述Category原理,如有錯(cuò)誤請(qǐng)留言指正。
第一部分:有關(guān)分類(lèi)的本質(zhì)、原理
Q:分類(lèi)的對(duì)象方法,類(lèi)方法都存在哪里?
一個(gè)類(lèi)的所有分類(lèi)的 對(duì)象方法放在類(lèi)對(duì)象中,所有分類(lèi)的類(lèi)方法存放在元類(lèi)中
clang查看編譯文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc NSObject+Test.m
編譯文件NSObject+Test.cpp中有關(guān)分類(lèi)內(nèi)容
//聲明結(jié)構(gòu)體
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
//對(duì)結(jié)構(gòu)體賦值
static struct _category_t _OBJC_$_CATEGORY_NSObject_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"NSObject",
0, // &OBJC_CLASS_$_NSObject,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_Test,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_NSObject_$_Test,
0,
};
Runtime中Category源碼解讀順序
objc-os.mm
- _objc_init
- map_images
- map_images_nolock
objc-runtime-new.mm
- _read_images
- remethodizeClass
- attachCategories
- attachLists
- realloc、memmove、 memcpy
Runtime中Category的底層結(jié)構(gòu)
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;
// 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);
};
objc-runtime-new.mm
// cls 類(lèi)
// cats 分類(lèi)列表
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
/*方法數(shù)組
[
[method_t,method_t]
[method_t,method_t]
]
*/
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
//屬性數(shù)組
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
//協(xié)議數(shù)組
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
//取出分類(lèi)
auto& entry = cats->list[i];
//取出分類(lèi)對(duì)象方法
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
Q:分類(lèi)的方法何時(shí)合并到類(lèi)對(duì)象中?
通過(guò)runtime動(dòng)態(tài)將分類(lèi)的方法合并到類(lèi)對(duì)象、元類(lèi)對(duì)象中的
Q:分類(lèi)的方法是如何添加到類(lèi)對(duì)象方法列表中的?
runtime源碼展示
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
//array()->lists:原來(lái)類(lèi)對(duì)象的方法列表
//內(nèi)存移動(dòng)
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//addedLists:所有分類(lèi)的方法列表
//內(nèi)存拷貝
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
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;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
重要代碼:
//array()->lists:原來(lái)類(lèi)對(duì)象的方法列表
//內(nèi)存移動(dòng)
memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0]));
//addedLists:所有分類(lèi)的方法列表
//內(nèi)存拷貝
memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));
演示圖例

大概流程
- 1.獲取分類(lèi)列表的
count,然后原來(lái)的類(lèi)方法列表內(nèi)存移動(dòng)count - 2.分類(lèi)列表內(nèi)存拷貝到原來(lái)的類(lèi)方法列表的前方
- 3.同樣的方法,優(yōu)先調(diào)用分類(lèi)的方法
- 4.分類(lèi)具有同樣的方法,根據(jù)編譯順序決定,取最后編譯分類(lèi)的方法列表
Q:Category的加載處理過(guò)程?
- 1.通過(guò)Runtime加載某個(gè)類(lèi)的所有Category數(shù)據(jù)
- 2.把所有Category的方法、屬性、協(xié)議數(shù)據(jù),合并到一個(gè)大數(shù)組中
- 3.后面參與編譯的Category數(shù)據(jù),會(huì)在數(shù)組的前面
- 4.將合并后的分類(lèi)數(shù)據(jù)(方法、屬性、協(xié)議),插入到類(lèi)原來(lái)數(shù)據(jù)的前面
Q:Category的實(shí)現(xiàn)原理
- Category編譯之后的底層結(jié)構(gòu)是struct category_t,里面存儲(chǔ)著分類(lèi)的對(duì)象方法、類(lèi)方法、屬性、協(xié)議信息
- 在程序運(yùn)行的時(shí)候,runtime會(huì)將Category的數(shù)據(jù),合并到類(lèi)信息中(類(lèi)對(duì)象、元類(lèi)對(duì)象中)
Q:Category和Class Extension的區(qū)別是什么?
- Class Extension在編譯的時(shí)候,它的數(shù)據(jù)就已經(jīng)包含在類(lèi)信息中
- Category是在運(yùn)行時(shí),才會(huì)將數(shù)據(jù)合并到類(lèi)信息中
Q:memmove和memcpy的區(qū)別?
memmove會(huì)根據(jù)內(nèi)存大小,移動(dòng)方向,數(shù)量來(lái)移動(dòng)內(nèi)存;memcpy是按照一定規(guī)則一個(gè)地址一個(gè)地址拷貝。memmove能保證原數(shù)據(jù)完整性,內(nèi)部移動(dòng)最好不要使用memcpy,外部?jī)?nèi)存移動(dòng)可以使用。
第二部分:+load 和 +initialize方法
Q: +load方法調(diào)用原理?
objc4源碼解讀過(guò)程:
objc-os.mm
_objc_init
load_images
prepare_load_methods
schedule_class_load
add_class_to_loadable_list
add_category_to_loadable_list
call_load_methods
call_class_loads
call_category_loads
(*load_method)(cls, SEL_load)
objc-loadmethod.mm
struct loadable_class {
Class cls; // may be nil
IMP method; // +load
};
struct loadable_category {
Category cat; // may be nil
IMP method; // 分類(lèi)的+load
};
調(diào)用視圖

Q:+load方法調(diào)用順序?
1. 先調(diào)用類(lèi)的+load方法
- 1.1按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
- 1.2先調(diào)用父類(lèi)的+load再調(diào)用子類(lèi)的+load
2. 再調(diào)用分類(lèi)的+load方法
- 2.1按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
- 每個(gè)類(lèi)、分類(lèi)的+load,在程序運(yùn)行過(guò)程中只調(diào)用一次,只有在加載類(lèi)時(shí)候調(diào)用一次
- 不存在分類(lèi)的+load方法覆蓋類(lèi)的+load方法
Q:+load方法為什么和其他的類(lèi)方法調(diào)用方式不同?
其他分類(lèi)類(lèi)方法是通過(guò)消息轉(zhuǎn)發(fā)機(jī)制調(diào)用的,isa和superclass來(lái)尋找的;而+load是通過(guò)函數(shù)指針指向函數(shù),拿到函數(shù)地址,分開(kāi)來(lái)直接調(diào)用的,直接通過(guò)內(nèi)存地址查找調(diào)用的。
Q:Category中有l(wèi)oad方法嗎?load方法是什么時(shí)候調(diào)用的?load 方法能繼承嗎?
- 有l(wèi)oad方法
- load方法在runtime加載類(lèi)、分類(lèi)的時(shí)候調(diào)用
- load方法可以繼承,但是一般情況下不會(huì)主動(dòng)去調(diào)用load方法,都是讓系統(tǒng)自動(dòng)調(diào)用
Q:+initialize方法是怎么調(diào)用的?
+initialize方法會(huì)在類(lèi)第一次接收到消息時(shí)調(diào)用,消息轉(zhuǎn)發(fā)機(jī)制調(diào)用的(objc_send)
Q:+initialize方法調(diào)用順序?
先調(diào)用父類(lèi)的+initialize,再調(diào)用子類(lèi)的+initialize;(先初始化父類(lèi),再初始化子類(lèi),每個(gè)類(lèi)只會(huì)初始化1次),子類(lèi)內(nèi)部+initialize會(huì)主動(dòng)調(diào)用父類(lèi)的+initialize
runtime探索objc_msgSend內(nèi)部調(diào)用initialize
objc4源碼解讀過(guò)程
objc-msg-arm64.s
objc_msgSend
objc-runtime-new.mm
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls, SEL_initialize)
部分源碼展示:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
//.......中間省略一部分源碼
//下面代碼展示如果initialize需要初始化 && 類(lèi)未被初始化過(guò)就執(zhí)行以下函數(shù)
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
}
Q:+initialize和+load的很大區(qū)別是?
- +initialize是通過(guò)objc_msgSend進(jìn)行調(diào)用的
- 如果子類(lèi)沒(méi)有實(shí)現(xiàn)+initialize,會(huì)調(diào)用父類(lèi)的+initialize(所以父類(lèi)的+initialize可能會(huì)被調(diào)用多次)
- 如果分類(lèi)實(shí)現(xiàn)了+initialize,就覆蓋類(lèi)本身的+initialize調(diào)用
- +load是通過(guò)函數(shù)指針指向函數(shù),拿到函數(shù)地址,分開(kāi)來(lái)直接調(diào)用的,直接通過(guò)內(nèi)存地址查找調(diào)用的。
Q:load、initialize方法的區(qū)別什么?
1.調(diào)用方式
1> load是根據(jù)函數(shù)地址直接調(diào)用
2> initialize是通過(guò)objc_msgSend調(diào)用2.調(diào)用時(shí)刻
1> load是runtime加載類(lèi)、分類(lèi)的時(shí)候調(diào)用(只會(huì)調(diào)用1次)
2> initialize是類(lèi)第一次接收到消息的時(shí)候調(diào)用,每一個(gè)類(lèi)只會(huì)initialize一次(父類(lèi)的initialize方法可能會(huì)被調(diào)用多次)
Q:load、initialize的調(diào)用順序?
1.load
1> 先調(diào)用類(lèi)的load
a) 先編譯的類(lèi),優(yōu)先調(diào)用load
b) 調(diào)用子類(lèi)的load之前,會(huì)先調(diào)用父類(lèi)的load2> 再調(diào)用分類(lèi)的load
a) 先編譯的分類(lèi),優(yōu)先調(diào)用load
2.initialize
1> 先初始化父類(lèi)
2> 再初始化子類(lèi)(可能最終調(diào)用的是父類(lèi)的initialize方法)
第三部分:關(guān)聯(lián)對(duì)象
Q:分類(lèi)聲明屬性,系統(tǒng)都做了什么?
分類(lèi)聲明屬性,系統(tǒng)只生成setter和getter方法的聲明,但是成員變量、setter和getter方法的實(shí)現(xiàn)均沒(méi)有。
Q:為什么不能用字典為分類(lèi)增加實(shí)例變量
- 字典為分類(lèi)增加實(shí)例變量存在全局變量中,內(nèi)存泄漏
- 多線(xiàn)程訪(fǎng)問(wèn)會(huì)存在同時(shí)訪(fǎng)問(wèn)變量的情況,還得加鎖處理
- 每增加一個(gè)變量,字典、setter、getter方法就得重新編寫(xiě)
Q:Category能否添加成員變量?如果可以,如何給Category添加成員變量?
- 不能直接給Category添加成員變量,但是可以間接實(shí)現(xiàn)Category有成員變量的效果
- 默認(rèn)情況下,因?yàn)榉诸?lèi)底層結(jié)構(gòu)的限制,不能添加成員變量到分類(lèi)中。但可以通過(guò)關(guān)聯(lián)對(duì)象來(lái)間接實(shí)現(xiàn)
Q:如何給分類(lèi)關(guān)聯(lián)對(duì)象?
- 添加關(guān)聯(lián)對(duì)象
// object:當(dāng)前對(duì)象
// key:標(biāo)記key
//value :關(guān)聯(lián)屬性值
//objc_AssociationPolicy:關(guān)聯(lián)對(duì)象策略
void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy)
- 獲得關(guān)聯(lián)對(duì)象
id objc_getAssociatedObject(id object, const void * key) - 移除所有的關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id object)
關(guān)聯(lián)對(duì)象策略

關(guān)聯(lián)對(duì)象代碼示例:
#import "Person.h"
@interface Person (Test1)
@property (nonatomic, copy) NSString *name;
@end
#import "Person+Test1.h"
#import <objc/runtime.h>
@implementation Person (Test1)
//保證nameKey唯一即可
static void * nameKey = &nameKey;
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, nameKey);
}
@end
保證關(guān)聯(lián)對(duì)象key唯一的其他方法
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
//static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)
//使用屬性名作為key
//直接使用的@"name"類(lèi)似的變量是存在常量區(qū)的,所以地址會(huì)相同
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");
//使用get方法的@selecor作為key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))
objc_getAssociatedObject(self, _cmd);
//_cmd:表示當(dāng)前方法的selector方法
Q:關(guān)聯(lián)對(duì)象的原理
實(shí)現(xiàn)關(guān)聯(lián)對(duì)象技術(shù)的核心對(duì)象有
- AssociationsManager
- AssociationsHashMap
- ObjectAssociationMap
- ObjcAssociation
objc4源碼解讀:objc-references.mm
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);
}
核心對(duì)象內(nèi)部:
//AssociationsManager內(nèi)包含AssociationsHashMap
class AssociationsManager {
static AssociationsHashMap *_map;
}
//AssociationsHashMap內(nèi)包含ObjectAssociationMap
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator>
//ObjectAssociationMap內(nèi)包含ObjcAssociation
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator>
//ObjcAssociation 內(nèi)包含策略和屬性值
class ObjcAssociation {
uintptr_t _policy;
id _value;
}

Q:關(guān)聯(lián)對(duì)象是否儲(chǔ)存在類(lèi)對(duì)象內(nèi)存中?
答案:不是的
- 關(guān)聯(lián)對(duì)象并不是存儲(chǔ)在被關(guān)聯(lián)對(duì)象本身內(nèi)存中
- 關(guān)聯(lián)對(duì)象存儲(chǔ)在全局的統(tǒng)一的一個(gè)AssociationsManager,AssociationsHashMap中
Q:設(shè)置關(guān)聯(lián)對(duì)象為nil,會(huì)發(fā)生什么?
相當(dāng)于是移除關(guān)聯(lián)對(duì)象,內(nèi)部會(huì)有一個(gè)erase操作
Q:如何移除所有關(guān)聯(lián)對(duì)象?
移除所有的關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id object)
Q:如果類(lèi)對(duì)象銷(xiāo)毀,分類(lèi)的關(guān)聯(lián)對(duì)象會(huì)移除么?
會(huì)的