在前幾篇文章中咱們研究了對象、類和方法,此次咱們就來研究一下在開發中很是重要的類到底是如何加載的。swift
咱們從_objc_init
函數開始看起,其實在這以前還包括dyld
對動態庫的加載、連接等一系列操做,而後纔會來到_objc_init
函數,這個過程咱們往後再另出文章研究。數組
先來看源碼:緩存
/***********************************************************************
* _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);
}
複製代碼
能夠看來這裏面調用了多個函數,咱們來依次解析一下:bash
這個函數的做用主要是讀取影響運行時的環境變量,若是須要還能夠打印環境變量,在其內部有這樣一段代碼:app
// Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
if (PrintHelp || PrintOptions) {
if (PrintHelp) {
_objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
_objc_inform("OBJC_HELP: describe available environment variables");
if (PrintOptions) {
_objc_inform("OBJC_HELP is set");
}
_objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
}
if (PrintOptions) {
_objc_inform("OBJC_PRINT_OPTIONS is set");
}
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
}
}
複製代碼
若是咱們把for
循環拿到上面去,就會獲得一些系統環境變量的信息:函數
objc[15152]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[15152]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[15152]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[15152]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[15152]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[15152]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[15152]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[15152]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[15152]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[15152]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[15152]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[15152]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[15152]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[15152]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[15152]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[15152]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[15152]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[15152]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[15152]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[15152]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[15152]: OBJC_PRINT_CUSTOM_RR: log classes with un-optimized custom retain/release methods
objc[15152]: OBJC_PRINT_CUSTOM_AWZ: log classes with un-optimized custom allocWithZone methods
objc[15152]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
複製代碼
咱們能夠經過改變環境變量的值來達到調試的目的。oop
這個函數主要做用是對於線程key的綁定優化
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
_objc_pthread_key = TLS_DIRECT_KEY;
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
複製代碼
/***********************************************************************
* 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]();
}
}
複製代碼
這個函數做用是運行C++靜態構造函數,在dylb調用咱們的靜態構造函數以前libc就會調用_objc_init
,因此須要本身實現。ui
void lock_init(void)
{
}
複製代碼
這個裏面是空實現,因此咱們也不知道里面究竟作了什麼。this
/***********************************************************************
* exception_init
* Initialize libobjc’s exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
複製代碼
這個函數主要是初始化libobjc的異常處理系統,註冊相應的監聽回調機制,從而監控異常。
//
// 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);
複製代碼
從註釋中咱們能夠知道:
objc-image-info
的鏡像文件的數組回調給mapped
函數。dyld_objc_notify_register
期間,dyld將調用mapped
函數來使用已加載的objc鏡像文件。initializers
的時候會調用init
函數。從這一系列流程咱們能夠得知,前面幾個函數基本都是準備條件,這最後一個函數_dyld_objc_notify_register
纔是加載類的開始。
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
複製代碼
咱們看到_dyld_objc_notify_register
有三個參數,map_images
在image加載到內存的時候會觸發,load_images
在初始化image的時候會觸發,unmap_image
在移除image的時候觸發。
/***********************************************************************
* 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
函數裏調用了map_images_nolock
函數,map_images_nolock
裏調用了_read_images
函數,這個函數正是map_images
裏的核心內容。
_read_images
函數的代碼不少,咱們須要分段來進行研究。
if (!doneOnce) {
doneOnce = YES;
......
// 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");
}
複製代碼
變量doneOnce控制了這段代碼只會執行一次,首先建立了兩張表,gdb_objc_realized_classes
這張表存儲了不在dyld共享緩存裏的全部的類,包括已經實現的和沒實現的,其容量是全部類數量的4/3。allocatedClasses
這張表只存儲已經初始化的類。這麼作的目的猜想是在使用的時候只帶着allocatedClasses
這張小表就行了,效率也高。
// 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函數獲取處理後的新類,
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");
複製代碼
這裏的重點在於readClass
這個函數,在這個函數中能夠看到以下代碼:
Class replacing = nil;
if (Class newCls = popFutureNamedClass(mangledName)) {
// This name was previously allocated as a future class.
// Copy objc_class to future class's struct. // Preserve future's rw data block.
if (newCls->isAnySwift()) {
_objc_fatal("Can’t complete future class request for '%s' "
"because the real class is too big.",
cls->nameForLogging());
}
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro;
memcpy(newCls, cls, sizeof(objc_class));
rw->ro = (class_ro_t *)newCls->data();
newCls->setData(rw);
freeIfMutable((char *)old_ro->name);
free((void *)old_ro);
addRemappedClass(cls, newCls);
replacing = cls;
cls = newCls;
}
複製代碼
乍一看好像在這裏會進行ro的讀取和rw的賦值,但其實若是咱們這個判斷條件上打個斷點會發現程序跟不會走到這裏,也就是說通常的系統類和自定義類並不會走這裏,只有符合popFutureNamedClass
條件的類纔會走這裏。接着往下看:
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
複製代碼
/***********************************************************************
* 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());
}
複製代碼
addNamedClass
做用是將當前類插入到總表gdb_objc_realized_classes
中。
/***********************************************************************
* 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);
}
複製代碼
addClassTableEntry
做用是將當前類插入到allocatedClasses
這張表中。
在readClass
後咱們會拿到一個newCls,用它來和cls作比較,不一樣的話就會作一些特殊處理,但在實際調試的過程當中並無走,在readClass
中咱們知道只有符合popFutureNamedClass
條件的類纔會走特殊處理,走了纔會致使newCls和cls不同,因此這裏的符合條件也是同樣的,通常的系統類和自定義類並不會走。
接下來是修復重映射,不過通常走不進來,暫時也不用過多關注。
// 主要是修復重映射 - 通常走不進來
// 將未映射Class和Super Class重映射,被remap的類都是非懶加載的類
if (!noClassesRemapped()) {
for (EACH_HEADER) {
// 重映射Class,注意是從_getObjc2ClassRefs函數中取出類的引用
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");
複製代碼
namedSelectors
表中// 將全部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);
}
}
}
ts.log("IMAGE TIMES: fix up selector references");
複製代碼
再來看一下sel_registerNameNoLock
的源碼:
static SEL __sel_registerName(const char *name, bool shouldLock, bool copy)
{
SEL result = 0;
if (shouldLock) selLock.assertUnlocked();
else selLock.assertLocked();
if (!name) return (SEL)0;
result = search_builtins(name);
if (result) return result;
conditional_mutex_locker_t lock(selLock, shouldLock);
if (namedSelectors) {
result = (SEL)NXMapGet(namedSelectors, name);
}
if (result) return result;
// No match. Insert.
if (!namedSelectors) {
namedSelectors = NXCreateMapTable(NXStrValueMapPrototype,
(unsigned)SelrefCount);
}
if (!result) {
result = sel_alloc(name, copy);
// fixme choose a better container (hash not map for starters)
NXMapInsert(namedSelectors, sel_getName(result), result);
}
return result;
}
複製代碼
這部分代碼比較好懂,其實就是從Macho文件的數據段中讀出全部的SEL,再將全部SEL插入到namedSelectors
表中。
// 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++) {
// 內部將經常使用的alloc、objc_msgSend等函數指針進行註冊,並fix爲新的函數指針
fixupMessageRef(refs+i);
}
}
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
複製代碼
這一部分也不是重點,只作瞭解。
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);
}
}
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) {
// 須要注意到是,下面的函數是_getObjc2ProtocolRefs,和上面的_getObjc2ProtocolList不同
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}
ts.log("IMAGE TIMES: fix up @protocol references");
複製代碼
這一部分是將全部的協議都添加到protocol_map
表中。而後會對協議列表的協議引用進行修復。
首先咱們要知道懶加載類與非懶加載類的區別,根據蘋果官方文檔的解釋: 二者之間的主要區別在因而否實現了+load
方法,實現了+load
方法則爲非懶加載類,沒有實現則爲懶加載類。 接下來會遍歷調用realizeClassWithoutSwift
來實現全部非懶加載的類。
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);
}
複製代碼
首先程序會讀取類的data信息獲取到ro,ro是一個只讀的結構,在編譯期就已經賦值了,主要存儲了類的實例變量、屬性列表、方法列表和協議列表等信息,在這一步rw只是進行了初始化,還並未有賦值操做。
// Realize superclass and metaclass, if they aren’t already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
// This assumes that none of those classes have Swift contents,
// or that Swift’s initializers have already been called.
// fixme that assumption will be wrong if we add support
// for ObjC subclasses of Swift classes.
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
複製代碼
以前咱們在探索類的結構時講過,類結構中包含isa
和superclass
,這裏正是利用這一點去遞歸實現類的元類和父類,以保證類的繼承鏈的完整性。至於遞歸的出口,咱們知道全部類的基類是NSObject
,而NSObject
的父類是nil
,因此遞歸到nil
就會跳出去。而元類不一樣,類經過isa
會找到元類,接着找到根元類,而根元類的元類指向本身,這樣會進入死循環,不過蘋果確定是作的很完善的,在remapClass
中其實作了判斷:
/***********************************************************************
* remapClass
* Returns the live class pointer for cls, which may be pointing to
* a class struct that has been reallocated.
* Returns nil if cls is ignored because of weak linking.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static Class remapClass(Class cls)
{
runtimeLock.assertLocked();
Class c2;
if (!cls) return nil;
NXMapTable *map = remappedClasses(NO);
if (!map || NXMapMember(map, cls, (void**)&c2) == NX_MAPNOTAKEY) {
return cls;
} else {
return c2;
}
}
複製代碼
這裏實際上是類的查找,若是在表裏已經存在該類就會返回c2
,其實也就是nil
,從而跳出元類的遞歸實現。
// Connect this class to its superclass’s subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(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);
}
// Root classes get bonus method implementations if they don’t have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don’t flush caches*/);
複製代碼
咱們能夠看到不論是方法、屬性仍是協議都是經過attachLists
來裝載到rw
中的,那麼咱們就有必要來看一下attachLists
中是如何操做的:
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`所指的內存區域的起始位置拷貝
* `oldCount * sizeof(array()->lists[0])`個字節到
* `array()->lists + addedCount`所指的內存區域。
* 能夠避免由於兩塊內存有重疊區域而被覆蓋
*/
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
/**
* 從`addedLists`所指的內存區域的起始位置拷貝
* `addedCount * sizeof(array()->lists[0])`個字節到
* `array()->lists + addedCount`所指的內存區域。
* 沒法避免由於兩塊內存有重疊區域致使的內存被覆蓋問題
* 使用時必須確保兩塊內存沒有重疊部分
* 效率上比`memmove`要高一些
*/
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
// 原來沒有元素而且新增元素個數爲1
list = addedLists[0];
}
else {
// 1 list -> many lists
// 原來有1個元素而且新增元素個數爲複數
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]));
}
}
複製代碼
這個函數的做用實際上是在原有的數組上作擴容操做,而後把原有的元素向後移,新增的元素插入到最前面。由此可知所謂的分類會覆蓋類中的同名方法是一個假象,其實兩個方法是同時存在的,只是分類的方法在前面,由於方法查找是按順序查找的,因此調用的是分類的方法。
// Discover categories.
// 發現和處理全部Category
for (EACH_HEADER) {
// 外部循環遍歷找到當前類,查找類對應的Category數組
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
// 內部循環遍歷當前類的全部Category
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category’s target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class’s method lists (etc) if
// the class is realized.
// 首先,經過其所屬的類註冊Category。若是這個類已經被實現,則從新構造類的方法列表。
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// 將Category添加到對應Class的value中,value是Class對應的全部category數組
addUnattachedCategoryForClass(cat, cls, hi);
// 將Category的method、protocol、property添加到Class
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
// 這塊和上面邏輯同樣,區別在於這塊是對Meta Class作操做,而上面則是對Class作操做
// 根據下面的邏輯,從代碼的角度來講,是能夠對原類添加Category的
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
複製代碼
至此,一個非懶加載類的加載過程基本就完成了。
其實大部分開發者都知道懶加載類是在調用的時候纔會去初始化的,只不過是沒有深刻探索過具體流程,此次咱們順便就探索一下。
既然咱們知道懶加載類在使用的時候纔會初始化,類建立對象又是經過alloc
方法來進行,而方法的本質就是消息發送,因此咱們就須要去到一個消息發送流程中很重要的函數lookUpImpOrForward
,在這個函數內部有這樣一段代碼:
if (!cls->isRealized()) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
複製代碼
這裏的判斷條件是是否已經實現過,沒實現過才符合條件。若是條件判斷成立,就會調用realizeClassMaybeSwiftAndLeaveLocked
,而realizeClassMaybeSwiftAndLeaveLocked
內部又調用了realizeClassMaybeSwiftMaybeRelock
:
/***********************************************************************
* realizeClassMaybeSwift (MaybeRelock / AndUnlock / AndLeaveLocked)
* Realize a class that might be a Swift class.
* Returns the real class structure for the class.
* Locking:
* runtimeLock must be held on entry
* runtimeLock may be dropped during execution
* ...AndUnlock function leaves runtimeLock unlocked on exit
* ...AndLeaveLocked re-acquires runtimeLock if it was dropped
* This complication avoids repeated lock transitions in some cases.
**********************************************************************/
static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
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);
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;
}
複製代碼
從源碼中咱們能夠發現程序最終會調用realizeClassWithoutSwift
,而該函數的內容正是咱們前面已經分析過的類的加載流程,由此咱們能夠肯定,懶加載類是在第一次被調用的時候纔會開始加載到內存的。
咱們知道+load
方法是區分懶加載類和非懶加載類的重要方法,那麼+load
是怎麼調起的呢,這就須要咱們研究一下上面提到過的_dyld_objc_notify_register
的第二個參數load_images
:
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);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
複製代碼
從源碼中咱們能夠看到有兩個重要的函數:prepare_load_methods
和call_load_methods
。
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
// 對類的處理
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// 對分類的處理
category_t **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);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
複製代碼
這個函數主要是對非懶加載類和非懶加載分類進行處理。
對非懶加載類的處理:
_getObjc2NonlazyClassList
獲取全部的非懶加載類的集合。schedule_class_load
,此函數會遞歸尋找沒有加載+load
方法的父類並調用add_class_to_loadable_list
將父類和當前類及其對應的+load
方法加入列表,注意是先父類後子類。static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
複製代碼
對非懶加載分類的處理:
_getObjc2NonlazyCategoryList
獲取全部的非懶加載類的集合。realizeClassWithoutSwift
將主類初始化。add_category_to_loadable_list
將分類及其對應的+load
方法加入到列表中。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 {
// 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();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
複製代碼
call_load_methods
內部實現了全部類的+load
方法的調用。經過一個do-while循環不斷遍歷調用類和分類的+load
方法,call_class_loads
函數內實現了對類的+load
方法的調用,call_category_loads
函數內實現了對分類的+load
方法的調用。同時從代碼的執行順序咱們也能看出來,+load
方法的調用順序是先主類後分類。
前面講完了類的加載流程,其中也包括了分類。跟主類同樣分類也存在懶加載和非懶加載的狀況,因此類和分類搭配加載就會存在四種狀況:
這種狀況是類和分類都實現了+load
方法。就是正常的先加載類,再加載分類,屬於比較好理解的一種狀況。
這種狀況是類中沒有實現+load
方法而分類中實現了+load
方法。經過前面的研究咱們知道懶加載類是在第一次發送消息的時候纔會加載,而非懶加載分類在read_image
中就加載了,這就形成了一個問題,分類已經加載了而類卻沒有,固然蘋果已經給出瞭解決方案,就在前面咱們研究load_image
中有一個prepare_load_methods
函數,這個函數咱們已經知道處理非懶加載分類的時候會把對應的主類初始化,因此在這種狀況下懶加載類的加載就不是在第一次發送消息的時候而是提早到load_image
中的prepare_load_methods
裏。
這種狀況是類中實現了+load
方法而分類中沒有實現。這裏要說明的一點是:**分類的懶加載不一樣於類,分類的懶加載是編譯時就已經加載完成。**因此這種狀況就是類會走正常的加載流程read_images -> realizeClassWithoutSwift -> methodlizeClass
,在添加分類的時候直接從data()->ro
裏拿就好了。
這種狀況是類和分類都沒有實現+load
方法。和上面那種狀況區別只在於類的加載時機不同,分類是同樣的。類會在第一次發送消息的時候加載,走方法查找的流程消息發送 -> lookuporforward -> realizeClassWithoutSwift -> methodlizeClass
,一樣在須要添加分類的時候直接從data()->ro
裏拿就好了。
本篇文章咱們詳細探索了類的加載(包括非懶加載類和懶加載類)、分類的加載以及類和分類搭配加載的不一樣狀況,流程已經比較清楚,本人能力有限,若有錯誤還請指正。