普通视图

发现新文章,点击刷新页面。
昨天以前掘金专栏-Joe天青色

iOS底层之类扩展和关联对象

作者 Joe天青色
2023年3月23日 16:57

iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址

iOS底层之类的加载
iOS底层之分类的加载

序言

在前面文章中,从底层源码探索了分类的加载流程,今天从源码层面探索一下类扩展及关联对象的本质。

类扩展

通过cpp文件查看类扩展

定义类LGStudent的属性方法,以及类扩展实现

@interface LGStudent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

- (void)instanceMethod;
+ (void)classMethod;

@end

// 类扩展
@interface LGStudent ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end

@implementation LGStudent
- (void)instanceMethod{
    NSLog(@"%s", __func__ );
}

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

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

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

@end

通过clang然后生成cpp文件,查看类扩展属性

image.png

在类扩展中定义的属性ext_nameext_age,在cpp文件和主类的属性nameage一样处理生成带下划线成员变量

image.png

image.png

这里对比可以看到,

  • 在成员列表_ivar_list_t中是有_age_ext_age_name_ext_name4个成员变量;
  • 在属性列表_prop_list_t中只有nameage两个属性,说明在类扩展中的属性为私有;

看一下类扩展中的方法

image.png

类扩展中定义的实例方法ext_instanceMethod以及属性ext_nameext_agesettergetter方法,都编译在主类实例方法中。

image.png

类扩展中定义的类方法ext_classMethod和主类的类方法classMethod在编译在主类的类方法中。

通过类的加载查看类扩展

定义类LGPerson和类扩展LGPerson+LG

image.png

《iOS底层之类的加载》中我们知道了非懒加载类加载到内存是在启动时完成的。

为方便调试在主类实现+load方法,类扩展方法以ext开头,在LGperson.m中不实现类扩展方法ext_saySomthing

image.png

methodizeClass方法中对LGPerson准确下断点,运行调试

image.png

先来到的为LGPerson的元类,在从ro读取的method_list_t中,包含主类中的类方法+load和类扩展中的类方法ext_classMethod

过掉断点会再次进来,这次是对类LGPerson的加载 image.png

LGPerson类加载时,从ro读取的method_list_t中,包含主类中的实例方法sayHellosayByeBye,同时还有分类中的实例方法ext_instanceMethod,属性ext_namesettergetter方法,并没有未实现的方法ext_saySomthing

类扩展总结

  • 类扩展可以给类添加成员属性,但是是私有变量;
  • 类扩展可以给类添加方法,也是私有方法;

关联对象

分类的作用是给类添加新的方法,但是不能添加属性,即使添加了属性,也只会生成属性的settergetter方法声明,不能生成方法实现和带下划线的成员变量

我们可以通过Runtime给分类添加属性,就是通过关联对象的形式实现,今天从源码层面实例探究一下关联对象是如何实现的给分类添加属性的。

源码分析

关联对象存储

关联对象存储值时使用的apiobjc_setAssociatedObject,参数有4个

  • object: 关联对象;
  • key:标识符;
  • value:关联值;
  • policy:关联策略;
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}

objc_setAssociatedObject直接调用_object_set_associative_reference,这样做是维持最上层的api稳定,不管底层怎么更新,我们做关联对象使用的就是objc_setAssociatedObject

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // 关联对象为nil切关联值也为nil
    if (!object && !value) return;
    // object不允许关联对象,直接崩
    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));

    // 包装关联对象成统一对象类型,
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // 将value和policy包装成ObjcAssociation类型
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();

    bool isFirstAssociation = false; // 首次关联对象标识
    {
        AssociationsManager manager; // 实例化关联对象处理manager
        AssociationsHashMap &associations(manager.get()); // 获取关联对象表
        if (value) { // 关联值判断
            // 从关联对象表中,根据disguised获取关联对象存储地址,没有就创建插入
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) { // 是否为第一次关联值
                /* it's the first association we make */
                isFirstAssociation = true;
            }

            /* establish or replace the association */
            // 获取disguised的关联对象存储地址
            auto &refs = refs_result.first->second;
            // 以key-value的形式,存储association
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) { 
            // 如果之前存储过关联值,用之前的值first替换调value,后面将association释放
                association.swap(result.first->second);
            }
        } else { // 关联值位nil,如果之前有key对应关联值,则清除
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) { // 获取到关联对象的存储地址
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) { // 判断key对应的关联值,有的话就清除释放掉
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) { // 关联对象中没有关联值,则释放掉关联对象
                        associations.erase(refs_it);
                    }
                }
            }
        }
    }

//     Call setHasAssociatedObjects outside the lock, since this
//     will call the object's _noteAssociatedObjects method if it
//     has one, and this may trigger +initialize which might do
//     arbitrary stuff, including setting more associated objects.
    // 给关联对象object的isa中的has_assoc设置值true,表示有关联对象
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // release the old value (outside of the lock).
    association.releaseHeldValue(); // 如果有新值替换则释放旧值
}

_object_set_associative_reference中的工作处理流程:
1.value有值

  • 实例化全局关联表associations
  • associations调用try_emplace将关联对象disguised作为key,获取disguised对应的关联对象表ObjectAssociationMap的地址,如果没有就创建一个ObjectAssociationMap,返回为refs_result
  • 如果refs_result.second有值,则disguised为第一次做关联值;
  • 通过refs_result.first->second获取关联对象表的存储地址refs
  • refs调用try_emplace以标识符key存储关联值association,返回为result
  • 如果result.second为false,说明之前已经做过key对应的关联值,调用association.swap方法存储旧值地址存入association准备释放旧值;
  • 如果为第一次关联值,调用setHasAssociatedObjectsisahas_assoc设置为true,表示object有关联对象,在dealloc时将关联表释放。
  • 如果不是第一次关联,此时association存储的是旧值,调用releaseHeldValue释放掉旧值

