Objc中類的初始化是從_objc_init方法開始的。該方法的結(jié)構(gòu)如下圖:
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);
}
首先是調(diào)用的是environ_init();這個方法。
這個方法里面主要是對環(huán)境變量的配置,方法中有段代碼可以打印出所有的環(huán)境變量:
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);
}
解鎖這段打印代碼的限制條件,就會打印下面所有的環(huán)境變量:
objc[1473]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[1473]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[1473]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[1473]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[1473]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[1473]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[1473]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[1473]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[1473]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[1473]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[1473]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[1473]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[1473]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[1473]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[1473]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[1473]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[1473]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[1473]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[1473]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[1473]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[1473]: OBJC_PRINT_CUSTOM_RR: log classes with un-optimized custom retain/release methods
objc[1473]: OBJC_PRINT_CUSTOM_AWZ: log classes with un-optimized custom allocWithZone methods
objc[1473]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
objc[1473]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
objc[1473]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
objc[1473]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
objc[1473]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
objc[1473]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
objc[1473]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
objc[1473]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
objc[1473]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
objc[1473]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
objc[1473]: OBJC_DISABLE_VTABLES: disable vtable dispatch
objc[1473]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
objc[1473]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[1473]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[1473]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[1473]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
以上環(huán)境變量中有我們常用的:
- OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
該環(huán)境變量會禁止non-pointer isa。這樣所有的對象都會使用正常的,非優(yōu)化的isa。該isa指向?qū)ο笏鶎俚念悺?br>non-pointer isa
上圖是我們正常的環(huán)境,可以看到通過x/4gx打印出對象的地址,地址的第一段為對象的isa,和上面的p/x打印的類對象的地址對比可以發(fā)現(xiàn),兩個地址是不相等了。
接下來我們設(shè)置環(huán)境變量OBJC_DISABLE_NONPOINTER_ISA:

然后重新運(yùn)行程序:

