iOS底层

Objective-C 类的加载原理(中)

2021-07-16  本文已影响0人  HotPotCat

上篇文章分析了 _objc_init与read_images 的逻辑,最后定位到了类的初始化是在realizeClassWithoutSwift中的,这篇文章将继续分析。

一、 realizeClassWithoutSwift

realizeClassWithoutSwift中发现了对rorw等的一系列操作。在read_imags中要进入这个方法需要实现+ load方法。
核心逻辑精简后如下:

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    class_rw_t *rw;
    Class supercls;
    Class metacls;

……
1.生成rw数据逻辑
    //cache的初始化
    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();


#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
    //为32位设计的。objc_indexed_classes_count 记录类的数量 最终存入objc_indexed_classes中(其中记录了index-cls的关系)。为isa是否纯指针做的处理。
    cls->chooseClassArrayIndex();
2.关联元类与父类
    

3.调整ivars

4.同步flags标志位

5.关联子类与相邻类

    // Attach categories
    //分类处理
    methodizeClass(cls, previously);

    return cls;
}

1.1、生成rw数据

auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;

//调试代码 开始
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "HPObject") == 0 && !isMeta) {
    printf("%s %s\n",__func__,mangledName);
}
//调试代码 结束

if (ro->flags & RO_FUTURE) {
    // 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空间
    rw = objc::zalloc<class_rw_t>();
    //将ro数据存入rw中。这也是为什么从data()->ro()的原因。
    rw->set_ro(ro);
    //flags标志位设置 uint32_t 1<<31 1<<19 1<<0 (元类为1,非元类为0)
    rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
    //设置rw数据,这个时候data()拿出来的就是rw数据了。
    cls->setData(rw);
}

⚠️这里有个细节是调试代码中判断了isMeta,因为元类和类的名称相同。否则元类也会进入这块逻辑。
ro数据是在llvm编译期就已经生成了。class_ro_tllvm中的结构:

image.png
read函数实现:
image.png
可以看到是直接进行的macho文件的操作,直接进行的赋值。调用方是Read_class_row
image.png

验证ro数据(类加载进内存就有了):