2.value为空值

  • 如果关联值为空,获取disguised的关联表,清除掉key对应的关联值,如果disguised关联表中没有关联值,则将disguised关联表释放。

关联对象取值

关联对象取值时使用的apiobjc_getAssociatedObject,参数为关联对象object和标识符key

id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}

同样是一层封装过渡,直接调用_object_get_associative_reference

id
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};
    { 
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        // 通过关联对象object获取关联表
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
           // 获取关联表的地址
            ObjectAssociationMap &refs = i->second;
            // 通过key值获取关联值
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                association = j->second;
                association.retainReturnedValue(); // retain处理
            }
        }
    }
    return association.autoreleaseReturnedValue(); // 返回value
}
  • 通过关联对象object获取关联表;
  • 获取关联表的地址,通过key值获取关联值

关联对象销毁

对象销毁时会调用dealloc方法

- (void)dealloc {
    _objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);
    obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
    // TaggedPointer小对象直接返回
    if (isTaggedPointer()) return// fixme necessary?
    /**
     * nonpointer & !weakl & !has_*assoc & !has_cxx_dtor & !has_sidetable_rc
     * nonpointer类型
     * 无弱引用
     * 无关联对象
     * 无C++析构函数
     * 无类的C++析构函数
     * 无引用计数
     */ 
    if (fastpath(isa().nonpointer                     &&
                 !isa().weakly_referenced             &&
                 !isa().has_assoc                     &&
#if ISA_HAS_CXX_DTOR_BIT
                 !isa().has_cxx_dtor                  &&
#else
                 !isa().getClass(**false**)->hasCxxDtor() &&
#endif
                 !isa().has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);// 直接释放
    } 
    else {
        object_dispose((id)this);
    }
}

id 
object_dispose(id obj)
{
    if (!obj) return nil;
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        // 获取是否有关联对象
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        // 如果有关联对象则移除关联对象
        if (assoc) _object_remove_associations(obj, /*deallocating*/true);
        obj->clearDeallocating();
    }

    return obj;
}

inline bool
objc_object::hasAssociatedObjects()
{
    if (isTaggedPointer()) return true;
    if (isa().nonpointer) return isa().has_assoc;
    return true;
}

  • dealloc中调用_objc_rootDealloc
  • _objc_rootDealloc中调用对象的rootDealloc方法;
  • rootDealloc中判断对象为TaggedPointer不用处理;
  • 不是TaggedPointer,如果为nonpointer指针且无弱引用无关联对象无C++析构函数无类的C++析构函数无引用计数则直接free,否则就调用object_dispose
  • object_dispose中先调用objc_destructInstance,然后再free
  • objc_destructInstance中根据hasAssociatedObjects判断有无关联对象,如果有就调用_object_remove_associations移除关联对象;
  • hasAssociatedObjectsnonpointer类型的isa,根据值has_assoc判断有无关联对象;

看一下_object_remove_associations的源码是怎么移除关联对象的

void
_object_remove_associations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            // 获取关联对象表的地址
            refs.swap(i->second);

            // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
            bool didReInsert = false;
            if (!deallocating) { // deallocating值为true
                for (auto &ref: refs) {
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        i->second.insert(ref);
                        didReInsert = true;
                    }
                }
            }
            if (!didReInsert)
                associations.erase(i); // 全局关联表中释放对象关联表
        }

    }

    // Associations to be released after the normal ones.
    SmallVector<ObjcAssociation *, 4> laterRefs;
    // release everything (outside of the lock).
    for (auto &i: refs) {
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // If we are not deallocating, then RELEASE_LATER associations don't get released.
            if (deallocating)
                laterRefs.append(&i.second);
        } else {
            i.second.releaseHeldValue();
        }
    }
    for (auto *later: laterRefs) {
        later->releaseHeldValue();
    }
}
  • 全局关联表associations通过对象object获取关联对象表对应的表i
  • 获取表地址空间refs
  • for循环取出所有的关联值,先清除释放策略不是OBJC_ASSOCIATION_SYSTEM_OBJECT,策略为OBJC_ASSOCIATION_SYSTEM_OBJECT放入laterRefs
  • 再对laterRefs循环清除。

实例验证

定义主类LGPerson和分类LGPerson+CatA,在分类中设置属性cate_name,并在分类中实现cate_namesettergetter方法

- (void)setCate_name:(NSString *)cate_name{
    /**对象,标识符,value,策略* */
    objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)cate_name{
    return  objc_getAssociatedObject(self, "cate_name");
}

mian函数中给cate_name赋值

image.png_object_set_associative_reference中加断点调试

第一次关联值

image.png

  • 第一次关联值为Hello cat!,这里isFirstAssociation值为true
  • 此时refs还没有调用try_emplaceBuckets的值为nil

继续Step Over一下

image.png

这里refs的存储了值,result.secondtrue说明第一次关联不需要association.swap

setHasAssociatedObjects打断点,进入调试 image.png

这里对newisahas_assoc赋值为true

releaseHeldValue打断点,进入调试

image.png

这里_valuenil,且_policy0

第二次关联值

放掉断点,进入第二次关联值流程,断点来到refs.try_emplace

image.png

  • 第二次关联值为Hello dog!,这里isFirstAssociation值为false
  • refs的存储了值,Buckets的值为关联值的地址;

继续Step Over一下

