preface

If you want to become a good iOS developer, it is essential to learn the basic principles of OC. I have compiled a series of basic articles about OC, hoping to help you. This article mainly explains the underlying structure analysis of the class.

1. Alloc principle of OC object creation in iOS

2. Align the OC object memory of iOS

3. The underlying principle of iOS OC ISA

Before we get into the structure of a class, let me ask you if there is one question that you have thought about during development. When creating multiple objects of the same type, does this object have multiple classes? With this problem in mind, we have the following code to get the class in different forms

Class class1 = [TestJason class];
Class class2 = [TestJason alloc].class;
Class class3 = object_getClass([TestJason alloc]);
NSLog(@"%p====%p===%p",class1,class2,class3); = = = = = = = = = = = running results = = = = = = = = = = = LGTest [1541-33345] 0 x100002848 x100002848 = = = = = = = 0 0 x100002848Copy the code

As you can see from the above results, there is only one class in memory.

1. Preliminary study on class structure

Or use Apple objC4-756.2 source code to learn, specifically can see iOS OC object creation alloc principle this article has been introduced. Through the source of Class

/// An opaque type that represents an Objective-C class.
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

    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) { bits.setData(newData); }... /// Represents an instance of a class. struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; };Copy the code

Class is a structure of objC_class, and objc_class is derived from objc_Object, because in object-oriented programming everything is an object, from this can also know that the Class is an object. The class has bits of type ISA, superclass, cache_T, and class_datA_bits_t inherited from its parent. Since classes are ultimately compiled into structures at the bottom, are you curious what most of the base class NSObject looks like at the bottom?

OBJC_ROOT_CLASS OBJC_EXPORT @interface NSObject <NSObject> {OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0) OBJC_ROOT_CLASS OBJC_EXPORT @interface NSObject <NSObject> {#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
Copy the code

As you can see from the source code, the underlying NSObject is the same as objc_Object. In everyday development, classes are defined with properties, variables, and methods. Have you ever wondered where properties, member variables, and methods are stored at the bottom of the class? I’m going to go through them all.

2. Class_data_bits_t Where attributes and instance methods are stored

A TestObject class is defined for the sake of introduction. The following is the definition and implementation of this class, and is introduced through the LLDB directive.

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN @interface TestObject : NSObject{ NSString *nickName; } @property(nonatomic,copy) NSString *name; -(void)sayName; +(void)sayNickName; NS_ASSUME_NONNULL_END // Implementation code TestObject *testObject = [TestObject alloc];
Class tClass = object_getClass(testObject);
NSLog(@"%@===%p".testObject,tClass);
Copy the code

The LLDB instruction is as follows:

0x001d800100001659
isa
0x0000000100b37140
superclass
0x00000001003da290
cache_t
0x0000000000000000
bits
superclass
cache_t
bits
class_rw_t

class_rw_t *data() { 
    return bits.data();
}
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
Copy the code

0x0000000000000000 printed from the LLDB is 0. No, we can find the memory value of the final bits by analyzing the byte size of each attribute in the class and using the memory offset.

2.1. The size of each pointing value of objc_class

The internal definition of objc_class is available from the source code

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 ···} struct cache_t {struct bucket_t *_buckets; mask_t _mask; mask_t _occupied; ···} typedef uint32_t // x86_64 & arm64 asm are less efficient with 16-bitsCopy the code

Isa isa union of 8 bytes, superclass isa class of 8 bytes, and cache isa struct of 8 bytes. The size of cache is determined by the capacity of its attributes. Since _buckets is a structure pointer with 8 bytes and mask_t with 4 bytes, the cache has 16 bytes. From ISA to cache, the total number of bytes is 32, which is 0x20 in hexadecimal format. As shown in the figure above, the memory value of tClass starts at 0x100001680. 0x1000016A0 can be obtained by offset 0x20. It can also be directly printed with x/5gx tClass, and the obtained memory value is also 0x1000016A0

bits
data()
class_rw_t
class_rw_t
lldb
class_rw_t
0x1000016a0

class_rw_t
properties
properties
list
property_list_t
name

$7
TestObject
nickName
class_rw_t
const class_ro_t *ro

2.2 class_ro_t

You can know each value of class_ro_t through the source code

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;

    method_list_t *baseMethods() const {
        returnbaseMethodList; }};Copy the code

This RO also has method_list_t, PROTOCOL_list_t, and property_list_t, where ivar_list_t is added. The nickName was finally found using the LLDB directive.

TestObject
nickName
TestObject
nickName
lldb

Method_array_t in RW and method_list_t in RO can be used to obtain TestObject methods. Here is the LLDB instruction to obtain methods in RW

Cxx_destruct (sayName); sayNickName (sayName); sayNickName (sayNickName); cxx_destruct (sayName)

The name getter and setter methods, the sayName instance method, and a system cxx_destruct method without sayNickName. Why is that?

Note: instance methods are not included in rw and ro if they are declared in the class’s.h file but not implemented in the.m file

Consider a question: why does a member variable exist in class_ro_t and not in class_rw_t? How is class_rw_t different from class_ro_t?

2.3 class method

According to the above, it can be known that the class method does not exist in the class, but in the metaclass, which can be searched through the LLDB command. The isa of the current class can be found first, and then the metaclass can be obtained through isa&ISA_MASK.

0x0000000100002700
The metaclass
x/5gx
The metaclass
class_data_bits_t
bits

bits
sayNickName

3. The last

As you can see from the above, the underlying structure of a class is ISA, SuperClass,cache_t, and bits, which have different sizes. The bits RW contains instance methods and attributes, but does not contain member variables. The RW contains ro. Ro holds instance methods, properties, and member variables. Member variables and attributes are stored in IVAR, and attributes are in the form of underscores. The class methods of the class are in the bits method of the metaclass. At this point, the structure analysis of the class is introduced.