iOS底层探索之map_images

在上一篇文章 objc_init 分析 中,最后有三个函数 map_imagesload_imagesunmap_image

一、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);
}
1.1 map_images_nolock
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    // 省略准备逻辑...

    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(hList, hCount, totalClasses, unoptimizedTotalClasses); 这一行代码。

二、_read_images

因为源码比较长,这里就不贴了。

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    // 省略代码...

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

        classref_t const *classlist = _getObjc2ClassList(hi, &count);

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

        for (i = 0; i < count; i++) {
            // 断点1
            Class cls = (Class)classlist[i];
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);  // 读取类
        }
    }
    // 省略 protocol 处理...

    // Realize non-lazy classes (for +load methods and static instances) 
    // 实现非惰性类(用于+ load方法和静态实例)
    for (EACH_HEADER) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &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
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }
}

主要流程如下:

  1. 条件控制进行一次的加载
  2. 修复预编译阶段的 @selector 的混乱问题
  3. 错误混乱的类处理
  4. 修复重映射⼀些没有被镜像⽂件加载进来的类
  5. 修复⼀些消息
  6. 当我们类⾥⾯有协议的时候 : readProtocol
  7. 修复没有被加载的协议
  8. 分类处理
  9. 类的加载处理
  10. 没有被处理的类 优化那些被侵犯的类

在上面代码中 断点1 打个断点,运行

(lldb) po cls
0x000000010048dfe8
--- 走过断点1和readClass之后再打印
(lldb) po newCls
OS_dispatch_io

在没有 readClass 之前,classlist 里面存的还是内存地址,readClass 之后,就有名字了。下面看下 readClass 具体做了什么?

2.1 readClass

读取编译器编写的类和元类。返回新的类指针。

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->mangledName();
    
    if (missingWeakSuperclass(cls)) {
        // 省略...
    }
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (Class newCls = popFutureNamedClass(mangledName)) {
        // 省略...
    }
    
    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;
}

mangleName 打个断点

mangledName断点.png

发现是系统的类。我们还是研究自定义类 GLPerson,在mangleName 下面添加如下代码:

    // godlong test begin
    const char *GLPersonName = "GLPerson";
    if (strcmp(mangledName, GLPersonName) == 0) {
        printf("%s 来到了自定义类 %s", __func__, mangledName);
    }
    // godlong test end

断点到 if 条件里面,运行工程,成功进入断点

进入自定义类.png

2.2 addNamedClass

走到 addNamedClass 断点查看

addNamedClass.png

可以发现这时 cls 还是地址,而 mangledName 就是我们的类名。

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

解释将类的名称添加到非元类的类映射表中。警告有关重复的类名,并保留旧的映射

2.3 addClassTableEntry
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.
    auto &set = objc::allocatedClasses.get();

    ASSERT(set.find(cls) == set.end());

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

解释 : 将一个类添加到所有类的表中。如果 addMetatrue,也自动添加该类的元类

三、realizeClassWithoutSwift

先重点关注的加载,在 read_images 的源码中,往下看的时候,发现注释

Realize non-lazy classes (for +load methods and static instances)
【译】实现非懒加载类(用于+ load方法和静态实例)

那什么是 non-lazy classes 呢?

3.1 non-lazy classes 非懒加载类

如果一个类实现了 + load 方法,那这个类就是 non-lazy class(非懒加载类)。

反之,没实现 + load 方法,就是懒加载类。

3.2 进入自定义类的 realizeClassWithoutSwift

GLPerson 类添加 + load 方法,然后把 for(EACH_HEADER) 循环里面的代码改成如下:

    // 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;
            
            // loong test begin
            const char *mangledName = cls->mangledName();
            const char *GLPersonName = "GLPerson";
            if (strcmp(mangledName, GLPersonName) == 0) {
                printf("%s 来到了自定义类 %s", __func__, mangledName); // 断点2
            }
            // loong test end

            addClassTableEntry(cls);
            // class is a Swift class from the stable Swift ABI 所以不会走这里
            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);
        }
    }

断点到上面代码的 断点2,确认来到了自定义类 GLPerson