可以看出對象的isa和類對象的地址是相同的,也就是對象的isa真正指向了所屬的類。然后我們通過
po打印isa得到了所屬的類。現(xiàn)在我們大概明白了環(huán)境變量的意義。
- OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
開啟該環(huán)境變量后會打印出所有實(shí)現(xiàn)+(void)load方法的對象。我們可以通過這個來優(yōu)化程序的啟動速度。
接下來我們返回到_objc_init函數(shù),繼續(xù)探究:
tls_init();用于線程的key綁定,我們暫時略過;
然后就是static_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]();
}
}
通過注釋我們也可以了解到,該函數(shù)的作用是:靜態(tài)函數(shù)初始化。那么為什么不是在dyld中初始化呢?因?yàn)閘ibc調(diào)用_objc_init()是在dyld之前,所以我們這里需要手動調(diào)用初始化。
注意:這里的靜態(tài)函數(shù)是指的系統(tǒng)的靜態(tài)函數(shù),我們自定義的不算在里面。
然后調(diào)用的就是lock_init();,我們跟蹤該函數(shù)發(fā)現(xiàn)函數(shù)實(shí)現(xiàn)為空:
void lock_init(void)
{
}
為什么為空實(shí)現(xiàn)呢?可能是這部分代碼沒有開源。我們繼續(xù)往下走,_objc_init(void)中接下來調(diào)用的是exception_init();
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
通過注釋說明,這里是注冊異常的回調(diào)
/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler.
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
@try {
__cxa_rethrow();
} @catch (id e) {
// It's an objc object. Call Foundation's handler, if any.
(*uncaught_handler)((id)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
例如我們調(diào)用一個未實(shí)現(xiàn)的方法時,程序就會跑到這里來。
我們繼續(xù)回到_objc_init(void)方法中,接下來調(diào)用的就是_dyld_objc_notify_register(&map_images, load_images, unmap_image);
這個方法就是dyld中的方法
//
// 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);
此方法主要是注冊了三個回調(diào)函數(shù),回調(diào)函數(shù)會在dyld中進(jìn)行調(diào)用。當(dāng)images被maped、unmapped、initiallized時候的回調(diào)。
map_images回調(diào)
在map_images中調(diào)用了map_images_nolock(count, paths, mhdrs);然后map_images_nolock中調(diào)用了_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
下面我們來解讀_read_images方法。
在該方法中有個以下的doneOnce判斷
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);
......
}
在doneOnce(只執(zhí)行一次)中創(chuàng)建了兩張表:gdb_objc_realized_classes和allocatedClasses。然后我們搜索這兩張表,可以看到它們的定義
// This is a misnomer: gdb_objc_realized_classes is actually a list of
// named classes not in the dyld shared cache, whether realized or not.
NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.h
gdb_objc_realized_classes主要是用來存儲那些不在共享緩存中,已經(jīng)初始化或者未初始化的類
/***********************************************************************
* allocatedClasses
* A table of all classes (and metaclasses) which have been allocated
* with objc_allocateClassPair.
**********************************************************************/
static NXHashTable *allocatedClasses = nil;
allocatedClasses主要是用來存儲那些已經(jīng)初始化的類。
我們繼續(xù)往下讀_read_images方法的內(nèi)容,主要包含以下幾點(diǎn):
- 類處理;
- 方法編號處理;
- 協(xié)議處理;
- 非懶加載類處理;
- 待處理的類;
- 分類處理;
上面我們是從宏觀上列出了_read_images的主要的功能,下面我們開始細(xì)致的分析。
類的處理
首先獲取類的list列表:
// 從編譯后的類列表中取出所有類,獲取到的是一個classref_t類型的指針
classref_t *classlist = _getObjc2ClassList(hi, &count);
然后for循環(huán)處理:
for (i = 0; i < count; i++) {
......
// 通過readClass函數(shù)獲取處理后的新類,
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
......
}
在for循環(huán)中調(diào)用readClass方法,對類進(jìn)行處理。我們進(jìn)入該方法看下,里面調(diào)用下面的兩行代碼
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
在這兩個方法里面,分別將待處理的class存入到上面我們再doneOnce里面創(chuàng)建的兩張表中。
然后我們返回過來繼續(xù)看_read_images下面的流程。我們暫時略過與方法無關(guān)的內(nèi)容(協(xié)議、方法、分類等)。然后找到方法:
realizeClassWithoutSwift(cls);
進(jìn)入realizeClassWithoutSwift(cls);方法,看到對讀取并且賦值了ro:
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);
}
然后遞歸的對父類和元類進(jìn)行初始化調(diào)用
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
然后綁定賦值綁定superClass以及isa
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
初始化創(chuàng)建superClass后,同樣也會將superClass的subClass執(zhí)行本class。
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
我們繼續(xù)往下看,發(fā)現(xiàn)調(diào)用了下面的方法:
// Attach categories
methodizeClass(cls);
進(jìn)入該方法看看
auto rw = cls->data();
auto ro = rw->ro;
讀取到rw和ro
// 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);
}
將ro中的methoList attach到rw中
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
將ro中的propertyList attach到rw中
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
將ro中的protocolList attach到rw中。
上面代碼就是講ro中的list 貼到rw中。為什么要這樣做呢?我們搜索attachLists方法,發(fā)現(xiàn)在
addMethod
class_addProtocol
_class_addProperty
attachCategories
中都調(diào)用了attachLists方法,所以可以推測,我們平時用的分類方法、協(xié)議方法、動態(tài)的增加方法等都會添加到rw中。而ro中只是類中實(shí)現(xiàn)的方法。
tips: rw是readwrite的縮寫,是可以動態(tài)修改的。ro是readonly的縮寫,在編譯時期確定的,不能動態(tài)修改。比如用戶的ivarsList就存儲在ro,所以我們不能動態(tài)的給對象添加成員變量。
下面我們來看下attachLists方法是怎樣對方法或者協(xié)議等列表進(jìn)行attach處理的?
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]));
}
}
如果現(xiàn)在list中已經(jīng)存在了一個Array。那么就會開辟一個新的list空間,將原來的list move到新創(chuàng)建的list的末尾,然后新插入的list copy到新創(chuàng)建list的頭部。
