类的结构分析

398 阅读3分钟

类的本质

探索C++源码

环境:xcode 11.5

源码:objc4-781

本篇文章主要是对于类结构进行探索。首先我们创建一个自定义的类。

ZHYPerson.h
@interface ZHYPerson : NSObject{
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickName;

- (void)sayHello;
+ (void)sayHappy;

@end
main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ZHYPerson *person = [ZHYPerson alloc];
        Class pClass = object_getClass(person);
        NSLog(@"%@ - %p",person,pClass);
    }
    return 0;
}

我们利用clangmain.m转换成cpp文件。相关命令为:

clang -rewrite-objc main.m -o main.cpp

打开对应的cpp文件,寻找ZHYPerson相关的代码段

typedef struct objc_class *Class;
...
typedef struct objc_object ZHYPerson;
typedef struct {} _objc_exc_ZHYPerson;
#endif

struct ZHYPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *hobby;
};

struct NSObject_IMPL {
	Class isa;
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        ZHYPerson *person = ((ZHYPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ZHYPerson"), sel_registerName("alloc"));
        Class pClass = object_getClass(person);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_zh_w27k77q94sdgr84dr5dnbqgm0000gn_T_main_1df9cf_mi_9,person,pClass);
    }
    return 0;
}
...

通过上述代码可以看到:

  • ZHYPerson本质上是一个objc_object的结构体,它和NSObject的关联在于ZHYPerson_IMPL结构体中存在一个NSObject_IMPL类型的结构体NSObject_IVARS,其内部有一个变量为isa,可以将ZHYPersonNSObject联系起来。
  • Class是一个指向objc_class的指针。

objc_object

首先来看objc_object的定义

struct objc_object {
private:
    isa_t isa;

public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
    // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
    Class rawISA();
    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    uintptr_t isaBits() const;
    ...一些函数...
}

objc_object结构体中包含了一个isa_t的联合体,关于isa_tisa的初始化&指向分析一文中有相关的探索。

NSObject和objc_object的关系

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

NSObject的内部也存在一个isa,二者是同一事物的不同表现形式。

isa_t和Class isa的关系

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL

(lldb) p/t 0x00007ffffffffff8ULL
(unsigned long long) $0 = 0b0000000000000000011111111111111111111111111111111111111111111000

__x86_64__架构下,ISA_MASK的二进制表现如上所示,其中为1的一共有44位,isa.bits & ISA_MASK操作得出的正是当前类的指针。

objc_class

接下来看objc_class的定义

typedef struct objc_class *Class;
typedef struct objc_object *id;

struct objc_class : objc_object {
    // Class ISA; // 8
    Class superclass; // 8
    cache_t cache;    // 16 不是8         // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

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

可以看出,Class是指向objc_class结构体的指针。objc_class继承自objc_objectISA就是父结构体objc_object中的变量isa

  • Class ISA
    实例ISA指向类对象类对象ISA指向元类Class是指针类型,占8位。
  • Class superclass
    父类Class类型,占8位。
  • cache 用于缓存指针和 vtable,加速方法的调用,后续会有进一步的分析。占16位。
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

struct cache_t {
	...
    explicit_atomic<struct bucket_t *> _buckets; //指针      8位
    explicit_atomic<mask_t> _mask;			     //uint32_t 4位
	...
    ...
#if __LP64__
    uint16_t _flags;							 //uint16_t 2位
#endif
    uint16_t _occupied;							 //uint16_t 2位
public:
    ...
};
  • class_data_bits_t 存储类的方法、属性、遵循的协议等信息的地方。
struct class_data_bits_t {
    friend objc_class;

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

    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    ...
};

friend关键字解释:详解C++ friend关键字

class_data_bits_t结构体内部只有一个64位的bits用于存储与类有关的信息。我们看到注释,class_data_bits_t 相当于 class_rw_t 指针加上 rr/alloc 的标志。

class_rw_t

执行 class_data_bits_t 结构体中的 data() 方法或者调用 objc_class 中的 data() 方法会返回同一个 class_rw_t * 指针,因为 objc_class 中的方法只是对 class_data_bits_t 中对应方法的封装。
类中的属性、方法还有遵循的协议等信息都保存在class_rw_t中。

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;

private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
    ...
}

class_rw_t结构体中还存在一个ro_or_rw_ext的变量,该变量支持两种类型模板,分别是 class_ro_tclass_rw_ext_t,分别看一下这两个结构体的组成如下:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
    ...
}

