iOS底层原理_14类的加载原理(上)

第十四节课 类的加载原理(上)

上一篇文章我们了解了一下应用程序的加载,我们通过逐步推断,了解了dyld的大致流程,dyld是如何一步步的加载我们的库,与镜像文件的。而这片文章我们就接着来研究后续的各项步骤,理解类的相关信息是如何加载到内存的,其中重点关注map_imagesload_images

回到_dyld_objc_notify_register的位置,查看这两个参数:map_imagesload_images

map_images:主要是管理文件中和动态库中的所有符号,即class、protocol、selector、category等

load_images:加载执行load方法

read_images流程引入

map_images源码流程

进入map_images源码

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_nolock 源码,其关键代码是_read_images

void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    //...省略

    // Find all images with Objective-C metadata.查找所有带有Objective-C元数据的映像
    hCount = 0;

    // Count classes. Size various table based on the total.计算类的个数
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    //代码块:作用域,进行局部处理,即局部处理一些事件
    {
        //...省略
    }
    
    //...省略

    if (hCount > 0) {
        //加载镜像文件!!!(重点)
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // Call image load funcs after everything is set up.一切设置完成后,调用镜像加载功能。
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[I]);
        }
    }
}

read_images主体流程

_read_images主要是主要是加载类信息,即类、分类、协议等,进入_read_images源码实现,可以通过系统的ts.log 查看到各部分代码块的主要内容:

  • 1、条件控制进行的一次加载
  • 2、修复预编译阶段的@selector的混乱问题
  • 3、错误混乱的类处理
  • 4、修复重映射一些没有被镜像文件加载进来的类
  • 5、修复一些消息
  • 6、当类里面有协议时:readProtocol 读取协议
  • 7、修复没有被加载的协议
  • 8、分类处理
  • 9、类的加载处理
  • 10、没有被处理的类,优化那些被侵犯的类

接下来我们一个个来看

  1. 条件控制进行的一次加载
    doneOnce流程中通过NXCreateMapTable 创建表,存放类信息,即创建一张类的哈希表gdb_objc_realized_classes,其目的是为了类查找方便、快捷
        //...省略

        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
        //总容积:8*4/3
        //
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        //创建表
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

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

查看gdb_objc_realized_classes的注释说明,这个哈希表用于存储不在共享缓存且已命名类,无论类是否实现,其容量是类数量的4/3

14-创建表.png

  1. 修复预编译阶段的@selector的混乱问题
    主要是通过通过_getObjc2SelectorRefs拿到Mach_O中的静态段__objc_selrefs,遍历列表调用sel_registerNameNoLockSEL添加到namedSelectors哈希表中
// 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_registerNameNoLock(name, isBundle);
              if (sels[i] != sel) {
                  sels[i] = sel;
              }
          }
      }
  }

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

其中_getObjc2SelectorRefs的源码如下,表示获取Mach-O中的静态段__objc_selrefs,后续通过_getObjc2开头的Mach-O静态段获取,都对应不同的section name

14-_getObjc2SelectorRefs.png

sel_registerNameNoLock源码路径如下:sel_registerNameNoLock -> __sel_registerName

进入search_builtins后我们发现,先取值的地方是_dyld_get_objc_selector

14-search_builtins.png

退出来往下看,其关键代码是auto it = namedSelectors.get().insert(name);,即将sel插入namedSelectors哈希表

14-__sel_registerName.png

其中selector --> sel并不是简单的字符串,是带地址的字符串。sels[i]与sel字符串一致,但是地址不一致,所以需要调整为一致的。即fix up,可以通过打印调试验证。

14-打印调试.png

  1. 错误混乱的类处理
    主要是从Mach-O中取出所有类,在遍历进行处理
    // 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;
      }
      //从编译后的类列表中取出所有类,即从Mach-O中获取静态段__objc_classlist,是一个classref_t类型的指针
      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];
          //读取类,经过这步后,cls获取的值才是一个名字
          Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
          //经过调试,并未执行if里面的流程
          //初始化所有懒加载的类需要的内存空间,但是懒加载类的数据现在是没有加载到的,连类都没有初始化
          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");

通过代码调试,知道了在未执行readClass方法前,cls只是一个地址,在执行后,cls是一个类的名称。

14-调试readClass.png

