iOS 類的加載分析

前言

通過前面對應用啓動加載流程的分析《iOS 應用程序加載》,對程序的加載有一定的瞭解,是通過objc_init()方法註冊回調函數,然後加載鏡像文件,那麼當鏡像文件加載後,是怎麼讀到內存中的呢?有是以什麼樣的方式存儲的呢?接下來我們分析一下libObjc的源碼,瞭解一下。

1. objc_init分析

libObjc的源碼中查看objc_init方法,如下:

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);
}

1.1  environ_init() 環境變量

void environ_init(void) 
{
    if (issetugid()) {
        // All environment variables are silently ignored when setuid or setgid
        // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
        return;
    } 

    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;
        }
        
        const char *value = strchr(*p, '=');
        if (!*value) continue;
        value++;
        
        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_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) {
        const char *insert = getenv("DYLD_INSERT_LIBRARIES");
        const char *zombie = getenv("NSZombiesEnabled");
        const char *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;
        }
    }
    /*
    自己修改判斷條件,打印環境變量。
    for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
        const option_t *opt = &Settings[i];            
        _objc_inform("%s: %s", opt->env, opt->help);
        if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
    }
    */ 
    // 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);
        }
    }
}

可以通過修改判斷條件,打印所有的環境變量,如上源碼中註釋部分。我們也可以在lldb上用命令export OBJC_HELP=1來打印環境變量。

環境變量:

objc[36792]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[36792]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[36792]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[36792]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[36792]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[36792]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[36792]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[36792]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[36792]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[36792]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[36792]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[36792]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[36792]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[36792]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[36792]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[36792]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[36792]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[36792]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[36792]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[36792]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[36792]: OBJC_PRINT_CUSTOM_RR: log classes with un-optimized custom retain/release methods
objc[36792]: OBJC_PRINT_CUSTOM_AWZ: log classes with un-optimized custom allocWithZone methods
objc[36792]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
objc[36792]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
objc[36792]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
objc[36792]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
objc[36792]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
objc[36792]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
objc[36792]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
objc[36792]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
objc[36792]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
objc[36792]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
objc[36792]: OBJC_DISABLE_VTABLES: disable vtable dispatch
objc[36792]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
objc[36792]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[36792]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[36792]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[36792]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork

上面代碼都是我們的環境變量,可以在xcode中設置環境變量,Edit Scheme->Arguments->Environment Variables

  • 設置OBJC_DISABLE_NONPOINTER_ISAYES:設置nonpointer_isa,優化內存結構。
  • 設置OBJC_PRINT_LOAD_METHODSYES:可以打印所有實現了load方法的類,對優化程序啓動有幫助,能更快的找到實現load方法的類。
  • 設置OS_ACTIVITY_MODEdisable,可以進行屏蔽系統日誌。

1.2   tls_init()

進行線程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
}

1.3   static_init()

運行C++靜態構造函數,在dyld加載靜態構造函數之前,libc調用_objc_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]();
    }
}

1.4   lock_init()

void lock_init(void)
{
}

空空如也,本身是通過C++寫的,objc通用C++C的那套鎖的機制,只是在上層封裝了一下。

1.5   exception_init()

異常處理函數,源碼如下:

初始化libobjc的異常處理系統

/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/

// 初始化libobjc的異常處理系統
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

在此方法中進行異常處理,先檢測是否是objc異常,是,回調我們註冊的回調對象callback(即:uncaught_handler),

