第一部分:有關(guān)分類的本質(zhì)附帽、原理
Q:分類的對象方法,類方法都存在哪里圣猎?
一個類的所有分類的 對象方法放在類對象中士葫,所有分類的類方法存放在元類中
clang查看編譯文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc NSObject+Test.m
編譯文件NSObject+Test.cpp中有關(guān)分類內(nèi)容
//聲明結(jié)構(gòu)體
struct _category_t {
const char *name; //類的名字(name)
struct _class_t *cls; //類(cls)
const struct _method_list_t *instance_methods;//category中所有給類添加的實例方法的列表(instanceMethods)
const struct _method_list_t *class_methods;//category中所有添加的類方法的列表(classMethods)
const struct _protocol_list_t *protocols; //category實現(xiàn)的所有協(xié)議的列表(protocols)
const struct _prop_list_t *properties; //category中添加的所有屬性(instanceProperties)
};
//對結(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 類
// cats 分類列表
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--) {
//取出分類
auto& entry = cats->list[i];
//取出分類對象方法
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:分類的方法何時合并到類對象中?
通過runtime動態(tài)將分類的方法合并到類對象爪模、元類對象中的
Q:分類的方法是如何添加到類對象方法列表中的欠啤?
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:原來類對象的方法列表
//內(nèi)存移動
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//addedLists:所有分類的方法列表
//內(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:原來類對象的方法列表
//內(nèi)存移動
memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0]));
//addedLists:所有分類的方法列表
//內(nèi)存拷貝
memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));
演示圖例
大概流程
1.獲取分類列表的count,然后原來的類方法列表內(nèi)存移動count
2.分類列表內(nèi)存拷貝到原來的類方法列表的前方
3.同樣的方法屋灌,優(yōu)先調(diào)用分類的方法
4.分類具有同樣的方法洁段,根據(jù)編譯順序決定,取最后編譯分類的方法列表
Q:Category的加載處理過程共郭?
- 1.通過Runtime加載某個類的所有Category數(shù)據(jù)
- 2.把所有Category的方法祠丝、屬性、協(xié)議數(shù)據(jù)除嘹,合并到一個大數(shù)組中
- 3.后面參與編譯的Category數(shù)據(jù)写半,會在數(shù)組的前面
- 4.將合并后的分類數(shù)據(jù)(方法、屬性尉咕、協(xié)議)叠蝇,插入到類原來數(shù)據(jù)的前面
Q:Category的實現(xiàn)原理
- Category編譯之后的底層結(jié)構(gòu)是struct category_t,里面存儲著分類的對象方法年缎、類方法悔捶、屬性、協(xié)議信息
- 在程序運行的時候单芜,runtime會將Category的數(shù)據(jù)蜕该,合并到類信息中(類對象、元類對象中)
Q:memmove和memcpy的區(qū)別洲鸠?
memmove會根據(jù)內(nèi)存大小堂淡,移動方向,數(shù)量來移動內(nèi)存;memcpy是按照一定規(guī)則一個地址一個地址拷貝淤齐。memmove能保證原數(shù)據(jù)完整性股囊,內(nèi)部移動最好不要使用memcpy,外部內(nèi)存移動可以使用更啄。
第二部分:+load 和 +initialize方法
Q: +load方法調(diào)用原理稚疹?
objc4源碼解讀過程:
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; // 分類的+load
};
調(diào)用視圖
Q:+load方法調(diào)用順序?
- 先調(diào)用類的+load方法
- 1.1按照編譯先后順序調(diào)用(先編譯祭务,先調(diào)用)
- 1.2先調(diào)用父類的+load再調(diào)用子類的+load
- 再調(diào)用分類的+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();
- 每個類、分類的+load义锥,在程序運行過程中只調(diào)用一次柳沙,只有在加載類時候調(diào)用一次
- 不存在分類的+load方法覆蓋類的+load方法
Q:+load方法為什么和其他的類方法調(diào)用方式不同?
其他分類類方法是通過消息轉(zhuǎn)發(fā)機制調(diào)用的拌倍,isa和superclass來尋找的赂鲤;而+load是通過函數(shù)指針指向函數(shù),拿到函數(shù)地址柱恤,分開來直接調(diào)用的数初,直接通過內(nèi)存地址查找調(diào)用的。
Q:Category中有l(wèi)oad方法嗎梗顺?load方法是什么時候調(diào)用的泡孩?load 方法能繼承嗎?
- 有l(wèi)oad方法
- load方法在runtime加載類寺谤、分類的時候調(diào)用
- load方法可以繼承仑鸥,但是一般情況下不會主動去調(diào)用load方法,都是讓系統(tǒng)自動調(diào)用
Q:+initialize方法是怎么調(diào)用的变屁?
+initialize方法會在類第一次接收到消息時調(diào)用眼俊,消息轉(zhuǎn)發(fā)機制調(diào)用的(objc_send)
Q:+initialize方法調(diào)用順序?
先調(diào)用父類的+initialize敞贡,再調(diào)用子類的+initialize泵琳;(先初始化父類,再初始化子類誊役,每個類只會初始化1次)获列,子類內(nèi)部+initialize會主動調(diào)用父類的+initialize
runtime探索objc_msgSend內(nèi)部調(diào)用initialize
objc4源碼解讀過程
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需要初始化 && 類未被初始化過就執(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是通過objc_msgSend進行調(diào)用的
- 如果子類沒有實現(xiàn)+initialize蛔垢,會調(diào)用父類的+initialize(所以父類的+initialize可能會被調(diào)用多次)
- 如果分類實現(xiàn)了+initialize击孩,就覆蓋類本身的+initialize調(diào)用
- +load是通過函數(shù)指針指向函數(shù),拿到函數(shù)地址鹏漆,分開來直接調(diào)用的巩梢,直接通過內(nèi)存地址查找調(diào)用的创泄。
Q:load、initialize方法的區(qū)別什么括蝠?
1.調(diào)用方式
1> load是根據(jù)函數(shù)地址直接調(diào)用
2> initialize是通過objc_msgSend調(diào)用2.調(diào)用時刻
1> load是runtime加載類鞠抑、分類的時候調(diào)用(只會調(diào)用1次)
2> initialize是類第一次接收到消息的時候調(diào)用,每一個類只會initialize一次(父類的initialize方法可能會被調(diào)用多次)
Q:load忌警、initialize的調(diào)用順序搁拙?
1.load
1> 先調(diào)用類的load
a) 先編譯的類,優(yōu)先調(diào)用load
b) 調(diào)用子類的load之前法绵,會先調(diào)用父類的load2> 再調(diào)用分類的load
a) 先編譯的分類箕速,優(yōu)先調(diào)用load
2.initialize
1> 先初始化父類
2> 再初始化子類(可能最終調(diào)用的是父類的initialize方法)
第三部分:關(guān)聯(lián)對象
Q:分類聲明屬性,系統(tǒng)都做了什么朋譬?
分類聲明屬性盐茎,系統(tǒng)只生成setter和getter方法的聲明,但是成員變量徙赢、setter和getter方法的實現(xiàn)均沒有字柠。
Q:為什么不能用字典為分類增加實例變量
字典為分類增加實例變量存在全局變量中,內(nèi)存泄漏
多線程訪問會存在同時訪問變量的情況犀忱,還得加鎖處理
每增加一個變量募谎,字典、setter阴汇、getter方法就得重新編寫
Q:Category能否添加成員變量?如果可以节槐,如何給Category添加成員變量搀庶?
不能直接給Category添加成員變量,但是可以間接實現(xiàn)Category有成員變量的效果
默認情況下铜异,因為分類底層結(jié)構(gòu)的限制哥倔,不能添加成員變量到分類中。但可以通過關(guān)聯(lián)對象來間接實現(xiàn)
Q:如何給分類關(guān)聯(lián)對象揍庄?
添加關(guān)聯(lián)對象
// object:當前對象
// key:標記key
//value :關(guān)聯(lián)屬性值
//objc_AssociationPolicy:關(guān)聯(lián)對象策略
void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy)
獲得關(guān)聯(lián)對象
id objc_getAssociatedObject(id object, const void * key)移除所有的關(guān)聯(lián)對象
void objc_removeAssociatedObjects(id object)
關(guān)聯(lián)對象策略
關(guān)聯(lián)對象代碼示例:
#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)對象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"類似的變量是存在常量區(qū)的咆蒿,所以地址會相同
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:表示當前方法的selector方法
Q:關(guān)聯(lián)對象的原理
實現(xiàn)關(guān)聯(lián)對象技術(shù)的核心對象有
- 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);
}
核心對象內(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;
}
關(guān)聯(lián)對象原理:
Q:關(guān)聯(lián)對象是否儲存在類對象內(nèi)存中?
答案:不是的
- 關(guān)聯(lián)對象并不是存儲在被關(guān)聯(lián)對象本身內(nèi)存中
- 關(guān)聯(lián)對象存儲在全局的統(tǒng)一的一個AssociationsManager蚂子,AssociationsHashMap中
Q:設(shè)置關(guān)聯(lián)對象為nil沃测,會發(fā)生什么?
相當于是移除關(guān)聯(lián)對象食茎,內(nèi)部會有一個erase操作
Q:如何移除所有關(guān)聯(lián)對象蒂破?
移除所有的關(guān)聯(lián)對象
void objc_removeAssociatedObjects(id object)
Q:如果類對象銷毀,分類的關(guān)聯(lián)對象會移除么别渔?
會的