iOS底層探索 --- 類的加載(上)

image

我們在iOS底層探索 --- dyld加載流程提到了_objc_init。但是我們并沒有對這個函數(shù)做詳細的探索,當時我們只是探索到_dyld_objc_notify_register里面的參數(shù)load_images

這里呢,我們要探索是如何從Mach-O文件中,加載到內(nèi)存里面的。所以我們今天要回過頭,從新探索一下_objc_init這個函數(shù)。

本章主要探索一下_objc_init中函數(shù)的執(zhí)行流程,對于類的加載有一個基本的理解。

一、_objc_init源碼

首先我們來回顧一下_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();
    runtime_init();
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    // map_images()
    // load_images()
#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

我們可以看到,在_dyld_objc_notify_register函數(shù)被調(diào)用之前,還有一些函數(shù)的調(diào)用,那么這些函數(shù)又起到什么樣的作用呢?下面我們就來一一講解一下。

1.1 environ_init()

environ_init():讀取影響運行時的環(huán)境變量。(如果需要,還可以打印環(huán)境變量幫助,終端:$ export OBJC_HELP=1

這里我們可以簡單的看一下其中的一些代碼:


image
  • 日常的開發(fā)過程中,我們是利用Xcode來設置環(huán)境變量的。

  • 比如OBJC_PRINT_LOAD_METHODS我們只要設置了之后,控制臺就可以打印出工程中所有用到的load函數(shù):

    image

    image

  • 具體的一些環(huán)境變量的作用,可以參考下表

環(huán)境變量名 說明
OBJC_PRINT_OPTIONS 輸出OBJC已設置的選項
OBJC_PRINT_IMAGES 輸出已load的image信息
OBJC_PRINT_LOAD_METHODS 打印 Class 及 Category 的 + (void)load 方法的調(diào)用信息
OBJC_PRINT_INITIALIZE_METHODS 打印 Class 的 + (void)initialize 的調(diào)用信息
OBJC_PRINT_RESOLVED_METHODS 打印通過 +resolveClassMethod: 或 +resolveInstanceMethod: 生成的類方法
OBJC_PRINT_CLASS_SETUP 打印 Class 及 Category 的設置過程
OBJC_PRINT_PROTOCOL_SETUP 打印 Protocol 的設置過程
OBJC_PRINT_IVAR_SETUP 打印 Ivar 的設置過程
OBJC_PRINT_VTABLE_SETUP 打印 vtable 的設置過程
OBJC_PRINT_VTABLE_IMAGES 打印 vtable 被覆蓋的方法
OBJC_PRINT_CACHE_SETUP 打印方法緩存的設置過程
OBJC_PRINT_FUTURE_CLASSES 打印從 CFType 無縫轉(zhuǎn)換到 NSObject 將要使用的類(如 CFArrayRef 到 NSArray * )
OBJC_PRINT_GC 打印一些垃圾回收操作
OBJC_PRINT_PREOPTIMIZATION 打印 dyld 共享緩存優(yōu)化前的問候語
OBJC_PRINT_CXX_CTORS 打印類實例中的 C++ 對象的構(gòu)造與析構(gòu)調(diào)用
OBJC_PRINT_EXCEPTIONS 打印異常處理
OBJC_PRINT_EXCEPTION_THROW 打印所有異常拋出時的 Backtrace
OBJC_PRINT_ALT_HANDLERS 打印 alt 操作異常處理
OBJC_PRINT_REPLACED_METHODS 打印被 Category 替換的方法
OBJC_PRINT_DEPRECATION_WARNINGS 打印所有過時的方法調(diào)用
OBJC_PRINT_POOL_HIGHWATER 打印 autoreleasepool 高水位警告
OBJC_PRINT_CUSTOM_RR 打印含有未優(yōu)化的自定義 retain/release 方法的類
OBJC_PRINT_CUSTOM_AWZ 打印含有未優(yōu)化的自定義 allocWithZone 方法的類
OBJC_PRINT_RAW_ISA 打印需要訪問原始 isa 指針的類
OBJC_DEBUG_UNLOAD 卸載有不良行為的 Bundle 時打印警告
OBJC_DEBUG_FRAGILE_SUPERCLASSES 當子類可能被對父類的修改破壞時打印警告
OBJC_DEBUG_FINALIZERS 警告實現(xiàn)了 -dealloc 卻沒有實現(xiàn) -finalize 的類
OBJC_DEBUG_NIL_SYNC 警告 @synchronized(nil) 調(diào)用,這種情況不會加鎖
OBJC_DEBUG_NONFRAGILE_IVARS 打印突發(fā)地重新布置 non-fragile ivars 的行為
OBJC_DEBUG_ALT_HANDLERS 記錄更多的 alt 操作錯誤信息
OBJC_DEBUG_MISSING_POOLS 警告沒有 pool 的情況下使用 autorelease,可能內(nèi)存泄漏
OBJC_DEBUG_DUPLICATE_CLASSES 當出現(xiàn)類重名時停機
OBJC_USE_INTERNAL_ZONE 在一個專用的 malloc 區(qū)分配運行時數(shù)據(jù)
OBJC_DISABLE_GC 強行關(guān)閉自動垃圾回收,即使可執(zhí)行文件需要垃圾回收
OBJC_DISABLE_VTABLES 關(guān)閉 vtable 分發(fā)
OBJC_DISABLE_PREOPTIMIZATION 關(guān)閉 dyld 共享緩存優(yōu)化前的問候語
OBJC_DISABLE_TAGGED_POINTERS 關(guān)閉 NSNumber 等的 tagged pointer 優(yōu)化
OBJC_DISABLE_NONPOINTER_ISA 關(guān)閉 non-pointer isa 字段的訪問

1.2 tls_init()

tls_init():關(guān)于線程key的綁定,比如每個線程數(shù)據(jù)的析構(gòu)函數(shù)。

void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}