断点2.png
3.3【重点】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.
    【译】* 对类 cls 进行首次初始化,包括分配其读写数据。不执行任何 Swift 端初始化。返回该类的真实类结构。
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw; // 读写数据
    Class supercls; // 父类
    Class metacls;  // 元类

    if (!cls) return nil; // 如果为空,返回nil
    if (cls->isRealized()) return cls;  // 如果已经实现,直接返回
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?

    auto ro = (const class_ro_t *)cls->data(); // 读取类的数据
    auto isMeta = ro->flags & RO_META; // 是否是元类
    if (ro->flags & RO_FUTURE) { // rw已经有值的话走这里
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else { // 正常的类走这里
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>(); // 开辟rw
        rw->set_ro(ro); // 把cls的数据ro赋值给rw
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta; // 更新flags
        cls->setData(rw); // 再把rw设置为cls的data数据
    }

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

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

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // Realize superclass and metaclass, if they aren't already.
    //实现超类和元类(如果尚未实现)。
    // This needs to be done after RW_REALIZED is set above, for root classes.
    //对于根类,需要在上面设置了RW_REALIZED之后执行此操作。
    // This needs to be done after class index is chosen, for root metaclasses.
    //对于根元类,需要在选择类索引之后执行此操作。
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    // 递归调用 realizeClassWithoutSwift ,实现父类和元类
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) { // 如果是元类,对isa处理
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else { // 不是元类,也是对isa处理
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->name, "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->superclass  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // 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
    // 赋值一些 ro 中的 flags标识位 到 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.
    // 从ro或父类传播关联的对象禁止标志。
    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, previously);

    return cls;
}

确认是 GLPerson 的时候进入 realizeClassWithoutSwift
,在 auto ro = (const class_ro_t *)cls->data(); 这行代码打一个端点,这一步是 cls 读取 data 数据赋值给 ro

然后往下走一步,看看读取到了什么数据?

类的ro数据查看.png
3.4 递归调用 realizeClassWithoutSwift
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

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

realizeClassWithoutSwift 的源码中,递归调用了 realizeClassWithoutSwift,分别传入的是父类和元类(ISA())。

这是为了确定继承链的关系。

3.5 懒加载类实现时机

知道了非懒加载类在 map_images 时,调用 realizeClassWithoutSwift 实现的,那懒加载类是什么时候实现的呢?

还是上面的代码,把 GLPerson 中的 + load 方法去掉,然后再realizeClassWithoutSwift 方法里面添加如下代码并断点在 断点3

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;
    
    // loong test begin
    if (cls) {
        const char *mangledName = cls->mangledName();
        const char *GLPersonName = "GLPerson";
        if (strcmp(mangledName, GLPersonName) == 0) {
            printf("%s 来到了自定义类 %s", __func__, mangledName); // 断点3
        }
    }
    // loong test end
    // 省略...
}

main.m 中添加,并添加断点

        GLPerson *p = [GLPerson alloc];

运行工程,首先断点在 GLPerson *p = [GLPerson alloc];

继续运行,发现走到了 断点3。查看堆栈

懒加载类加载堆栈.png

可知:懒加载类 是在第一次发送消息的时候,调用 realizeClassWithoutSwift 实现类的。

3.6 懒加载类的优点

因为正常的工程中,实现 + load 方法的类很少,大部分类都是懒加载类。

如果所有的类都在启动的时候实现完成,就会非常慢,懒加载类等到调用的时候再去实现,这样会加快启动速度。

再就是每个类都有很多的代码,包括变量,方法等,会占用很多内存,而如果你这个类在工程中就没调用,或者在很深的页面才会调用,正常情况下很少人会使用到,如果在启动加载了,就会造成内存浪费。

懒加载类优点:

  • 加快启动速度
  • 节省应用初始内存

四、methodizeClass 附加分类

作用:修复 cls 的方法列表,协议列表和属性列表。附加任何未解决的分类。

源码:

static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro(); // 读取ro数据
    auto rwe = rw->ext(); // 读取ext,赋值给rwe

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods(); // 获取ro中的方法列表
    if (list) {
        // 对方法列表list重新排序
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        if (rwe) rwe->methods.attachLists(&list, 1); // 如果有rwe,添加方法列表list到rwe的methodsList
    }

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

    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->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 根元类添加initialize方法
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories. 附加分类
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#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
}

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

推荐阅读更多精彩内容