iOS 类的加载

我们app的运行都会通过DYLD加载动静态库,然后进入到_objc_init函数,首先我们看一下这个函数中都干了什么?

1、_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);
}

其中3、4、5行代码只是一些判断,我们不用管,下面执行了environ_init()

1.1environ_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;
        }
    }

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

看到这是不是想说,这是什么鬼?其实我们在仔细看一下,这些都是我们项目的一些环境变量,我们可以在xcodeEdit Scheme->Arguments->Environment Variables

WX20200109-213724@2x.png

进行设置运行时环境变量,这些环境变量的具体使用运行时环境变量( Environment Variables )

  • 添加OS_ACTIVITY_MODEdisable,进行屏蔽系统日志
  • 添加OBJC_DISABLE_NONPOINTER_ISAYES,可以进行isa优化,可以使isa存储更多的内容.
  • 添加OBJC_PRINT_LOAD_METHODSYES,可以打印类和分类的load方法,对我们优化启动有帮助。

1.2tls_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.3static_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.4lock_init()

void lock_init(void)
{
}

这个函数是空的,这里可能就是可以采用CC++那一套锁的机制。

1.5exception_init()

这里是处理异常的一个函数,我们看一下源码:

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

exception_init初始化libobjc异常处理系统。

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

这里进行异常的输出,首先检查这是否是一个异常,如果是,检查是否是Objective-C异常,如果是,叫我们的注册回调对象uncaught_handler函数,最后终止处理程序。

1.6_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_objc_notify_register(&map_images, load_images, unmap_image)有3个参数

  • 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映射的给定图像。
  • 取得ABI特定的锁后,调用与ABI无关的代码。
    继续走就进入map_images_nolock函数,但是这个函数代码很长,里面都是一些打印和操作hCount,经过分析我们定位到:
if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

map_images执行的就是加载镜像文件,所以最终来到_read_images读取镜像文件,这就是map_images_nolock的重点。

2.1_read_images

_read_images你如果打开源码会看到,里面这个函数有400行代码,太多了,我们只挑重点的分析。看下面一段源码:

 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) {...}
# 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) {...}

        // 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) {...}
# endif

#endif

        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }
        
        initializeTaggedPointerObfuscator();

        if (PrintConnecting) {...}

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

我折叠的部分都是一些打印。看这一段源码首先判断了一个doneOnce字面意思,我们可以理解这里只执行一次,为什么只执行一次?我们看代码会发现有两行这个代码:

gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        
        allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);

创建2张表:gdb_objc_realized_classesallocatedClasses,我们看一下这两张表分别是存什么的?

// This is a misnomer: gdb_objc_realized_classes is actually a list of 
// named classes not in the dyld shared cache, whether realized or not.
NXMapTable *gdb_objc_realized_classes;  // exported for debuggers in objc-gdb.h

static Class getClass_impl(const char *name)
{
    runtimeLock.assertLocked();

    // allocated in _read_images
    assert(gdb_objc_realized_classes);

    // Try runtime-allocated table
    Class result = (Class)NXMapGet(gdb_objc_realized_classes, name);
    if (result) return result;

    // Try table from dyld shared cache
    return getPreoptimizedClass(name);
}
  • 通过注释我们可以知道,这张表是存储所有的类,包括实现的和未实现的。
/***********************************************************************
* allocatedClasses
* A table of all classes (and metaclasses) which have been allocated
* with objc_allocateClassPair.
**********************************************************************/
static NXHashTable *allocatedClasses = nil;
  • 这个就是存储被分配过的类和元类的表,就是已经实现的类和元类的表。
    我们可以这样说gdb_objc_realized_classes是包含allocatedClasses的,既然是包含,是不是只创建一张表就够了,为什么苹果会创建两张表?主要是为了断开,让我们进行精确查找,因为有时候会发现有一些特殊情况的类没有被初始化,在当前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表中。
那么,这个是在什么地方添加的呢?我们可以看到这个函数的调用readClass:

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->mangledName();

    Class replacing = nil;
    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()) {...}
        
        class_rw_t *rw = newCls->data();
        const class_ro_t *old_ro = rw->ro;
        memcpy(newCls, cls, sizeof(objc_class));
        rw->ro = (class_ro_t *)newCls->data();
        newCls->setData(rw);
        freeIfMutable((char *)old_ro->name);
        free((void *)old_ro);
        
        addRemappedClass(cls, newCls);
        
        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(getClassExceptSomeSwift(mangledName));
    } else {
        addNamedClass(cls, mangledName, replacing);
        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;
}

