一沽翔,應(yīng)用程序加載回顧
通過前面的學(xué)習(xí)我們對iOS應(yīng)用程序的加載有了一個大致的認(rèn)識部翘,
- 1 系統(tǒng)調(diào)用
exec()
會讓我們的應(yīng)用程序映射到信的地址空間 - 2 然后通過
dyld
進(jìn)行加載砾医、鏈接亡脸、初始化主程序和應(yīng)用程序所依賴的各種動態(tài)庫 - 3 最后在
initializeMainExecutable
方法中進(jìn)過一系列的初始化調(diào)用notifySingle
函數(shù)校镐,該函數(shù)會執(zhí)行一個load_images
的回調(diào) - 4 然后在
doModInitFunctions
函數(shù)內(nèi)部調(diào)用__attribute__((contrustor))
的c++ 函數(shù) - 5 然后
dyld
返回主程序的入口函數(shù)亿扁,開始進(jìn)入主程序的main
函數(shù)。
在main
函數(shù)執(zhí)行過程中鸟廓,其實dyld
還會在流程中初始化libSystem
,而libSystem
又會去初始化libDispatch
,在libDispatch
初始化方法里面又會調(diào)用os_object_int
,在os_object_int
內(nèi)部就會調(diào)用objc_init
,從而才會進(jìn)入我們相隔的類的加載過程从祝。這就是整個應(yīng)用程序加載的大致過程。
二引谜,類的加載
通過上一章節(jié)應(yīng)用程序加載回顧的流程可知牍陌,我們知道相關(guān)的加載,編譯在main
函數(shù)之前员咽,所以在此我們進(jìn)入libObjc
開源的代碼中全局搜索read_image
字段呐赡。、進(jìn)入到相關(guān)的定義如下 :摘取片段展示
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
static bool doneOnce;
bool launchTime = NO;
TimeLogger ts(PrintImageTimes);
骏融。链嘀。。档玻。怀泊。。误趴。霹琼。。。枣申。
既然本文重點是研究類的加載售葡,那么我們就在read_images
中找到關(guān)于類的信息,從而重點的進(jìn)行研究和學(xué)習(xí)忠藤,進(jìn)行一個深刻的了解挟伙。從而達(dá)到我們想要的目的,我們會發(fā)現(xiàn)在此方法中存在一個關(guān)于非懶加載 Realize non-lazy classes (for +load methods and static instances)
代碼如下
for (EACH_HEADER) {
classref_t const *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
const char *mangledName = cls->mangledName();
if (!cls) continue;
addClassTableEntry(cls);
if (cls->isSwiftStable()) {
if (cls->swiftMetadataInitializer()) {
_objc_fatal("Swift class %s with a metadata initializer "
"is not allowed to be non-lazy",
cls->nameForLogging());
}
} // alloc init - 類存在 完備 實例
realizeClassWithoutSwift(cls, nil);
}
}
從此段代碼就知道只是針對非懶加載
實現(xiàn)的類才走到這個方法中模孩,那么懶加載類
又如何進(jìn)行加載的尖阔,那么就讓我們分別對這兩種情況進(jìn)行明確和詳細(xì)的學(xué)習(xí)和探索。
2.1榨咐、非懶加載類
在此方法中我們用斷點調(diào)試相關(guān)的程序介却,我們知道系統(tǒng)類的實現(xiàn)
對我們來說是不可見的,所所以我們研究系統(tǒng)類的實現(xiàn)以及加載難度太大以及成本太高并且是得不償失块茁。所以我們研究自己定義的類最好不過齿坷,我們在自己實現(xiàn)的類中實現(xiàn)+load
方法,
+ (void)load{
NSLog(@"%s",__func__);
}
再次通過mangledName
,斷點篩選出我們所定義的類的實現(xiàn)数焊,定位到當(dāng)前類
通過控制臺打印出相關(guān)結(jié)果:
(lldb) p/x LGPersonName
(const char *) $0 = 0x000000010032ad3e "LGPerson"
此時我們知道胃夏,此處的LGPersonName
還是一個帶地址的名字,并不是一個類昌跌,所以我們繼續(xù)探索究竟是什么時候我們所定義的類加載到內(nèi)存中仰禀,從而進(jìn)行相關(guān)的方法調(diào)用?
順著代碼查找到相關(guān)的realizeClassWithoutSwift
定義下
static Class realizeClassWithoutSwift(Class cls, Class previously)
在中途通過mangledName
篩選到我們當(dāng)前的類
if (!cls) return nil;
if (cls->isRealized()) return cls;
ASSERT(cls == remapClass(cls));
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
以及
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
以上代碼主要的作用就是
- 1 通過
macho
文件里的data
返回一個類的class_ro_t
從而賦值到相應(yīng)的ro
中 即clean-memory
- 2 判斷是否是元類蚕愤,通過第一步的
ro
數(shù)據(jù)答恶,讀取到rw
; - 3 如果非元類,寄進(jìn)行相應(yīng)的數(shù)據(jù)從
ro
到rw
中萍诱,通過rw->set_ro(ro);
操作完成 - 4 完成相關(guān)類的繼承鏈悬嗓,從而為以后的方法查找以及后續(xù)的初始化埋下伏筆
雖然此時我們的類還只是帶地址的一個名字,并沒有實現(xiàn)裕坊,但是我們相關(guān)的ro
和rw
是有值的包竹,驗證如下;
通過以上的方法我們知道實現(xiàn)了相關(guān)的類的信息籍凝,并且成功的將數(shù)據(jù)映射到內(nèi)存周瞎,包括ro
和rw
的賦值都在此時實現(xiàn),所以在此去實現(xiàn)相關(guān)的元類meta
信息,從而在此進(jìn)入到最后的
methodizeClass(cls, previously);
中饵蒂;
跟著斷點調(diào)試進(jìn)入到attachToClass
void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
ASSERT((flags & ATTACH_CLASS) ||
(flags & ATTACH_METACLASS) ||
(flags & ATTACH_CLASS_AND_METACLASS));
const char *mangledName = cls->mangledName();
const char *LGPersonName = "LGPerson";
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
從而進(jìn)行相關(guān)的類的方法
声诸、協(xié)議、的實現(xiàn)退盯;
2.2彼乌、懶加載類
在+load
方法沒有實現(xiàn)的時候泻肯,我們會發(fā)現(xiàn)程序的執(zhí)行和之前的流程不太一樣,程序會先進(jìn)入到底層的objc_msgSent
,因為我們在創(chuàng)建
LGPerson *person = [LGPerson alloc];
程序的底層會進(jìn)行一次消息轉(zhuǎn)發(fā)慰照,從而進(jìn)入到消息的查找從而進(jìn)入到lookUpImpOrForward
然后進(jìn)入程序?qū)崿F(xiàn)的判斷
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
進(jìn)入到realizeClassMaybeSwiftAndLeaveLocked
方法實現(xiàn)如下:
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
const char *mangledName = cls->mangledName();
const char *LGPersonName = "LGPerson";
if (strcmp(mangledName, LGPersonName) == 0) {
auto kc_ro = (const class_ro_t *)cls->data();
printf("%s: 這個是我要研究的 %s \n",__func__,LGPersonName);
}
if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
// Non-Swift class. Realize it now with the lock still held.
// fixme wrong in the future for objc subclasses of swift classes
realizeClassWithoutSwift(cls, nil);
if (!leaveLocked) lock.unlock();
} else {
// Swift class. We need to drop locks and call the Swift
// runtime to initialize it.
lock.unlock();
cls = realizeSwiftClass(cls);
ASSERT(cls->isRealized()); // callback must have provoked realization
if (leaveLocked) lock.lock();
}
return cls;
}
從而和非懶加載類一樣 執(zhí)行realizeClassWithoutSwift
和 methodizeClass
的流程灶挟,從而實現(xiàn)了該類的信息。
總結(jié):通過以上兩個類的對比和學(xué)習(xí)的過程毒租,對iOS開發(fā)過程中的類的懶加載
和非懶加載
有了一個深刻的認(rèn)知稚铣,
三,分類的加載
3.1蝌衔、分類的概念和結(jié)構(gòu)
在main.mm
文件中我們通過手寫分類實現(xiàn)相關(guān)的定義
@interface LGPerson (LG)
@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod2;
+ (void)cate_sayClassMethod;
@end
@implementation LGPerson (LG)
- (void)cate_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)cate_sayClassMethod{
NSLog(@"%s",__func__);
}
@end
通過clang
命令生成相關(guān)的cpp文件
進(jìn)入到這個文件中查詢到分類的結(jié)構(gòu)定義
// 分類 : 方法 - attachtoclass
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;
};
- 1
name
代表分類的名字 - 2
cls
代表分類的類 - 3
instance_methods
實例方法榛泛,也即是對象方法 - 4
class_methods
類方法 - 5
protocols
協(xié)議列表 - 6
properties
屬性列表
通過以上我們清楚的知道了一個分類的內(nèi)部結(jié)構(gòu)蝌蹂,那么分類是如何同類的加載載入到內(nèi)存的噩斟,接下來就進(jìn)入源碼分析流程;
3.2孤个、分類何時加載到應(yīng)用程序
隨即我們進(jìn)入到類的加載執(zhí)行的methodizeClass(cls, previously);
隨著代碼的注釋我們知道剃允,這就是程序加載分類的機制和時機
// Attach categories - 分類
methodizeClass(cls, previously)
通過定位到我們的類然后進(jìn)行打印相關(guān)的列表
通過list 如果無序的時候我們可以進(jìn)行相關(guān)的排序操作
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
if (rwe) rwe->methods.attachLists(&list, 1);
}
沒有進(jìn)行prepareMethodLists
的結(jié)果是
(lldb) p list->get(0)
(method_t) $5 = {
name = "cate_instanceMethod1"
types = 0x0000000100001dfb "v16@0:8"
imp = 0x0000000100001a00 (KCObjc`-[LGPerson(LG) cate_instanceMethod1] at main.m:29)
}
p list->get(1)
(method_t) $6 = {
name = "cate_instanceMethod3"
types = 0x0000000100001dfb "v16@0:8"
imp = 0x0000000100001a30 (KCObjc`-[LGPerson(LG) cate_instanceMethod3] at main.m:33)
}
p list->get(2)
(method_t) $7 = {
name = "cate_instanceMethod2"
types = 0x0000000100001dfb "v16@0:8"
imp = 0x0000000100001a60 (KCObjc`-[LGPerson(LG) cate_instanceMethod2] at main.m:37)
}
而進(jìn)行prepareMethodLists
的結(jié)果是
p list->get(0)
(method_t) $8 = {name = "cate_instanceMethod1"
types = 0x0000000100001dfb "v16@0:8"
imp = 0x0000000100001a00 (KCObjc`-[LGPerson(LG) cate_instanceMethod1] at main.m:29)
}
p list->get(1)
(method_t) $10 = {
name = "cate_instanceMethod2"
types = 0x0000000100001dfb "v16@0:8"
imp = 0x0000000100001a60 (KCObjc`-[LGPerson(LG) cate_instanceMethod2] at main.m:37)
}
p list->get(2)
(method_t) $9 = {
name = "cate_instanceMethod3"
types = 0x0000000100001dfb "v16@0:8"
imp = 0x0000000100001a30 (KCObjc`-[LGPerson(LG) cate_instanceMethod3] at main.m:33)
}
從而去加載相關(guān)的分類的屬性,方法齐鲤,協(xié)議等
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
通過以上想分類的實現(xiàn)斥废,從而對內(nèi)存中的rwe
進(jìn)行調(diào)用和開辟
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
通過以上的attachList
方法,從而循環(huán)對分類的屬性
给郊,協(xié)議
,方法等操作
具體實現(xiàn)如下
- 1 判斷要添加的數(shù)量是否為0牡肉,如果是0 ,則直接返回
- 2 然后對
ATTACH_BUFSIZ - ++mcount
所有的方法進(jìn)行倒序插入 - 3判斷 attachLists的list_array_tt 二維數(shù)組有多個一位數(shù)組淆九;
如果是统锤,說明多對多的關(guān)系
通過realloc對容器進(jìn)行重新分配,大小是原來的大小加上新增的大小
通過memmove把原來的數(shù)據(jù)移動到尾部
最后把新的數(shù)據(jù)拷貝到容器的起始位置炭庙,
- 4 如果調(diào)用的attachLists的list_array_tt二維數(shù)組為空并且新增空間大小數(shù)目是1饲窿,則直接取attachList的第一個list
- 5 如果當(dāng)前調(diào)用的attachLists的list_array_tt二維數(shù)組只有一個一位數(shù)組,
如果是焕蹄,說明是一對多的關(guān)系
通過realloc對容器進(jìn)行重新分配逾雄,大小是原來的大小加上新增的大小
因為原來只有一個一位數(shù)組,所以直接賦值到新array的最后一個位置
最后把新的數(shù)據(jù)拷貝到容器的起始位置
四腻脏,總結(jié)
這就是整個類鸦泳、分類的加載大致過程,雖然很多地方理解還不是很到位永品,但是我相信只要堅持下去辽故,總會有收獲的。