Runtime objc4-779.1 App启动过程中Runtime都干了什么?

Posted Jsen_Wang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Runtime objc4-779.1 App启动过程中Runtime都干了什么?相关的知识,希望对你有一定的参考价值。

知识准备

首先我们要知道一个场景,我们点击屏幕上的应用,到我们看到应用完全展示,并可以操作,这个过程中,系统、runtime、我们自己的代码,都做了大量的工作,这个过程有很多优秀的博文已经详细的讲述过,例如深入理解iOS App的启动过程 —by 为自己丶拼个未来,这篇文章的内容其实绝大部分来自于苹果WWDC2016中的一个官方视频,如果对App启动过程不熟悉的同学可以这两个结合着看一下.

Runtime在启动过程中做了什么?

首先我们要知道启动过程中涉及的步骤

  • 1, Dyld 加载可执行文件
  • 2,加载动态库(递归执行加载所有)
  • 3,Rebase(解决Mach-O因ASLR导致的内部符号指针指向问题)
  • 4,Bind(解决Mach-O因ASLR导致的外部符号指针指向问题)
  • 5,Objc(解决类、类别加载等问题)
  • 6,Initializers(执行load、C/C++静态初始化和标记为attribute(constructor)的方法)

不言而喻,我们这篇博文写的就是第五步的详细解析.

注册dyld通知 objc-os.mm line 913

void _objc_init(void)

    ....各种初始化过程省略
	
	// 注册map_images,load_images,unmap_image方法对应的通知,根据App启动过程中dyld初始化ImageLoader加载各种image的过程,这里很明显是要配合dyld去做一些image加载时的操作.
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

看到这里,我们大可断定,在App启动过程中除了必要的环境初始化,Runtime主要的操作都在map_imagesload_images.

处理dyld映射的image objc-runtime-new.mm line 2938

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


// 这个方法比较长,大约有150行,我们只留关键代码
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])

	// 所有images的数量如果大于0
    if (hCount > 0) 
    	// 这里开始进入核心逻辑 
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    


核心逻辑1 _read_images objc-runtime-new.mm line 3244

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)

	// image头部信息
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t i;
    Class *resolvedFutureClasses = nil;
    size_t resolvedFutureClassCount = 0;
    static bool doneOnce;
    bool launchTime = NO;
    TimeLogger ts(PrintImageTimes);

    runtimeLock.assertLocked();

// 定义一个EACH_HEADER宏,用来for循环便利每一个header信息
#define EACH_HEADER \\
    hIndex = 0;         \\
    hIndex < hCount && (hi = hList[hIndex]); \\
    hIndex++

    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
        // Swift3.0之前的版本处理
        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

// 如果是OSX系统,这里我们可以不用看
# 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) 
            DisableNonpointerIsa = true;
            if (PrintRawIsa) 
                _objc_inform("RAW ISA: disabling non-pointer isa because "
                             "the app is too old (SDK version " SDK_FORMAT ")",
                             FORMAT_SDK(dyld_get_program_sdk_version()));
            
        

        // 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();
        
        // 初始化标记指针混淆器,ALSR技术的执行对象
        initializeTaggedPointerObfuscator();

		// 打印加载类数量信息
        if (PrintConnecting) 
            _objc_inform("CLASS: found %d classes during launch", totalClasses);
        
		
		// 这里是为了给类加载准备一个hashmap,因为此map的荷载系数为4/3,也就是当其内容占据表容量的3/4时就不能够继续往里正常存储了,一般会对其进行扩容,(扩容方式一般为容量翻倍,可以参考关联对象的hash表的扩容方式)
        // 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);

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

	// 将所有的SEL注册到hash表中,这里和上一步为class准备的hash表不是同一张
    // Fix up @selector references
    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 = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) 
                    sels[i] = sel;
                
            
        
    

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

    // 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;
        
		
		// 从头部信息中读取classlist的指针位置
        classref_t const *classlist = _getObjc2ClassList(hi, &count);

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

        for (i = 0; i < count; i++) 
            Class cls = (Class)classlist[i];
            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");

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

