The analysis of the isa

Take LGPerson as an example

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *p  = [LGPerson alloc];
        NSLog(@ "-- - > % @",p);       
}
Copy the code

LLDB debugging is as follows:

// View the value of the object
(lldb) po p
<LGPerson: 0x107c0bcc0>

// Print the memory of the object. Usually the first section stores the isa address
(lldb) x/4gx p
0x107c0bcc0: 0x011d8001000082c5 0x0000000000000000
0x107c0bcd0: 0x0000000000000000 0x0000000000000000

// The isa address of the object is' & 'to the mask
// 0x00007FFFFFFFF8ul The mask value is fixed
(lldb) p/x 0x011d8001000082c5 & 0x00007ffffffffff8UL  
(unsigned long) $2 = 0x00000001000082c0

// We get LGPerson
(lldb) po 0x00000001000082c0
LGPerson
Copy the code

So let’s go ahead and analyze LGPerson

// Check the memory of LGPerson
(lldb) x/4gx  0x00000001000082c0
0x1000082c0: 0x0000000100008298 0x00007fff88b86cc8
0x1000082d0: 0x000000010052b7a0 0x0002802400000003

// Make the isa of LGPerson with the mask '&'
(lldb) p/x 0x0000000100008298 & 0x00007ffffffffff8UL
(unsigned long) $4 = 0x0000000100008298

// Print the value stored at the address
(lldb) po 0x0000000100008298
LGPerson

Copy the code

We got a LGPerson in the previous step, and now we get a LGPerson with the same name but a different memory address 0x00000001000082C0! = 0x0000000100008298, what is this? LGPerson is the class name, are there more than one of these classes in memory? Can classes be created multiple times in memory like objects?

The deep exploration of class

On top of that, create multiple classes in multiple ways and view their memory addresses

Class cls1 = [LGPerson class];
Class cls2 = [[LGPerson alloc] init].class;
Class cls3 = object_getClass([LGPerson alloc]);
NSLog(@"cls1 ==== %p", cls1);
NSLog(@"cls2 ==== %p", cls2);
NSLog(@"cls3 ==== %p", cls3);
Copy the code

Print result:

2021-06-26 10:44:29.889553+0800 [1452:53722] cls1 ==== 0x1000082d8
2021-06-26 10:44:29.889632+0800 [1452:53722] cls2 ==== 0x1000082d8
2021-06-26 10:44:31.411735+0800 [1452:53722] cls3 ==== 0x1000082d8
Copy the code

As you can see, these three different ways of creating a class all result in the same memory address, indicating that the class is unique in memory. So when we first explored, why are there two lgPersons with the same name, and what is the LGPerson with the different address? Let’s keep exploring.

Put the executable file into MachOView, find the symbol table, and find the corresponding symbol location according to the address: 0x00000001000082d8 and 0x00000001000082B0

You can see that the first of the two classes that we got at the beginning is our LGPerson class _OBJC_CLASS_$_LGPerson, and the second class LGPerson is _OBJC_METACLASS_$_LGPerson which we call the metaclass of LGPerson, It’s generated by the system.

Isa’s walk

Based on the above analysis, the direction of ISA is further explored

(lldb) x/4gx 0x00000001000082b0
0x1000082b0: 0x00007fff88b86ca0 0x00007fff88b86ca0
0x1000082c0: 0x00000001078c71a0 0x0002e03500000003

(lldb) p/x 0x00007fff88b86ca0 & 0x00007ffffffffff8UL
(unsigned long) $4 = 0x00007fff88b86ca0

(lldb) po $4
NSObject

(lldb) x/4gx 0x00007fff88b86ca0
0x7fff88b86ca0: 0x00007fff88b86ca0 0x00007fff88b86cc8
0x7fff88b86cb0: 0x00000001078c74c0 0x0003e03100000007

Copy the code

As you can see from the results above, the metaclass’s ISA points to NSObject, and NSObject’s ISA points to itself, and we call NSObject the root metaclass.

Through the above exploration, the flow chart of ISA can be verified:

Inheritance chain of metaclasses

The object_getClass method returns the address that isa points to.

The inheritance chain of metaclass is analyzed by example

LGPerson *object = [LGPerson alloc];
Class class1 = object_getClass(object);
Class metaClass = object_getClass(class1);
Class rootMetaClass = object_getClass(metaClass);
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@" Instance object: %p",object);
NSLog(% @ "class: p",class1);
NSLog(% @ "yuan class: p",metaClass);
NSLog(@" Metaclass parent :%p",class_getSuperclass(metaClass));
NSLog(@" Root metaclass :%p",rootMetaClass);
NSLog(@" Root metaclass parent :%p",class_getSuperclass(rootMetaClass));
NSLog(@" Root metaclass of root metaclass: %p",rootRootMetaClass);
        