/*
_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)();
        }
    }
}

1.5   _dyld_objc_notify_register()

源碼如下:

//
// 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運行時使用,
objc鏡像被映射、未映射和初始化時調用的寄存器處理程序。Dyld將使用包含objc-image-info部分的圖像數組調用mapped函數。在調用Dyld時,Dyld將調用init函數.

_dyld_objc_notify_register(&map_images, load_images, unmap_image)

接下來我們來探索一下_dyld_objc_notify_register的三個參數

  • map_images

  • load_images

  • unmap_image

2.   map_images

查看map_images源碼如下:

/***********************************************************************
* 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);
}

通過註釋對其分析:

  • 處理由dyld映射到的給定鏡像文件。
  • 然後進入map_images_nolock函數

map_images_nolock函數進行分析,去除打印和對hCount的一些操作,直接定位到下面代碼:

if (hCount > 0) {
    _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}

進入_read_images鏡像文件讀取。

2.1   _read_images

由於_read_images源碼過於長,我們對關鍵代碼進行分析:

#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++

    if (!doneOnce) {
        doneOnce = 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
# 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();
        }
        
        initializeTaggedPointerObfuscator();

        if (PrintConnecting) {
            _objc_inform("CLASS: found %d classes during launch", totalClasses);
        }

        // 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");
    }

  • 看上面源碼,當doneOnceNO時,即第一次進來時,會進入都if判斷裏面,然後將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);

查看源碼註釋:

1. gdb_objc_realized_classes 中存放是所有的類,不管是否實現,都會存儲在其中。
2. allocatedClasses 中存放是所有被開闢分配的類,
3. gdb_objc_realized_classes 表包含 allocatedClasses 表 

由此:在_read_images中,先加載所有類到gdb_objc_realized_classes表中,那麼爲什麼會創建兩張表呢?是爲了精確查找,不用每次都帶着一個大表去查找。當在allocatedClasses中沒有查找到時,說明這個類沒有初始化,就沒有必要在去gdb_objc_realized_classes中查找了。

當創建好兩張表後,是怎樣將所有的類添加到表中的呢?我們接着往下看

  • 對所有類進行重映射
// 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;
            }
        }
    }

上面這一段就是從編譯後的類列表中遍歷所有的類,並添加到對應的gdb_objc_realized_classesallocatedClasses表中。那麼,這個是在什麼地方添加的呢?我們先留個疑問,先探索_read_images整個流程。

  • 將所有SEL都註冊到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);
            }
        }
    }

  • 將所有協議添加到protocol_map協議表中,並對所有的Protocol重映射
// 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");

  • 初始化所有非懶加載的類,並進行rorw操作
// Realize non-lazy classes (for +load methods and static instances)
    // 實現非懶加載的類,對於load方法和靜態實例變量
    for (EACH_HEADER) {
        classref_t *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            // printf("non-lazy Class:%s\n",cls->mangledName());
            if (!cls) continue;

            // hack for class __ARCLite__, which didn't get this above
#if TARGET_OS_SIMULATOR
            if (cls->cache._buckets == (void*)&_objc_empty_cache  &&  
                (cls->cache._mask  ||  cls->cache._occupied)) 
            {
                cls->cache._mask = 0;
                cls->cache._occupied = 0;
            }
            if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache  &&  
                (cls->ISA()->cache._mask  ||  cls->ISA()->cache._occupied)) 
            {
                cls->ISA()->cache._mask = 0;
                cls->ISA()->cache._occupied = 0;
            }
#endif
            
            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
            }
            // 實現所有非懶加載的類(實例化類對象的一些信息,例如rw)
            realizeClassWithoutSwift(cls);
        }
    }

在這裏,是怎樣給rwro賦值的呢?我們先留下疑問,接着先探索_read_images整個流程。

  • 遍歷resolvedFutureClasses數組,對已標記的懶加載類,初始化
    // Realize newly-resolved future classes, in case CF manipulates them
    // 遍歷resolvedFutureClasses數組,實現懶加載的類
    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);
            cls->setInstancesRequireRawIsa(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }    
  • 處理所有的category,包括ClassMeteClass
// 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);
                }
            }
        }
    }

至此,_read_images的整個過程分析完畢。接下來,對這個過程中留下的疑問點,

比如:

  1. 編譯後的類列表中遍歷所有的類,並添加到對應的gdb_objc_realized_classesallocatedClasses表中的
  2. 在加載非懶加載類的時候,對rorw是怎麼處理賦值的?

接下來。我們對重點步驟進行分析。

2.2   readClass

在上述過程,我們瞭解到,在第一次進入創建gdb_objc_realized_classesallocatedClasses兩張表,來存儲類。然後會對所有的類重映射。系統是通過readClass函數對類進行處理的,查看readClass的源碼發現有下面源碼:

看代碼貌似是對rorw的處理,但經過斷點調試,發現程序並未進到這裏。

通過if (Class newCls = popFutureNamedClass(mangledName))這個判斷,得知:這裏只是對未來待處理的類進行操作。

接着分析查看readClass,發現下面代碼:

addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);

查看addNamedClass源碼:

/***********************************************************************
* 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中,通過NXMapInsert(gdb_objc_realized_classes, name, cls)將類添加到之前創建的gdb_objc_realized_classes中。

addClassTableEntry源碼:

/***********************************************************************
* 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方法中的allocatedClassesaddNamedClass兩個方法,將類添加到創建好的表中的。

在將類通過readClass進行重映射處理之後,會對比這兩個類,如下圖代碼:

一般情況是相等的,當有懶加載的類時,會在readClass中進行一些處理,致使newClscls不相等,然後將其加入到數組中。然後對懶加載的類進行初始化,上面已經講過。

2.3   realizeClassWithoutSwift

在初始化所有非懶加載的類時,是怎樣給rw、ro賦值的呢?查看源碼主要有以下步驟:

  1. remapClass將類進行重映射
  2. addClassTableEntry(cls),將類插入到allocatedClasses表中,如果存在就不插入
  3. realizeClassWithoutSwift(cls),實現所有非懶加載的類(實例化類對象的一些信息,例如rw)

那麼我們主要看一下realizeClassWithoutSwift源碼:

/***********************************************************************
* 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
**********************************************************************/
// 在類cls上執行首次初始化,包括分配讀寫數據,不執行任何Swift端初始化。
static Class realizeClassWithoutSwift(Class cls)
{
    runtimeLock.assertLocked();
    // 初始化 ro rw 父類 元類
    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;
    
    // 對類進行判斷,下面有遞歸,isa的經典走位圖,最終父類和元類指向nil
    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    assert(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?
    // 讀取class中的data,對ro、rw賦值
    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 {// 當前類,讀取cls中的data數據,對rw->ro進行賦值(ro在編譯時已經賦值),但是rw並沒有賦值
        // 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);
    }

    isMeta = ro->flags & RO_META;

    rw->version = isMeta ? 7 : 0;  // old runtime went up to 6
    ...
    // 遞歸,父類和元類實現
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
    ...
    // Update superclass and metaclass in case of remapping
    // 父類和元類的歸屬關係,將父類和元類賦值到類的父類和isa,
    cls->superclass = supercls;  // 對Class 的superclass 賦值,
    cls->initClassIsa(metacls);  // 對Class 的Isa初始化,

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);
    ...
    // Connect this class to its superclass's subclass lists
    // 雙向鏈表指向關係 父類中可以找到子類 子類中也可以找到父類
    // 將此類鏈接到其超類的子類列表
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls);

    return cls;
}