struct class_rw_ext_t {
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};

其中的class_ro_t存储了当前类在编译期就已经确定的属性、方法以及遵循的协议,而class_rw_ext_t除了包含一个class_ro_t *指针外也包含了一些methods、properties、protocols等信息。关于这两个结构体的信息可以观看WWDC2020的视频,上面有做介绍。

class_rw_t结构体中还发现了如下方法:

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 *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->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 *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->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 *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }

通过这些方法,我们可以获取类对应的方法、属性以及协议列表。

lldb验证

接下来我们对类的结构进行验证。修改类ZHYPerson的代码如下:

@protocol ZHYPersonProtocol1 <NSObject>
@property (nonatomic, copy) NSString *protocolProperty1;
- (void)protocolMethod1;
@end

@protocol ZHYPersonProtocol2 <NSObject>
@property (nonatomic, copy) NSString *protocolProperty2;
- (void)protocolMethod2;
@end

@interface ZHYPerson : NSObject<ZHYPersonProtocol1, ZHYPersonProtocol2>{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)sayHappy;
@end

增加一个ZHYPerson的分类

@interface ZHYPerson (Category)

- (void)categoryMethod;

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ZHYPerson *person = [ZHYPerson alloc];
        Class pClass     = object_getClass(person);
    }
    return 0;
}

首先通过lldb打印pClass的内存分布。

(lldb) x/5gx pClass
0x100001620: 0x00000001000015f8 0x0000000100332148
0x100001630: 0x000000010212a1a0 0x0001802400000003
0x100001640: 0x000000010212a124

其中,0x100001620为的pClass在堆上的地址,该地址存放的内容为0x00000001000015f8,我们已经知道:

  • 0x00000001000015f8objc_class中的isa 占8位
  • 0x0000000100332148objc_class中的superclass 占8位
  • 0x000000010212a1a0 0x0001802400000003objc_class中的cache
  • 0x000000010212a124则为objc_class中的class_data_bits_t,指针为0x100001640
class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

获取class_rw_t

(lldb) p (class_data_bits_t*)0x100001640
(class_data_bits_t *) $1 = 0x0000000100001640
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010212a120

(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294972640
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

根据前面WWDC视频可以知道,在没有进行动态添加方法等操作的时候,ro_or_rw_ext是以class_ro_t的形式存在的,我们接下来验证一下:

p (class_ro_t*)4294972640
(class_ro_t *) $4 = 0x00000001000014e0
(lldb) p *$4
(class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100000e30 "\x02"
  name = 0x0000000100000e26 "ZHYPerson"
  baseMethodList = 0x0000000100001070
  baseProtocols = 0x00000001000014c8
  ivars = 0x0000000100001528
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100001570
  _swiftMetadataInitializer_NEVER_USE = {}
}

接下来我们根据class_rw_t提供的方法来校验一下上面的信息是否正确。

(lldb) p $3.methods()
(const method_array_t) $6 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100001070
      arrayAndFlag = 4294971504
    }
  }
}
(lldb) p $3.properties()
(const property_array_t) $7 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x0000000100001570
      arrayAndFlag = 4294972784
    }
  }
}
(lldb) p $3.protocols()
(const protocol_array_t) $8 = {
  list_array_tt<unsigned long, protocol_list_t> = {
     = {
      list = 0x00000001000014c8
      arrayAndFlag = 4294972616
    }
  }
}

通过lldb可以发现,通过class_rw_t方法获取的方法、属性、协议列表和class_ro_t结构体中的列表是一一对应的。

methods

(lldb) p $3.methods()
(const method_array_t) $6 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100001070
      arrayAndFlag = 4294971504
    }
  }
}

listbaseMethodList的地址是一样的。

