
我們在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)
這里我們可以簡單的看一下其中的一些代碼:

日常的開發(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)境初始化,主要是unattachedCategories 和 allocatedClasses。
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。其官方注釋如下:

從官方注釋中我們可以知道:
- 僅供
objc運行時使用 - 注冊處理程序,以便于在
映射、取消映射和初始化objc鏡像時調(diào)用 -
dyld將會通過一個包含objc-image-info的鏡像文件的數(shù)組回調(diào)mapped函數(shù)。
其中_dyld_objc_notify_register的三個參數(shù)的意義如下:
-
map_images:dyld將鏡像文件加載到內(nèi)存時,會觸發(fā)該函數(shù)。注意,該函數(shù)傳的是地址,也就是說是實時同步變化的,相當于Block。 -
load_images:dyld初始化鏡像文件會觸發(fā)該函數(shù)。 -
unmap_image:dyld移除鏡像文件時,會觸發(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)在我們要做的就是跟進去。

在閱讀
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)不一樣的效果。

可以看到,當我們把代碼折疊之后,會看到一些層次分明的
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");
將未映射的Class和Super 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");