(lldb) p ro
(const class_ro_t *) $0 = 0x00000001000080a8
(lldb) p *$0
(const class_ro_t) $1 = {
  flags = 128
  instanceStart = 8
  instanceSize = 8
  reserved = 0
   = {
    ivarLayout = 0x0000000000000000
    nonMetaclass = nil
  }
  name = {
    std::__1::atomic<const char *> = "HPObject" {
      Value = 0x0000000100003f2e "HPObject"
    }
  }
  baseMethodList = 0x00000001000080f0
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000000000000
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1.baseMethodList
(void *const) $2 = 0x00000001000080f0
(lldb) p *$2
(lldb) p $1.baseMethods()
(method_list_t *) $3 = 0x00000001000080f0
(lldb) p *$3
(method_list_t) $4 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1)
}
(lldb) p $4.get(0).big()
(method_t::big) $5 = {
  name = "instanceMethod"
  types = 0x0000000100003f57 "v16@0:8"
  imp = 0x0000000100003ea0 (HPObjcTest`-[HPObject instanceMethod])
}

验证data数据:

image.png

验证一般情况下rw的方法列表指向ro的方法列表:

image.png

1.2 cls关联元类与父类

    //父类和元类的实例化
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        //元类isa是纯指针。
        cls->setInstancesRequireRawIsa();
    } else {
        //isa是否纯指针, flags中第13位
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;
        //这就是环境变量中配置的 OBJC_DISABLE_NONPOINTER_ISA
        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            //配置环境变量为YES后,isa是一个纯指针。
            instancesRequireRawIsa = true;
        }
        //OS_object类时纯指针
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        //父类是纯指针,并且父类还有父类。那么自己也要是纯指针。rawIsaIsInherited 表示继承的是纯指针
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {

            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }
        //递归设置isa为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。rawIsaIsInherited只是控制打印。
        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    //关联父类与元类。也就是继承链与isa走位。
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

1.3 调整ivar offset

//调整ivar的offset,可能会重新创建`class_ro_t`来更新ivar
if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

//设置成员变量占用空间大小
cls->setInstanceSize(ro->instanceSize);

对于成员变量的解读,将放在下一篇文章。这篇文章的重点是方法的加载。

1.4 rw同步ro标志位

//拷贝ro的flags到rw中  flags第2位 c++构造方法 RO_HAS_CXX_STRUCTORS,flags第8位 RO_HAS_CXX_DTOR_ONLY(c++析构方法)
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
    cls->setHasCxxDtor();
    if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
        cls->setHasCxxCtor();
    }
}

//是否禁止关联对象 第20位标记是否允许关联对象。
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
    (supercls && supercls->forbidsAssociatedObjects()))
{
    rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}

1.5 子类与根类的设置

if (supercls) {
    //关联子类
    addSubclass(supercls, cls);
} else {
    //设置根类 nextSiblingClass 为 _firstRealizedClass 根类是第一个被实例化的类。
    addRootClass(cls);
}

1.5.1 addSubclass

static void addSubclass(Class supercls, Class subcls)
{
……
    if (supercls  &&  subcls) {
……

        objc_debug_realized_class_generation_count++;
        //相邻类
        subcls->data()->nextSiblingClass = supercls->data()->firstSubclass;
        //第一个子类
        supercls->data()->firstSubclass = subcls;
        //同步父类的c++析构和构造
        if (supercls->hasCxxCtor()) {
            subcls->setHasCxxCtor();
        }

        if (supercls->hasCxxDtor()) {
            subcls->setHasCxxDtor();
        }
        
……
        //同步子类isa是否纯指针
        if (supercls->instancesRequireRawIsa()  &&  supercls->getSuperclass()) {
            subcls->setInstancesRequireRawIsaRecursively(true);
        }
    }
}

1.5.2 addRootClass

static void addRootClass(Class cls)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());

    objc_debug_realized_class_generation_count++;
    //自己的相邻类设置为第一个初始化的类(nil)。第一个初始化的类设置为自己。
    cls->data()->nextSiblingClass = _firstRealizedClass;
    _firstRealizedClass = cls;
}

但是经过断点调试发现这个方法会进入两次:


image.png

第一次符合预期。第二次是__NSAtom进入的。它的相邻类是NSObject_firstRealizedClass变成了__NSAtom。堆栈是在+ load方法后:

image.png

那么__NSAtom是什么呢?根据上面的逻辑__NSAtom的相邻类为NSObject,第一个初始化的类变成了__NSAtom。在objc源码中并没有搜到相关类,只有OBJC_TAG_NSAtom。在class-dumpCoreFoundationCoreFoundation源码中也没有开源这一部分)头文件中发下了如下声明:

@interface __NSAtom : _UKNOWN_SUPERCLASS_ {

    Class isa;

}
+(void)initialize;
@end

那就证明__NSAtom是属于CoreFoundation的。目前暂不清楚这个类的用法以及作用。
参考

二、methodizeClass

realizeClassWithoutSwift中最后调用了如下代码:

// Attach categories
methodizeClass(cls, previously);

根据注释可以看到应该是对分类的处理,参数cls没问题,previously是从_read_images中传过来的为nil

realizeClassWithoutSwift(cls, nil);

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

   ……
    //获取ro方法列表
    method_list_t *list = ro->baseMethods();
    if (list) {
        //RO_FROM_BUNDLE 29 位标记是否bundleclass
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
    
    //获取ro属性列表
    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.
    //是否根元类,根元类加了initialize方法。
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }
    ……
    分类的处理
    // Attach categories.
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
   ……
}

2.1 prepareMethodLists

//cls, &list, 1, YES, isBundleClass(cls), nullptr
static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle, const char *why)
{
……
    // Add method lists to array.
    // Reallocate un-fixed method lists.
    // The new methods are PREPENDED to the method list array.
    //addedCount 为 1
    for (int i = 0; i < addedCount; i++) {
        //addedLists 也就是list。
        method_list_t *mlist = addedLists[i];
        ASSERT(mlist);

        // Fixup selectors if necessary
        //是否已经排序,没有则进行排序。对ro methodlist 排序
        if (!mlist->isFixedUp()) {
            //修正并且排序methodList
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }
……
}

验证mlist

image.png

2.1.1 fixupMethodList

static void 
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) {
            //SEL转成name字符串
            const char *name = sel_cname(meth.name());
            //将name和地址设置进meth中
            printf("before setName:%s address:%p\n",name,meth.name());
            //设置SEL,SEL有可能在 __sel_registerName 最终调用了_dyld_get_objc_selector的值,相当于修正到dyld中。
            meth.setName(sel_registerNameNoLock(name, bundleCopy));
            printf("after setName:%s address:%p\n",name,meth.name());
        }
    }

    // Sort by selector address.
    // Don't try to sort small lists, as they're immutable.
    // Don't try to sort big lists of nonstandard size, as stable_sort
    // won't copy the entries properly.
    //排序,通过SEL的地址排序。samll lists 不可变,不排序。这里就与慢速消息查找的二分查找对应上了。在这里进行的排序。
    if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
    }

    // Mark method list as uniqued and sorted.
    // Can't mark small lists, since they're immutable.
    //设置标志位
    if (!mlist->isSmallList()) {
        mlist->setFixedUp();
    }
}

small lists不可变不进行排序。

修正前后SEL验证:

image.png
发现run的地址发生了变化,修正为dyld提供的地址了。

添加log进行排序前后验证:

image.png
其它方法顺序没有发生变化,run发生了变化。根据以上验证可以得出以下结论:

2.2 分类的探索

prepareMethodLists执行完成后是没有rwe数据的,所以后续的attachLists相关操作都不会执行。根据之前WWDC的介绍rwe在有分类的情况下会出现,那么就加个分类:

@interface HPObject (HP)

@property (nonatomic, copy) NSString *hp_name;
@property (nonatomic, assign) int hp_age;

- (void)hp_instanceMethod1;

- (void)hp_instanceMethod2;

+ (void)hp_classMethod1;

@end

@implementation HPObject (HP)

- (void)hp_instanceMethod1 {
    NSLog(@"%s",__func__);
}

- (void)hp_instanceMethod2 {
    NSLog(@"%s",__func__);
}

+ (void)hp_classMethod1 {
    NSLog(@"%s",__func__);
}

@end

2.2.1 clang还原底层代码

既然要研究分类的实现,不防先探索下底层的实现。
在转换后的cpp文件中有如下实现:

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_HPObject_$_HP,
};

_CATEGORY_HPObject_$_HP是一个_category_t类型:

struct _category_t {
    const char *name;
    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;
};

接着有以下代码:

static struct _category_t _OBJC_$_CATEGORY_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "HPObject",//名字
    0, // &OBJC_CLASS_$_HPObject,//cls
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_HPObject_$_HP,//实例方法
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_HPObject_$_HP,//类方法
    0,///协议
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_HPObject_$_HP,//属性
};

直接遵循NSObject协议再重新编译下:

image.png
static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_NSObject
};

这个时候协议就有值了,正是NSObject协议。

这个时候属性确实已经有了:

static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    2,
    {{"hp_name","T@\"NSString\",C,N"},
    {"hp_age","Ti,N"}}
};

但是并没有属性队形的set & get

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_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    2,
    {{(struct objc_selector *)"hp_instanceMethod1", "v16@0:8", (void *)_I_HPObject_HP_hp_instanceMethod1},
    {(struct objc_selector *)"hp_instanceMethod2", "v16@0:8", (void *)_I_HPObject_HP_hp_instanceMethod2}}
};

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_HPObject_$_HP __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"hp_classMethod1", "v16@0:8", (void *)_C_HPObject_HP_hp_classMethod1}}
};

所以只能通过关联对象处理。

2.2.2 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 *_classProperties;

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

2.2.3 文档探索

Xcode文档中搜索Category得到以下内容:

//An opaque type that represents a category.
typedef struct objc_category *Category;

在源码中搜索objc_category发现OBJC2_UNAVAILABLE

struct objc_category {
    char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

2.2.4 分类加载源码探究

通过上面的分析,大概了解了分类的结构。分类本身是一个结构体,那么它是怎么加载的呢?
通过类的加载源码的分析核心逻辑在attachListsattachToClass中。控制条件是rwe
rwe来源于rw->ext()

auto rwe = rw->ext();

ext实现如下:

class_rw_ext_t *ext() const {
    return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}

class_rw_ext_t *extAllocIfNeeded() {
    //获取rwe
    auto v = get_ro_or_rwe();
    if (fastpath(v.is<class_rw_ext_t *>())) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
    } else {
        //创建rwe
        return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
    }
}

extAllocIfNeeded中进行了rwe的创建。extAllocIfNeeded的调用分为以下情况(rwe创建情况):

可以看到除了attachCategories,其它要么是对类进行动态处理要么是修复类的时候创建rwe。这与WWDC上的介绍就相吻合了。那么显然核心逻辑就在attachCategories了。

attachCategories的调用逻辑在attachToClassload_categories_nolock中。

所以分类的加载就有了两条线路:

  1. methodizeClass -> attachToClass -> attachCategories
  2. load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

分类加载的详细实现将在下篇文章分析。

三、总结

上一篇下一篇

猜你喜欢

热点阅读