Study in harmony! Not anxious not impatient!! I am your old friend Xiao Qinglong

In the last article, we showed that class information can be accessed through isa+ masks. Directly above:

Do classes have isa and a layer above it, just like objects? Continue:

We can see that both the printed ones are called Direction, but the address is different. What is the address of 3d810 below? Let’s open Xcode project (ISA analysis) – “Products-” ISA analysis. App – “show in Finder -” right click to display the package contents. Drag the ISA analysis executable into the MachOView tool, expand it, find Symbol Table ->*Symbols, search for our class name “Direction” on the right, as shown in the figure:

We’ll find a new symbol in the list called METACLASS, which translates to “METACLASS.” This is something that the system generated for us at compile time. So the 3d810 we printed above is the Direction metaclass.

If we repeat the above operations on the metaclass, we will get a root metaclass address and find that isa refers to the root metaclass. We print out the root metaclass address, and it turns out to be NSObject. We repeat this for NSObject, and it turns out that isa for NSObject refers to the root metaclass address:

From this we can get the following relationship:

1, the object's ISA - class, the class's ISA - metaclass, the metaclass's ISA - root metaclass2, root class (NSObject) of isa - root metaclassCopy the code

To test the above guess, we wrote some code:

  • Two classes are definedDirectionAnd defineDirectionChild
// Define Direction, inherited from -nsobject
    @interface Direction:NSObject
@property (nonatomic,strong) NSString *name;
@property (nonatomic,assign) NSInteger indexValue;
@end
@implementation Direction
@end

// Define DirectionChild, inherited from -direction
@interface DirectionChild:Direction
@property (nonatomic,strong) NSString *hobby;
@property (nonatomic,assign) NSInteger runSpeed;
@end
@implementation DirectionChild
@end
Copy the code
  • In the main function:
int main(int argc, char * argv[]) {
    // Direction *st = [Direction alloc];
    // NSLog(@"%@",st);
    / / NSObject object
    NSObject *obj01  = [NSObject new];
    / / NSObject class
    Class obj02 = NSObject.class;
    / / NSObject metaclass
    Class obj03 = object_getClass(obj02);
    / / root metaclass
    Class obj04 = object_getClass(obj03);
    NSLog(@"\n NSObject -%p\n NSObject class -%p\n NSObject metaclass -%p\n NSObject root metaclass -%p\n\n",obj01,obj02,obj03,obj04);
    
    / / Direction of the yuan
    Class dMetaClass = object_getClass(Direction.class);
    // Superclass of metaclass
    Class dSuperClass = class_getSuperclass(dMetaClass);
    NSLog(@Direction of printing -- -- > "\ n \ n metaclass - $% p \ n class parent - % p % @ \ n \ n",dMetaClass,dSuperClass,dSuperClass);
    
    / / DirectionChild metaclass
    Class dChildMetaClass = object_getClass(DirectionChild.class);
    / / root metaclass
    Class dChildSuperClass = class_getSuperclass(dChildMetaClass);
    NSLog(@"\n DirectionChild case print --> \n metaclass -%p\n metaclass parent -%p %@\n",dChildMetaClass,dChildSuperClass,dChildSuperClass);
    
    // Print the superclass of class NSObject
    Class nSuperClass = class_getSuperclass(NSObject.class);
    NSLog(@"Print the superclass of class NSObject -->%@ - %p",nSuperClass,nSuperClass);
    // Print the parent class of the root metaclass
    Class objectSuperClass = class_getSuperclass(obj04);
    NSLog(@"Print root metaclass superclass -->%@ - %p",objectSuperClass,objectSuperClass); . . }Copy the code

The output is as follows:

It can be concluded that:

  • If a class inherits from NSObject, the metaclass of that class refers to the root metaclass;
  • If a class inherits from a subclass of NSObject, that class points to the metaclass of its parent class;
  • NSObject has no superclass
  • The parent class of the root metaclass is class NSObject

As can be seen from the above, metaclasses directly also have inheritance chain relationship.

Here’s a diagram:

Explain the following:

  • 1-2-3-4-4
Instance object ISA - class, ISA - metaclass, ISA - NSObject Root metaclass, ISA - NSObject classCopy the code
  • 7-8-9-4-4