// 修复旧的函数指针遗留
#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

    bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();
	
	// 遍历所有的协议列表,并将其加载到协议的hash表中
    // 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();

        // 如果image在shared cache中,则跳过这一步,因为dyld在加载过程中,有一个shared cache存在,它里边存储了所有的动态库,这样不仅提高了应用的image加载过程,也进而加快了应用启动速度.让系统更加的高效的运转.
        if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) 
            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");

    // Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    // 修复协议列表引用,优化后的images可能是正确的,但是并不能确定
    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 && cacheSupportsProtocolRoots && 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");


	// 遍历所有的类别并处理.
    // Discover categories.
    for (EACH_HEADER)  // 找到当前类,查找类对应的category数组
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();
		
		// 遍历所有类
        auto processCatlist = [&](category_t * const *catlist) 
        	// 遍历类对应的category数组
            for (i = 0; i < count; i++) 
                category_t *cat = catlist[i];
                Class cls = remapClass(cat->cls);
                locstamped_category_t lccat, hi;
                // 找不到对应的class
                if (!cls) 
                    // Category's target class is missing (probably weak-linked).
                    // Ignore the category.
                    if (PrintConnecting) 
                        _objc_inform("CLASS: IGNORING category \\?\\?\\?(%s) %p with "
                                     "missing weak-linked target class",
                                     cat->name, cat);
                    
                    continue;
                
                
                // 
                // Process this category.
                if (cls->isStubClass()) 
                    // /存根类永远不会实现。
                    // 存根类在初始化之前不知道它们的元类,
                    // 因此我们必须将带有类方法或属性的类别添加到存根本身。
                    // methodizeClass()将找到它们并酌情将它们添加到元类中。
                    if (cat->instanceMethods ||
                        cat->protocols ||
                        cat->instanceProperties ||
                        cat->classMethods ||
                        cat->protocols ||
                        (hasClassProperties && cat->_classProperties))
                    
                        objc::unattachedCategories.addForClass(lc, cls);
                    
                 else 
                   // 如果目标类已经实现, 首先将category的内容合并到目标类中,
                   // 如果没有实现,则实现后再进行这合并操作
                    if (cat->instanceMethods ||  cat->protocols
                        ||  cat->instanceProperties)
                    
                        if (cls->isRealized()) 
                            attachCategories(cls, &lc, 1, ATTACH_EXISTING);
                         else 
                            objc::unattachedCategories.addForClass(lc, cls);
                        
                    
                    
                    // 这里和上边逻辑一样,只不过是对meta class进行的操作,上边是对普通的class
                    // 所以我们也可以对meta class增加类别的
                    if (cat->classMethods  ||  cat->protocols
                        ||  (hasClassProperties && cat->_classProperties))
                    
                        if (cls->ISA()->isRealized()) 
                            attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                         else 
                            objc::unattachedCategories.addForClass(lc, cls->ISA());
                        
                    
                
            
        ;
        processCatlist(_getObjc2CategoryList(hi, &count));
        processCatlist(_getObjc2CategoryList2(hi, &count));
    

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

    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code before
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()

	// 实现所有非蓝加载的类,load方法和静态实例变量
    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) 
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) 
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;
			
			// 将类类信息注册到hash table中
            addClassTableEntry(cls);
			
			// 处理swift类
            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");

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

    if (DebugNonFragileIvars) 
        realizeAllClasses();
    

	// 打印预优化信息
    // Print preoptimization statistics
    if (PrintPreopt) 
        static unsigned int PreoptTotalMethodLists;
        static unsigned int PreoptOptimizedMethodLists;
        static unsigned int PreoptTotalClasses;
        static unsigned int PreoptOptimizedClasses;

        for (EACH_HEADER) 
            if (hi->hasPreoptimizedSelectors()) 
                _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
                             "in %s", hi->fname());
            
            else if (hi->info()->optimizedByDyld()) 
                _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
                             "in %s", hi->fname());
            

            classref_t const *classlist = _getObjc2ClassList(hi, &count);
            for (i = 0; i < count; i++) 
                Class cls = remapClass(classlist[i]);
                if (!cls) continue;

                PreoptTotalClasses++;
                if (hi->hasPreoptimizedClasses()) 
                    PreoptOptimizedClasses++;
                
                
                const method_list_t *mlist;
                if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) 
                    PreoptTotalMethodLists++;
                    if (mlist->isFixedUp()) 
                        PreoptOptimizedMethodLists++;
                    
                
                if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) 
                    PreoptTotalMethodLists++;
                    if (mlist->isFixedUp()) 
                        PreoptOptimizedMethodLists++;
                    
                
            
        

        _objc_inform("PREOPTIMIZATION: %zu selector references not "
                     "pre-optimized", UnfixedSelectors);
        _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
                     PreoptOptimizedMethodLists, PreoptTotalMethodLists, 
                     PreoptTotalMethodLists
                     ? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists 
                     : 0.0);
        _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
                     PreoptOptimizedClasses, PreoptTotalClasses, 
                     PreoptTotalClasses 
                     ? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
                     : 0.0);
        _objc_inform("PREOPTIMIZATION: %zu protocol references not "
                     "pre-optimized", UnfixedProtocolReferences);
    