1.3 static_init()

static_init():運行C++靜態(tài)構(gòu)造函數(shù)(系統(tǒng)級別的構(gòu)造函數(shù))。在dyld調(diào)用我們的靜態(tài)構(gòu)造函數(shù)之前,libc調(diào)用_objc_init()。也就是說系統(tǒng)級別的C++構(gòu)造函數(shù)早于自定義的C++構(gòu)造函數(shù)執(zhí)行。

/***********************************************************************
* 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]();
    }
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        UnsignedInitializer init(offsets[i]);
        init();
    }
}

1.4 runtime_init()

runtime_init()Runtime運行時環(huán)境初始化,主要是unattachedCategoriesallocatedClasses。

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}

1.5 exception_init()

exception_init():初始化libobjc的異常處理系統(tǒng),注冊異常處理的回調(diào),從而監(jiān)控異常的處理。

/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

當遇到crash的時候,會來到_objc_terminate,執(zhí)行到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.6 cache_t::init()

緩存條件初始化。

void cache_t::init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
    mach_msg_type_number_t count = 0;
    kern_return_t kr;

    while (objc_restartableRanges[count].location) {
        count++;
    }

    kr = task_restartable_ranges_register(mach_task_self(),
                                          objc_restartableRanges, count);
    if (kr == KERN_SUCCESS) return;
    _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
                kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}

1.7 _imp_implementationWithBlock_init()

啟動回調(diào)機制。通常不會做什么,因為所有的初始化都是惰性的,但是對于某些進程,我們會迫不及待的加載libobjc-trampolines.dylib。

/// 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
}

1.8 _dyld_objc_notify_register(&map_images, load_images, unmap_image)

到這里,就看到我們熟悉的_dyld_objc_notify_register。其官方注釋如下:

image

從官方注釋中我們可以知道:

  • 僅供objc運行時使用
  • 注冊處理程序,以便于在映射、取消映射初始化objc鏡像時調(diào)用
  • dyld將會通過一個包含objc-image-info的鏡像文件的數(shù)組回調(diào)mapped函數(shù)。

其中_dyld_objc_notify_register的三個參數(shù)的意義如下:

  • map_imagesdyld將鏡像文件加載到內(nèi)存時,會觸發(fā)該函數(shù)。注意,該函數(shù)傳的是地址,也就是說是實時同步變化的,相當于Block。
  • load_imagesdyld初始化鏡像文件會觸發(fā)該函數(shù)。
  • unmap_imagedyld移除鏡像文件時,會觸發(fā)該函數(shù)。

二、map_images

看到這里,不知道大家是否還記得我們這篇文章要探索的內(nèi)容。沒錯,就是類是如何加載到內(nèi)存中的?。

通過上面我們知道,鏡像文件加載到內(nèi)存,需要通過map_images函數(shù);因此,我們探索的重點就落到了map_images函數(shù)身上。

/***********************************************************************
* 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最終會調(diào)用map_images_nolock,那么根據(jù)我們之前探索的經(jīng)驗,現(xiàn)在我們要做的就是跟進去。

image

在閱讀map_images_nolock的源碼的時候,我們可以先將一些判斷先收起來,然后結(jié)合函數(shù)名還有官方給的注釋,我們很快就可以定位到我們要找的關(guān)鍵代碼。_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses)

注意:在閱讀源碼的時候,不是所有的代碼我們都要一行一行的去閱讀,我們只需要去尋找我們需要的關(guān)鍵代碼就可以了。

既然找到了_read_images,從字面意思看是讀取鏡像文件。那我們跟進去看一下就知道了。


2.1 _read_images

這里_read_images的源碼也是有很多的。同樣的我們把判斷的代碼塊折疊起來,會發(fā)現(xiàn)不一樣的效果。

image

可以看到,當我們把代碼折疊之后,會看到一些層次分明的log;這些正是我們閱讀源碼所需要的注釋。

根據(jù)官方的log提示,我們大致可以將_read_images分為以下10個部分:

  • 1、條件控制,進行一次加載。
  • 2、修復預編譯階段的@selector混亂的問題。
  • 3、錯誤混亂的類處理。
  • 4、修復重映射一些沒有被鏡像文件加載進來的類。
  • 5、修復一些消息。
  • 6、當我們的類里面有協(xié)議的時候,添加協(xié)議到協(xié)議列表。
  • 7、修復沒有被加載的協(xié)議。
  • 8、分類處理。
  • 9、類的加載處理。
  • 10、沒有被處理的類,優(yōu)化那些被侵犯的類。

2.1.1 條件控制,進行一次加載

首先來講,當我們第一次進入_read_images,會進入下面的代碼塊:

    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

# 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;
            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
        // objc::unattachedCategories.init(32);
        // objc::allocatedClasses.init();
        
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        ts.log("IMAGE TIMES: first time tasks");
    }

主要看這一段代碼:

int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

可以看到,在第一次進來的時候,會通過NXCreateMapTable創(chuàng)建一張表gdb_objc_realized_classes;用于存儲不在共享緩存,但是已經(jīng)命名的所有類。其容量是類數(shù)量的4/3。


2.1.2 修復預編譯階段的@selector混亂的問題

static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(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++) {
                const char *name = sel_cname(sels[i]);
                SEL sel = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }

可以看到,會從兩個表中提取名字相同SEL,當發(fā)現(xiàn)兩個SEL不相等的情況,就進行修復。(名字相同,但是里面的地址可能不同)


2.1.3 錯誤混亂的類處理

    // 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_t const *classlist = _getObjc2ClassList(hi, &count);

        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

        for (i = 0; i < count; i++) {
            //此時讀取的cls只是一個地址
            Class cls = (Class)classlist[i]; 
            //通過readClass函數(shù)獲取處理后的新類,處理后,cls是一個類名
            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");

2.1.4 修復重映射一些沒有被鏡像文件加載進來的類

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

    ts.log("IMAGE TIMES: remap classes");

將未映射的ClassSuper Class進行重新映射。

  • _getObjc2ClassRefs是獲取Mach-O文件中的__objc_classrefs
  • _getObjc2SuperRefs是獲取Mach-O文件中的__objc_superrefs

2.2.5 修復一些消息

#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

通過_getObjc2MessageRefs獲取Mach-O文件中的__objc_msgrefs。
當獲取的字段存在內(nèi)容的時候,遍歷執(zhí)行fixupMessageRef函數(shù)。


2.1.6 當我們的類里面有協(xié)議的時候,添加協(xié)議到協(xié)議列表

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

    ts.log("IMAGE TIMES: discover protocols");

調(diào)用_getObjc2ProtocolList獲取__objc_protolist列表,通過遍歷,執(zhí)行readProtocol函數(shù),將protocol添加到protocol_map表中。


2.1.7 修復沒有被加載的協(xié)議

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

    ts.log("IMAGE TIMES: fix up @protocol references");

通過_getObjc2ProtocolRefs獲取__objc_protorefs,然后遍歷,執(zhí)行remapProtocolRef函數(shù)。


2.1.8 分類處理

僅在分類初始化,并且數(shù)據(jù)加載到類之后才執(zhí)行。對于啟動時出現(xiàn)的分類,推遲到_dyld_objc_notify_register調(diào)用完成后的第一個load_images調(diào)用。

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

    ts.log("IMAGE TIMES: discover categories");

2.1.9 類的加載處理

    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t const *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);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");

2.1.10 沒有被處理的類,優(yōu)化那些被侵犯的類

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

    ts.log("IMAGE TIMES: realize future classes");
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容