preface

In the previous article we learned that Class information is stored in the object’s ISA pointer and that Class = isa & ISA_MASK. Today we will explore the inheritance chain of classes and metaclasses and the data structure of classes. ISA_MASK (mask)

# if __arm64__ # if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR # define ISA_MASK 0x007ffffffffffff8ULL # else #  define ISA_MASK 0x0000000ffffffff8ULL elif __x86_64__ # define ISA_MASK 0x00007ffffffffff8ULLCopy the code

1. The pointer ISA points to the inheritance chain of processes and classes

1.1 ISA points to processes

The ISA of any object points to the current class, the ISA of the current class points to the metaclass, the METaclass’s ISA points to NSObject(the root metaclass), and the root metaclass’s ISA points to the root metaclass. See the figure below

To verify, create a ZFPerson class that uses the LLVM print object’s ISA pointer to the process

1.2 Class inheritance chain

  • Any class inherits from its parent, which inherits from the root class (mostly NSObject), which inherits from nil,

To verify, create two classes, ZFStudent and ZFPerson.

@interface ZFPerson:NSObject
@end
@implementation ZFPerson
@end

@interface ZFStudent:ZFPerson
@end
@implementation ZFStudent

@end
Copy the code

The above conclusion can be drawn from the printed results

  • The metaclass of any class inherits from its parent metaclass, which inherits from the root metaclass, which inherits from the root metaclass

What makes perMeteSupClass an NSOject metaclass, perMeteSupSupClass an NSOject class?

It can be concluded from the printed results that the above conclusion is correct

2. Class structure

Typedef struct objc_class *Class;

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
    .....
}
Copy the code

Look at the picture below

3. Bits exploration of classes

3.1 Obtaining bits Data

From the class structure code, we can see that objc_class inherits from objc_object structure, and private inherits from objc_object member variable ISA

struct objc_class : objc_object { // Class ISA; // The isa pointer inherited from objc_object is 8 bytes Class superclass; // Structure pointer is 8 bytes cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; // Offset the class address by 32 bits to get bits}Copy the code

Why is cache 16 bytes?

struct cache_t { private: //typedef unsigned long uintptr_t; Explicit_atomic <uintptr_t> _bucketsAndMaybeMask; union { struct { explicit_atomic<mask_t> _maybeMask; //typedef uint32_t mask_t; #if __LP64__ uint16_t_flags; #endif uint16_t _occupied; //2 bytes}; explicit_atomic<preopt_cache_t *> _originalPreoptCache; //8 bytes}; }Copy the code

So the cache is 16 bytes, and the isa pointer of the class is offset by 32 bytes to get bits

3.2 Exploring the class_rw_t structure

From the bits data above we can get a class_rw_t data, which is covered in the Runtime video at WWDC 2020, as shown in the screenshot below. It contains methods, attributes, protocol lists and so on

And look at the class_rw_t structure source code, there are mainly the following attributes

 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;
}
Copy the code

FirstSubClass and nextSiblingClass: All classes are linked into a tree structure using firstSubClass and Next SiblingClass Pointers, which allow the runtime to traverse all classes currently in use.

Demangled Name: The Swift class uses the Demangled Name field, and the Swift class does not need this field unless something asks them for their Objective-C Name

Ro_or_rw_ext: stores the class_ro_t or class_rw_ext_t information. The following image is taken from the apple 2020wwdc video

By looking at the class_rw_t structure source code, you can see that it provides methods to get methods, attributes, protocols, and so on

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}; }}Copy the code

Let’s try to get its methods, properties, and protocols by using the methods provided below

3.2.1 LLDB Obtains the list of class methods

ZFPerson (LLDB) p/x ZFPerson. Class $0 = 0x0000000100002850 ZFPerson (LLDB) p/x 0x0000000100002850 + 0x20 $1 = 0x0000000100002870 (LLDB) p (class_data_bits_t*)0x0000000100002870 $2 = 0x0000000100002870 (LLDB) p $2->data() // Get the class_rw_t data, The data method is provided by the class_data_bits_t structure, $3 = 0x000000010060e620 (LLDB) p *$3 (class_rw_t) $4 = {flags = 2148007936 witness = 0 ro_or_rw_ext = { std::__1::atomic<unsigned long> = 4294976080 } firstSubclass = nil nextSiblingClass = NSUUID } (lldb) p $5 = {list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = { = { list = { ptr = 0x0000000100002298 } arrayAndFlag = 4294976152 } } } (lldb) p $5.list.ptr (method_list_t *const) $6 = 0x0000000100002298 (lldb) p *$6 (method_list_t) $7 = { entsize_list_tt<method_t,  method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 7) } (lldb) p $7.get(0) (method_t) $8 = {} (lldb) p $7.get(2) (method_t) $9 = {} (lldb) p $7.get(1) (method_t) $10 = {} (lldb) p $7.get(0).big (method_t::big) $11 = { name = "killPig" //sel types = 0x0000000100001ed7 "v16@0:8" // Code, imp = 0x0000000100001b10 (ZFObjcBuild '-[ZFPerson killPig])} fixed expression was: $7.get(0).big() (lldb) p $7.get(1).big (method_t::big) $12 = { name = "eat" types = 0x0000000100001ed7 "v16@0:8" imp = 0x0000000100001b00 (ZFObjcBuild`-[ZFPerson eat]) } Fix-it applied, fixed expression was: $7.get(1).big() (lldb) p $7.get(2).big (method_t::big) $13 = { name = "init" types = 0x0000000100001ecf "@16@0:8" imp = 0x0000000100001aa0 (ZFObjcBuild`-[ZFPerson init]) } Fix-it applied, fixed expression was: $7.get(2).big() (lldb) p $7.get(3).big (method_t::big) $14 = { name = "name" types = 0x0000000100001ecf "@16@0:8" imp = 0x0000000100001b20 (ZFObjcBuild`-[ZFPerson name]) } Fix-it applied, fixed expression was: $7.get(3).big() (lldb) p $7.get(4).big (method_t::big) $15 = { name = "setName:" types = 0x0000000100001edf "v24@0:8@16"  imp = 0x0000000100001b50 (ZFObjcBuild`-[ZFPerson setName:]) } Fix-it applied, fixed expression was: $7.get(4).big() (lldb) p $7.get(5).big (method_t::big) $16 = { name = "age" types = 0x0000000100001f91 "i16@0:8" imp = 0x0000000100001b80 (ZFObjcBuild`-[ZFPerson age]) } Fix-it applied, fixed expression was: $7.get(5).big() (lldb) p $7.get(6).big (method_t::big) $17 = { name = "setAge:" types = 0x0000000100001f99 "v20@0:8i16" imp = 0x0000000100001ba0 (ZFObjcBuild`-[ZFPerson setAge:]) } Fix-it applied, fixed expression was: $7.get(6).big() (lldb)Copy the code