(lldb) p $6.list
(method_list_t *const) $9 = 0x0000000100001070
(lldb) p *$9
(method_list_t) $10 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 5
    first = {
      name = "categoryMethod"
      types = 0x0000000100000e32 "v16@0:8"
      imp = 0x0000000100000b10 (objc-debug`-[ZHYPerson(Category) categoryMethod])
    }
  }
}

(lldb) p $10.get(0)
(method_t) $11 = {
  name = "categoryMethod"
  types = 0x0000000100000e32 "v16@0:8"
  imp = 0x0000000100000b10 (objc-debug`-[ZHYPerson(Category) categoryMethod])
}
(lldb) p $10.get(1)
(method_t) $12 = {
  name = "sayHello"
  types = 0x0000000100000e32 "v16@0:8"
  imp = 0x0000000100000b30 (objc-debug`-[ZHYPerson sayHello])
}
(lldb) p $10.get(2)
(method_t) $13 = {
  name = ".cxx_destruct"
  types = 0x0000000100000e32 "v16@0:8"
  imp = 0x0000000100000ba0 (objc-debug`-[ZHYPerson .cxx_destruct])
}
(lldb) p $10.get(3)
(method_t) $14 = {
  name = "setNickName:"
  types = 0x0000000100000efc "v24@0:8@16"
  imp = 0x0000000100000b70 (objc-debug`-[ZHYPerson setNickName:])
}
(lldb) p $10.get(4)
(method_t) $15 = {
  name = "nickName"
  types = 0x0000000100000e4d "@16@0:8"
  imp = 0x0000000100000b40 (objc-debug`-[ZHYPerson nickName])
}

methodslist中存在5个方法,分别是:

  • categoryMethod
  • sayHello
  • .cxx_destruct
  • setNickName
  • nickName

其中除了.cxx_destruct方法外的4个方法都是我们代码生成的方法,虽然我们实现了ZHYPersonProtocol1协议,但是我们并没有实现protocolMethod1方法,因此方法列表中并没有protocolMethod1

ivars

(lldb) p $5.ivars
(const ivar_list_t *) $16 = 0x0000000100001528
(lldb) p *$16
(const ivar_list_t) $17 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000015e8
      name = 0x0000000100000dc4 "hobby"
      type = 0x0000000100000f07 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $17.get(0)
(ivar_t) $18 = {
  offset = 0x00000001000015e8
  name = 0x0000000100000dc4 "hobby"
  type = 0x0000000100000f07 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $17.get(1)
(ivar_t) $19 = {
  offset = 0x00000001000015f0
  name = 0x0000000100000dca "_nickName"
  type = 0x0000000100000f07 "@\"NSString\""
  alignment_raw = 3
  size = 8
}

ivars的列表中一共有两个成员变量,分别是:

  • hobby
  • _nickName

虽然我们实现了ZHYPersonProtocol1协议,但是协议只会提供对应方法的声明,因此并不存在名为_protocolProperty1的实例变量。

properties

接下来验证properties

(lldb) p $7.list
(property_list_t *const) $20 = 0x0000000100001570
(lldb) p *$20
(property_list_t) $21 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 6
    first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
  }
}
(lldb) p $21.get(0)
(property_t) $22 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
(lldb) p $21.get(1)
(property_t) $23 = (name = "protocolProperty1", attributes = "T@\"NSString\",C,N")
(lldb) p $21.get(2)
(property_t) $24 = (name = "hash", attributes = "TQ,R")
(lldb) p $21.get(3)
(property_t) $25 = (name = "superclass", attributes = "T#,R")
(lldb) p $21.get(4)
(property_t) $26 = (name = "description", attributes = "T@\"NSString\",R,C")
(lldb) p $21.get(5)
(property_t) $27 = (name = "debugDescription", attributes = "T@\"NSString\",R,C")

其中,nickNameprotocolProperty1是我们代码加上的属性,hashsuperclassdescriptiondebugDescription为协议NSObject的属性,因为hobby只是成员变量,因此不存在与属性列表中。

protocols

