知識準備
首先我們要知道一個場景,我們點擊屏幕上的應用,到我們看到應用完全展示,並可以操作,這個過程中,系統、runtime、我們自己的代碼,都做了大量的工作,這個過程有很多優秀的博文已經詳細的講述過,例如深入理解iOS App的啓動過程 —by 爲自己丶拼個未來,這篇文章的內容其實絕大部分來自於蘋果WWDC2016中的一個官方視頻,如果對App啓動過程不熟悉的同學可以這兩個結合着看一下.
Runtime在啓動過程中做了什麼?
首先我們要知道啓動過程中涉及的步驟
- 1, Dyld 加載可執行文件
- 2,加載動態庫(遞歸執行加載所有)
- 3,Rebase(解決Mach-O因ASLR導致的內部符號指針指向問題)
- 4,Bind(解決Mach-O因ASLR導致的外部符號指針指向問題)
- 5,Objc(解決類、類別加載等問題)
- 6,Initializers(執行load、C/C++靜態初始化和標記爲attribute(constructor)的方法)
不言而喻,我們這篇博文寫的就是第五步的詳細解析.
註冊dyld通知 objc-os.mm line 913
void _objc_init(void)
{
....各種初始化過程省略
// 註冊map_images,load_images,unmap_image方法對應的通知,根據App啓動過程中dyld初始化ImageLoader加載各種image的過程,這裏很明顯是要配合dyld去做一些image加載時的操作.
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
看到這裏,我們大可斷定,在App啓動過程中除了必要的環境初始化,Runtime主要的操作都在map_images
、load_images
.
處理dyld映射的image objc-runtime-new.mm line 2938
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);
}
// 這個方法比較長,大約有150行,我們只留關鍵代碼
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
// 所有images的數量如果大於0
if (hCount > 0) {
// 這裏開始進入核心邏輯
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
核心邏輯1 _read_images objc-runtime-new.mm line 3244
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
// image頭部信息
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);
runtimeLock.assertLocked();
// 定義一個EACH_HEADER宏,用來for循環便利每一個header信息
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
if (!doneOnce) {
doneOnce = YES;
launchTime = YES;
#if SUPPORT_NONPOINTER_ISA
// Disable non-pointer isa under some conditions.
# if SUPPORT_INDEXED_ISA
// Disable nonpointer isa if any image contains old Swift code
// Swift3.0之前的版本處理
for (EACH_HEADER) {
if (hi->info()->containsSwift() &&
hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
{
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app or a framework contains Swift code "
"older than Swift 3.0");
}
break;
}
}
# endif
// 如果是OSX系統,這裏我們可以不用看
# if TARGET_OS_OSX
//
// Disable non-pointer isa if the app is too old
// (linked before OS X 10.11)
if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) {
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app is too old (SDK version " SDK_FORMAT ")",
FORMAT_SDK(dyld_get_program_sdk_version()));
}
}
// Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
// New apps that load old extensions may need this.
for (EACH_HEADER) {
if (hi->mhdr()->filetype != MH_EXECUTE) continue;
unsigned long size;
if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app has a __DATA,__objc_rawisa section");
}
}
break; // assume only one MH_EXECUTE image
}
# endif
#endif
if (DisableTaggedPointers) {
disableTaggedPointers();
}
// 初始化標記指針混淆器,ALSR技術的執行對象
initializeTaggedPointerObfuscator();
// 打印加載類數量信息
if (PrintConnecting) {
_objc_inform("CLASS: found %d classes during launch", totalClasses);
}
// 這裏是爲了給類加載準備一個hashmap,因爲此map的荷載係數爲4/3,也就是當其內容佔據表容量的3/4時就不能夠繼續往裏正常存儲了,一般會對其進行擴容,(擴容方式一般爲容量翻倍,可以參考關聯對象的hash表的擴容方式)
// 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);
ts.log("IMAGE TIMES: first time tasks");
}
// 將所有的SEL註冊到hash表中,這裏和上一步爲class準備的hash表不是同一張
// Fix up @selector references
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) 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的操作
SEL sel = sel_registerNameNoLock(name, isBundle);
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
ts.log("IMAGE TIMES: fix up selector references");
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
// 遍歷所有懶加載的類,並實現
for (EACH_HEADER) {
if (! mustReadClasses(hi, hasDyldRoots)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
// 從頭部信息中讀取classlist的指針位置
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
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;
}
}
}
ts.log("IMAGE TIMES: discover classes");
// Fix up remapped classes 修復重新映射的類
// Class list and nonlazy class list remain unremapped. 類列表和非惰性類列表保持未映射狀態。
// Class refs and super refs are remapped for message dispatching. 類引用和超級引用被重新映射以進行消息分派。
if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
// fixme why doesn't test future1 catch the absence of this?
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
}
}
ts.log("IMAGE TIMES: remap classes");
// 修復舊的函數指針遺留
#if SUPPORT_FIXUP
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
for (i = 0; i < count; i++) {
fixupMessageRef(refs+i);
}
}
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();
// 遍歷所有的協議列表,並將其加載到協議的hash表中
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
ASSERT(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->hasPreoptimizedProtocols();
// 如果image在shared cache中,則跳過這一步,因爲dyld在加載過程中,有一個shared cache存在,它裏邊存儲了所有的動態庫,這樣不僅提高了應用的image加載過程,也進而加快了應用啓動速度.讓系統更加的高效的運轉.
if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) {
if (PrintProtocols) {
_objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
hi->fname());
}
continue;
}
bool isBundle = hi->isBundle();
// 在編譯器中讀取並初始化協議
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
ts.log("IMAGE TIMES: discover protocols");
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
// 修復協議列表引用,優化後的images可能是正確的,但是並不能確定
for (EACH_HEADER) {
// At launch time, we know preoptimized image refs are pointing at the
// shared cache definition of a protocol. We can skip the check on
// launch, but have to visit @protocol refs for shared cache images
// loaded later.
if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
continue;
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}
ts.log("IMAGE TIMES: fix up @protocol references");
// 遍歷所有的類別並處理.
// Discover categories.
for (EACH_HEADER) { // 找到當前類,查找類對應的category數組
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
// 遍歷所有類
auto processCatlist = [&](category_t * const *catlist) {
// 遍歷類對應的category數組
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
// 找不到對應的class
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()) {
// /存根類永遠不會實現。
// 存根類在初始化之前不知道它們的元類,
// 因此我們必須將帶有類方法或屬性的類別添加到存根本身。
// methodizeClass()將找到它們並酌情將它們添加到元類中。
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
// 如果目標類已經實現, 首先將category的內容合併到目標類中,
// 如果沒有實現,則實現後再進行這合併操作
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
if (cls->isRealized()) {
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}
// 這裏和上邊邏輯一樣,只不過是對meta class進行的操作,上邊是對普通的class
// 所以我們也可以對meta class增加類別的
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
};
processCatlist(_getObjc2CategoryList(hi, &count));
processCatlist(_getObjc2CategoryList2(hi, &count));
}
ts.log("IMAGE TIMES: discover categories");
// Category discovery MUST BE Late to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
// 實現所有非藍加載的類,load方法和靜態實例變量
// 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]);
if (!cls) continue;
// 將類類信息註冊到hash table中
addClassTableEntry(cls);
// 處理swift類
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't disallow all Swift classes because of
// classes like Swift.__EmptyArrayStorage
}
realizeClassWithoutSwift(cls, nil);
}
}
ts.log("IMAGE TIMES: realize non-lazy classes");
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
Class cls = resolvedFutureClasses[i];
if (cls->isSwiftStable()) {
_objc_fatal("Swift class is not allowed to be future");
}
realizeClassWithoutSwift(cls, nil);
cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
}
free(resolvedFutureClasses);
}
ts.log("IMAGE TIMES: realize future classes");
if (DebugNonFragileIvars) {
realizeAllClasses();
}
// 打印預優化信息
// Print preoptimization statistics
if (PrintPreopt) {
static unsigned int PreoptTotalMethodLists;
static unsigned int PreoptOptimizedMethodLists;
static unsigned int PreoptTotalClasses;
static unsigned int PreoptOptimizedClasses;
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) {
_objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
"in %s", hi->fname());
}
else if (hi->info()->optimizedByDyld()) {
_objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
"in %s", hi->fname());
}
classref_t const *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
PreoptTotalClasses++;
if (hi->hasPreoptimizedClasses()) {
PreoptOptimizedClasses++;
}
const method_list_t *mlist;
if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) {
PreoptTotalMethodLists++;
if (mlist->isFixedUp()) {
PreoptOptimizedMethodLists++;
}
}
if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) {
PreoptTotalMethodLists++;
if (mlist->isFixedUp()) {
PreoptOptimizedMethodLists++;
}
}
}
}
_objc_inform("PREOPTIMIZATION: %zu selector references not "
"pre-optimized", UnfixedSelectors);
_objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
PreoptOptimizedMethodLists, PreoptTotalMethodLists,
PreoptTotalMethodLists
? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists
: 0.0);
_objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
PreoptOptimizedClasses, PreoptTotalClasses,
PreoptTotalClasses
? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
: 0.0);
_objc_inform("PREOPTIMIZATION: %zu protocol references not "
"pre-optimized", UnfixedProtocolReferences);
}
#undef EACH_HEADER
}
總結_read_images
的內容爲:
- 1.初始化標記指針混淆器
- 2.初始化類註冊用的hashmap
- 3.將所有的SEL註冊到hashmap中
- 4.初始化所有懶加載的類
- 5.進行所有類的重映射
- 6.遍歷所有的協議列表,將其初始化並加載到協議的hash表中
- 7.處理所有的類別,將其添加到對應類結構中(包含普通的class和meta class)
- 8.實現所有非藍加載的類,load方法和靜態實例變量
- 9.打印預優化信息
通過以上的源碼分析我們得知,_read_images
方法所做的事情主要是做了一些預優化的動作,方便後邊的load_images
以及整個程序運行過程的便捷和效率,
核心邏輯2 load_images
objc-runtime-new.mm line 2955
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// 找到所有的load方法,這裏的load方法就是我們OC中class裏經常用的load方法
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// 執行所有的load方法,
call_load_methods();
}
核心邏輯2.1 找到擁有load方法的類和類別,並記錄. prepare_load_methods
objc-runtime-new.mm line 3698
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
// 找到非懶加載的類列表
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
// 將 所有有load方法類記錄到loadable_classes中,在此方法中,會遞歸調用schedule_class_load(cls->superclass);所以,記錄的順序是子類依次到父類的繼承者鏈
schedule_class_load(remapClass(classlist[i]));
}
// 找到非懶加載的類別列表
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
// 將所有有load方法的類別記錄到loadable_categories中
add_category_to_loadable_list(cat);
}
}
核心邏輯2.2 根據核心邏輯2.1中記錄的類和類別,執行load方法 call_load_methods
objc-loadmethod.mm line 337
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
// 自動釋放池
void *pool = objc_autoreleasePoolPush();
do {
// 執行所有記錄的class的load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2.執行一次類別的load
more_categories = call_category_loads();
// 3. 如果有更多的class或者麼有執行過load方法的類別,則繼續
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
在核心邏輯2.1中記錄擁有load
的類和類別的時候是子類
->父類
->父類的父類
這樣記錄在數組中的.
在核心邏輯2.2中執行load方法時,是根據數組的尾部index
依次減一進行循環的,又因爲先執行class
的load
方法,再執行類別的,所以,load方法執行順序爲父類
->子類
->類別
至此Runtime在App加載過程中的動作我們都分析完了
總結一下
- 在預處理過程中,大量使用了hashmap,在關聯對象中也是一樣,正式因爲這種數據結構的高效性,得到了開發者的青睞,在我們實際開發中也可以將一些複雜的數據關係轉換成hashmap或者字典來處理,這樣雖然會損失一些空間,但是O(1)的時間複雜度還是很香的.
- ASLR是一種針對緩衝區溢出的安全保護技術,通過對堆、棧、共享庫映射等線性區佈局的隨機化,通過增加攻擊者預測目的地址的難度,防止攻擊者直接定位攻擊代碼位置,達到阻止溢出攻擊的目的的一種技術,這個技術在常見系統中都有應用,因爲它的應用纔有了dyld加載過程中的Rebase和Bind步驟,
load
方法的加載順序一直是一個iOS熱點問題,我們現在也能夠知道爲什麼他的執行順序是父類
->子類
->類別
,不同類別之間的load
方法執行順序和dyld加載類別的順序有關,這個我們可以在XCode中控制,百度一下,很多這方面的資料.- 在加載過程的代碼中有很多對老版本和swift的處理邏輯,這些都是歷史遺留問題但是我們可以看到頻繁的版本變更帶來的代碼整潔性,可讀性,結構都有很大的影響,蘋果的開發者運用了大量的宏定義,預編譯手段處理版本兼容問題,這是值得我們學習的.