image.png

image.png

refs.try_emplaceresult.second的值为false,进入swap方法,association的存储值为上一次关联的旧值。

releaseHeldValue打断点,进入调试 image.png

这里要释放掉的_value为上一次关联的Hello cat!

关联值值nil

过掉断点,进入关联值为nil的流程,断点到erase

image.png

  • 这里valuenil
  • association.swap的值为second_valueHello dog!,最后存储的关联值,准备释放;
  • refs.size的值为1,有一个关联值;

继续Step Over一下,过掉erase

image.png

refs.size的值为0,没有关联值,调用associationserase方法释放关联对象表refs_it

关联对象取值

注释掉关联对象赋值为nil,打印cate_name

image.png

_object_get_associative_reference加断点调试

image.png 这里取到的值为ObjectAssociationMapsecond最新值为Hello dog!

关联对象释放

走完main的作用域,person就会进入dealloc流程

image.pngdealloc中根据字符串匹配LGPerson精准下断点,然后进入rootDealloc

image.png 这里通过isa()输出,has_assoc值为1,进入object_dispose流程,在_object_remove_associations打断点

image.png 看到这里objectLGPerson对象,关联值有1

image.png 输出值为Hello dog!

关联对象总结

关联对象的存储结构为

  • 总哈希表AssociationsHashMap,存储的是关联对象和关联对象对应的关联表ObjectAssociationMap
  • 关联表ObjectAssociationMap中的Buckets为表的地址值;
  • 关联表中存储的是标识符key,和将valuepolicy包装在一起的ObjcAssociation

image.png

iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址

iOS底层之类的加载
iOS底层之分类的加载

以上是对类扩展和关联对象的探索总结,难免有不足和错误之处,如有疑问请在评论区留言。

iOS底层之分类的加载

作者 Joe天青色
2023年3月21日 15:22

iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址

序言

在前面文章《iOS底层之类的加载》中探索了类的加载流程,本篇将对分类展开探索,从分类的结构到分类的加载流程,来探索分类的本质。

Runtime优化

《WWDC 2020 关于Runtime的优化》中介绍了关于Runtime的优化内容,核心内容是对rw扩展出rwe,来优化整个运行时的性能。

我们的应用程序装载到设备时,系统会为安装程序分配一段内存,这段内存是不可变的称为clean memory也就是ro,当程序运行启动时,系统会开辟新的内存来运行程序,这段内存是可变化的称之为dirty memory也就是rw,因为系统内存有限,所以rw这段内存是比较宝贵的。

但是在rw中的数据很多是不会改变的,直接从ro读取即可,需要改变的数据通过Runtime运行时操作的数据,比如类的方法、属性、协议等,将这些数据存放在rwe上,这样就可以达到对dirty memory的优化。

分类的意义就是要动态的给类添加方法等,那么分类的探索就从rwe入手。

分类的结构

自定义类LGPerson和分类LGPerson (Cat),然后通过clang生成cpp文件查看

@interface LGPerson : NSObject
{
    NSString * name;
}

@property (nonatomic, copy) NSString * nickName;
- (void)instanceMethod;
+ (void)classMethod;

@end

@implementation LGPerson
- (void)instanceMethod {
    NSLog(@"%s", __func__ );
}

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

@interface LGPerson (Cat)

@property (nonatomic, copy) NSString * lg_nickName;

- (void)lg_categoryInstanceMethod;
+ (void)lg_categoryClassMethod;

@end

@implementation LGPerson (Cat)

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

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

@end
 

主类的cpp代码 image.png

LGPerson的主类cpp中有实例方法类方法、以及属性的setget方法;

分类的cpp代码

image.png

到在分类中有实例方法类方法,并没有属性lg_nickNamesetget方法

LGPerson (Cat)分类遵守协议NSObject,重新生成cpp

image.png 分类的类型是_category_t,通过category_t在源码中可以找到分类的结构定义

image.png

struct category_t {
    const char *name;
    classref_t cls;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> 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;
    }
};
  • name: 分类名称;
  • cls:主类;
  • instanceMethods:实例方法;
  • classMethods: 类方法;
  • protocols: 所遵守的协议;
  • instanceProperties:实例属性,并没有setget方法;
  • _classProperties:类属性

通过分类的结构可以看出,分类是没有元类的,主类的类方法是在元类中,分类的类方法是在classMethods中。

分类的加载

rwe是通过类中的extAllocIfNeeded方法创建,如果已有值直接返回rwe,如果没有则通过extAlloc创建。

image.png

在源码中全局搜索extAllocIfNeeded,调用的方法有
attachCategories分类、class_setVersion设置版本、addMethods_finish动态添加方法、class_addProtocol添加协议、_class_addProperty添加属性、objc_duplicateClass类的复制、demangledName修改类名

这些方法中有关分类的只有attachCategories方法

attachCategories方法探究