#undef EACH_HEADER

总结_read_images的内容为:

  • 1.初始化标记指针混淆器
  • 2.初始化类注册用的hashmap
  • 3.将所有的SEL注册到hashmap中
  • 4.初始化所有懒加载的类
  • 5.进行所有类的重映射
  • 6.遍历所有的协议列表,将其初始化并加载到协议的hash表中
  • 7.处理所有的类别,将其添加到对应类结构中(包含普通的class和meta class)
  • 8.实现所有非蓝加载的类,load方法和静态实例变量
  • 9.打印预优化信息

通过以上的源码分析我们得知,_read_images方法所做的事情主要是做了一些预优化的动作,方便后边的load_images以及整个程序运行过程的便捷和效率,

核心逻辑2 load_images objc-runtime-new.mm line 2955

void
load_images(const char *path __unused, const struct mach_header *mh)

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // 找到所有的load方法,这里的load方法就是我们OC中class里经常用的load方法
    
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    

    // 执行所有的load方法,
    call_load_methods();


核心逻辑2.1 找到拥有load方法的类和类别,并记录. prepare_load_methods objc-runtime-new.mm line 3698
void prepare_load_methods(const headerType *mhdr)

    size_t count, i;

    runtimeLock.assertLocked();
	
	// 找到非懒加载的类列表
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) 
    	// 将 所有有load方法类记录到loadable_classes中,在此方法中,会递归调用schedule_class_load(cls->superclass);所以,记录的顺序是子类依次到父类的继承者链
        schedule_class_load(remapClass(classlist[i]));
    
	// 找到非懒加载的类别列表
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) 
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) 
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        // 将所有有load方法的类别记录到loadable_categories中
        add_category_to_loadable_list(cat);
    


核心逻辑2.2 根据核心逻辑2.1中记录的类和类别,执行load方法 call_load_methods objc-loadmethod.mm line 337
void call_load_methods(void)

    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;
	
	// 自动释放池
    void *pool = objc_autoreleasePoolPush();

    do 
        // 执行所有记录的class的load方法
        while (loadable_classes_used > 0) 
            call_class_loads();
        

        // 2.执行一次类别的load
        more_categories = call_category_loads();

        // 3. 如果有更多的class或者么有执行过load方法的类别,则继续
     while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;


核心逻辑2.1中记录拥有load的类和类别的时候是子类->父类->父类的父类这样记录在数组中的.
核心逻辑2.2中执行load方法时,是根据数组的尾部index依次减一进行循环的,又因为先执行classload方法,再执行类别的,所以,load方法执行顺序为父类->子类->类别

至此Runtime在App加载过程中的动作我们都分析完了

总结一下

  • 在预处理过程中,大量使用了hashmap,在关联对象中也是一样,正式因为这种数据结构的高效性,得到了开发者的青睐,在我们实际开发中也可以将一些复杂的数据关系转换成hashmap或者字典来处理,这样虽然会损失一些空间,但是O(1)的时间复杂度还是很香的.
  • ASLR是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的的一种技术,这个技术在常见系统中都有应用,因为它的应用才有了dyld加载过程中的Rebase和Bind步骤,
  • load方法的加载顺序一直是一个ios热点问题,我们现在也能够知道为什么他的执行顺序是父类->子类->类别,不同类别之间的load方法执行顺序和dyld加载类别的顺序有关,这个我们可以在XCode中控制,百度一下,很多这方面的资料.
  • 在加载过程的代码中有很多对老版本和swift的处理逻辑,这些都是历史遗留问题但是我们可以看到频繁的版本变更带来的代码整洁性,可读性,结构都有很大的影响,苹果的开发者运用了大量的宏定义,预编译手段处理版本兼容问题,这是值得我们学习的.

以上是关于Runtime objc4-779.1 App启动过程中Runtime都干了什么?的主要内容,如果未能解决你的问题,请参考以下文章

Runtime objc4-779.1 一图看懂iOS Runtime消息转发

Runtime objc4-779.1 通过runtime源码对OC对象销毁过程解析

Runtime objc4-779.1 通过runtime源码对OC对象初始化过程解析

Runtime objc4-779.1 一图看懂iOS Runtime消息转发

Runtime objc4-779.1 OC中,为什么swizzleMethod时要先addMethod?

Runtime objc4-779.1 Runtime在实际开发中的应用之__attribute__