/*********************************************************************** * environ_init * Read environment variables that affect the runtime. * Also print environment variable help, if requested. **********************************************************************/ voidenviron_init(void) { if (issetugid()) { // All environment variables are silently ignored when setuid or setgid // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves. return; } // Turn off autorelease LRU coalescing by default for apps linked against // older SDKs. LRU coalescing can reorder releases and certain older apps // are accidentally relying on the ordering. // rdar://problem/63886091 // if (!dyld_program_sdk_at_least(dyld_fall_2020_os_versions)) // DisableAutoreleaseCoalescingLRU = true; bool PrintHelp = false; bool PrintOptions = false; bool maybeMallocDebugging = false; // Scan environ[] directly instead of calling getenv() a lot. // This optimizes the case where none are set. for (char **p = *_NSGetEnviron(); *p != nil; p++) { if (0 == strncmp(*p, "Malloc", 6) || 0 == strncmp(*p, "DYLD", 4) || 0 == strncmp(*p, "NSZombiesEnabled", 16)) { maybeMallocDebugging = true; } if (0 != strncmp(*p, "OBJC_", 5)) continue; if (0 == strncmp(*p, "OBJC_HELP=", 10)) { PrintHelp = true; continue; } if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) { PrintOptions = true; continue; } if (0 == strncmp(*p, "OBJC_DEBUG_POOL_DEPTH=", 22)) { SetPageCountWarning(*p + 22); continue; } constchar *value = strchr(*p, '='); if (!*value) continue; value++; for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) { constoption_t *opt = &Settings[i]; if ((size_t)(value - *p) == 1+opt->envlen && 0 == strncmp(*p, opt->env, opt->envlen)) { *opt->var = (0 == strcmp(value, "YES")); break; } } }
// Special case: enable some autorelease pool debugging // when some malloc debugging is enabled // and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO. if (maybeMallocDebugging) { constchar *insert = getenv("DYLD_INSERT_LIBRARIES"); constchar *zombie = getenv("NSZombiesEnabled"); constchar *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION"); if ((getenv("MallocStackLogging") || getenv("MallocStackLoggingNoCompact") || (zombie && (*zombie == 'Y' || *zombie == 'y')) || (insert && strstr(insert, "libgmalloc"))) && (!pooldebug || 0 == strcmp(pooldebug, "YES"))) { DebugPoolAllocation = true; } } // if (!os_feature_enabled_simple(objc4, preoptimizedCaches, true)) { // DisablePreoptCaches = true; // } // 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++) { constoption_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); } } }
jason@192 ~ % export OBJC_HELP=1 jason@192 ~ % ls objc[6097]: Objective-C runtime debugging. Set variable=YES to enable. objc[6097]: OBJC_HELP: describe available environment variables objc[6097]: OBJC_PRINT_OPTIONS: list which options are set objc[6097]: OBJC_PRINT_IMAGES: logimageand library names as they are loaded objc[6097]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps objc[6097]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods objc[6097]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods objc[6097]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod: objc[6097]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup objc[6097]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup objc[6097]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars objc[6097]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables objc[6097]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods objc[6097]: OBJC_PRINT_CACHE_SETUP: log processing of method caches objc[6097]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging objc[6097]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache objc[6097]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables objc[6097]: OBJC_PRINT_EXCEPTIONS: log exception handling objc[6097]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw() objc[6097]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers objc[6097]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations objc[6097]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions objc[6097]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools objc[6097]: OBJC_PRINT_CUSTOM_CORE: log classes with custom core methods objc[6097]: OBJC_PRINT_CUSTOM_RR: log classes with custom retain/release methods objc[6097]: OBJC_PRINT_CUSTOM_AWZ: log classes with custom allocWithZone methods objc[6097]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields objc[6097]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded objc[6097]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses objc[6097]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization objc[6097]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars objc[6097]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use objc[6097]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak objc[6097]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools objc[6097]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present objc[6097]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing objc[6097]: OBJC_DISABLE_VTABLES: disable vtable dispatch objc[6097]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache objc[6097]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al. objc[6097]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers objc[6097]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields objc[6097]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
/*********************************************************************** * 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. **********************************************************************/ staticvoidstatic_init() { size_t count; auto inits = getLibobjcInitializers(&_mh_dylib_header, &count); for (size_t i = 0; i < count; i++) { inits[i](); } auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count); for (size_t i = 0; i < count; i++) { UnsignedInitializer init(offsets[i]); init(); } }
这里会运行 C++ 的静态构造函数,在 dyld 调用我们的静态构造函数之前,libc 会调用 _objc_init,所以这里我们必须自己来做,并且这里只会初始化系统内置的 C++ 静态构造函数,我们自己代码里面写的并不会在这里初始化。
/*********************************************************************** * _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. **********************************************************************/ staticvoid(*old_terminate)(void)= nil; staticvoid _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)(); } } }
/// Initialize the trampoline machinery. Normally this does nothing, as /// everything is initialized lazily, but for certain processes we eagerly load /// the trampolines dylib. void _imp_implementationWithBlock_init(void) { #if TARGET_OS_OSX // Eagerly load libobjc-trampolines.dylib in certain processes. Some // programs (most notably QtWebEngineProcess used by older versions of // embedded Chromium) enable a highly restrictive sandbox profile which // blocks access to that dylib. If anything calls // imp_implementationWithBlock (as AppKit has started doing) then we'll // crash trying to load it. Loading it here sets it up before the sandbox // profile is enabled and blocks it. // // This fixes EA Origin (rdar://problem/50813789) // and Steam (rdar://problem/55286131) if (__progname && (strcmp(__progname, "QtWebEngineProcess") == 0 || strcmp(__progname, "Steam Helper") == 0)) { Trampolines.Initialize(); } #endif }
// // 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);
// call 'mapped' function with all images mapped so far try { notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true); } catch (constchar* msg) { // ignore request to abort during registration }
// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem) for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { ImageLoader* image = *it; if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) { dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0); (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); } } }
#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 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
通过宏 SUPPORT_NONPOINTER_ISA 判断当前是否支持开启内存优化的 isa
如果支持,则在某些条件下需要禁用这个优化
通过宏 SUPPORT_INDEXED_ISA 判断当前是否是将类存储在 isa 作为类表索引
如果是的话,再递归遍历所有的 Mach-O 的头部,并且判断如果是 Swift 3.0 之前的代码,就需要禁用对 isa 的内存优化
# if TARGET_OS_OSX // Disable non-pointer isa if the app is too old // (linked before OS X 10.11) // if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_11)) { // DisableNonpointerIsa = true; // if (PrintRawIsa) { // _objc_inform("RAW ISA: disabling non-pointer isa because " // "the app is too old."); // } // }
// 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; unsignedlongsize; 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
通过宏 TARGET_OS_OSX 判断是否是 macOS 执行环境
判断 macOS 的系统版本,如果小于 10.11 则说明 app 太陈旧了,需要禁用掉 non-pointer isa
然后再遍历所有的 Mach-O 的头部,判断如果有 __DATA__,__objc_rawisa 段的存在,则禁用掉 non-pointer isa ,因为很多新的 app 加载老的扩展的时候会需要这样的判断操作。
1 2 3 4 5 6 7
// 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);
// 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. // This list excludes lazily named classes, which have to be looked up // using a getClass hook. NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.h
// 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; } classref_tconst *classlist = _getObjc2ClassList(hi, &count);
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; } } }
Discover classes. Fix up unresolved future classes. Mark bundle classes. 发现类。修正未解析的 future 类,标记 bundle 类。
// 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]); } } }
// Fix up @selector references staticsize_t UnfixedSelectors; { mutex_locker_tlock(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++) { constchar *name = sel_cname(sels[i]); SEL sel = sel_registerNameNoLock(name, isBundle); if (sels[i] != sel) { sels[i] = sel; } } } } SEL sel_registerNameNoLock(constchar *name, bool copy){ return __sel_registerName(name, 0, copy); // NO lock, maybe copy }
修正 SEL 引用
操作前先加一个 selLock 锁
然后遍历EACH_HEADER
如果开启了预优化,contiue 到下一个 Mach-O
通过 _getObjc2SelectorRefs 拿到所有的 SEL 引用
然后对所有的 SEL 引用调用 sel_registerNameNoLock 进行注册
也就是说这一流程最主要的目的就是注册 SEL ,我们注册真正发生的地方: __sel_registerName ,这个函数如果大家经常玩 Runtime 肯定不会陌生:
static SEL __sel_registerName(constchar *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_tlock(selLock, shouldLock); auto it = namedSelectors.get().insert(name); if (it.second) { // No match. Insert. *it.first = (constchar *)sel_alloc(name, copy); } return (SEL)*it.first; }
我们简单分析一下 __sel_registerName 方法的流程:
判断是否要加锁
如果 sel 为空,则返回一个空的 SEL
从 builtins 中搜索,看是否已经注册过,如果找到,直接返回结果
从 namedSelectors 哈希表中查询,找到了就返回结果
如果 namedSelectors 未初始化,则创建一下这个哈希表
如果上面的流程都没有找到,则需要调用 sel_alloc 来创建一下 SEL ,然后把新创建的 SEL 插入哈希表中进行缓存的填充
Fix up old objc_msgSend_fixup call sites 流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#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
// 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();
// Skip reading protocols if this is an image from the shared cache // and we support roots // Note, after launch we do need to walk the protocol as the protocol // in the shared cache is marked with isCanonical() and that may not // be true if some non-shared cache binary was chosen as the canonical // definition if (launchTime && isPreoptimized) { 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); } }
发现协议,并修正协议引用
Fix up @protocol references 流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Fix up @protocol references // Preoptimized images may have the right // answer already but we don't know for sure. 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 && hi->isPreoptimized()) continue; protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count); for (i = 0; i < count; i++) { remapProtocolRef(&protolist[i]); } }
// Realize non-lazy classes (for +load methods and static instances) for (EACH_HEADER) { classref_tconst *classlist = hi->nlclslist(&count); for (i = 0; i < count; i++) { Class cls = remapClass(classlist[i]); if (!cls) continue;
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't disallow all Swift classes because of // classes like Swift.__EmptyArrayStorage } realizeClassWithoutSwift(cls, nil); } }
初始化非懒加载类( +load 方法和静态实例)**
Realize newly-resolved future classes 流程
1 2 3 4 5 6 7 8 9 10 11 12
// 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); }
初始化新解析出来的 future 类
Discover categories 流程
1 2 3 4 5 6 7 8 9
// Discover categories. Only do this after the initial category // attachment has been done. For categories present at startup, // discovery is deferred until the first load_images call after // the call to _dyld_objc_notify_register completes. rdar://problem/53119145 if (didInitialAttachCategories) { for (EACH_HEADER) { load_categories_nolock(hi); } }
/*********************************************************************** * readClass * Read a class and metaclass as written by a compiler. * Returns the new class pointer. This could be: * - cls * - nil (cls has a missing weak-linked superclass) * - something else (space for this class was reserved by a future class) * * Note that all work performed by this function is preflighted by * mustReadClasses(). Do not change this function without updating that one. * * Locking: runtimeLock acquired by map_images or objc_readClassPair **********************************************************************/ Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { constchar *mangledName = cls->nonlazyMangledName(); if (missingWeakSuperclass(cls)) { // No superclass (probably weak-linked). // Disavow any knowledge of this subclass. if (PrintConnecting) { _objc_inform("CLASS: IGNORING class '%s' with " "missing weak-linked superclass", cls->nameForLogging()); } addRemappedClass(cls, nil); cls->setSuperclass(nil); return nil; } cls->fixupBackwardDeployingStableSwift();
Class replacing = nil; if (mangledName != nullptr) { 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()); }
// Manually set address-discriminated ptrauthed fields // so that newCls gets the correct signatures. newCls->setSuperclass(cls->getSuperclass()); newCls->initIsa(cls->getIsa());
replacing = cls; cls = newCls; } } if (headerIsPreoptimized && !replacing) { // class list built in shared cache // fixme strict assert doesn't work because of duplicates // ASSERT(cls == getClass(name)); ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName)); } else { if (mangledName) { //some Swift generic classes can lazily generate their names addNamedClass(cls, mangledName, replacing); } else { Class meta = cls->ISA(); constclass_ro_t *metaRO = meta->bits.safe_ro(); ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass."); ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class."); } addClassTableEntry(cls); }
// for future reference: shared cache never contains MH_BUNDLEs if (headerIsBundle) { cls->data()->flags |= RO_FROM_BUNDLE; cls->ISA()->data()->flags |= RO_FROM_BUNDLE; } return cls; }
通过分析 read_class ,我们可以得知,类已经被注册到两个哈希表中去了,那么现在一切时机都已经成熟了。但是我们还是要略过像 Fix up remapped classes 、 Fix up @selector references 、 fix up old objc_msgSend_fixup call sites 、 Discover protocols. Fix up protocol refs 、 Fix up @protocol references ,因为我们的重点是类的加载,我们最终来到了 Realize non-lazy classes (for +load methods and static instances) ,略去无关信息之后,我们可以看到我们的 主角 realizeClassWithoutSwift 闪亮登场了:
1 2 3 4 5 6 7 8 9
/*********************************************************************** * realizeClassWithoutSwift * Performs first-time initialization on class cls, * including allocating its read-write data. * Does not perform any Swift-side initialization. * Returns the real class structure for the class. * Locking: runtimeLock must be write-locked by the caller **********************************************************************/ static Class realizeClassWithoutSwift(Class cls, Class previously)
从方法的名称以及方法注释我们可以知道, realizeClassWithoutSwift 是进行类的第一次初始化操作,包括分配读写数据也就是我们常说的 rw ,但是并不会进行任何的 Swift 端初始化。我们直接聚焦下面的代码:
1 2 3 4 5
// Normal class. Allocate writeable class data. rw = objc::zalloc<class_rw_t>(); rw->set_ro(ro); rw->flags = RW_REALIZED|RW_REALIZING|isMeta; cls->setData(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->getSuperclass()), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
这里可以看到父类和元类都会递归调用 realizeClassWithoutSwift 来初始化各自的 rw 。为什么在类的加载操作里面要去加载类和元类呢?回忆一下类的结构,答案很简单,要保证 superclass 和 isa 的完整性,也就是保证类的完整性,
1 2 3
// Update superclass and metaclass in case of remapping cls->setSuperclass(supercls); cls->initClassIsa(metacls);
上面的代码就是最好的证明,初始化完毕的父类和元类被赋值到了类的 superclass 和 isa 上面。
/*********************************************************************** * methodizeClass * Fixes up cls's method list, protocol list, and property list. * Attaches any outstanding categories. * Locking: runtimeLock must be held by the caller **********************************************************************/ staticvoidmethodizeClass(Class cls, Class previously)
对类的方法列表、协议列表和属性列表进行修正 附加 category 到类上面来
我们直接往下面走:
1 2 3 4 5 6
// Install methods and properties that the class implements itself. method_list_t *list = ro->baseMethods(); if (list) { prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr); if (rwe) rwe->methods.attachLists(&list, 1); }
// Attach categories. if (previously) { if (isMeta) { objc::unattachedCategories.attachToClass(cls, previously, ATTACH_METACLASS); } else { // When a class relocates, categories with class methods // may be registered on the class itself rather than on // the metaclass. Tell attachToClass to look for those. objc::unattachedCategories.attachToClass(cls, previously, ATTACH_CLASS_AND_METACLASS); } } objc::unattachedCategories.attachToClass(cls, cls, isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
voidprepare_load_methods(const headerType *mhdr) { size_t count, i; runtimeLock.assertLocked(); classref_tconst *classlist = _getObjc2NonlazyClassList(mhdr, &count); for (i = 0; i < count; i++) { 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()); add_category_to_loadable_list(cat); } } /*********************************************************************** * prepare_load_methods * Schedule +load for classes in this image, any un-+load-ed * superclasses in other images, and any categories in this image. **********************************************************************/ // Recursively schedule +load for cls and any un-+load-ed superclasses. // cls must already be connected. staticvoidschedule_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->getSuperclass()); add_class_to_loadable_list(cls); cls->setInfo(RW_LOADED); } /*********************************************************************** * add_class_to_loadable_list * Class cls has just become connected. Schedule it for +load if * it implements a +load method. **********************************************************************/ voidadd_class_to_loadable_list(Class cls) { IMP method; loadMethodLock.assertLocked(); method = cls->getLoadMethod(); if (!method) return; // Don't bother if cls has no +load method if (PrintLoading) { _objc_inform("LOAD: class '%s' scheduled for +load", cls->nameForLogging()); } if (loadable_classes_used == loadable_classes_allocated) { loadable_classes_allocated = loadable_classes_allocated*2 + 16; loadable_classes = (struct loadable_class *) realloc(loadable_classes, loadable_classes_allocated * sizeof(struct loadable_class)); } loadable_classes[loadable_classes_used].cls = cls; loadable_classes[loadable_classes_used].method = method; loadable_classes_used++; }
/*********************************************************************** * call_load_methods * Call all pending class and category +load methods. * Class +load methods are called superclass-first. * Category +load methods are not called until after the parent class's +load. * * This method must be RE-ENTRANT, because a +load could trigger * more image mapping. In addition, the superclass-first ordering * must be preserved in the face of re-entrant calls. Therefore, * only the OUTERMOST call of this function will do anything, and * that call will handle all loadable classes, even those generated * while it was running. * * The sequence below preserves +load ordering in the face of * image loading during a +load, and make sure that no * +load method is forgotten because it was added during * a +load call. * Sequence: * 1. Repeatedly call class +loads until there aren't any more * 2. Call category +loads ONCE. * 3. Run more +loads if: * (a) there are more classes to load, OR * (b) there are some potential category +loads that have * still never been attempted. * Category +loads are only run once to ensure "parent class first" * ordering, even if a category +load triggers a new loadable class * and a new loadable category attached to that class. * * Locking: loadMethodLock must be held by the caller * All other locks must not be held. **********************************************************************/ voidcall_load_methods(void) { staticbool 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; }