iOS底层探索-类的原理分析

577 阅读9分钟

1、元类MetaClass

首先,先来看看 是什么, 在OC中其实是一个指向objc_class的结构体指针,我们先看看结构体的构造,很多在OBJC2中即将废弃(单纯参考一下大概需要什么): image.png

现有的为: image.png

  • superclass为父类指针cache为缓存bits中的class_rw_t中存放property、protocol、实例methoed等数据,本节先着重分析 bits 数据

对象objc_object定义

image.png 这里又涉及到一个知识点__has_feature(ptrauth_calls),我们下边再补充

OC对象有一个大家都熟悉的特性:消息发送机制

  • 原理是OC对象在发送消息时,运行时库会根据对象的isa指针,得到对象所属的类,这个类 包含了这个类的所有实例方法以及指向父类的指针,以便可以找到父类的实例方法。运行时库检查这个类和其父类的方法列表,找到与消息对应的方法。编译器会将消息转换为消息函数objc_msgSend进行调用。

  • OC的类其实也是一个对象,一个对象就要有一个它属于的类,意味着类也要有一个isa指针,指向他所属的类,所以,元类就是类所属的类

    • 当你给对象发送消息时,消息是在寻找这个对象的类的方法列表
    • 当你给类发消息时,消息是在寻找这个类的元类的方法列表

元类定义

  • 元类就是类所属的类,提供类的通用方法列表

  • isa指向上根元类是终点:

    • 对象isa -> 类isa -> 元类isa -> 父元类isa -> 根元类isa -> 根元类
    • 根类isa -> 根元类isa
  • 继承关系上NSObject是终点:

    • 元类 -> 父元类 -> 根元类 -> 根类NSObject

image.png

元类的意义

  • 元类的设计主要是为了复用消息机制

    • objc_msgSend(消息接受者isa, 消息方法名)
    • 元类中存类方法,类中存实例方法,单一职责,各自找isa指针中的方法,省去了多余判断
  • 底层中并不区分类方法和实例方法,底层都是实例方法,只是类方法是元类的实例方法

    /***********************************************************************
    * class_getClassMethod.  Return the class method for the specified
    * class and selector.
    **********************************************************************/
    Method class_getClassMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
        //底层类方法执行了实例方法,只是执行者为元类
        return class_getInstanceMethod(cls->getMeta(), sel);
    }
    

__has_feature(ptrauth_calls)介绍

有些时候__has_feature(ptrauth_calls)TARGET_OS_SIMULATOR一起使用, 需要先普及ARM64e概念

// ARM64 simulators have a larger address space, so use the ARM64e\
// scheme even when simulators build for ARM64-not-e.

//ARM64模拟器有更大的地址空间,所以使用ARM64e\
//即使在为ARM64-not-e构建模拟器时也是如此。

ARM64earm64e架构,用于Apple A12及更高版本A系列处理器 或 新型OS_SIMULATOR 模拟器的设备。

  • __has_feature(ptrauth_calls): 是判断编译器是否支持指针身份验证功能
  • ptrauth_calls 指针身份验证,针对arm64e架构;使用Apple A12或更高版本A系列处理器的设备(如iPhone XS、iPhone XS Max和iPhone XR或更新的设备)支持arm64e架构

2、类结构分析

普通指针

image.png

  • a 和 b 为变量都指向10, 10是系统开辟的固定内存空间, 其他需要10的值的变量都可以指向内存固定生成的10
  • a 和 b 地址不一样, 这是一种拷贝, 属于值拷贝, 可发现a, b地址相差4个字节,stra、strb地址相差8个字节,这取决于a、b和stra和strb的类型

对象指针

image.png

  • p1/p2 是一级指针, 指向的 alloc 开辟空间的内存地址
  • &p1/&p2 是二级指针, 指向对象的指针的地址(0x7ffeefbff3f8, 0x7ffeefbff3f0 为对象指针)

数组指针

image.png

  • &c == &c[0] == 首地址, 其实他都是取的首地址, 数组地址其实就是数组第一个元素地址即数组名为首地址
  • &c[0]与&c[1]相差4字节, 取决于数据类型
  • 数组类型指针可以通过首地址+偏移量得到其他元素(偏移量为数组下标)
  • 移动的字节数 等于 偏移量 * 数据类型字节数, 这个根据&c[0], &c[1]看出, 两者相差4

3、类的结构内存

  • bits中存放类信息,可以通过偏移获取(32字节即0x20)class_data_bits_t bits内容 image.png

  • 计算偏移量需要分析 cache_t 大小:16字节 image.png

  • 0x20是一种整型常量的表示方式。以0x开头的整型常量,代表后续字符为16进制表达。于是0x20也就是16进制的20,即10进制的32。另外,0x20作为单字节表示,可以用于字符型变量的赋值,用于char时,其代表ascii码值0x20,即字符空格' '。