(lldb) p $8.list
(protocol_list_t *const) $28 = 0x00000001000014c8
(lldb) p *$28
(protocol_list_t) $29 = (count = 1, list = protocol_ref_t [] @ 0x00007f8eb827a368)
(lldb) p (protocol_t*)$29.list[0]
(protocol_t *) $31 = 0x00000001000016a8
(lldb) p *$31
(protocol_t) $32 = {
  objc_object = {
    isa = {
      cls = Protocol
      bits = 4298318032
       = {
        nonpointer = 0
        has_assoc = 0
        has_cxx_dtor = 0
        shiftcls = 537289754
        magic = 0
        weakly_referenced = 0
        deallocating = 0
        has_sidetable_rc = 0
        extra_rc = 0
      }
    }
  }
  mangledName = 0x0000000100000e13 "ZHYPersonProtocol1"
  protocols = 0x00000001000013c8
  instanceMethods = 0x00000001000013e0
  classMethods = 0x0000000000000000
  optionalInstanceMethods = 0x0000000000000000
  optionalClassMethods = 0x0000000000000000
  instanceProperties = 0x0000000100001430
  size = 96
  flags = 0
  _extendedMethodTypes = 0x0000000100001448
  _demangledName = 0x0000000000000000
  _classProperties = 0x0000000000000000
}
(lldb) p $32.instanceMethods
(method_list_t *) $33 = 0x00000001000013e0
(lldb) p *$33
(method_list_t) $34 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 24
    count = 3
    first = {
      name = "protocolMethod1"
      types = 0x0000000100000e32 "v16@0:8"
      imp = 0x0000000000000000
    }
  }
}
(lldb) p $34.get(0)
(method_t) $35 = {
  name = "protocolMethod1"
  types = 0x0000000100000e32 "v16@0:8"
  imp = 0x0000000000000000
}
(lldb) p $34.get(1)
(method_t) $36 = {
  name = "protocolProperty1"
  types = 0x0000000100000e4d "@16@0:8"
  imp = 0x0000000000000000
}
(lldb) p $34.get(2)
(method_t) $37 = {
  name = "setProtocolProperty1:"
  types = 0x0000000100000efc "v24@0:8@16"
  imp = 0x0000000000000000
}
(lldb) 
(lldb) p $32.instanceProperties
(property_list_t *) $38 = 0x0000000100001430
(lldb) p *$38
(property_list_t) $39 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "protocolProperty1", attributes = "T@\"NSString\",C,N")
  }
}
(lldb) p $32.protocols
(protocol_list_t *) $40 = 0x00000001000013c8
(lldb) p *$40
(protocol_list_t) $41 = (count = 1, list = protocol_ref_t [] @ 0x00007f8ed2c2b438)
(lldb) p (protocol_t*)$41.list[0]
(protocol_t *) $42 = 0x0000000100001648
(lldb) p *$42
(protocol_t) $43 = {
  objc_object = {
    isa = {
      cls = nil
      bits = 0
       = {
        nonpointer = 0
        has_assoc = 0
        has_cxx_dtor = 0
        shiftcls = 0
        magic = 0
        weakly_referenced = 0
        deallocating = 0
        has_sidetable_rc = 0
        extra_rc = 0
      }
    }
  }
  mangledName = 0x0000000100000e0a "NSObject"
  protocols = 0x0000000000000000
  instanceMethods = 0x00000001000010f0
  classMethods = 0x0000000000000000
  optionalInstanceMethods = 0x00000001000012c0
  optionalClassMethods = 0x0000000000000000
  instanceProperties = 0x00000001000012e0
  size = 96
  flags = 0
  _extendedMethodTypes = 0x0000000100001328
  _demangledName = 0x0000000000000000
  _classProperties = 0x0000000000000000
}

$32是一个类型为protocol_t的结构体,观察内部结构可以确定这个结构体就是我们通过代码声明的ZHYPersonProtocol1协议,同时我们可以发现协议继承了objc_object,也是个对象。协议的内部包含了包含了协议名ZHYPersonProtocol1,同时还有protocolMethod1protocolProperty1setProtocolProperty1:三个方法。

同时也可以发现ZHYPersonProtocol1内部还有一个协议列表,里面包含了名为NSObject的协议。经过上述lldb的打印,我们也验证了一个事实:

协议只提供了属性对应的set/get方法,并没有提供对应的实现以及相应的实例变量

以上是对class_rw_t的内部结构验证。

动态

之前我们验证的都是在编译期就确定了的内容,因为oc是一门动态的语言,我们可以通过运行时动态的给类添加属性方法等等。这些内容在此就不验证了,验证的方法和上面是一样的。

总结

类本质也是一个继承自objc_objectobjc_class结构体。它的isa指向其元类。内部主要有一下几个变量:

  • ISA ---isa指针
  • superclass ---父类
  • cache ---缓存
  • bits ---存放类的一些信息 64位系统下占8个字节。

NSObject协议

@protocol NSObject

- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;

@property (readonly) Class superclass;
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");
- (instancetype)self;

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

- (BOOL)isProxy;

- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

- (BOOL)respondsToSelector:(SEL)aSelector;

- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;

- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;

@property (readonly, copy) NSString *description;
@optional
@property (readonly, copy) NSString *debugDescription;

@end