美文网首页
Images加载一

Images加载一

作者: 半边枫叶 | 来源:发表于2020-01-11 12:21 被阅读0次

    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加载一

          本文链接:https://www.haomeiwen.com/subject/subvactx.html