在上兩章節,咱們已經瞭解了一個App從編譯到main函數的調用,發生了什麼事情,而且咱們知道了_objc_init在加載鏡像文件時,會在dyld動態連接器中去註冊,二者以前經過此來進行通信。可是dyld加載相關鏡像文件後,這些鏡像文件是如何加載到內存當中的,是以什麼方式存在於內存當中的,這就是本章探究的核心。html
相關文章傳送門:swift
☞iOS底層學習 - 類的前世此生(一)bash
咱們知道dyld的主體流程就是連接動態庫和鏡像文件,那麼objc的鏡像文件自己是如何進行讀取到內存中的,咱們從源碼來解讀app
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
複製代碼
經過源碼,咱們基本能夠得出如下結論:函數
_objc_init
由libSystem
庫調用下面咱們逐步進行分析,objc相關類等,是如何加載的oop
在鏡像加載以前,objc
進行了一系列的準備工做,咱們來逐步分析,以下圖:post
根據字面意思咱們能夠得出,這個方法是讀取影響運行時的環境變量,可使用 export OBJC_HELP=1
來打印環境變量,從而進行一些調試,能夠再Xcode中進行設置,從而達到想要的效果打印。相關能夠參考OBJC_HELP學習
OBJC_DISABLE_NONPOINTER_ISA
這個能夠設置non-pointer
的ISA,ISA的值不須要和mask
進行與操做,直接指向OBJC_PRINT_LOAD_METHODS
這個能夠打印類和分類的load
方法,對咱們進行啓動優化頗有幫助。這個函數是關於線程Key的綁定,好比線程數據的析構函數優化
根據註釋能夠得出,這個函數主要作了以下事情
libc
會調用_objc_init()
,因此必須在此前調用/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors,
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
}
複製代碼
是一個空實現,說明objc採用的是C++的加鎖機制
初始化 libobjc的異常處理系統,用來監控崩潰等,好比未實現的方法
經過上一章,咱們對這個方法已經有了瞭解,這是一個dyld的註冊回調函數,從而讓dyld能夠連接加載鏡像
objc_image_info
的鏡像文件的數組,回調給mapped
函數//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded. During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images. During any later dlopen() call,
// dyld will also call the "mapped" function. Dyld will call the "init" function when dyld would be called
// initializers in that image. This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
複製代碼
這個方法是將鏡像加載到內存時候,觸發的主要方法,因此咱們主要來探究這個方法是怎樣將數據,類,分類,方法等以什麼方式加載到內存中的。
/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
複製代碼
經過map_images_nolock
的源碼咱們能夠發現,若是hCount
表示鏡像文件的個數,則調用_read_images
函數來進行加載鏡像文件。因此加載內存確定在此
_read_images
解析因爲代碼比較長,咱們先作一個大致的歸納,而後逐步進行研究,基本處理以下:
第一次加載全部類到表中
gdb_objc_realized_classes
爲全部類的表-包括實現和未實現的
allocatedClasses
包含使用objc_allocateClassPair
分配的全部類(元類)的表
對全部的類作重映射
將全部的SEL
註冊到namedSelector
表中
修復舊的objc_msgSend_fixup
調用致使一些消息沒有處理
將全部的Protocol
都添加到protocol_map
表中
對全部的Protocol
作重映射,獲取到引用
初始化全部非懶加載的類,進行rw
、ro
等操做
遍歷已經標記的懶加載的類,並作相應的初始化
處理全部的Category
,包括類和元類
初始化全部未初始化的類
下面咱們主要對類的加載來進行重點的分析
變量doneOnce
表示這個操做只進行一次,由於是建立表的操做,因此只須要一次建立便可,主要的代碼以下
if (!doneOnce) {
doneOnce = YES;
...
initializeTaggedPointerObfuscator();
// namedClasses
// Preoptimized classes don't go in this table. // 4/3 is NXMapTable's load factor
//✅實例化存儲類的哈希表,而且根據當前類數量作動態擴容
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
//✅建立一張包含全部的類和元類的表
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
//✅建立只包含已經初始化的類的表
allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
ts.log("IMAGE TIMES: first time tasks");
}
複製代碼
類的重映射相關代碼以下。主要是從列表中遍歷出類,並進行處理和添加到相對應的表中
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
for (EACH_HEADER) {
// ✅從編譯後的類列表中取出全部類,獲取到的是一個classref_t類型的指針
classref_t *classlist = _getObjc2ClassList(hi, &count);
if (! mustReadClasses(hi)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
for (i = 0; i < count; i++) {
// ✅數組中會取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系統類,例如CF、Fundation、libdispatch中的類。以及本身建立的類
// ✅這時候的類只有相對應的地址,無其餘信息
Class cls = (Class)classlist[i];
// ✅經過readClass函數獲取處理後的新類,下面具體分析readClass
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
//✅ 初始化全部懶加載的類須要的內存空間 - 如今數據沒有加載到的 - 連類都沒有初始化的
if (newCls != cls && newCls) {
// Class was moved but not deleted. Currently this occurs
// only when the new class resolved a future class.
// Non-lazily realize the class below.
//✅ 將懶加載的類添加到數組中
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
複製代碼
下面看readClass
是如何處理類的?
咱們能夠看到有以下圖的代碼,裏面對cls
的rw
等進行了處理,咱們知道rw
裏存了類的方法等,因此是否是在這裏處理的呢?
可是咱們在方法裏打斷點,發現並無執行,說明咱們建立的類和系統方法的類都沒有走這個方法,因此類的rw
數據填充並非在此
經過紅框中的判斷,咱們可得,這個判斷條件是處理專門針對將來的待處理的類的特殊操做
那麼繼續向下能夠看到以下代碼,能夠看到主要是執行了addNamedClass
和addClassTableEntry
兩個函數
if (headerIsPreoptimized && !replacing) {
// class list built in shared cache
// fixme strict assert does not work because of duplicates
// assert(cls == getClass(name));
assert(getClassExceptSomeSwift(mangledName));
} else {
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
}
複製代碼
查看addNamedClass
相關代碼,主要將類添加到底層總的哈希表中
/***********************************************************************
* addNamedClass
* Adds name => cls to the named non-meta class map.
* Warns about duplicate class names and keeps the old mapping.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked();
Class old;
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);
// getMaybeUnrealizedNonMetaClass uses name lookups.
// Classes not found by name lookup must be in the
// secondary meta->nonmeta table.
addNonMetaClass(cls);
} else {
//✅ 將類添加到總表中
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
assert(!(cls->data()->flags & RO_META));
// wrong: constructed classes are already realized when they get here
// assert(!cls->isRealized());
}
複製代碼
查看addClassTableEntry
相關代碼,由於當前類已經有了地址,進行了初始化,因此也要添加到allocatedClasses
哈希表中
/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static void addClassTableEntry(Class cls, bool addMeta = true) {
runtimeLock.assertLocked();
// This class is allowed to be a known class via the shared cache or via
// data segments, but it is not allowed to be in the dynamic table already.
assert(!NXHashMember(allocatedClasses, cls));
if (!isKnownClass(cls))
NXHashInsert(allocatedClasses, cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
複製代碼
至此,初始化類已經添加到兩張表中了
SEL
添加到表中SEL
相關代碼的處理以下,主要也是一個表寫入的操做,寫入了namedSelector
表中,和類並非一張表
//✅ 將全部SEL都註冊到哈希表中,是另一張哈希表
// Fix up @selector references
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->isPreoptimized()) continue;
bool isBundle = hi->isBundle();
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
//✅ 註冊SEL的操做
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
}
複製代碼
Protocol
都添加到protocol_map
表中相關代碼以下。
// Discover protocols. Fix up protocol refs.
//✅ 遍歷全部協議列表,而且將協議列表加載到Protocol的哈希表中
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
//✅ cls = Protocol類,全部協議和對象的結構體都相似,isa都對應Protocol類
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
//✅ 獲取protocol哈希表
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->isPreoptimized();
bool isBundle = hi->isBundle();
//✅ 從編譯器中讀取並初始化Protocol
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
複製代碼
// Realize non-lazy classes (for +load methods and static instances)
//✅ 實現非懶加載的類,對於load方法和靜態實例變量
for (EACH_HEADER) {
//✅ 獲取到非懶加載類的列表
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
//✅ 從鏡像列表中映射出來
Class cls = remapClass(classlist[i]);
// printf("non-lazy Class:%s\n",cls->mangledName());
if (!cls) continue;
// hack for class __ARCLite__, which did not get this above
#if TARGET_OS_SIMULATOR
if (cls->cache._buckets == (void*)&_objc_empty_cache &&
(cls->cache._mask || cls->cache._occupied))
{
cls->cache._mask = 0;
cls->cache._occupied = 0;
}
if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache &&
(cls->ISA()->cache._mask || cls->ISA()->cache._occupied))
{
cls->ISA()->cache._mask = 0;
cls->ISA()->cache._occupied = 0;
}
#endif
//✅ 再次插入到表allocatedClasses表中
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());
}
// fixme also disallow relocatable classes
// We can not disallow all Swift classes because of
// classes like Swift.__EmptyArrayStorage
}
//✅ 實現全部非懶加載的類(實例化類對象的一些信息,例如rw)
realizeClassWithoutSwift(cls);
}
}
複製代碼
查看realizeClassWithoutSwift
相關代碼
ro
表示readonly,是在編譯時刻就已經賦值的,可是此時rw
還並無賦值,因此這一步,主要是初始化rw
// fixme verify class is not in an un-dlopened part of the shared cache?
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
}
複製代碼
經過下面的代碼咱們能夠發現,此時對類的superclass
和isa
進行了處理,會根據類的繼承鏈關係進行遞歸操做,知道類爲nil,也就是NSObject的父類
if (!cls) return nil;
...
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
cls->superclass = supercls;
cls->initClassIsa(metacls);
複製代碼
最後,咱們發現函數最後調用了methodizeClass
方法,根據明明,猜想是方法等的初始化
static Class realizeClassWithoutSwift(Class cls)
{
...
// Attach categories
methodizeClass(cls);
return cls;
}
複製代碼
在methodizeClass
函數的實現中,咱們發現了,剛剛初始化的rw
的賦值,是從ro
中取出相關數據,直接賦值給rw
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
複製代碼
那麼attachLists
是如何插入數據的呢
根據代碼咱們能夠發現,主要經過把oldList
向後偏移addedCount
的位置,而後把新的addedLists
總體插入到表的前面,從而實現分類的方法覆蓋本類同名方法,因此分類的方法會比原方法先調用,並無覆蓋
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;//10
uint32_t newCount = oldCount + addedCount;//4
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;// 10+4
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
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]));
}
}
複製代碼
一個類的加載的主體流程以下
read_images
內部先建立一個全局類的表gdb_objc_realized_classes
和一個已經初始化的類的表allocatedClasses
,以後對類進行初始化,並加載到表中,而後把SEL
和Protocol
等也映射到內存對應的表中中去,和類並非一個表,並在對非懶加載類進行處理的時候,經過realizeClassWithoutSwift
對ro
進行賦值,而且初始化rw
,以後經過methodizeClass
對rw
賦值,完成數據的加載
至此,一個類所須要屬性的賦值加載都已經完成