preface

OC language is highly encapsulated to C and C ++ language, becoming an object-oriented development language. For those with certain development experience, a project is bound to be composed of a number of different classes. Each class contains its own attributes, member variables, method functions, etc. However, how does this manifest at the bottom? Let’s explore and analyze it together today:

One: the direction of ISA

We all know that when we create an object with a class, we can see the structure of the object’s memory allocation by using the p/x command on the console, and we can find the ISA address of each object. We can use the following figure to briefly understand:

First, we declare a LGPerson class and use it to create an object p. Using the p/x p command, we can obtain the initial address of the p object: 0x000000010072A840, then we can obtain the memory distribution structure of P by x/4gx P, from the displayed content we can get the ISA memory address of P is: 0x011D800100008365, at this time we can verify by and operation with the mask;

Through verification, we can find that 0x011D800100008365 is the ISA address of P; In the test, we can get the address 0x0000000100008360 of the current class through isa and mask operation. LGPerson can be printed out through Po to verify the accuracy of ISA. After doing this, we might think, what do I get for x/4gx 0x0000000100008360? Let’s verify it together:

As we can see from the picture, after executing x/4gx 0x0000000100008360 we have a new set of data. From this data we can get a new ISA address 0x0000000100008338. At this time we use isa and mask to do and operation. And with the Po command, you can see that a LGPerson is also output. Is it circled by this point? Why is that? Can classes and objects be created in memory indefinitely? Let’s verify this with the code:

We create multiple objects with this code, and we print their memory addresses, and by looking at them, we can see that the printed address is exactly the same as 0x0000000100008360, which is the same as the memory address of the first class that we got earlier, So we know that 0x0000000100008360 is the memory address of our current class; But what about the new ISA 0x0000000100008338 that we got up there? We can analyze this with the help of the MachOView tool.

As shown in the figure above, after opening MachOView, we drag and drop the generated executable file from the Products directory in the project to open it. If we open Section64(__DATA_objc_classrefs), we can see 0x0000000100008360, which is the memory address of our class. Let’s move on:

When we open *Symbols under the Symbol Table and type the class keyword in the upper right corner of the input box, we can find the content in the picture. At this time, we can see: When the system compiles our code, it generates _OBJC_CLASS__LGPreson and _OBJC_METACLASS__LGPerson. But what is _OBJC_METACLASS? We didn’t create a class of this type in the project. Metaclass is the metaclass that the system automatically generates for us during compilation. We can call it metaclass. And during execution, the isa of the class points to the ISA of the metaclass, so 0x0000000100008338 is the ISA of the metaclass of the LGPerson class that we created; We can use the following picture to perfectly illustrate the above exploration:

The isa of an object points to the ISA of the class, the ISA of the class points to the ISA of the metaclass, and the ISA of the metaclass points directly to the ISA of the root metaclass, and the ISA of the metaclass points to itself

Two: the inheritance trend of class

Above we explore the class isa in iOS, so whether the inheritance of iOS classes is the same as isa, we continue to see; First let’s look at some code:

So we can get the class of NSObject, the metaclass, the root metaclass, the root metaclass, and we can see that the metaclass and the root metaclass of NSObject are the same as the root metaclass, and we’re looking at the code below:

We have a class called LGPerson, and we can get the LGPreson metaclass from object_getClasss, and the parent of the metaclass from class_getSuperclass. I can print out that the parent of the LGPerson metaclass is NSObject, so now if we have a LGTeacher that inherits from LGPerson, what does that LGTeacher metaclass look like? Object_getClass and class_getSuperclass can also be used to obtain object_getClass and class_getSuperclass. The LGTeacher metaclass inherits from LGPerson. Now, do we think that every child class has its own parent, so does every parent class have its own parent? So we print NSObjec the same way, and you can see that NSObject inherits NULL which means it doesn’t have its own parent class, but what if it’s a metaclass? When we test it, we get that the parent of the meta-class is NSObjecj, so what does all this text look like on the diagram? Please look down:

Note: Dotted lines are isa bits, solid lines are class inherited bits

The isa of an object points to isa of the class, the ISA of the class points to isa of the metaclass, the ISA of the metaclass points to the parent metaclass, and the ISA of the root class points to itself. You can also see that every object is created by a class, and that class inherits from its parent class, and that parent class inherits from the root class, and that metaclass inherits from its parent metaclass, and the parent metaclass inherits from the root class, and the root metaclass inherits from the root class, and the root class inherits from nil;

Three: Exploring the attributes of classes

Now that we know the inheritance relationship between classes and metaclasses, how are attributes stored in a class? I’m going to explore it together. First of all, we know that the nature of a class is a structure, and the size of the memory occupied by the structure is determined by the attributes in the structure, and the address of the attributes in the structure is continuous. Let’s look at the source code of the underlying class:

If you want to get class_datA_bits_t directly, you need to offset the first memory address of the class by 32 bits.

To get the address of class_datA_bits_t, we can use p $3->data() to get class_rw_t. So what’s in class_rw_t? Let’s move on

The p *$5 command yields the above result; The source code structure of class_data_bits_t includes methods, properties, and protocols.

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

private: using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR(“class_ro_t”), PTRAUTH_STR(“class_rw_ext_t”)>;

const ro_or_rw_ext_t get_ro_or_rwe() const {
    return ro_or_rw_ext_t{ro_or_rw_ext};
}

void set_ro_or_rwe(const class_ro_t *ro) {
    ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
}

void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
    // the release barrier is so that the class_rw_ext_t::ro initialization
    // is visible to lockless readers
    rwe->ro = ro;
    ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
}

class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
Copy the code

public: void setFlags(uint32_t set) { __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED); }

void clearFlags(uint32_t clear) 
{
    __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
}

// set and clear must not overlap
void changeFlags(uint32_t set, uint32_t clear) 
{
    ASSERT((set & clear) == 0);

    uint32_t oldf, newf;
    do {
        oldf = flags;
        newf = (oldf | set) & ~clear;
    } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
}

class_rw_ext_t *ext() const {
    return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}

class_rw_ext_t *extAllocIfNeeded() {
    auto v = get_ro_or_rwe();
    if (fastpath(v.is<class_rw_ext_t *>())) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
    } else {
        return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
    }
}

class_rw_ext_t *deepCopy(const class_ro_t *ro) {
    return extAlloc(ro, true);
}

const class_ro_t *ro() const {
    auto v = get_ro_or_rwe();
    if (slowpath(v.is<class_rw_ext_t *>())) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
    }
    return v.get<const class_ro_t *>(&ro_or_rw_ext);
}

void set_ro(const class_ro_t *ro) {
    auto v = get_ro_or_rwe();
    if (v.is<class_rw_ext_t *>()) {
        v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
    } else {
        set_ro_or_rwe(ro);
    }
}

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

};

At this point we get the following result from p $5.properties

This is where we go further, and by using the method shown below we can finally get the properties we declare in the class

So how do we get the methods we declare in a class? See the picture below:

P $18.get(0).big(); p $18.get(0).big();

conclusion

The isa of the object points to isa of the class, the ISA of the class points to ISA of the metaclass, the ISA of the metaclass points to the root class, and the ISA of the root class points to itself. 2. The class inherits from its parent, which inherits from its parent, which eventually inherits from the root class NSObject, which inherits from nil; 3. Metaclass inherits from parent metaclass, parent metaclass inherits from parent metaclass, parent metaclass inherits from root class NSObject, NSObject inherits nil; 4. We can view the attributes and methods of the class by class_datA_bits_t structure and combining LLDB; Class attribute method exploration is not complete, the next article will continue to explore