好了,前期的准备工作做完了,让我们按步分析 bits 中的内容吧:

4、bits探索

4.1、bits结构

struct class_data_bits_t {
    //与objc_class成为友元
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit) const
    {
        return bits & bit;
    }

    // Atomically set the bits in `set` and clear the bits in `clear`.
    // set and clear must not overlap.
    void setAndClearBits(uintptr_t set, uintptr_t clear)
    {
        ASSERT((set & clear) == 0);
        uintptr_t newBits, oldBits = LoadExclusive(&bits);
        do {
            newBits = (oldBits | set) & ~clear;
        } while (slowpath(!StoreReleaseExclusive(&bits, &oldBits, newBits)));
    }

    void setBits(uintptr_t set) {
        __c11_atomic_fetch_or((_Atomic(uintptr_t) *)&bits, set, __ATOMIC_RELAXED);
    }

    void clearBits(uintptr_t clear) {
        __c11_atomic_fetch_and((_Atomic(uintptr_t) *)&bits, ~clear, __ATOMIC_RELAXED);
    }

public:
    //存放类的实例方法、协议、属性等
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    void setData(class_rw_t *newData)
    {
        ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        // Set during realization or construction only. No locking needed.
        // Use a store-release fence because there may be concurrent
        // readers of data and data's contents.
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }

    // Get the class's ro data, even in the presence of concurrent realization.
    // fixme this isn't really safe without a compiler barrier at least
    // and probably a memory barrier when realizeClass changes the data field
    const class_ro_t *safe_ro() const {
        class_rw_t *maybe_rw = data();
        if (maybe_rw->flags & RW_REALIZED) {
            // maybe_rw is rw
            return maybe_rw->ro();
        } else {
            // maybe_rw is actually ro
            return (class_ro_t *)maybe_rw;
        }
    }
    ......
}
  • friend友元,C++中提供的修饰符,为了能通过一些函数方法访问其他类中受保护的私有成员

4.2、clean memorydirty memory

  • clean memory
    • 加载后不会发生改变的内存
    • 可以进行移除,因为需要时可以从磁盘中重新加载
  • dirty memory : 进程运行时会发生改变的内存
    • 会发生改变的内存
    • 进程在运行它就必须一直存在

4.3、class_ro_tclass_rw_tclass_rw_ext_t

4.3.1、什么是 class_ro_t
  • 当编译产生二进制文件后,类中会包含指向 MetaClassSuperClassFlagsMethod cache的指针,和一个指向更多数据的指针,这个存放额外信息的地方就叫做 class_ro_t

  • ro 是 clean memory (read only),是编译时产生

  • 当类第一次从磁盘加载到内存时,结构还是如此,但类结构一经使用就会变成 dirty memory,因为运行时会向它写入新的数据

ro的内容
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    //Size
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    //方法列表
    void *baseMethodList;
    //协议
    protocol_list_t * baseProtocols;
    //成员变量
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            return _swiftMetadataInitializer_NEVER_USE[0];
        } else {
            return nil;
        }
    }
    ......
}
4.3.2、什么是 class_rw_t
  • 当一个类首次被使用时,运行时会为它分配额外的存储容量用于读取-编写数据这个储存空间叫做 class_rw_t

  • rw 是 dirty memory (read write),是运行时产生

  • rw 会把 ro 数据剪切进来

rw的内容
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;
    
    ......
public:
    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }

    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }
    //实例方法
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
        }
    }
    //属性
    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
        }
    }
    //协议
    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
};
  • First SubclassNext Subling Class:运行时才会生成的信息,所有的类都会变成一个树状结构,就是通过First SubclassNext Subling Class指针实现的,从而实现运行时遍历当前使用的所有类
