iOS 底层学习15

前言

iOS 底层第15天的学习。在第14天的学习中,已经分析了 readClass,而 ro,rw 是在何时进行赋值我们还不清楚,接下来继续进行探索。

read_images 探索

  • read_images 里继续寻找有关 class 代码
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
  //... 省略部分代码

 // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        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
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }
    ts.log("IMAGE TIMES: realize non-lazy classes");
  //... 省略部分代码
}
  • 静态分析发现了在 non-lazy classes 有关于 class 的处理,
  • 找到核心代码后加入 XKStudent 进行普通类拦截
  • 在分析前查看注解 (for +load methods and static instances) 后发现要先在 XKStudent 类里实现 load 方法才会调用。
  • 加入 load 方法进行动态分析, 来到了 realizeClassWithoutSwift
  • 进入 realizeClassWithoutSwift ,开始动态分析 XKStudent
  • 打印输出👇
  • 由输出的 methods_list count = 3 我们得知在进行ro 赋值时,已经把 methods给加入到 ro里,我们继续 step
  • 由⤴️得知:复制了一份 rorw ,继续往下 step
  • 由⤴️ 两个代码可知:印证了类的继承链图isa走位图
  • 继续step 进入 methodizeClass
static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();
    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();
   // ... 
    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
   // ... 
}
  • 进入 prepareMethodLists
static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle, const char *why)
{
   // ... 
    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[I];
        ASSERT(mlist);
        // Fixup selectors if necessary
        if (!mlist->isFixedUp()) {
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }
   // ... 
}
  • 进入 fixupMethodList
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    ASSERT(!mlist->isFixedUp());

    // fixme lock less in attachMethodLists ?
    // dyld3 may have already uniqued, but not sorted, the list
    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            meth.setName(sel_registerNameNoLock(name, bundleCopy));
            printf(" name is %s - meth.name is %p \n",name,meth.name());
        }
    }
   printf("----------------- Sort 后 ----------------- \n");
    // Unique selectors in list.
    for (auto& meth : *mlist) {
        const char *name = sel_cname(meth.name());
        printf(" name is %s - meth.name is %p \n",name,meth.name());
    }
    
    // Mark method list as uniqued and sorted.
    // Can't mark small lists, since they're immutable.
    if (!mlist->isSmallList()) {
        mlist->setFixedUp();
    }
  • 我们 在 fixupMethodList 加入了 两段打印,一个是在 sort 前,一个在sort后,打印👇
  • 得出的结论 methods的排列顺序是按照指针地址由小到大进行排序
  • 最后我们梳理一下在 realizeClassWithoutSwift里做了哪些事情
    • ro = cls->data(),ro的赋值
    • rw = ro ,ro 复制一份给rw
    • 类的继承链, isa走位图初始化
    • basemethods的排序

这时你是否会有个疑问,在上面可知只有实现 load 方法才会调用 read_images -> realizeClassWithoutSwift,当不实现 load 方法时是怎么加载的呢?

load 探索

  • load 方法去掉,动态运行程序
  • 我们发现没有调用 read_images,但还是会进入到 realizeClassWithoutSwift这个方法里,觉得很奇怪 bt一下
  • 当把 load 方法去掉,调用方法时候会发送消息进行 lookUpImpOrForward 进行慢速查找,最终也是会来到 realizeClassWithoutSwift。这就是所谓的懒加载只有当方法调用的时候才会去做相应的ro,rw处理。数据加载推迟到第一次消息的时候。
  • 非懒加载map_images 的时候,加载所有类数据。
  • 流程图👇

what is category

  • 新建一个 category ,代码👇
@interface XKStudent (XK)

@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;

- (void) readBook1;
- (void) readBook2;
+ (void) readBook3;

@end

@implementation XKStudent (XK)

- (void) readBook1 {
    NSLog(@"%s",__func__);
}
- (void) readBook2 {
    NSLog(@"%s",__func__);
}
+ (void) readBook3 {
    NSLog(@"%s",__func__);
}

@end
  • clang一下
clang -rewrite-objc main.m -o main.cpp
  • 查看 .cpp 文件代码
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_XKStudent_$_XK,
};
// 在 _category_t 生成了 CATEGORY_XKStudent_$_XK 
  • 全局搜索 _category_t
struct _category_t {
    const char *name;  // 别名 = XK
    struct _class_t *cls; // 类的引用
    const struct _method_list_t *instance_methods; // 存储实例方法
    const struct _method_list_t *class_methods;    // 存储类方法
    const struct _protocol_list_t *protocols;      // 存储协议
    const struct _prop_list_t *properties;         // 存储属性
};
  • 全局搜索 _method_list_t 查看一下方法的定义
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_XKStudent_$_XK __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"readBook1", "v16@0:8", (void *)_I_XKStudent_XK_readBook1},
    {(struct objc_selector *)"readBook2", "v16@0:8", (void *)_I_XKStudent_XK_readBook2}}
};

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_XKStudent_$_XK __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"readBook3", "v16@0:8", (void *)_C_XKStudent_XK_readBook3}}
};
  • 可知在编译时 有实例方法类方法,却没有属性 get,set,所以我们可以通过 runtime关联对象 进行处理
  • 接下来通过底层源码来验证一下 category_t 内部结构
struct category_t {
    const char *name;
    classref_t cls;
    WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
    WrappedPtr<method_list_t, PtrauthStrip> classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};
  • 根据 _classProperties 注解验证了 属性 不是一直存在于 disk,而是通过运行时去添加

还有一点疑问为什么 category 的方法要将实例方法类方法分开进行定义呢?

  • 我想最主要的原因就是 category 没有元类

总结

  • 今天我们从 read_images 来到了 realizeClassWithoutSwift,在 realizeClassWithoutSwift 做了对 ro,rw 的赋值,以及 类的继承链,isa走位图的处理;
  • 根据类 load 方法的实现与否还得知了类的加载有 懒加载非懒懒加载 ,它们之间的流程是完全不同的;
  • 最后还简单的分析了 category 的内部结构
  • category 是如何加载到 里,让 能够调用其内部的方法的?我们还不清楚,期待下一次的分析。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容