在上面的一段代码中我们看到对类cls进行了rwro的处理,那么这些是不是真的在这个方法里面处理呢?
我们进行断点调试之后发现,代码都执行完了都没有进入到这里,所以说明我们创建的类和系统方法的类都没有走这个方法,所以类的rw数据填充并非在此处理。
通过if (Class newCls = popFutureNamedClass(mangledName))这个判断我们得知,这里只是对未来待处理的类进行操作。
在往下,我们看到了添加表的代码:

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

我们查看addNamedClass:

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());
}
  • 通过NXMapInsert(gdb_objc_realized_classes, name, cls);将类添加到我们上述所说的总表中。
    继续查看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);
}

因为当前的类已经进行的初始化分配处理,已经有了地址,所以也要添加到allocatedClasses表中。
至此,已经初始化的类都已添加到gdb_objc_realized_classesallocatedClasses表中。

处理@selector
// 将所有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);
            }
        }
    }

这里简单介绍一下就是把我们的方法注册到内存中,也就是我们调用写好的方法会有方法名的提示,其实就是在内存中获取,通过我们的方法名获取到sel

处理非懒加载的类
 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);
        }
    }

这段代码三个重要点:

  • remapClass将类进行重映射
  • addClassTableEntry将类插入到allocatedClasses表中,如果存在就不插入
  • realizeClassWithoutSwift实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
    接下来我们就看一下realizeClassWithoutSwift具体实现的什么
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?
// ✅读取cls中的data数据,对ro进行赋值
    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) {// ✅未来的类
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro;
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {// ✅当前的类,读取cls中的data数据,对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


    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {...}// 一些打印

    // ✅remapClass源码,我们可以知道,remapClass主要是对类在表中进行查找的操作,如果表中已有该类,则返回一个空值,如果没有,则返回当前类,这样保证了类只加载一次,并结束递归归
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
   ...
// ✅ 将父类和元类赋值到类的父类和isa,这就是我们在lldb指令打印isa指向元类的原因
    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

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

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

// ✅双向链表指向关系 父类中可以找到子类 子类中也可以找到父类
    // 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();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();// ✅对rw进行赋值,其实这里rw还是空的
    auto ro = rw->ro;

    
    // 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 (cats) free(cats);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name));
        }
        assert(sel_registerName(sel_getName(meth.name)) == meth.name); 
    }
#endif
}

ro是在编译时就进行赋值的,只能读取,不能进行改变,rw可以在进行调试的时候动态添加和处理方法、属性和协议。
上述把ro里面的方法、属性和协议赋值给rw的时候都是通过attachLists进行添加,我们看一下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]));
        }
    }

可以看到attachLists有三种方法:

  • 0 lists -> 1 list:直接通过addedLists直接赋值
  • 1 list -> many lists: 将类开辟新的内存空间,将新数据进行存储
  • many lists -> many lists:扩大现有容器的内存,将旧数据放在指定的位置,将需要增加的数据放在前面

总结

  • 首先类的加载会先进行_objc_init函数的调用,最后来到_dyld_objc_notify_register函数,传入map_images
  • map_images执行的就是加载镜像文件,最终来到_read_images读取镜像文件
  • _read_images进行类存储到表gdb_objc_realized_classesallocatedClasses中。对类进行重映射,把所有的sel注册到nameSelectors表中,将所有的Protocol添加到protocol_map表中。
  • 存储类到表gdb_objc_realized_classesallocatedClasses是通过readClass对类进行插入表的处理
    • remapClass将类进行重映射
    • addClassTableEntry将类插入到allocatedClasses表中,如果存在就不插入
    • realizeClassWithoutSwift实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
  • 然后通过realizeClassWithoutSwift实现非懒加载类的一些信息相关的操作,如给类创建rw结构,对父类,元类也进行相关初始化,对父类,元类,根类,子类进行相关的绑定
  • 然后通过methodizeClass处理类的方法列表,协议列表,属性列表,将ro的值赋给rw
  • 同时也会将分类的信息进行加载。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,563评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,694评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,672评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,965评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,690评论 6 413
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,019评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,013评论 3 449
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,188评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,718评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,438评论 3 360
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,667评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,149评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,845评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,252评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,590评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,384评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,635评论 2 380

推荐阅读更多精彩内容