// 将方法列表、属性和协议从类别附加到类。假设cats中的类别都是按加载顺序加载和排序的,最旧的类别优先。
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    /*
     * Only a few classes have more than 64 categories during launch.
     * This uses a little stack, and avoids malloc.
     * 只有少数类在启动期间具有超过64个类别。这使用了一个小堆栈,并避免了malloc。
     *
     * Categories must be added in the proper order, which is back
     * to front. To do that with the chunking, we iterate cats_list
     * from front to back, build up the local buffers backwards,
     * and call attachLists on the chunks. attachLists prepends the
     * lists, so the final result is in the expected order.
     * 类别必须以正确的顺序添加,即从后到前。
     * 为了实现分块,我们从前到后迭代cats_list,向后构建本地缓冲区,
     * 并对块调用attachList。attachLists预先准备好列表,
     * 因此最终结果按预期顺序排列。
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);   // 是否为元类
    auto rwe = cls->data()->extAllocIfNeeded(); // 初始化rwe
    // cats_count分类数量,循环处理分类
    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];
        // 方法处理
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) { // 第一次mcount = 0
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__ );
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            //++mcount,将mlist放在mlists的最后
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist; 
            fromBundle |= entry.hi->isBundle();
        }
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            //++propcount,将proplist放在proplists的最后
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        // 向类中添加方法并排序
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__ );
        // rwe中添加方法
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__ , [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

    // rwe中添加属性
    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
    // rwe中添加协议
    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

分析一下方法

  • 初始化rwe
  • 通过分类数量cats_count,循环处理分类中的方法属性协议
  • 循环中按倒序插入法,将所有的方法存在mlists,协议存放在protolists,属性存放在proplists
  • 如果mcount大于0,说明有分类方法,通过prepareMethodLists向类中添加方法并排序,然后rwe中的methods调用attachLists,添加分类方法到rwe
  • rwe中的properties调用attachLists,添加分类属性到rwe
  • rwe中的protocols调用attachLists,添加分类协议到rwe

rwe中的方法属性协议都是调用的attachLists插入数据,是因为他们的定义都继承list_array_tt,我们看一下list_array_tt中的attachLists方法

attachLists方法探究

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) { // 已有数据且为多条
            // many lists -> many lists
            uint32_t oldCount = array()->count; // 取出旧值
            uint32_t newCount = oldCount + addedCount; // 总数据数量
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount)); // 开辟新空间
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; i--) // 将旧值从后往前取出,插入到新数组的后面
                newArray->lists[i + addedCount] = array()->lists[i];
            for (unsigned i = 0; i < addedCount; i++) // 将新值从前往后添加到新数组
                newArray->lists[i] = addedLists[i];
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) { // 没有数据且插入数据只有一条
            // 0 lists -> 1 list
            list = addedLists[0];// 直接插入
            validate();
        } 
        else { // 没有数据且插入多条数据
            // 1 list -> many lists
            Ptr<List> oldList = list; // 取出旧值
            uint32_t oldCount = oldList ? 1 : 0; // 有旧值oldCount=1否则oldCount=0
            uint32_t newCount = oldCount + addedCount;// 总数据数量
            setArray((array_t *)malloc(array_t::byteSize(newCount)));// 开辟新空间
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList; //将旧值存放在最后的位置
            for (unsigned i = 0; i < addedCount; i++) // 从前往后循环插入新值
                array()->lists[i] = addedLists[i];
            validate();
        }
    }

算法分析:根据已有数据做不同情况处理

  1. 0 -> 1:无数据且新插入数据为1条
  • 直接插入到list
  1. 1 list -> many lists: 只有1条数据或插入多条数据
  • 取出旧值;
  • oldCount赋值:有旧值oldCount=1否则oldCount=0
  • 计算插入后数据总量newCount = oldCount + addedCount
  • 开辟新空间;
  • 将旧值存放在新空间最后;
  • 根据addedCount从前往后插入新值;
  1. many lists -> many lists: 已有多条数据
  • 取出旧值;
  • 计算插入后数据总量newCount = oldCount + addedCount
  • 开辟新空间;
  • 根据oldCount循环从后往前取出旧值,存放在新空间的后面位置;
  • 根据addedCount从前往后插入新值;

根据hasArraysetArray判断,只要调用setArrayarray()->count中的count值大于0,hasArray即为YESimage.png

所有分类加载后,rwemethods结构应该为

动态方法决议-导出.png

分类加载实例探究

类的加载探索中,我们知道了类的加载时机区分为懒加载类非懒加载类,即是否实现+load方法,分类的加载我们同样按照懒加载非懒加载的形式探索。

  1. 主类和分类都为懒加载;
  2. 主类非懒加载,分类懒加载;
  3. 主类懒加载,分类非懒加载;
  4. 主类和分类都非懒加载;

实例类为LGPerson,就在attachCategories方法中通过类名mangledName比较LGPerson来精确加断点、打印输出调试

image.png

1.主类和分类都为懒加载

主类LGPerson声明实例方法sayHellosayByeBye image.png 分类CatA中声明实例方法sayHello_A,以及重写主类方法sayHello image.pngmain函数中调用sayHello

image.png 并没有进入attachCategories函数中,同时可以看到sayHello是调用的分类的方法,由此可以知道如果主类分类都未实现+load方法,分类的方法等信息是在编译时就和主类编译在一起了,在类的加载流程中验证

image.pngmain函数中调用LGPersonalloc方法开始加载类,这里的method_list_t是从ro中读取的数据,输出查看

image.png

image.png 此时,list中的方法数主类和分类的方法集合,主类方法放在集合的最后位置,但这里方法还没有经过排序处理,通过prepareMethodLists会对list进行排序修复

image.png 通过断点跟进,再输出一下经过fixupMethodList处理后的list

image.png 处理后,主类的sayHello方法排在了分类sayHello后面。在调用sayHello时,消息查找流程在对排序好的list进行二分法查找,并且会通过while循环找到最前面的同名方法,这样分类方法就覆盖了主类方法

image.png

2.主类非懒加载、分类懒加载

LGPerson的主类实现+load方法,分类不实现

image.pngmain函数中调用sayHello

image.png 也没有进入attachCategories函数中,sayHello是调用的分类的方法,由此可以知道如果主类非懒加载和分类懒加载,分类的方法等信息也是在编译时就和主类编译在一起了。
下面在类的加载流程中验证

image.png 这里可以看出

  • LGPerson是在程序启动时_read_images实现加载的,
  • 分类的方法也是编译时就和主类方法编译在一起了,这里是通过ro获取的method_list_t,分类方法也在ro中。

3.主类懒加载、分类非懒加载

主类LGPerson不实现+load方法,分类实现

image.png 运行查看

image.png 也没有进入attachCategories函数中,sayHello是调用的分类的方法,由此可以知道如果主类懒加载和分类非懒加载,分类的方法等信息也是在编译时就和主类编译在一起了。
在类的加载流程中验证

image.png

  • LGPerson是在程序启动时_read_images实现加载的,分类非懒加载会导致主类被迫成为非懒加载类
  • 分类的方法也是编译时就和主类方法编译在一起了,这里是通过ro获取的method_list_t,分类方法也在ro中。

4.主类和分类都为非懒加载

主类LGPerson和分类CatA都实现+load方法,运行查看

image.png

通过输出打印可以看出,主类的加载分类的加载是在不同的流程中

主类加载

image.png

可以看出主类的加载是在_read_images流程,这里从ro读取的method_list_t中只有主类的两个方法。

分类的加载

image.png

分类的加载流程是load_images->loadAllCategories->load_categories_nolock->attachCategories,在attachCategories中会创建rwe,并调用prepareMethodLists对分类方法进行排序处理,然后调用rwemethodsattachLists插入分类的mlist

这里的LGPerson的分类有3个,只在分类CatA中实现了+load方法,我们调用分类CatC的方法

image.png

发现加载分类的时候,并没有输出分类CatC的名字,也就是没有分类CatC的加载,为什么可以调用sayHello_C成功了呢?

猜测:分类CatC没有实现+load,会不会是编译时和主类编译在一起了

再跟踪一下主类的加载流程

image.png

这里看到,主类ro中只有主类自己实现的两个方法,所以猜想不成立。

再跟踪一下分类的加载流程

image.png

这里看到,在加载分类CatB时,确有分类CatC的方法,他们的共同点是都没有实现+load,系统在编译时会把未实现+load方法的分类合并成一个分类来处理,从而简化分类的加载流程。

我们再增加一个分类CatD做验证

image.png

image.png 可以看到,分类加载时把未实现+load方法的CatBCatCCatD一起加载的,验证了系统在编译时会把未实现+load方法的分类合并成一个分类来处理。

总结

分类的加载原理和类同样区分为是否为懒加载,两者组合可分为4种情况

动态方法决议-导出.png

以上是对分类的加载流程探索过程的总结,难免有不足和错误之处,如有疑问请在评论区留言吧

iOS底层之Runtime探索(三)

作者 Joe天青色
2023年3月10日 15:17

序言

iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址

iOS底层之Runtime探索(一)
iOS底层之Runtime探索(二)
前面的文章中讲到了objc_msgSend的方法查找过程,在通过lookUpImpOrForward方法的慢速查找没有找到imp后,就到了动态方法决议流程resolveMethod_locked

动态方法决议

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();
    // 元类判断
    if (! cls->isMetaClass()) { // 对象方法流程
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else { // 类方法流程
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
  • 判断当前的class是否为元类
  • 不是元类,走实力方法流程resolveInstanceMethod
  • 元类,走类方法流程resolveClassMethod,判断resolveClassMethod是否已解决问题,未解决再走resolveInstanceMethod流程;
  • 走完动态决议流程,重新找一次imp

resolveInstanceMethod方法解析

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    // 1.查询元类是否实现了resolveInstanceMethod:方法
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }

    // 2.发送消息resolveInstanceMethod:
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls

    // 3.再次查询sel对应的imp,resolveInstanceMethod可动态修改
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
  1. 定义resolveInstanceMethod:方法;
  2. lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(true)):查询元类是否实现了resolveInstanceMethod:方法,cls->ISA(true)为类isa指向的元类,如果未实现直接返回;
  3. 通过objc_msgSend发送resolveInstanceMethod:消息,将返回结果给resolved
  4. lookUpImpOrNilTryCache(inst, sel, cls):再次查询当前类cls是否实现了sel对应的imp

lookUpImpOrNilTryCache方法解析

image.png

  • lookUpImpOrNilTryCache:是直接调用了_lookUpImpTryCachebehavior值为LOOKUP_NIL
  • _lookUpImpTryCache:的核心功能是再次走一次快速慢速方法查询。behavior值为LOOKUP_NIL,所以即使再次查不到也不会再走resolveMethod_locked流程。

resolveClassMethod方法解析

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    // 1.检查元类是否实现方法resolveClassMethod:
    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    // 2.通过cls,获取处理的nonmeta
    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }

    // 3.发送resolveClassMethod: 消息
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls

    // 4.再次查询imp是否实现
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
  1. 定义resolveClassMethod:方法;
  2. 通过方法lookUpImpOrNilTryCache,检查元类是否实现方法resolveClassMethod:,如果未实现直接返回;
  3. 通过cls,获取处理的nonmeta
  4. 通过objc_msgSend发送resolveClassMethod:消息,将返回结果给resolved
  5. lookUpImpOrNilTryCache(inst, sel, cls):再次查询当前元类cls是否实现了sel对应的imp

动态方法决议流程图

动态方法决议-导出(1).png

动态决议实例

定义一个类LGTeacher,声明两个方法,不在m文件实现。

  • 实例方法sayHelllo
  • 类方法sayByeBye

实现方法resolveInstanceMethod:但不做处理

image.png

实例方法

调用实例方法sayHello方法运行

image.pngLGTeacher中的resolveInstanceMethod:方法成功进来,并且进来了两次,然后打开resolveInstanceMethod:中的注释代码运行。

image.png 运行成功,我们成功通过动态向类添加方法lg_instanceMethod处理了sayHello方法找不到,引起的崩溃问题。

类方法

调用类方法sayByeBye方法运行

image.pngLGTeacher中的resolveClassMethod:方法成功进来,也是进来了两次,然后打开resolveClassMethod:中的注释代码运行。

image.png 运行成功,通过动态向元类添加方法lg_instanceMethod处理了sayByeBye方法找不到,引起的崩溃问题。

总结

我们可以在给公共父类NSObject添加分类,重写方法实现resolveInstanceMethod:,这样就可以成功解决所有imp找不到引起的崩溃问题了,同时我们也可以通过方法命名规范知道是哪个模块的哪个功能引起的问题,做BUG收集。
缺点:

  • 如果要对各个功能进行不同处理,需要大量业务功能逻辑判断,变得很复杂;
  • NSObject的分类中,很多系统的方法处理也会进来这里,会降低运行效率。

消息转发

如果我们未实现动态决议方法resolveInstanceMethod:,调用未实现的方法系统就会崩溃,这是我们通过bt指令查看堆栈信息 image.png 是通过doesNotRecognizeSelector:抛出错误的,在这之前还会经过_CF_forwarding_prep_0___forwarding___流程,这都是在CoreFoundation库里,苹果并没有真正开源CoreFoundation库。

image.png

在查阅苹果文档动态方法解析中,提到了消息转发 Message Forwarding,消息转发正是苹果给你提供的imp找不到的第二次机会。

Sending a message to an object that does not handle that message is an error. However, before announcing the error, the runtime system gives the receiving object a second chance to handle the message.

原理

  • 当对象发送的sel找不到imp时,系统会发送forwardInvocation:来通知该对象;
  • forwardInvocation:是从NSObject继承而来的,而在NSObject中只是调用doesNotRecognizeSelector:抛出错误;
  • 我们可以重写forwardInvocation:来实现消息的转发。
  • forwardInvocation:的唯一参数是NSInvocation
  • NSInvocation中,可以指定消息接收者target,调用invoke实现消息转发;
  • 消息转发完成,返回值将会返回给原始消息发送者;

文档里还介绍了有意思的点,在我们自己的类中要实现一个特定功能的方法,该功能已经在别的类中实现,我们又不能继承这个类,因为可能设计在不同的模块中,那我们就可以通过forwardInvocation:把消息转发特有的类去实现,这就实现类似多继承了。

image.png

实例

在关于forwardInvocation:api文档中,有个重要提示: image.png

除了forwardInvocation:之外,还必要重写methodSignatureForSelector:转发消息的机制使用从methodSignatureForSelector获得的信息来创建要转发的NSInvocation对象。重写方法必须为给定的选择器提供适当的方法签名方法签名可以是预先制定的,也可以是向另一个对象请求方法签名

LGTeacher中加上methodSignatureForSelectorforwardInvocation:方法后,再调用sayHello运行

image.png 成功运行,进入methodSignatureForSelectorforwardInvocation:方法,没有引起崩溃。
定义LGStudent实现sayHello方法,在LGTeacher中的forwardInvocation:,将消息转发给LGStudent

image.png

同样,在类方法的消息流程转发中,跟实例方法的实现是一致的,不过方法上要注意是+

image.png

forwardingTargetForSelector

在文档 forwardInvocation中还提供了forwardingTargetForSelector:方法

image.png

该方法的主要作用

无法识别的消息首先应指向的对象

如果对象实现(或继承)此方法,并返回非nil(和非self)结果,则返回的对象将用作新的接收方对象消息分派将恢复到该新对象。(显然,如果从该方法返回self,代码将陷入无限循环。)

如果您在非根类中实现此方法,如果您的类对于给定的选择器没有要返回的内容,那么您应该返回调用super实现的结果。

该方法为对象提供了一个机会,在更昂贵的forwardInvocation:机械接管之前重定向发送给它的未知消息。当您只想将消息重定向到另一个对象,并且可以比常规转发快一个数量级时,这非常有用。如果转发的目标是捕获NSInvocation,或在转发过程中操纵参数返回值,则此选项不适用

  • 大概意思是,如果只需要对未识别的消息,做简单的重定向,那么用此方法会比forwardInvocation:流程快很多很多,如果有额外的复杂操作,如捕获NSInvocation操纵参数返回值则不适用该方法。
  • forwardingTargetForSelector:的调用要在forwardInvocation:的前面。

image.png

forwardingTargetForSelector:实现重定向到LGStudent

image.png

流程图

KC消息转发机制.png

消息总结

到这里整个Runtime中对消息的处理流程objc_msgSend的解读都已完成,可能不够全面,中间有些细节难免有遗漏,不过重在整个流程的思维探索。

  1. 方法调用 --> objc_msgSend(id receiver, Sel)
  2. 通过isa获取类的地址cls
  3. 快速查找流程CacheLookup
  4. 慢速查找流程lookUpImpOrForward
  5. 动态方法解析resolveMethod_locked;
  6. 消息重定向forwardingTargetForSelector:;
  7. 消息转发forwardInvocation:

有兴趣的伙伴也可以搞一份源码跑一跑
iOS 全网最新objc4 可调式/编译源码

还有编译好的源码的下载地址

iOS底层之类的结构分析

作者 Joe天青色
2023年3月1日 15:08

isa流程分析

首先来看一张比较熟悉的isa的流程走位图

isa流程图.png

isa指向链

  • 实例对象instanceisa指向类class;
  • 类对象class也有isa指向的是元类meta
  • 元类mata中也有isa指向的是根元类root meta

isa走位图(1).png

类的继承链

  • 子类继承与父类父类继承与根类根类指向的是nil
  • 元类中也存在继承,子类的元类继承与父类的元类父类的元类继承与根元类根元类又继承与根类;

类的继承链.png

isa流程实例验证

补充:在获取到对象的isa值后,可以通过&(与)一个掩码ISA_MASK 0x007ffffffffffff8ULL来获取到对象关联的类地址。

image.png

isa指向链验证

定义一个LGPerson继承NSObject,同时定义LGTeacher继承LGPerson

LGPerson *p = [LGPerson alloc];
NSLog(@"%@",p);
LGTeacher *t = [LGTeacher alloc];
NSLog(@"%@",t);

LLDB调试对象t,通过tisa找到关联类LGTeacher的地址为0x0000000100008310

image.png 接着我们按同样的方式输出,找到LGTeacher类对象的isa关联类地址为0x00000001000082e8image.png 往下找,找到LGTeacher元类对象的isa关联类地址为0x00007ff85e5f2220

image.png 按同样的方式,找到NSObject类对象的isa关联地址为0x00007ff85e5f2220,和LGTeacher元类对象的isa关联类地址一致,可以验证元类isa指向根元类

image.png根元类isa指向自己。 image.png

类的继承链验证

同样按照LGPerson继承NSObjectLGTeacher继承LGPerson的定义。

Class tClass = LGTeacher.class;
Class pClass = class_getSuperclass(tClass);
Class nClass = class_getSuperclass(pClass);
Class rClass = class_getSuperclass(nClass);
NSLog(@"\n tClass-%@ \n pClass-%@ \n nClass-%@ \n rClass-%@ \n", tClass, pClass, nClass, rClass);

image.png

通过上面代码输出打印,很明显看出subClass->superClass->NSObject->nil的继承链关系。

LGTeacher * teacher = [LGTeacher alloc];
Class tClass = object_getClass(teacher);
Class mtClass = object_getClass(tClass);
Class mtSuperClass = class_getSuperclass(mtClass);
NSLog(@"\n teacher %p 实例对象 -- %p 类 -- %p 元类 -- %p 元类父类", teacher, tClass, mtClass, mtSuperClass);

LGPerson * person = [LGPerson alloc];
Class pClass = object_getClass(person);
Class mpClass = object_getClass(pClass);
Class mpSuperClass = class_getSuperclass(mpClass);
NSLog(@"\n person %p 实例对象 -- %p 类 -- %p 元类 -- %p 元类父类", person, pClass, mpClass, mpSuperClass);

NSObject * obj = [NSObject alloc];
Class objClass = object_getClass(obj);
Class mobjClass = object_getClass(objClass);
Class mobjSuperClass = class_getSuperclass(mobjClass);
NSLog(@"\n NSObject %p 实例对象 -- %p 类 -- %p 元类 -- %p 元类父类", obj, objClass, mobjClass, mobjSuperClass);

teacher 0x600000201ba0 实例对象 -- 0x100008310 类 -- 0x1000082e8 元类 -- 0x100008338 元类父类

person 0x600000201bc0 实例对象 -- 0x100008360 类 -- 0x100008338 元类 -- 0x7ff85e5f2220 元类父类

NSObject 0x600000004030 实例对象 -- 0x7ff85e5f2270 类 -- 0x7ff85e5f2220 元类 -- 0x7ff85e5f2270 元类父类

通过输出信息我们可以看出

  • teacher的元类父类地址0x100008338 == person的元类地址0x100008338;
  • person的元类父类地址0x7ff85e5f2220 == NSObject的元类地址0x7ff85e5f2220;
  • NSObject的元类父类地址0x7ff85e5f2270 == NSObject的类地址0x7ff85e5f2270;

综合,我们可以得出元类的继承链关系为Sub Meta->Super Meta->Root Meta-> NSObject

类的结构

前面对象的本质中,我们知道Class的定义是objc_class结构体类型,那么就看一下objc_class的结构体的结构是怎样定义的。

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;             // formerly cache pointer and vtable
class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
... 其他代码
}

这里objc_class的继承与objc_objectobjc_object的结构中只有一个isa指针, 因此,objc_class的成员组成为:

  1. objc_object继承的指针isa;
  2. superclass: Class类型指向父类的指针
  3. cache:缓存相关的东西
  4. bits:数据

类的结构.png

cache结构

cache_t也是一个结构体定义

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8字节
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; // uint32_t 4字节
#if __LP64__
            uint16_t                   _flags;     // 2字节
#endif
            uint16_t                   _occupied;  // 2字节
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;  // 8字节
    };
 }

分析整个cache_t的结构,发现cache_t的内存总共为16字节。 关于cache_t的探究专门写了一篇文章。

bits探究

bits是今天的探究重点,bits到底存储了哪些数据,在objc_class里有一段源码是data操作。

class_rw_t *data() const {
    return bits.data();
}
void setData(class_rw_t *newData) {
    bits.setData(newData);
}

这里data的类型是class_rw_t,在class_rw_t的源码中,我们可以看到几个方法:

image.png

ro:成员变量、methods:方法、properties:属性、protocols协议 ,我们在类中定义的方法,属性等就行通过调取class_rw_t结构体中的方法获取的。

实例探究

下面我们通过实例,来验证一下类的结构是否如上面分析的一致。

我们自定义一个类LGPerson继承自NSObject,定义一些属性和实力方法以及类方法。

image.pngLLDB调试输出

image.png

这里我们可以看出第一个地址0x0000000100008228是类的第一个成员isa,第二个地址0x0000000100816140是类的第二个成员superclass

前面我们知道isa指针8字节,superclass指针8字节,cache结构体16字节,通过LGPerson的首地址便宜8 + 8 + 16 = 32字节我们就可以得到bits的内存地址。

image.png 这里用LGPerson的首地址0x100008250加上32字节,得到地址0x0000000100008270,然后转换一下指针类型,得到$9class_data_bits_t类型,调用data方法就得到$10地址为class_rw_t类型,下面就来探究class_rw_t中的属性、方法、协议。

注意

在转换class_data_bits_t这一步,需要在源码环境调试,关于源码的下载和编译,介绍一个靓仔,# iOS 全网最新objc4 可调式/编译源码还有编译好的源码的下载地址

属性properties

调用class_rw_tproperties()方法,得到property_array_t类型的数组,继承与list_array_tt,找到list下的ptrimage.png

image.pngptrproperty_list_t类型,是继承与entsize_list_tt

image.pngentsize_list_tt部分源码

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;  // 数量

    uint32_t entsize() const {
        return entsizeAndFlags & ~FlagMask;
    }
    uint32_t flags() const {
        return entsizeAndFlags & FlagMask;
    }
    Element& getOrEnd(uint32_t i) const {
        ASSERT(i <= count);
        return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
    }
    Element& get(uint32_t i) const { // 获取元素方法
        ASSERT(i < count);
        return getOrEnd(i);
    }
    // ... 省略
};

通过get方法,获取元素,可以看到下面的结果,LGPerson中的属性nameageproperties()里,而成员变量hobby不在这里。

image.png

方法methods

调用class_rw_tmethods()方法,得到method_array_t类型的数组,继承与list_array_tt,同样找到list下的ptr

image.png 这里看到ptrmethod_list_t类型,同样继承与entsize_list_tt,其中有count为6,输出查看

image.png

这里的元素为method_t类型,method_t为结构体类型,其中的一个成员变量为big的结构体,里面是方法名称等信息。

image.png 我们在调用method_tbig方法,查看输出

image.png 这里看到6个方法分别是

  • 实例方法:sayHello
  • 属性name的get方法;
  • C++析构函数:.cxx_destruct
  • 属性nameset方法;
  • 属性ageget方法;
  • 属性ageget方法;

这里的6个方法都是实力对象方法,并没有我们定义的类方法sayWorld

协议protocols

我们自定义一个协议LGPersonDelegate,让LGPerson遵守并实现协议方法

image.png 调用class_rw_tprotocols()方法,得到protocol_array_t类型的数组,继承与list_array_tt,同样找到list下的ptr

image.png 这里看到ptrprotocol_list_t类型,不是继承自entsize_list_tt,那我们看一下protocol_list_t的定义

image.png 看到protocol_list_t的定义,我们知道count值为1,说明是有值,但是其成员是protocol_ref_tuintptr_t类型,那怎么输出查看这个count中的1到底是什么呢 image.png 查看protocol_ref_t的定义,通过注释信息,我们可以看到protocol_ref_t未映射到protocol_t类型,那我们就找protocol_t的定义

image.png 这里看到protocol_t中有mangledName以及instanceMethods等,只要得到protocol_t就可以输出我们想要的名称方法等信息,怎么才能从protocol_ref_t映射到protocol_t呢,全局找一下吧

image.png 一不小心找到了😁,这里我们看到,protocol_ref_t是可以强转protocol_t的,那我们就试试

image.png 强转成功,调用demangledName方法,我们就得到了LGPersonDelegate,那我们再找一下协议方法

image.png 按照method输出的经验,成功找到协议方法personDelegateMethod

ro

调用class_rw_tro方法,得到class_ro_t的结构体

image.png 查看class_ro_t的内容,看到这里有成员ivars,这引起了我们的注意

image.png 接着查看ivars,是ivar_list_t类型的结构体,也是继承entsize_list_tt,那么我们就可以调用get方法查看成员。

image.png

image.png

调用get方法查看输出,这里就可以看到3个成员变量,自定义hobby和系统帮我们定义的属性生成带_的成员变量。

image.png

类方法

在上面的methods方法中,我们并没有得到定义的类方法sayWorld,不由得我陷入了沉思,我们定义的类结构中,没有我们定义的类方法,那类方法会存在哪里呢?

这里我们换个思考角度想一下,在methods中全都是实例方法,也就是对象方法对象方法是存在类中;类方法类对象在调用,那类方法是不是存在元类中呢?不得而知,那么我们就去验证一下。

image.png

首先获取到LGPerson的元类地址,然后按照methods方面的方法步骤,我们成功输出sayWorld方法,也验证了上面的猜想。

这里我们不由的想,OC的底层是CC++实现的,在C语言C++中,不存在对象方法类方法的区分,有的都是函数实现,在OC的设计中,一个可以new出无数个对象,因此把方法存在中,而不是动态创建的对象中,是合理的。

因为OC对象方法类方法的定义是-+的区分,那么方法名称就会有重名的存在,因此才会引入元类的概念,元类的存在就是解决类方法的问题。

类的结构总结

类的结构总结用一张流程图

objc_class.png 其中protocol_ref_t是一个无符号长整型,可以强转为protocol_t,这是后面才找到的。

以上就是对于OC中类的结构分析,如有疑问或错误之处,请评论区留言或私信我,非常感谢!!!

❌
❌