Superclass instance object ISA - superclass, ISA - superclass metaclass, ISA - NSObject root metaclass, ISA - NSObjectCopy the code
  • 6-5- 4-4-4
NSObject instance object ISA - NSObject, ISA - NSObject root metaclass isa - NSObjectCopy the code
  • 2-8-5 (Inheritance chain of classes)
Subclass - Superclass - NSObject - nilCopy the code
  • 3-9-4-5 (metaclass inheritance chain)
Subclass metaclass - NSObject root metaclass - NSObject class; Parent metaclass - NSObject Root metaclass - NSObject class;Copy the code

As mentioned in the article the nature of Objects in iOS Underlying Implementation Analysis, Class objects contain an ISA, whose type is Class, and we know that the underlying implementation of Class is objc_class. What about the internal implementation of objc_class? Next, let’s study:

typedef struct objc_class *Class;
Copy the code

Open objC4-818.2 and search for “struct objc_class” to see the following code:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if! __OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE;constchar * _Nonnull name OBJC2_UNAVAILABLE; . #endif } OBJC2_UNAVAILABLE;/* Use `Class` instead of `struct objc_class *` */

#endif
Copy the code

“If” means “no version 2.0”. Obviously we are using the latest version 2.0, so the code will not go away. Let’s just ignore it and move on to the next result:

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // 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{... }void setSuperclass(Class newSuperclass){... } class_rw_t *data()const {
        return bits.data();
    }
    void setData(class_rw_t *newData){ bits.setData(newData); }... };Copy the code

We can see that it has a commented CLASS ISA, search for “objc_object” :

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

We see that objc_Object has an ISA pointer, so we know that objc_class also has ISA so the information objc_class contains is something like this:

    1, Class ISA;//
    2, Class superclass;/ / parent class
    3, cache_t cache;// Method cache
    4, class_data_bits_t bits;// Use isa+ mask to get the address of the class
Copy the code

Struct objc_class: objc_object

    class_rw_t *data() const {
        return bits.data();
    }
Copy the code

Click data to go in:

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
Copy the code

Bits & FAST_DATA_MASK returns a class_rw_t structure. Class_rw_t = class_rw_t;

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#ifSUPPORT_INDEXED_ISA uint16_t index; .const class_ro_t *ro() const{... }// The list of methods
    const method_array_t methods() const{... }// Attribute list
    const property_array_t properties() const{... }/// Protocol list
    const protocol_array_t protocols() const {...}
    
    
  };
Copy the code
~ rw: readWrite; Ro: readOnly Read-onlytList:Copy the code

As you can see, class_rw_t contains some properties, a list of methods, a list of properties, a list of protocols, and class_ro_t. So what is class_ro_t? If you click on class_ro_t, you can see:

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

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    constuint8_t * weakIvarLayout; property_list_t *baseProperties; . method_list_t *baseMethods()const{... }; . }Copy the code

Class_ro_t also contains some properties and methods. Unlike class_rw_t, class_ro_t is read-only.

Now that we have a basic understanding of the structure of objC_class, I have a question: how does the system find property values from class? Here we take a test and write some code:

    int a[5] = {11.22.33.44.55};
    For (int I = 0; int I = 0; i<5; i++) { printf("\n%d", a[i]); } * /
    /// Next print each value and its corresponding address
    for (int i = 0; i<5; i++) {
        printf("\n print %d -->%d - %p",i,a[i],&a[i]);
    }
    printf("\na array address - %p",&a);
Copy the code

The print result is as follows:

We find that if we print the address of a directly, the result is the same as the address of the first value in the array a. We also find that the address between each value is 4 bytes off. This is because int takes 4 bytes in 64 bits. Next, we can make a guess by replacing the printed a[I] with *(a+ I) :

Printf ("\n\n "); printf("\n\n "); for (int i = 0; i<5; I++) {printf (" \ n to print the first % d -- -- > % d % p ", I, * (a + I), & a [I]); }Copy the code

Print result:

It follows from this that the array pointer accesses the attribute value by the offset of bytes per element, also known as the memory offset. So how does a class find an attribute value with a memory offset? We know a Class whose member order is ISA, superclass, cache, and bits. We can calculate the first three sizes to get the addresses of bits. (The property list is in bits)

  • ISA ISA pointer to a structure and takes 8 bytes under 64-bit.
  • The superclass type is Class, which is a pointer to a structure, that is, 8 bytes in size.
  • Cache, which is of type cache_T, we click on:
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; }; ./ * * omit part of some static code is modified, the method, the former in global area, the latter method in area, do not take up the structure size, so you can ignore the part, only need to calculate the size of the above * /
    
    /** uintptr_t unsigned integer. The bucketsandmaybemask takes 8 bytes. Because the union is mutually exclusive, _originalPreoptCache is a pointer, and the union takes 8 bytes. So cache_t has 16 bytes. * /
    
    
  };
Copy the code

Open objC4-818.2 source project, write code, LDB debug:





As the picture shows, one mealLDB commandI go down and I get PIclass_rw_tI think some of you might be right$2->data()Class_rw_t is obtained by bits.data(), and $2 is obviously a pointer->To continue with the LDB command:

Finally, we print the first property of DirectionChild, Hobby.

Let’s go ahead and print the second property:

During the execution of the LDB command, the following message may be displayed:

Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x40).
The process has been returned to the state before expression evaluation.
Copy the code

Do not panic when such a prompt appears. You can start over from the previous steps. If not, you can re-build and go through the process again.

Next, let’s read the methods in class_rw_t in the same way we read the properties. Before that, let’s add a few methods to the DirectionChild class:

// Define DirectionChild, inherited from -direction
@interface DirectionChild:Direction
@property (nonatomic,strong) NSString *hobby;
@property (nonatomic,assign) NSInteger runSpeed;
/ / / method- (void)runSpeedNonTime; + (void)runSpeedWithTime:(NSString *)time; - (void)eatSomethingNonTime; + (void)eatSomethingWithTime:(NSString *)time;
@end

@implementation DirectionChild
-(void)runSpeedNonTime{}
+(void)runSpeedWithTime:(NSString *)time{}
-(void)eatSomethingNonTime{}
+(void)eatSomethingWithTime:(NSString *)time{}
@end
Copy the code

LDB command debugging:

At this point, it looks like it flipped over? The method information printed is empty. Why does the property information appear when printed in this way? We print property information by printing property_t, and we print method by printing method_t. Let’s look at the structure of these two guys:

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){}};// Look at the <--
struct property_t {
    const char *name;
    const char *attributes;
};
Copy the code
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() const {
        return beginLists();
    }
    
    const method_list_t_authed_ptr<method_list_t> *endCategoryMethodLists(Class cls) const;
};

// Look at the <--
struct method_t {
    static const uint32_t smallMethodListFlag = 0x80000000;

    method_t(const method_t &other) = delete;

    // The representation of a "big" method. This is the traditional
    // representation of three pointers storing the selector, types
    // and implementation.
    struct big {
        SEL name;
        constchar *types; MethodListIMP imp; }; . };Copy the code

We see that method_t does not directly expose properties as property_t does, but wraps another layer in the big structure, so in this case, we will access one more layer of big() :

As shown in the figure, the method list contains the following six methods:

  • runSpeedNonTime
  • eatSomethingNonTime
  • hobby
  • setHobby
  • runSpeed
  • setRunSpeed

But there is no such class method:

  • +(void)runSpeedWithTime:(NSString *)time;
  • +(void)eatSomethingWithTime:(NSString *)time;

1. Where are class methods stored? BgColorValue is not printed in the property list. Where is it stored?

@interface DirectionChild:Direction
{
    NSString *bgColorValue;
}
@property (nonatomic,strong) NSString *hobby;
@property (nonatomic,assign) NSInteger runSpeed;
/ / / method- (void)runSpeedNonTime; + (void)runSpeedWithTime:(NSString *)time; - (void)eatSomethingNonTime; + (void)eatSomethingWithTime:(NSString *)time;
@end
Copy the code

Related code has been uploaded to Baidu web disk:

  • Isa analysis 0620 extract code: K7N2
  • Objc4-818.2 Extract code: Q71K