4.3.3、什么是 class_rw_ext_t
  • 如果按照原有思路 rw 中包含一份ro,当该 类有分类或者动态修改时又需要复制一整份ro内容用于修改及后续使用,但复制一整份ro内容明显会生成更多的dirty memory(真正改变自身方法的类大概只有10%

  • 为了节省内存,苹果将 被修改的内容单独存到了一个空间中这个单独存放被修改的内容的空间叫做 class_rw_ext_t,如此一来,就 不需要复制整个ro内容,而只需复制ro中被修改的内容即可

  • rw 中调用内容时需要判断是否存在 rwe如果有 rwe 则从 rwe 中获取数据(动态修改过),如果没有 rwe 则直接从ro中获取数据

rwe的内容
struct class_rw_ext_t {
    DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
    class_ro_t_authed_ptr<const class_ro_t> ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};
  • MethodsPropertiesprotocols:包含这3个是因为它们可以在运行时进行修改,当category被加载时,它可以向类中添加新的方法,也可以通过runtime API添加它们

  • Demangled Name:这个是只有Swift才会使用的字段,因为整个数据结构OC与Swift是共享的,但是Swift类本身并不需要这个字段,是为了有人要访问Swift的OC名称的时候使用,利用率比较低

list_array_tt模板容器
  • method_array_tproperty_array_tprotocol_array_t都是通过这个模板容器创建的
    //继承自list_array_tt 模板容器
    class method_array_tpublic list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
    {
        typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> Super;
    
    public:
        method_array_t() : Super() { }
        method_array_t(method_list_t *l) : Super(l) { }
    
        const method_list_t_authed_ptr<method_list_t> *beginCategoryMethodLists() const {
            return beginLists();
        }
    
        const method_list_t_authed_ptr<method_list_t> *endCategoryMethodLists(Class cls) const;
    };
    
  • list_array_tt<>模板容器结构:
    template <typename Element, typename List, template<typename> class Ptr>
    class list_array_tt {
        ......
    }
    
    • Element:代表元素类型,List:代表容器类型
    • 专门用来初始化容器(method_array_tprotocol_array_tprotocol_array_t
ro、rw、rwe优化后结构

image.png

class_ro_t (二进制文件中)class_rw_t (类首次使用时)class_rw_ext_t (有动态修改时)
flagsflagsmethods
instanceStartwitnessproperties
instanceSizefirstSubclassprotocols
reservednextSiblingClassdemangledName
nameversion
ivarLayout一份ro数据
weakIvarLayoutrwe数据(可选)
ivars
baseMethodList
baseProtocols
baseProperties
总结
  • rw 中可以获取 Methods、Properties、Protocols,但实际直接来源是rw中复制的ro或者rwe
  • 被修改过的内容才生成rwe,不修改不生成
  • 通过终端命令查看一下class_rw占用情况(以苹果自带的Safari为例)
    //Safari 
    heap Safari | egrep 'class_rw|COUNT'
    
    image.png
    • 我们看到class_rw_t一共占用162240字节,而class_rw_ext_t则占用了25056字节,相当于其15%

4.4、根据 bits 结构验证打印 Properties、Methods、Protocols

image.png

1、根据偏移获取class_data_bits_t类型的 bits
  • x/4gx test其中首地址 0x100645940
  • 0x100645940平移32字节为0x100645960
  • 因为bitsclass_data_bits_t类型, 我们要取地址所以class_data_bits_t *类型强转一下, 变成指针地址
2、获取 bits 中class_rw_t *类型的 data
  • p $2->data()是因为struct class_data_bits_t源码(->是因为当前的是指针, 结构体的话用.)

image.png

3、打印 data 下的 Properties 与 Methods
  • 根据上面class_rw_t(结构体)结构,里面提供一些属性properties方法列表methods协议列表protocols的方法、list_array_tt<> 提供对应容器

  • 打印属性列表 image.png image.png image.png 验证打印步骤 : image.png

  • 打印方法列表 image.png image.png image.png 验证打印步骤 : image.png

    • 根据芯片英特尔和M1不同,英特尔大端模式用.big(),M1小端模式用 small 提供的.getDescription()方法
    struct method_t {
       static const uint32_t smallMethodListFlag = 0x80000000;
    
       method_t(const method_t &other) = delete;
    
       // The representation of a "big" method. This is the traditional
       // representation of three pointers storing the selector, types
       // and implementation.
       
       //big中存放了方法名和函数指针地址
       struct big {
           //方法名称
           SEL name;
           const char *types;
           //函数指针地址
           MethodListIMP imp;
       };
    
    private:
       bool isSmall() const {
           return ((uintptr_t)this & 1) == 1;
       }
    
       // The representation of a "small" method. This stores three
       // relative offsets to the name, types, and implementation.
       struct small {
           // The name field either refers to a selector (in the shared
           // cache) or a selref (everywhere else).
           RelativePointer<const void *> name;
           RelativePointer<const char *> types;
           RelativePointer<IMP> imp;
    
           bool inSharedCache() const {
               return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
                       objc::inSharedCache((uintptr_t)this));
           }
       };
       ......
    }
    
  • 打印协议列表 协议列表的list结构, 获取datap $3.protocols()即可

补充

  • objc_object VS NSObjectobjc_class VS Classdispatch_objc VS dispatch,iOS底层实现使用带objc内容进行操作
  • 类中有成员变量时methods 中会有一个.cxx_destruct方法,是ARC下自动生成的析构函数

MachOView(反编译)

image.png Symbol Table → Symbols中可看到, 实际在底层多了个_OBJC_METACLASS(meta class : 元类)

image.png