La esencia de las clases de iOS

objc_object 与objc_class

Primero, veamos los siguientes tipos: NSObject, Class, objc_object, objc_class, idla conexión y la diferencia.

  • NSObject: la clase base en OC, la mayoría de las clases heredan NSObject( NSProxytambién la clase base ~)
  • Clase: NSObjectel tipo, que se puede ver en el archivo de objccódigo fuenteNSObject.mm
+ (Class)class {
    return self;
}
复制代码
  • objc_object: NSObjectel nombre de la estructura de la implementación subyacente de la clase en C++, como puede ver en el archivo cpp generado, NSObjectes lo mismo, pero con diferentes nombres en diferentes lugares. En la estructura, solo hay un puntero objc_objectde variable miembro .isa
typedef struct objc_object NSObject;

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
复制代码
  • objc_class: ClassEl nombre de la estructura implementada en la parte inferior de C++, como se puede ver en el archivo cpp generado, Classes lo mismo, pero el nombre es diferente en las diferentes localidades.
typedef struct objc_class *Class;
复制代码
  • id: puede apuntar a cualquier tipo en el entorno OC y no necesita estar marcado con *
typedef struct objc_object *id;
复制代码
Exploración de isa y superClass

Veamos primero la dirección de la clase en la memoria ¿No es que cada vez que se crea un objeto, se creará una nueva clase correspondiente a él?

void getSJPersonClass(void) {
    Class cls1 = SJPerson.class;
    Class cls2 = [SJPerson alloc].class;
    Class cls3 = object_getClass([SJPerson alloc]);
    Class cls4 = [SJPerson alloc].class;
    NSLog(@"cls1 : %p \n cls1 : %p \n cls1 : %p \n cls1 : %p \n", cls1, cls2, cls3, cls4);
}
复制代码

Ejecutar getSJPersonClass, el resultado de la impresión es el siguiente:

image.pngEs decir, una misma clase solo tendrá una copia en memoria.

Imprimimos pla memoria de la variable de instancia, sabemos que los primeros 8 bits son el isapuntero

image.png

isaDirección y ISA_MASKdo y operación, puede obtener la dirección del objeto de clase.

image.png

Continuamos tomando la isadirección de la clase y ISA_MASKhacemos la operación AND para ver qué podemos obtener.

image.png

我们看到,最后打印的结果也是SJPerson,但是两个SJPerson的地址不一样,一个0x00000001000085f8,一个0x00000001000085d0,也就是说这两个不是一个类型。在这就不卖关子了,第二个SJPerson就是我们常说的元类。 为什么是元类呢,我们可以在machOView中找到相关内容:

image.png

看到有一个metaclass也就是元类。 那我们继续看下元类的isa指向哪里。

image.png

我们看到SJPerosn元类的isa打印出来是ONObject,但是地址和NSObject类对象地址不一样,我们在打印NSObject的元类地址,发现地址一致了,也就是说SJPerson的元类的isa指向NSObject元类。SJPerson继承自NSObject,那是不是说明直接指向父类的元类呢,我们再创建一个SJStudent继承SJPerson,再试一下。

image.png

image.png 我们看到$3$5相同,也就是说,无论对象继承谁,自定义对象的元类的isa都指向根元类。 继续往下,我们看下NSObject元类的isa指向哪里。

image.png 1 1与 2相同,也就是说根元类的isa指向它自己。 isa指向链我们看完了,再看下继承关系,元类也有继承吗,我们用以下代码测试以下:

NSObject *obj = [NSObject alloc];
    Class objCls = object_getClass(obj);
    Class objMetaCls = object_getClass(objCls);
    Class objRootMetaCls = object_getClass(objMetaCls);
    NSLog(@"NSObject实例对象: %p \n NSObject类对象: %p \n NSObject元类对象: %p \n NSObject根元类对象: %p \n ", obj, objCls, objMetaCls, objRootMetaCls);
    
    Class pMetaCls = object_getClass([SJPerson class]);
    Class pMetaSuperCls = class_getSuperclass(pMetaCls);
    NSLog(@"SJPerson元类对象: %p \n SJPerson元类对象的父类: %p \n ", pMetaCls, pMetaSuperCls);
    
    Class sMetaCls = object_getClass([SJStudent class]);
    Class sMetaSuperCls = class_getSuperclass(sMetaCls);
    NSLog(@"SJStudent元类对象: %p \n SJStudent元类对象的父类: %p \n ", sMetaCls, sMetaSuperCls);
    
    Class rootMetaSuperCls = class_getSuperclass(objRootMetaCls);
    NSLog(@"根元类对象的父类: %p", rootMetaSuperCls);