進入methodizeClass

static void methodizeClass(Class cls)
{
    runtimeLock.assertLocked();
    // 對類中的rw賦值
    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods(); //讀取ro裏面的方法列表
    if (list) {// 有,把ro裏面的baseMethods賦值給rw中的methods
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties; //讀取ro裏面的屬性列表
    if (proplist) {// 有,把ro裏面的baseProperties賦值給rw中的properties
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;//讀取ro裏面的協議列表
    if (protolist) {// 有,把ro裏面的baseProtocols賦值給rw中的protocols
        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*/);

    if (PrintConnecting) {
        if (cats) {
            for (uint32_t i = 0; i < cats->count; i++) {
                _objc_inform("CLASS: attached category %c%s(%s)", 
                             isMeta ? '+' : '-', 
                             cls->nameForLogging(), cats->list[i].cat->name);
            }
        }
    }
    
    if (cats) free(cats);

}

由此看出:rw的賦值是在對非懶加載類初始化時賦值的,通過調用realizeClassWithoutSwift,對類的rw->ro賦值,在methodizeClass中,對rw的其他屬性賦值。

ro是在編譯時就進行賦值的,只能讀取,不能進行改變,rw可以在進行調試的時候動態添加和處理方法、屬性和協議。

methodizeClass中,方法、屬性和協議賦值給rw的時候都是通過attachLists進行添加,接下來,我們查看一下attachLists源碼

2.4   attachLists 分析

attachLists源碼:

    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]));
        }
    }

有三種情況

  • 0 對一,即:0 lists -> 1 list,直接通過addedLists直接賦值
  • 1 對多,即:1 list -> many lists,將類開闢新的內存空間,將新數據進行存儲
  • 多對多,即:many lists -> many lists,擴大現有容器的內存,將舊數據放在指定的位置,將需要增加的數據放在前面

除了加載類的時候調用addedLists,還會在以下情況調用:

  • 類的加載,處理方法、屬性、協議時methodizeClass
  • 添加方法addMethods
  • 添加屬性_class_addProperty
  • 添加協議class_addProtocol
  • 分類的加載attachCategories

3.   總結

  • dyld進入程序,讀取數據後,通過map_Images回調,在_read_images中,將數據加載到內存中。

      1. 加載所有的類到 gdb_objc_realized_classes 和 allocatedClasses 兩張表中
      2. 通過 readClass 對所有類進行重映射
      3. 將所有 SEL 註冊到 namedSelectors 哈希表中
      4. 將所有協議添加到 protocol_map 協議表中,並對所有的 Protocol 重映射
      5. 初始化所有非懶加載的類,並進行ro、rw操作
      6. 遍歷resolvedFutureClasses數組,對已標記的懶加載類,初始化
      7. 處理所有的category,包括Class和MeteClass。
    
  • readClass
    1. 判斷是不是後期加載的類,是,則讀取class 的 date() 設置 rw 、 ro
    2. addNamedClass(cls, mangledName, replacing)
    3. addClassTableEntry(cls) 插入到表中

  • realizeClassWithoutSwift 實現非懶加載類的一些信息相關的操作,給類創建rw結構,對父類,元類也進行相關初始化,對父類,元類,根類,子類進行相關的綁定

  • methodizeClass 把讀取的ro,寫入到rw

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章