所以到这步为止,类的信息目前仅存储了地址+名称
我们也终于看到了本篇文章的重点readClass,一会我们单独对其进行分析~

  1. 修复重映射一些没有被镜像文件加载进来的类
    主要是将未映射的ClassSuper Class进行重映射,其中
    _getObjc2ClassRefs是获取Mach-O中的静态段__objc_classrefs即类的引用
    _getObjc2SuperRefs是获取Mach-O中的静态段__objc_superrefs即父类的引用
    通过注释可以得知,被remapClassRef的类都是懒加载的类,所以最初经过调试时,这部分代码是没有执行的
    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.
    //经过调试,并未执行if里面的流程
    //将未映射的Class 和 Super Class重映射,被remap的类都是懒加载的类  
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            //Mach-O的静态段 __objc_classrefs
            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?
            //Mach_O中的静态段 __objc_superrefs
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[I]);
            }
        }
    }

    ts.log("IMAGE TIMES: remap classes");
  1. 修复一些消息
    主要是通过_getObjc2MessageRefs 获取Mach-O的静态段 __objc_msgrefs,并遍历通过fixupMessageRef将函数指针进行注册,并fix为新的函数指针
    // 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");
  1. 当类里面有协议时:readProtocol 读取协议
    // Discover protocols. Fix up protocol refs. 发现协议。修正协议参考
    //遍历所有协议列表,并且将协议列表加载到Protocol的哈希表中
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        //创建protocol哈希表,表的名称为protocol_map
        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();
        //通过_getObjc2ProtocolList 获取到Mach-O中的静态段__objc_protolist协议列表,即从编译器中读取并初始化protocol
        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            //循环遍历协议列表,通过readProtocol方法将协议添加到protocol_map哈希表中
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

    ts.log("IMAGE TIMES: discover protocols");
  1. 修复没有被加载的协议
    主要是通过 _getObjc2ProtocolRefs 获取到Mach-O的静态段 __objc_protorefs(与6中的__objc_protolist并不是同一个东西),然后遍历需要修复的协议,通过remapProtocolRef比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换
    // 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");
  1. 分类处理
    主要是处理分类,需要在分类初始化并将数据加载到类后才执行,对于运行时出现的分类,将分类的发现推迟推迟到对_dyld_objc_notify_register的调用完成后的第一个load_images调用为止
    // 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");
  1. 类的加载处理
    主要是实现类的加载处理,实现非懒加载类
  • 通过_getObjc2NonlazyClassList获取Mach-O的静态段__objc_nlclslist非懒加载类表

  • 通过addClassTableEntry将非懒加载类插入类表,存储到内存,如果已经添加就不会载添加,需要确保整个结构都被添加

  • 通过realizeClassWithoutSwift实现当前的类,因为前面3中的readClass读取到内存的仅仅只有地址+名称,类的data数据并没有加载出来

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

    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        //通过_getObjc2NonlazyClassList获取Mach-O的静态段__objc_nlclslist非懒加载类表
        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
            }
            //实现当前的类,因为前面readClass读取到内存的仅仅只有地址+名称,类的data数据并没有加载出来
            //实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
            realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");
  1. 没有被处理的类,优化那些被侵犯的类
    主要是实现没有被处理的类,优化被侵犯的类
    // 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();
    }

readClass分析

readClass主要是读取类,在未调用该方法前,cls只是一个地址,执行该方法后,cls是类的名称,其源码实现如下,关键代码是addNamedClassaddClassTableEntry,源码实现如下

/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be: 
* - cls
* - nil  (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by 
* mustReadClasses(). Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    //通过mangledName获取类的名字
    const char *mangledName = cls->nonlazyMangledName();
    //如果想进入自定义,自己加一个判断
    const char *LGPersonName = "LGPerson";
    if (strcmp(mangledName, LGPersonName) == 0) {
        auto kc_ro = (const class_ro_t *)cls->data();
        printf("%s -- 研究重点--%s\n", __func__,mangledName);
    }
    //当前类的父类中若有丢失的weak-linked类,则返回nil
    if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked). 
        // Disavow any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->setSuperclass(nil);
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();
    
    //判断是不是后期要处理的类
    //正常情况下,不会走到popFutureNamedClass,因为这是专门针对未来待处理的类的操作
    //通过断点调试,不会走到if流程里面,因此也不会对ro、rw进行操作
    Class replacing = nil;
    if (mangledName != nullptr) {
        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()) {
                _objc_fatal("Can't complete future class request for '%s' "
                            "because the real class is too big.",
                            cls->nameForLogging());
            }

            class_rw_t *rw = newCls->data();
            const class_ro_t *old_ro = rw->ro();
            memcpy(newCls, cls, sizeof(objc_class));

            // Manually set address-discriminated ptrauthed fields
            // so that newCls gets the correct signatures.
            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());

            rw->set_ro((class_ro_t *)newCls->data());
            newCls->setData(rw);
            freeIfMutable((char *)old_ro->getName());
            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(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        if (mangledName) { //some Swift generic classes can lazily generate their names
            //加入表里
            addNamedClass(cls, mangledName, replacing);
        } else {
            Class meta = cls->ISA();
            const class_ro_t *metaRO = meta->bits.safe_ro();
            ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
            ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
        }
        // 插入表,即相当于从mach-O文件 读取到 内存 中,类与元类同步加入
        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;
}

经过调试我们发现readClass主要分为以下几步

  1. 通过mangledName获取类的名字

  2. 当前类的父类中若有丢失的weak-linked类,则返回nil

  3. 判断是不是后期需要处理的类,在正常情况下,不会走到popFutureNamedClass,因为这是专门针对未来待处理的类的操作,也可以通过断点调试,可知不会走到if (mangledName != nullptr)流程里面,因此也不会对ro、rw进行操作。

  4. 通过addNamedClass将当前类添加到已经创建好的gdb_objc_realized_classes哈希表,该表用于存放所有类

  5. 通过addClassTableEntry,将初始化的类添加到allocatedClasses表,是在_objc_init中的runtime_init就创建了allocatedClasses

总结:

所以综上所述,readClass的主要作用就是将Mach-O中的类读取到内存,即插入表中,但是目前的类仅有两个信息:地址以及名称,而mach-O的其中的data数据还未读取出来。rwro的具体操作我们也暂时不知道是在哪里,下一篇文章我们再来继续讨论。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容