复制代码

打印结果:

NSObject实例对象: 0x101a30050 
NSObject类对象: 0x10036a140 
NSObject元类对象: 0x10036a0f0 
NSObject根元类对象: 0x10036a0f0 
 
SJPerson元类对象: 0x100008530 
SJPerson元类对象的父类: 0x10036a0f0 
 
SJStudent元类对象: 0x100008580 
SJStudent元类对象的父类: 0x100008530 
 
根元类对象的父类: 0x10036a140
复制代码

根据结果,我们看到SJStudent元类的父类就是SJPerson元类,SJPerson元类的父类就是NSObject的元类,NSObject元类的父类就是NSObject类对象。 isa的流程图与继承流程图如下:

image.png

类的内存数据

OC的类在C++中是objc_class类型,我们在objc源码中找objc_class的结构。 我们找到3个结构

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
复制代码

因为我们当前环境是OBJC2,所以这个标明OBJC2_UNAVAILABLE,我们就可以忽略不计。 还有两个分别在objc-runtime-new.hobjc-runtime-old.h文件中,我们想都不想直接看new。里面的结构省略方法后:

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
}

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
复制代码

也就是说,类结构里面有isa指针,superclasscachebitsisa我们已经分析过了,superclass就是父类,cache我们以后再说,我们接下来研究下bits。 在此之前我们验证下superclass:

image.png SJPerson的父类是NSObject,没有问题。 要研究bits,首先我们需要知道bits在内存存储的位置,isa占8字节,superclass占8字节没什么好说的,那么cache占多少字节呢,我们点进去看下cache_t的结构:

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
}
复制代码

再看下explicit_atomic实现

template <typename T>
struct explicit_atomic : public std::atomic<T> {
    explicit explicit_atomic(T initial) noexcept : std::atomic<T>(std::move(initial)) {}
    operator T() const = delete;
    
    T load(std::memory_order order) const noexcept {
        return std::atomic<T>::load(order);
    }
    void store(T desired, std::memory_order order) noexcept {
        std::atomic<T>::store(desired, order);
    }
    
    // Convert a normal pointer to an atomic pointer. This is a
    // somewhat dodgy thing to do, but if the atomic type is lock
    // free and the same size as the non-atomic type, we know the
    // representations are the same, and the compiler generates good
    // code.
    static explicit_atomic<T> *from_pointer(T *ptr) {
        static_assert(sizeof(explicit_atomic<T> *) == sizeof(T *),
                      "Size of atomic must match size of original");
        explicit_atomic<T> *atomic = (explicit_atomic<T> *)ptr;
        ASSERT(atomic->is_lock_free());
        return atomic;
    }
};
复制代码

T泛型,这个变量的大小取决于泛型T的大小。uintptr_t指针占8字节,第二个变量共用体,mask_ttypedef uint32_t mask_t;,占4字节,uint16_t占2字节,结构体占8字节,_originalPreoptCache是个*也就是指针类型也是8字节,所以,cache_t一共占16字节。bits从32字节开始。 再看下怎么获取bits里面数据,在源码中可以找到data方法。

class_rw_t *data() const {
        return bits.data();
    }
复制代码

image.png 里面唯一能根据单词猜意思的只有firstSubclass,第一个子类,但是SJPerson有一个子类SJStudent啊,为什么这里面数据是nil呢?因为OC中的类是懒加载的,如果没有用到,是不会加载这个类,那我们代码改一下:

SJStudent *s = [SJStudent alloc];
SJPerson *p = [SJPerson alloc];
复制代码

image.png 这时候我们就看到firstSubclass里面有值了,没毛病。但是我又想,如果SJPerson还有别的子类,这个值又会是什么呢? 新建一个SJHandsomeMan继承自SJPerson

