Images加载一

Objc中类的初始化是从_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);
}
首先是调用的是environ_init();这个方法。

这个方法里面主要是对环境变量的配置,方法中有段代码可以打印出所有的环境变量:

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

解锁这段打印代码的限制条件,就会打印下面所有的环境变量:

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

以上环境变量中有我们常用的:

  • OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
    该环境变量会禁止non-pointer isa。这样所有的对象都会使用正常的,非优化的isa。该isa指向对象所属的类。
    non-pointer isa

    上图是我们正常的环境,可以看到通过x/4gx打印出对象的地址,地址的第一段为对象的isa,和上面的p/x打印的类对象的地址对比可以发现,两个地址是不相等了。
    接下来我们设置环境变量OBJC_DISABLE_NONPOINTER_ISA:
设置环境变量

然后重新运行程序:

pointer isa

可以看出对象的isa和类对象的地址是相同的,也就是对象的isa真正指向了所属的类。然后我们通过po打印isa得到了所属的类。
现在我们大概明白了环境变量的意义。

  • OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
    开启该环境变量后会打印出所有实现+(void)load方法的对象。我们可以通过这个来优化程序的启动速度。

接下来我们返回到_objc_init函数,继续探究:

tls_init();用于线程的key绑定,我们暂时略过;
然后就是static_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]();
    }
}

通过注释我们也可以了解到,该函数的作用是:静态函数初始化。那么为什么不是在dyld中初始化呢?因为libc调用_objc_init()是在dyld之前,所以我们这里需要手动调用初始化。
注意:这里的静态函数是指的系统的静态函数,我们自定义的不算在里面。
然后调用的就是lock_init();,我们跟踪该函数发现函数实现为空:

void lock_init(void)
{
}

为什么为空实现呢?可能是这部分代码没有开源。我们继续往下走,_objc_init(void)中接下来调用的是exception_init();

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

通过注释说明,这里是注册异常的回调

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

例如我们调用一个未实现的方法时,程序就会跑到这里来。
我们继续回到_objc_init(void)方法中,接下来调用的就是_dyld_objc_notify_register(&map_images, load_images, unmap_image);
这个方法就是dyld中的方法

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

此方法主要是注册了三个回调函数,回调函数会在dyld中进行调用。当images被maped、unmapped、initiallized时候的回调。

map_images回调
map_images中调用了map_images_nolock(count, paths, mhdrs);然后map_images_nolock中调用了_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
下面我们来解读_read_images方法。
在该方法中有个以下的doneOnce判断

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

在doneOnce(只执行一次)中创建了两张表:gdb_objc_realized_classes和allocatedClasses。然后我们搜索这两张表,可以看到它们的定义

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

gdb_objc_realized_classes主要是用来存储那些不在共享缓存中,已经初始化或者未初始化的类

/***********************************************************************
* allocatedClasses
* A table of all classes (and metaclasses) which have been allocated
* with objc_allocateClassPair.
**********************************************************************/
static NXHashTable *allocatedClasses = nil;

allocatedClasses主要是用来存储那些已经初始化的类。

我们继续往下读_read_images方法的内容,主要包含以下几点:

  • 类处理;
  • 方法编号处理;
  • 协议处理;
  • 非懒加载类处理;
  • 待处理的类;
  • 分类处理;

上面我们是从宏观上列出了_read_images的主要的功能,下面我们开始细致的分析。

类的处理

首先获取类的list列表:

// 从编译后的类列表中取出所有类,获取到的是一个classref_t类型的指针
classref_t *classlist = _getObjc2ClassList(hi, &count);

然后for循环处理:

for (i = 0; i < count; i++) {
    ......
    // 通过readClass函数获取处理后的新类,
    Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
    ......
}

在for循环中调用readClass方法,对类进行处理。我们进入该方法看下,里面调用下面的两行代码

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

在这两个方法里面,分别将待处理的class存入到上面我们再doneOnce里面创建的两张表中。
然后我们返回过来继续看_read_images下面的流程。我们暂时略过与方法无关的内容(协议、方法、分类等)。然后找到方法:

realizeClassWithoutSwift(cls);

进入realizeClassWithoutSwift(cls);方法,看到对读取并且赋值了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 {
        // 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);
    }

然后递归的对父类和元类进行初始化调用

    supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));

然后绑定赋值绑定superClass以及isa

    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

初始化创建superClass后,同样也会将superClass的subClass执行本class。

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

我们继续往下看,发现调用了下面的方法:

    // Attach categories
    methodizeClass(cls);

进入该方法看看

auto rw = cls->data();
auto ro = rw->ro;

读取到rw和ro

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

将ro中的methoList attach到rw中

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

将ro中的propertyList attach到rw中

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

将ro中的protocolList attach到rw中。
上面代码就是讲ro中的list 贴到rw中。为什么要这样做呢?我们搜索attachLists方法,发现在
addMethod
class_addProtocol
_class_addProperty
attachCategories
中都调用了attachLists方法,所以可以推测,我们平时用的分类方法、协议方法、动态的增加方法等都会添加到rw中。而ro中只是类中实现的方法。
tips: rw是readwrite的缩写,是可以动态修改的。ro是readonly的缩写,在编译时期确定的,不能动态修改。比如用户的ivarsList就存储在ro,所以我们不能动态的给对象添加成员变量。
下面我们来看下attachLists方法是怎样对方法或者协议等列表进行attach处理的?

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

如果现在list中已经存在了一个Array。那么就会开辟一个新的list空间,将原来的list move到新创建的list的末尾,然后新插入的list copy到新创建list的头部。

你可能感兴趣的:(Images加载一)