Copy the code
2021-06-26 16:36:11.113527+0800[2512:198583] Instance object:0x1074040c0
2021-06-26 16:36:11.113853+0800[2512:198583] :0x1000082d0
2021-06-26 16:36:11.113889+0800[2512:198583] yuan class:0x1000082a8
2021-06-26 16:36:11.113930+0800[2512:198583] metaclass superclass:0x7fff88b86ca0
2021-06-26 16:36:11.113963+0800[2512:198583[root metaclass:0x7fff88b86ca0
2021-06-26 16:36:11.113989+0800[2512:198583Parent class of root metaclass:0x7fff88b86cc8
2021-06-26 16:36:24.996991+0800[2512:198583The root metaclass of the root metaclass:0x7fff88b86ca0
Copy the code

As you can see from the above, the parent class of the metaclass is the same as the root metaclass, that is, the parent class of the metaclass of the object is the root metaclass of the current object.

Class structure analysis

Class is an objc_class structure at the bottom

In objc/ Runtime. h, the objc_class structure is defined as follows:

struct objc_class {

        Class isa  OBJC_ISA_AVAILABILITY; // A pointer to an objc_class structure that points to a metaclass. In iOS, a class is also an object. A metaclass holds the class methods of a class object.
        #if! __OBJC2__
        Class super_class                       OBJC2_UNAVAILABLE;  / / parent class
        const char *name                        OBJC2_UNAVAILABLE;  / / the name of the class
        long version                                OBJC2_UNAVAILABLE;  // Version information for the class. Default is 0
        long info                                      OBJC2_UNAVAILABLE;  // Class information, some bit identification for runtime use
        long instance_size                      OBJC2_UNAVAILABLE;  // The size of the instance variable for the class
        struct objc_ivar_list *ivars           OBJC2_UNAVAILABLE;  // Lists the member variables of the class
        struct objc_method_list支那methodLists   OBJC2_UNAVAILABLE;  // A linked list of method definitions
        struct objc_cache *cache                       OBJC2_UNAVAILABLE;  // Method cache
        struct objc_protocol_list *protocols        OBJC2_UNAVAILABLE;  // List of protocols
        #endif
} OBJC2_UNAVAILABLE;

Copy the code

Definition of objc_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 Class getSuperclass() const {Copy the code

Definition of objc_object:

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
Copy the code

Class structure memory calculation

Analyze the memory of LGPerson

(lldb) x/4gx LGPerson.class
0x100008600: 0x0000000100008628 0x000000010036a140
0x100008610: 0x0000000100640fd0 0x000780480000000f
Copy the code

Class ISA takes 8 bytes, so 0x0000000100008628 stores ISA;

0x000000010036A140 contains a superClass, which takes 8 bytes.

The cache_t structure is complex and needs to be analyzed:

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

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    // _bucketsAndMaybeMask is a buckets_t pointer
    // _maybeMask is the buckets mask

    static constexpr uintptr_t bucketsMask = ~0ul; ...Copy the code

Static variables do not affect the size of the structure, so you only need to consider the memory occupied by the first few member variables.

The _bucketsAndMaybeMask is the UintPtr_T type and takes 8 bytes; Member variables of a union are mutually exclusive. Internal member variables occupy the same memory block. The memory size of a larger member variable is the size of the union

union { struct { explicit_atomic<mask_t> _maybeMask; // uint32_t = 4 bytes #if __LP64__ __16_t_flags; // uint16_t = 2 bytes; // uint16_t = 2 bytes}; explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8 bytes};Copy the code

Thus, the union takes 8 bytes and the cache_T takes 16 bytes.

The bits address is the offset of the first address of the class 8 + 8 + 16 = 32 bytes and the offset 0x20.

The LLDB analysis class structure

LGPerson class

@interface LGPerson : NSObject

@property (nonatomic) int age;
@property (nonatomic.strong) NSString *hobby;

- (void)saySomething;

+ (void)doSomething;
@end



#import "LGPerson.h"

@interface LGPerson(a)
{
    NSString *name;
}
@end

@implementation LGPerson

- (instancetype)init{
    if (self = [super init]) {
        
    }
    return self;
}

- (void)saySomething{
    
}

+ (void)doSomething{
    
}
@end
Copy the code

The main function

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
    }
    return 0;
}

Copy the code

LLDB debugging:

(lldb) x/4gx person
0x1013040a0: 0x011d8001000083e1 0x0000000000000000
0x1013040b0: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x1013040a0 + 0x20 
(long) The $1 = 0x00000001013040c0
(lldb) p (class_data_bits_t *)0x00000001013040c0
(class_data_bits_t *) $2 = 0x00000001013040c0
(lldb) p $2
(class_data_bits_t *) $2 = 0x00000001013040c0
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00006957534e5b28
(lldb) p *$3
Copy the code

If the $3 value is not printed, the object’s uniqueness constraint is violated. Print as follows:

(lldb) x/4gx LGPerson.class
0x1000083e0: 0x0000000100008408 0x000000010036a140
0x1000083f0: 0x0000000100362380 0x0000802800000000
(lldb) p/x 0x1000083e0+0x20
(long) The $1 = 0x0000000100008400
(lldb) p (class_data_bits_t *)0x0000000100008400
(class_data_bits_t *) $2 = 0x0000000100008400
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101211190
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000440
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
Copy the code

Properties for

(lldb) p $4.properties()
(const property_array_t) $5 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x00000001000082c0
      }
      arrayAndFlag = 4295000768
    }
  }
}

(lldb) p $5.list
(const RawPtr<property_list_t>) $6 = {
  ptr = 0x00000001000082c0
}
(lldb) p $6.ptr
(property_list_t *const) $7 = 0x00000001000082c0
(lldb) p *$7
(property_list_t) $8 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)}//get is a C++ method
(lldb) p $8.get(0)
(property_t) $9 = (name = "age", attributes = "Ti,N,V_age")
(lldb) p $8.get(1)
(property_t) $10 = (name = "hobby", attributes = "T@\"NSString\",&,N,V_hobby")