image.png

image.png

两次结果不一样,也就是说firstSubclass的值是该类子类中最后初始化的那个类。

类的属性

目前能看懂看完了,比如我们想看类里面的属性怎么办呢?答:从class_rw_t结构里面找成员变量或者方法。

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};
    }
}
复制代码

成员变量没找到,方法倒是看上去有一个,那我们接着调试。

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};
复制代码
template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
    struct array_t {
        uint32_t count;
        Ptr<List> lists[0];

        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }
        size_t byteSize() {
            return byteSize(count);
        }
    };
}
复制代码
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
复制代码
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
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);
    }

    size_t byteSize() const {
        return byteSize(entsize(), count);
    }
    
    static size_t byteSize(uint32_t entsize, uint32_t count) {
        return sizeof(entsize_list_tt) + count*entsize;
    }

    struct iterator;
}
复制代码

分析下property_array_t 结构:是个数组,数组里面每个元素都是property_list_t 类型,property_list_t 也是个数组,因为entsize_list_tt 里面有迭代器iterator property_list_t 数组里面的元素是property_t 。大致结构:property_array_t = [property_list_t[property_t, property_t], property_list_t[property_t]]

image.png 再看下property_t的结构:

struct property_t {
    const char *name;
    const char *attributes;
};
复制代码
类的方法

属性我们看完了,再看下方法,在SJPerson里面添加两个方法,一个实例方法,一个类方法。

@interface SJPerson : NSObject

@property (nonatomic, copy) NSString *name;

- (void)sayNB;
+ (void)say666;

@end
复制代码

找方法同样我们看到class_rw_t里面有个methods方法,不出意外应该能拿到方法。

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()};
    }
}
复制代码

method_array_t的结构和property_array_t一样,都是二维数组,只不过最里面的对象换成了method_t

image.png 我们看到同样的操作,怎么最后打印不出来数据,是不是我们操作不对?这就要说下property_t里面有两个成员,打印的时候直接把两个成员打印出来,我们再看下method_t的结构里面没有这种成员,但是里面有一个结构体:

struct big {
    SEL name;
    const char *types;
    MethodListIMP imp;
};
复制代码

这里面成员能看到方法所有内容,是不是我们打印这个big就行呢?测试一下:

image.png 非常完美方法都打印出来了,但是自己观察,一共就3个方法,say666没有打印出来,为什么呢?对象方法存在类结构中,那猜测类方法会不会放在元类里面呢?我们来验证下:

image.png

image.png 我们看到say666的确在元类里面,也就是类方法存储在元类。 这也就是为什么iOS要设计元类的原因,因为OC中有实例方法和类方法,也就是所谓的减号方法和加号方法,但是在OC底层,是没有这个含义的,也就是所有方法都转化成同名函数,如果没有元类,我们写两个方法+(void)say666-(void)say666,这两个方法都存储在类里面,那我们执行方法的时候,根据函数名,就无法确定到底是执行实例方法还是类方法了,所以设计元类把两种方法分离开。

类的成员变量

class_ro_t演变 在WWDC视频中,我们可以详细看到class_ro_t演变优化过程,也知道成员变量存在class_ro_t中。在SJPerson中添加一个成员变量hobby

@interface SJPerson : NSObject
{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
- (void)sayNB;
+ (void)say666;

@end
复制代码

class_ro_t怎么找呢,同样在源码中找方法

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);
}
复制代码

调试验证:

image.png

image.png

TypeEncoding

上面打印方法里面types一堆符号就是iOS中TypeEncoding编码,每个符号的含义可以参照# Type Encodings 。 在解释下方法中types含义: 第一个字母:返回值类型。第一个数字:这个方法所有参数占内存大小。 第二个字母:第一个参数的类型。第二个数字:第一个参数从第几号内存开始。 第三个字母:第二个参数的类型。第三个数字:第二个参数从第几号内存开始。 .......以此类推。

总结:类本质就是结构体,里面有isasuperclasscachebitsisa利用位域存着类相关信息,superclass存着父类,cache存着方法缓存,bits里面存着属性列表、实例变量列表、方法列表、协议列表等。

