天天看點

iOS底層原理篇(三)----類的編譯、連結與加載

我們都知道,App啟動時,代碼中+(void)load方法早于main函數調用,main函數是我們程式的入口,那麼我們程式在main函數之前,都做了些什麼工作呢?今天我們一起去探讨一下main函數之前的流程!

首先我們的應用程式,會依賴很多的庫-----被作業系統寫入記憶體的可執行代碼的二進制!

庫又有靜态庫如.a檔案和動态庫如.framework

  • App啟動之後,main函數加載之前的過程:
    iOS底層原理篇(三)----類的編譯、連結與加載
  • dyld::_main的流程
    iOS底層原理篇(三)----類的編譯、連結與加載
    • 上圖中的兩張圖檔
      iOS底層原理篇(三)----類的編譯、連結與加載
      iOS底層原理篇(三)----類的編譯、連結與加載
    看到dylb::_main流程後,我們能知道,程式如何從dyld調用到objc庫_objc_init()函數運作的過程!

那麼,我們我們接下來看_objc_init()之後的調用流程及類是如何加載到記憶體的!

  • _objc_init(): 引導程式初始化,注冊我們的鏡像通知與dyld.在庫初始化之前被libSystem調用!
    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();		//關于線程key的綁定 例如析構函數設定靜态密鑰key
      	
      	//運作c++靜态構造函數,libc在dyld調用靜态構造函數之前調用_objc_init(),
      	//是以我們必須自己做.
      	static_init();
      	lock_init();//空實作
      	//初始化libobjc的異常處理系統,由map_images()調用
      	exception_init();
      	//注冊鏡像加載通知回調
      	/**
      	注:僅供objc運作時使用
      	映射、未映射和初始化objc映像時要調用的寄存器處理程式
      	Dyld将使用包含objc-image-info部分的鏡像數組調用“mapped”函數
      	那些是dylib的鏡像會自動替換ref計數,是以objc不再需要對它們調用dlopen()來防止它們被解除安裝
      	在調用_dyld_objc_notify_register()期間,dyld将調用已經加載了objc鏡像的“映射”函數
      	在以後的dlopen()調用期間,dyld還将調用“mapped”函數
      	當Dyld在該鏡像中被稱為初始化器時,Dyld将調用“init”函數
      	這是objc調用圖像中的任何+load方法的時候
      	*/
      	_dyld_objc_notify_register(&map_images, load_images, unmap_image);
      }
               
  • map_images分析
    iOS底層原理篇(三)----類的編譯、連結與加載
    iOS底層原理篇(三)----類的編譯、連結與加載
    iOS底層原理篇(三)----類的編譯、連結與加載
    iOS底層原理篇(三)----類的編譯、連結與加載
    iOS底層原理篇(三)----類的編譯、連結與加載
    iOS底層原理篇(三)----類的編譯、連結與加載
    我們上面思維導圖分析了map_images的流程,大緻了解了類的讀取,初始化,映射等的操作,下面我們看看load_images對類的加載!

在講load_images方法之前,我們需要了解一些知識點:

  • 懶加載類與非懶加載類
    • 在read_images()思維導圖中的:對所有類進行重映射步驟中,在源碼中有一段注釋這麼寫的: Realize non-lazy classes (for +load methods and static instances).有道翻譯:實作非惰性類(用于+load方法和靜态執行個體).我們可以看出,在read_images()中,初始化的都是非懶加載的類,既然有非懶加載的類,那麼就應該有懶加載的類,那麼非懶加載與懶加載類是怎麼區分的呢?懶加載的類又是什麼時候初始化的呢?
    • 非懶加載類在編譯期就進行編譯連結實作了,而懶加載類是在我們用到的時候才去調用,初始化!這兩種類的區分方法就是:看這個類的實作中是否實作了+load方法!!!
    • 如果我們在類中實作了+load方法,那麼這個類在編譯期就會進行初始化實作,沒有實作load方法,那麼這個類在read_images()中就不會進行初始化,映射等的操作!
    • 那麼.懶加載的類的調用時機,我們在這裡提一下,後期會講到這個!
    • 我的部落格裡有兩篇是講runtime的博文,其中我們有一個方法印象深刻:消息查找或轉發:lookupimporforward:
      //此處就是懶加載類的調用,沒有實作的類就會走到這裡(消息轉發過程調用!!!)
        //實作這個懶加載的類,非懶加載的類(編譯期實作的)是不會走下面的流程的.
        if (!cls->isRealized()) {
        	cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        }
        static Class realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock) 
        {
        	return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
        }
        static Class realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked) {
        	lock.assertLocked();
        	if (!cls->isSwiftStable_ButAllowLegacyForNow()) {//OC
        		// Non-Swift class. Realize it now with the lock still held.
        		//傳回類的實際類結構.就是進行類的初始化操作!!!
        		realizeClassWithoutSwift(cls);
        		if (!leaveLocked) lock.unlock();
        	} else {//swift
        		// 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;
        }
                 
    • 是以.下面的load_images()也是對已經加入到類清單的類進行load操作!(不包含懶加載的類!)
  • load_images分析:
    void load_images(const char *path __unused, const struct mach_header *mh) {
      	//hasLoadMethods判斷是否實作了+load方法,沒有直接傳回
      	//也就是說,我們加載的是實作了load方法的類(系統的一些類我們不去管)
      	// Return without taking locks if there are no +load methods here.
      	if (!hasLoadMethods((const headerType *)mh)) return;
      	recursive_mutex_locker_t lock(loadMethodLock);
      	
      	// Discover load methods
      	//找到實作了+load方法的類并加入到一個表,友善操作,load方法調用完成,這個表會被釋放.
      	{
      		mutex_locker_t lock2(runtimeLock);
      		prepare_load_methods((const headerType *)mh);
      	}
    
      	// Call +load methods (without runtimeLock - re-entrant)
      	//調用
      	call_load_methods();
      }
               
iOS底層原理篇(三)----類的編譯、連結與加載

上面我們經過分析,知道load方法的調用時機以及是如何調用的!

經過上面的流程,非懶加載類及分類的加載完成,以及load方法的調用時機以及是如何調用的,_objc_init()方法至此調用完成!下一篇我們會談談類的拓展、類拓展與分類的差別、類與分類的關系,以及分類屬性關聯、initialize方法調用時機等内容!

謝謝觀賞!

繼續閱讀