Examples of structures in method_t are init methods

Name = “init” // The name of the method (sel). The selector is a string, which is unique, so they can be compared using pointer equality

Types = 0x0000000100001ecF “@16@0:8” // Type encoding: a string representing the parameter and return type, which is not used to send messages, but is required for runtime introspection and message forwarding

Imp = 0x0000000100001aa0 (ZFObjcBuild ‘-[ZFPerson init])

As you can see, there are no class methods in the list of methods we output, because class methods are stored in the metaclass of the class. Declare a class method sleep on ZFPerson, and I won’t write down the steps to verify it, just like getting LLDB above, but offset it with the metaclass address of the class

Why are class methods in metaclasses? This is to avoid conflicts with object methods of the same name, which are stored in sel and IMP

3.2.2 LLDB Obtains the property list of the class

3.2.3 LLDB Obtains the protocol list of the class

Declare a ZFPersonDelegate protocol for ZFPerson to follow

@protocol ZFPersonDelegate<NSObject>
- (void)killPig;
@end
Copy the code
(lldb) p/x ZFPerson.class (Class) $13 = 0x0000000100002850 ZFPerson (lldb) p 0x0000000100002850 + 0x20 (long) $14 = 4294977648 (lldb) p (class_data_bits_t*)4294977648 (class_data_bits_t *) $15 = 0x0000000100002870 (lldb) p $15->data() (class_rw_t *) $16 = 0x0000000100627ba0 (lldb) p *$16 (class_rw_t) $17 = { flags = 2148007936 witness = 0 ro_or_rw_ext =  { std::__1::atomic<unsigned long> = 4294976080 } firstSubclass = nil nextSiblingClass = NSUUID } (lldb) p $17.protocols() (const protocol_array_t) $18 = { list_array_tt<unsigned long, protocol_list_t, RawPtr> = { = { list = { ptr = 0x0000000100002348 } arrayAndFlag = 4294976328 } } } (lldb) p $18.list (RawPtr<protocol_list_t>) $19 = { ptr = 0x0000000100002348 } (lldb) p $19.ptr (protocol_list_t *const) $20 = 0x0000000100002348 (lldb) p *$20 (protocol_list_t) $21 = (count = 1, list = protocol_ref_t [] @ 0x00007fd025b50828) (lldb) p $21.list[0] (protocol_ref_t) $22 = 4294977696 (lldb) p/x 4294977696 (long) $23 = 0x00000001000028a0 (lldb) p (protocol_t *)0x00000001000028a0 (protocol_t *) $24 = 0x00000001000028a0 (lldb) p *$24 (protocol_t) $25 = { objc_object = { isa = { bits = 4298453192 cls = Protocol = { nonpointer = 0 has_assoc = 0 has_cxx_dtor = 0 shiftcls = 537306649 magic = 0 weakly_referenced = 0 unused = 0 has_sidetable_rc = 0 extra_rc = 0 } } } mangledName = 0x0000000100001ea9 "ZFPersonDelegate" protocols = 0x0000000100002430 instanceMethods = 0x0000000100002448 classMethods = 0x0000000000000000 optionalInstanceMethods = 0x0000000000000000 optionalClassMethods = 0x0000000000000000 instanceProperties = 0x0000000000000000 size = 96 flags = 0  _extendedMethodTypes = 0x0000000100002468 _demangledName = 0x0000000000000000 _classProperties = 0x0000000000000000 } (lldb)Copy the code

The protocol information in the class_rw_T structure is output through the LLDB step above

Added 4.

4.1 TypeEncoding Obtain the type encoding of a method using method_getTypeEncoding

Apple documentation on type coding