__has_feature(ptrauth_calls)介绍

  • __has_feature:此函数的功能是判断编译器是否支持某个功能

  • ptrauth_calls:指针身份验证,针对arm64e架构;使用Apple A12或更高版本A系列处理器的设备(如iPhone XSiPhone XS MaxiPhone XR或更新的设备)支持arm64e架构

  • 参考链接:Preparing Your App to Work with Pointer Authentication

  • 下面分别使用真机iPhone 12iPhone 8进行验证

验证方法一:通过isa存储数据验证
  • 因为arm64架构中if分支和else分支存储的数据结构是不一样的,这里根据weakly_referenced值做验证
  • 测试代码
LGPerson *p = [LGPerson alloc];
__weak typeof(p) weakP = p;
NSLog(@"p:%@", p);
复制代码
复制代码
  • __weak typeof(p) weakP = p;处添加断点,这行代码执行前weakly_referenced的值为0,执行后会变为1

  • 测试iPhone 8

    • 执行前

    image.png

    • 执行后:

    image.png

  • 测试iPhone 12

    • 执行前

    image.png

    • 执行后

    image.png

  • 验证结果: 从上面两个设备的验证结果可以看出,执行__weak typeof(p) weakP = p;前后,iPhone 8修改的是第43位(从右往左),而iPhone 12修改的是第3位(从右往左),分别对应的是arm64架构中的else分支和if分支的存储结构

验证方法二:通过断点和汇编验证
  • [LGPerson alloc]处设置断点
  • 执行到断点处后,添加符号断点_objc_rootAllocWithZone,并继续运行
  • iPhone 8

image.png

  • iPhone 12

image.png

  • 验证结果: iPhone 8中使用了0xffffffff8iPhone 12中使用了0x7ffffffffffff8,在objc源码的isa.h中搜索发现,这两个值分别对应的是arm64架构中else分支的ISA_MASK值和if分支的ISA_MASK

总结
  • __has_feature(ptrauth_calls)的作用是判断编译器是否支持指针身份验证功能

  • iPhone X系列以上的设备(arm64e架构,使用Apple A12或更高版本A系列处理器的设备)支持指针身份验证

  • 对于arm64架构

    • iPhone X系列以上(包含)设备使用如下结构:
    #     define ISA_MASK        0x007ffffffffffff8ULL
    #     define ISA_MAGIC_MASK  0x0000000000000001ULL
    #     define ISA_MAGIC_VALUE 0x0000000000000001ULL
    #     define ISA_HAS_CXX_DTOR_BIT 0
    #     define ISA_BITFIELD                                                      \
            uintptr_t nonpointer        : 1;                                       \
            uintptr_t has_assoc         : 1;                                       \
            uintptr_t weakly_referenced : 1;                                       \
            uintptr_t shiftcls_and_sig  : 52;                                      \
            uintptr_t has_sidetable_rc  : 1;                                       \
            uintptr_t extra_rc          : 8
    #     define RC_ONE   (1ULL<<56)
    #     define RC_HALF  (1ULL<<7)
    复制代码
    复制代码
    • iPhone X以下(不包含)设备使用如下结构:
    #     define ISA_MASK        0x0000000ffffffff8ULL
    #     define ISA_MAGIC_MASK  0x000003f000000001ULL
    #     define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #     define ISA_HAS_CXX_DTOR_BIT 1
    #     define ISA_BITFIELD                                                      \
            uintptr_t nonpointer        : 1;                                       \
            uintptr_t has_assoc         : 1;                                       \
            uintptr_t has_cxx_dtor      : 1;                                       \
            uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
            uintptr_t magic             : 6;                                       \
            uintptr_t weakly_referenced : 1;                                       \
            uintptr_t unused            : 1;                                       \
            uintptr_t has_sidetable_rc  : 1;                                       \
            uintptr_t extra_rc          : 19
    #     define RC_ONE   (1ULL<<45)
    #     define RC_HALF  (1ULL<<18)
    复制代码

Supongo que te gusta

Origin juejin.im/post/7064949004223643656
Recomendado
Clasificación