From the nature of objects in nature of Objects, we have analyzed what is underneath the ISA pointer and its bit operations. So let’s look at some of the things that we need to be aware of and understand about ISA.

From 1.ISAAnalyze to the metaclass

1.1. The ISA pointer to the class points to something unknown

That’s the old rule, put it in the code. To create aTestPersonClass, and in themain.mFile to initialize:

Check the address through breakpoint debugging. X /4gx = x/4gx = x/4gx = x/4gx Return (Class)(isa.bits & ISA_MASK);) .

objc_object::ISA() { ASSERT(! isTaggedPointer()); #if SUPPORT_INDEXED_ISA if (isa.nonpointer) { uintptr_t slot = isa.indexcls; return classForIndex((unsigned)slot); } return (Class)isa.bits; #else return (Class)(isa.bits & ISA_MASK); // The ISA_MASK is the first 3 bits of the isa pointer shiftcls and the next position 0. This gives the isa pointer class information, and thus the ISA pointer is associated with the class. #endif }Copy the code

The mask: (ULLIs an unsigned long integerunlonglongMark)

Now, according to the steps just said, throughlldbDebug:

Through such a operation, get is the address of the TestPerson class, the total feeling process is too flat, have to do something, excited heart, trembling hands, shout loud slogan: do something, do something, do something… 😄

So if I just usex/4gxLook at the end of the pageTestPersonaddress(0 x0000000100008260)If you can get the memory structure, perform the above operation & again, what will be the result?Compare the results of two & operations: First:0x0000000100008260, the second time:0x0000000100008238These are not the same, but the classes they point to are bothTestPerson.

So can we make a guess that the current class, like an object, is infinitely open, that is, there is more than one class in memory?

So once you have a conjecture like that, you verify it, you create itvoid verTestPersonNum(void)Method by printing the results:By printing the result, by finding the resultTestPersonClass is0x100008260, and the address of the pointer obtained by the first & operation0x0000000100008260It’s the same thing.

At this point, you get a result: the second & operation results in a pointer address of 0x0000000100008238 that is not a class. So, what is it? Is it possible that it is a new structure given by The Apple system?

Since The design of Apple’s system follows the path shown below, what comes next needs to be explored further:

Through 1.2.MachOViewAnalysis is a metaclass

The next part of the analysis is going to be usingMachOViewThis tool

Put the projectmachOFile and drag it into the tool:

With this tool, you can analyzemachOThe symbol table that we have insymbol TabeltheSymbolsInside, searchclass, you can find itTestPersonClass (as shown in the figure below), inTestPersonBefore class, there’s one moremetaClassIn the class.

We’re just creatingTestPersonClass, but in the compiled executable, there is one moremetaclass, that is,The metaclass.This is created by the system or the compiler itself.

2.ISAThe bitmap and inheritance chain of the class

2.1. Exploration of root metaclasses

After analysis, the object ofisaA pointer to a class, and a pointer to a class points to a metaclass, so, can there be a metaclassisaPointer to another class? Continue analysis, passlldbDebug, find the answer:

So,isaThe bitmap of the pointer should be:

Now that we have the root metaclass isNSObjectIs it possible to use it directlyNSObjectClass went to find its root metaclass?According to thelldbDebug, discover, root classNSObjecttheisaThe pointer directly points to the root metaclassisaPointer.

2.2.ISAA bitmap of the pointer

TestPerson (root metaclass, root metaclass, root metaclass, root metaclass);

#pragma mark -nsobject metaclass string void lgTestNSObject(void){// NSObject instance object NSObject *object1 = [NSObject alloc]; // NSObject Class Class = object_getClass(object1); // NSObject Class metaClass = object_getClass(Class); // NSObject Class rootMetaClass = object_getClass(metaClass); NSObject Class rootRootMetaClass = object_getClass(rootMetaClass); NSLog (@ "\ n \ n % p % p instance objects class p metaClass \ n \ n % % p root metaClass \ n % p spikes metaClass", object1, class, metaClass, rootMetaClass, rootRootMetaClass); Class pMetaClass = object_getClass(testPerson.class); Class psuperClass = class_getSuperclass(pMetaClass); // NSLog(@"%@ - %p",psuperClass,psuperClass);Copy the code

inmainFunction, calls the method, prints the result:

Following our normal reasoning process,TestPersonthroughclass_getSuperclassIs the superclass foundNSObjectClass object, however, the printed result is the address of the metaclass. So can we come to the conclusion thatAny object whose metaclass is its parent class is its root metaclass. As shown in this diagram:

For example, the isa pointer to the instance object of a subclass points to the subclass, the ISA pointer to the subclass points to the subclass, and the isa pointer to the subclass points to the root metaclass.

2.3. Inheritance chain of classes

Any of our classes will satisfy an inheritance relationship. So, let’s create another T in the projectestSonClass to inherit fromTestPersonclass

So in the lgTestNSObject method, add a few more lines of code:

#pragma mark -nsobject metaclass string void lgTestNSObject(void){// NSObject instance object NSObject *object1 = [NSObject alloc]; // NSObject Class Class = object_getClass(object1); // NSObject Class metaClass = object_getClass(Class); // NSObject Class rootMetaClass = object_getClass(metaClass); NSObject Class rootRootMetaClass = object_getClass(rootMetaClass); NSLog (@ "\ n \ n % p % p instance objects class p metaClass \ n \ n % % p root metaClass \ n % p spikes metaClass", object1, class, metaClass, rootMetaClass, rootRootMetaClass); Class pMetaClass = object_getClass(testPerson.class); Class psuperClass = class_getSuperclass(pMetaClass); NSLog(@"%@ - %p",psuperClass,psuperClass); Class sonMetaClass = object_getClass(TestSon.class); Class sonSuperClass = class_getSuperclass(sonMetaClass); NSLog(@"%@ - %p",sonSuperClass,sonSuperClass); }Copy the code

Run it again to get a print:

It turns out that the superclass of the TestSon class is the TestPerson class. Then the conclusion just reached (that any object whose metaclass is its parent is its root metaclass) is proved wrong. As we all know, TestSon inherits from TestPerson, and TestPerson inherits from NSObject. According to what we printed earlier, the metaclass of TestPerson is pointing to the metaclass of NSObject, but the superclass of TestSon is pointing to TestPerson, so that means that the metaclass also has an inheritance chain. That is: child metaclasses inherit from parent metaclasses, and parent metaclasses inherit from root metaclasses.

However, it seems like once we get to NSObject, there’s always a special case, so let’s explore NSObject one more time:

#pragma mark -nsobject metaclass string void lgTestNSObject(void){// NSObject instance object NSObject *object1 = [NSObject alloc]; // NSObject Class Class = object_getClass(object1); // NSObject Class metaClass = object_getClass(Class); // NSObject Class rootMetaClass = object_getClass(metaClass); NSObject Class rootRootMetaClass = object_getClass(rootMetaClass); NSLog (@ "\ n \ n % p % p instance objects class p metaClass \ n \ n % % p root metaClass \ n % p spikes metaClass", object1, class, metaClass, rootMetaClass, rootRootMetaClass); Class pMetaClass = object_getClass(testPerson.class); Class psuperClass = class_getSuperclass(pMetaClass); NSLog(@"%@ - %p",psuperClass,psuperClass); //TestSon Class sonMetaClass = object_getClass(TestSon.class); Class sonSuperClass = class_getSuperclass(sonMetaClass); NSLog(@"%@ - %p",sonSuperClass,sonSuperClass); // NSObject root Class special case Class nsuperClass = class_getSuperclass(nsobject.class); NSLog(@"%@ - %p",nsuperClass,nsuperClass); -> NSObject Class rnsuperClass = class_getSuperclass(metaClass); NSLog(@"%@ - %p",rnsuperClass,rnsuperClass); }Copy the code

Run again to get the result:According to the printed results, foundNSObjectIs the parent classnilThe parent class of the root metaclass turns out to beNSObjectClass, so this goes back to the beginning, and you can also conclude that all classes are derived fromNSObject. So you get this down hereisaPointer chain andsuperclassInheritance chain diagram:

Here’s an image from apple’s official documentation:

3. Analyze the structure of the class through source code

Just finished exploring the classisaThe chain of Pointers and the chain of inheritance of the class, so what is the composition of the class? Such as:Let’s go straight to itTestPersonClass memory condition, found, is content exists. Just like an object, the memory store isisaPointers and member variables, what about classes? What’s in the memory?

Then it’s time to examine the underlying principles of the class. From the previous section, we know that the bottom of a class isobjc_classStructure of. So we’re going straight toobjcIn the source code (Alloc of iOS low-level explorationFull-text indexstruct objc_classTo find its claim. Search results, found there are two statements, the first1.Where the statement has been made clear that only inobjc2Context, but we’re not in this context right now, so don’t worry about this statement, so just look at the other one.

So let’s look at another statement:From the bottom, you can see that the bottom of the class isobjc_classIs inherited fromobjc_object, it also contains many contents, such as:cache,bits. Wait, so what does something like this really mean? Well, that calls for further investigation.

Then we can get the structure diagram of the class:

4. Structure memory calculation of class

inobjcSource code, create aTestPersonClass, add one in therenameMember variables of. Then, inmain.mFile initialization. First of all byx/4gxtheTestPerson(inherited fromNSObjectThe memory structure of the () class is printed:We just went through the source analysis of the class, so in the memory structure printed out,isaThe pointer address is first,isa:0x0000000100008480And, by inference,0x000000010036a140It should beClass superclass(according to the source code, the contents of the class structure)throughlldbDebug print, sure enough is the parent classNSObject.

And then,0x000000010161d470It should becache_t cacheThe, this inquiry first put a put, first to explore the firstfourAn address0x0001801800000003, then the firstfourAn address. I think that’s itclass_data_bits_t bits. I want to get the firstfourThe value that corresponds to the addressFirst address pointerIf I shift it to this position, the length of the shift is going to be zeroisa(8Bytes) +superclass (8Bytes) +cacheThe sum of the lengths of the three. Now knownisasuperclassThe length of PI, butcacheI don’t know the length of PI. We can look at itcache_tThe underlying type.

cache_tThe underlying type is a structure whose memory size is determined by the contents of the structure. Because, the structure inside the method, function memory is not stored in the structure of the memory area inside, and the global variable memory is in the global area, also does not occupy the structure of the memory, by looking at the source (source code is too long, not pasted out), so, finally only left:So there’s only one leftexplicit_atomic<uintptr_t> _bucketsAndMaybeMaskAnd aA consortium. And in theexplicit_atomicThe statement found

struct explicit_atomic : public std::atomic<T> {***}
Copy the code

Is a public type, so its size depends on the size of

. So uintPtr_t is a long unsigned integer, so it’s 8 bytes (64 bits).

typedef unsigned long   uintptr_t;
Copy the code

In the combination, similarly, explicit_atomic

_maybeMask; The size of depends on

, while mask_t, is

typedef uint32_t mask_t; 
Copy the code

Explicit_atomic

_maybeMask memory size 4; uint16_t _flags; And uint16_t _occupied; They’re both 2, and the sum of them is 8. Explicit_atomic

_originalPreoptCache; The size of is dependent on , and preopt_cache_t * is a pointer, so the memory size is 8.

Because the union is mutually exclusive, the memory size of the union is 8. So, the final size of the cache is 8 + 8 = 16.

So, to look at the value of the fourth address, you have to shift the first address8 plus 8 plus 16 is 32Byte size location. If I convert it to binary, I get0x20. Initial address is:0x1000084a8, and then passlldbDebug:The reason for usingclass_data_bits_tThe reasons are:We are currently fetching the address, and the address refers to the bits space, so we can strongly convert to(class_data_bits_t *)This is a pointer address.

5. LLDB analysis class structure

Just in the source code analysis, got itbitsThen, the structure of the class is analyzed through LLDB. Through the source code, knowbitsIt containsclass_rw_tAnd returned to theclass_rw_t, then guess:class_rw_tThere’s an interface inside.

According to the source code inside the hint, can take out firstbitsThe inside of thedata(): In this case, just takebitsthedataIt’s printed. View the printed information and find inTestPersonThe name member variable in the class doesn’t appear in there at all. So we need to keep looking.

Recall that when we printed data(), the data in the source code was class_rw_t, so go to the bottom of class_rw_t to find the answer. Since data can be fetched outside the class_rw_t layer, there are methods to provide access to it. That’s exactly what we thought it would be. So that’s a good place to start. In the underlying source code for class_rw_t, there is a section of code:

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

Because we’re making a statementTestPersonClass member variables are used in the following way@property (nonatomic, copy) NSString *name;, then, look forproperty_array_t properties()It should be ok. throughlldbDebug:

At this point, we still have property_array_t, and we need to go into property_array_t to keep tracking. Enter its underlying source code:

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

As you can see, there are two layers of copies (i.e. the outermost layer is an array, and the elements inside the array are still arrays), oneproperty_tThe other one isproperty_list_tAnd then enter thelist_array_ttIf you look at the bottom layer (too much source code to stick out), there are a lot of iterators in it (such as a set of iterators, which is an iterator). Here’s what we’re looking fornameInformation. thenlldbDebug:So layer by layer to find down, finally throughgetThe method,nameThe information for this member variable is found. s

6. ClassbitsThe data analysis

throughlldbI analyzed it and found what I needed to findnameInformation, inTestPersonAdd more member variables to the class, add an instance method and a class method:

And then run it againobjcSource, to the breakpoint, and thenlldbDebug:In the end, we found that we could only findnameandhobbyThese two member variables, one moresubjectNot found. There are two methods defined. This is what we passproperty_array_t, only to findnameandhobbyThe two member variables.

So next, try to pass againmethods()Go dig around and see if you can find anything new.To find the5One method, but all methods are empty. Why is that? That is to go to the underlying source code to find the answer. inclass_rw_tIn the bottom, we have

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

The underlying property_list_t of property_array_t is not implemented

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

In the source code, the underlying property_list_t is not implemented

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};

Copy the code

The full text index property_t {, as you can see, is a structure. The reason why we can query by data() step by step to obtain the information about name and hobby is here.

Look at the underlying method_array_t, method_list_t, which is implemented

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

The underlying implementation of method_list_t

struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> { bool isUniqued() const; bool isFixedUp() const; void setFixedUp(); 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() const { return flags() & method_t::smallMethodListFlag; } bool isExpectedSize() const { if (isSmallList()) return entsize() == method_t::smallSize; else return entsize() == method_t::bigSize; } method_list_t *duplicate() 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()); return dup; }};Copy the code

So again, the information we need is in method_t, so let’s look at the declaration for method_t, the full-text index method_t {, look at the source.

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;
        const char *types;
        MethodListIMP imp;
    };

private:
    bool isSmall() const {
        return ((uintptr_t)this & 1) == 1;
    }

    // The representation of a "small" method. This stores three
    // relative offsets to the name, types, and implementation.
    struct small {
        // The name field either refers to a selector (in the shared
        // cache) or a selref (everywhere else).
        RelativePointer<const void *> name;
Copy the code

There is a big() output, which can be used by a method. Big () will print out the corresponding method information

big &big() const { ASSERT(! isSmall()); return *(struct big *)this; }Copy the code

Then it will passlldbdebuggingSome of the methods can be printed, but they are still missingsubjectMember variables, and+ (void)say666;Class method. These will be supplemented in later articles.

Supplementary content – pointer and memory translation

How to obtain the current memory structure —– internal offset

  • Ordinary pointer

For example, start a new project and print two int variables to see its memory, data and address:

int a = 10; //
int b = 10; //
NSLog(@"%d -- %p",a,&a);
NSLog(@"%d -- %p",b,&b);
Copy the code

According to this print, there are two Pointers to the same region of memory.There are two Pointers to a value of10, meaning that the value area can be accessed by any pointer. This phenomenon is similar to value copying (not really copying), giving the values in the value region toaandb.

  • Pointer to the object

Next, create a TestPerson class and initialize it twice to form two objects:

TestPerson *p1 = [TestPerson alloc];
TestPerson *p2 = [TestPerson alloc];
NSLog(@"%@ -- %p",p1,&p1);
NSLog(@"%@ -- %p",p2,&p2);
Copy the code

The printouts show that their addresses are different, and they point to different Spaces.

  • Pointer to an array
Int c[4] = {1,2,3,4}; int *d = c; NSLog(@"%p - %p - %p",&c,&c[0],&c[1]); NSLog(@"%p - %p - %p",d,d+1,d+2);Copy the code

Printed results:According to the printout,&cand&c[0]It’s the same address. That’s because&c[0]Is the first address, is the array of addresses stored thimble, or is it stored contiguously.

And d, d+1, d+2, where d is the pointer address of &c, d+1 is according to the existing data structure translation 1 interval of 4 units. In the same way, d plus 2 is a shift of 4 by 2. In the case of an object, the interval unit size is 8.

Int value = *(d+ I); int value = *(d+ I); int value = *(d+ I); , then it can also be traversed:

Int c [4] = {1, 2, 3, 4}; int *d = c; for (int i = 0; i<4; i++) { // int value = c[i]; int value = *(d+i); NSLog(@"%d",value); }Copy the code

Print result:Come to the conclusion:Memory can be shifted, and when you shift it, you take the value in the address, and you get the value in memory.

We just got the address of the class, so the address of the class is the first address in the current memory structure. So once you have the first address, you can do what you just did to shift memory, shift it by some size, and you can get what’s in there.

At this point, oye, the work is done, the bottom research of the principle of the class has been completed, have you any harvest, (do not have <( ̄▽ ̄)/) thank you for your presence