Copy the code

The above debugging results in the property list of the class.

Source exploration process:

Methods to obtain

(lldb) p $4.methods()
(const method_array_t) $11 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x00000001000081c0
      }
      arrayAndFlag = 4295000512
    }
  }
}
(lldb) p $11.list
(const method_list_t_authed_ptr<method_list_t>) $12 = {
  ptr = 0x00000001000081c0
}
(lldb) p $12.ptr
(method_list_t *const) $13 = 0x00000001000081c0
(lldb) p *$13
(method_list_t) $14 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 6)
}
(lldb) p $14.get(0)
(method_t) $15 = {}
Copy the code

If I use the same method as the attribute to get the method list, I can’t get it. Why is that? Presumably, their internal storage structures may be different.

Check out the methods source code:

struct class_rw_t{.const method_array_t methods(a) 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()}; }}... }Copy the code

Returns type method_array_t.

class method_array_t : 
    public 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(a) const {
        return beginLists();
    }
    
    const method_list_t_authed_ptr<method_list_t> *endCategoryMethodLists(Class cls) const;
};
Copy the code

Method_array_t inherits from list_array_tt

, look at method_t and method_list_t
,>

Definition of method_list_t:

struct method_list_t : entsize_list_tt<method_t.method_list_t.0xffff0003.method_t::pointer_modifier> {
    bool isUniqued(a) const;
    bool isFixedUp(a) const;
    void setFixedUp(a);

    uint32_t indexOfMethod(const method_t *meth) const {
        uint32_t i = 
            (uint32_t) (((uintptr_t)meth - (uintptr_t)this) / entsize());
        ASSERT(i < count);
        return i;
    }

    bool isSmallList(a) const {
        return flags() & method_t::smallMethodListFlag;
    }

    bool isExpectedSize(a) const {
        if (isSmallList())
            return entsize() == method_t::smallSize;
        else
            return entsize() == method_t::bigSize;
    }

    method_list_t *duplicate(a) const {
        method_list_t *dup;
        if (isSmallList()) {
            dup = (method_list_t *)calloc(byteSize(method_t::bigSize, count), 1);
            dup->entsizeAndFlags = method_t::bigSize;
        } else {
            dup = (method_list_t *)calloc(this->byteSize(), 1);
            dup->entsizeAndFlags = this->entsizeAndFlags;
        }
        dup->count = this->count;
        std::copy(begin(), end(), dup->begin());
        returndup; }};Copy the code

Definition of method_t:

struct method_t {.struct big {
        SEL name;
        const char*types; MethodListIMP imp; }; . }Copy the code

In method_t, the structure of big and property_t is very detailed.

(lldb) p $14.get(0).big()
(method_t::big) $16 = {
  name = "hobby"
  types = 0x0000000100003f67 "@ @ 0:8 16"
  imp = 0x0000000100003da0 (KCObjcBuild`-[LGPerson hobby])
}
(lldb) p $14.get(1).big()
(method_t::big) $18 = {
  name = "setHobby:"
  types = 0x0000000100003f77 "v24@0:8@16"
  imp = 0x0000000100003dc0 (KCObjcBuild`-[LGPerson setHobby:])
}
(lldb) p $14.get(2).big()
(method_t::big) $19 = {
  name = "saySomething"
  types = 0x0000000100003f6f "v16@0:8"
  imp = 0x0000000100003d50 (KCObjcBuild`-[LGPerson saySomething])
}
(lldb) p $14.get(3).big()
(method_t::big) $20 = {
  name = "init"
  types = 0x0000000100003f67 "@ @ 0:8 16"
  imp = 0x0000000100003d00 (KCObjcBuild`-[LGPerson init])
}
(lldb) p $14.get(4).big()
(method_t::big) $21 = {
  name = "age"
  types = 0x0000000100003f84 "i16@0:8"
  imp = 0x0000000100003d60 (KCObjcBuild`-[LGPerson age])
}
(lldb) p $14.get(5).big()
(method_t::big) $22 = {
  name = "setAge:"
  types = 0x0000000100003f8c "v20@0:8i16"
  imp = 0x0000000100003d80 (KCObjcBuild`-[LGPerson setAge:])
}
Copy the code

Finally, the corresponding method was successfully obtained.