The opening
Study hard, not anxious not impatient. Make little progress every day.
Course review and supplement
1. Isa byte size
Last time, we did a derivation of ISA, and in case there’s any confusion about the size of ISA, which is 8 bytes, let’s add to that;
With the source code, we go directly into NSObject
As you can see, isa isa Class type; Global lookup Class
As can be seen from the figure above, Class is a pointer to a structure, alias Class, but the real corresponding to the underlying LLVM is type objc_class *; We saw earlier that a pointer to a structure has a size of 8 bytes; So ISA here is going to be 8 bytes;
2. Internal analysis of the consortium
In the previous analysis of the internal structure of the combination bit field isa_t, it may be a little unclear why shiftcls is 44 bits under __x86_64__, while others are 17 bits and 3 bits.
From the figure above, we can see that the first 3 bits occupy 3 bytes, the shiftCLs occupies 44 bytes, and the last 5 bits occupy 17 bytes. Our iOS is in little endian mode, so we read from right to left, and we store from right to left; The diagram below:
Class information analysis
1, the ISA
1. Isa trend process
Last time, we looked at the nature of objects and a simple derivation of ISA. Today we will analyze the principle of the class direction, and in-depth understanding of the classic ISA direction diagram;
Walk through the code, view the class and its underlying relationship;
LGPerson *p = [LGPerson alloc]; Declare a LGPerson class; Query class information by p/x p; Then obtain the address memory information through x/ 4Gx; Using ISA and mask, we will get the address of this class;Copy the code
You might wonder why you want to use masks.
The class information is stored in 'shiftcls' in' isa_t '. 'shiftcls' is 3 bytes later than' isa_t '. So in order to retrieve 'shiftcls', we need to do the shift operation. The mask is provided by the system. The mask under the _x86_64 system can be quickly calculated.Copy the code
So by doing this, we can get the address of the class from the address, so what is the underlying information of the address of the class? So let’s do SAO, let’s take this class address, and let’s see what kind of memory information does this address have?
In practice, LGPerson’s class address will continue to print out the class information, and the class address will print out the class information, and the class information will not be the same as the class information printed above;
Continuing with the practice, try using this memory structure isa to go with the upper mask
Nani ????? How is it the same?? If I alloc one LGPerson, how can I have two addresses?
Check it out, get it in multiple ways and see if there are multiple copies of the class object in memory…
void lgTestClassNum(void){
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
Class class4 = [LGPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}
Copy the code
It turns out that there’s only one address, and it’s the same as the first LGPerson address that I printed above, so what’s the second address above? Why is it LGPerson?
Which brings us to the class parent ---- > metaclasses
Just to recap, what happened was: Through isa for objects, we touched classes, and through ISA for classes, we touched metaclasses
However, in iOS, there is no such thing as a metaclass; Let’s go the other way and see if metaclasses actually exist;
Drag the project executable file into the MachOView tool to view the oc underlying file
We do not declare Metal classes in the project. Instead, Metal classes are declared automatically at the bottom of the system. We do not need to be responsible for and maintenance.
So metaclasses, can they point to other unknown classes? Let’s try printing again and see if we can get to the lower level of the metaclass;
So, by going through LGPerson, we finally got to NSObject, and what does this NSObject have to do with the fact that we’re using NSObject directly?
2. Summary of ISA Trend
Through this process, we draw a simple flowchart to summarize the isa direction;
Through ISA for objects, we go to classes, through ISA for classes, we go to metaclass, metaclass ISA, we go to the root metaclass, which is NSObject, and we go to NSObject, and we go to NSObject.
And we find that, as the root class, NSObject, his path graph, after only two steps, returns to NSObject, so the path of NSObject, can be summarized as: root class ISA –> root metaclass isa —-> root metaclass ISA
Through all of this exploration, we've been able to unify the idea that, in iOS, any object, the superclass of its metaclass, is the root metaclass, which is NSObject, so NSObject is the basis for all classes.
2. Class inheritance chain
As we all know, in iOS, classes are inherited. But it’s not clear who the ultimate parent is, and where the parent comes from, so let’s sort out the inheritance completely;
LGTeacher inherits from LGPerson, but when looking for the underlying metaclass, it doesn’t directly show the root metaclass at the bottom.
The root metaclass is not the root metaclass, but the root metaclass, and then open a branch to point to the root metaclass; the root metaclass is the root metaclass.
So, objects will have an ISA chain, metaclasses will have an ISA chain, root metaclasses will have an isa chain; The same is true of inheritance chains;
And, when we get the root metaclass through the metaclass, and the root metaclass gets the parent class, the value we get is null, which means that the parent class of NSObject is null
And from that, we have a very classical bitmap of the inheritance chain
A kind of analysis
1. Analysis of the internal structure of class
After analyzing ISA and inheritance chains, let’s take a look at the internal structure of the class;
As we all know, an object internally stores data like ISA, ShiftCLs, member variables, etc. What about the class, what kind of storage structure is going to be inside the class?
With x/4gx lgPerson. class, we can print out the internal information of LGPerson directly; The bottom layer of the class is actually objc_object, so we need to go through the source code to analyze the inside information of the class;
In runtime.h, we find one particular declaration. Let’s record it here:
In the objc_runtime_new.h file, we find the current objc_class declaration method, which declares:
struct objc_class : objc_object { ...... 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 { #if __has_feature(ptrauth_calls) # if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH ...... } class_rw_t *data() const { return bits.data(); } void setData(class_rw_t *newData) { bits.setData(newData); }... There is a lot of code behind, you can view the source codeCopy the code
Class superclass: stores the current superclass;
cache_t cache; : used to cache Pointers and vtables, speeding up method calls
__has_feature: determines whether the compiler supports the condition in (); Ptrauth_calls: For arm64 architecture, pointer validation; Devices using Apple A12 or later A series processors (such as the iPhone XS, iPhone XS Max, and iPhone XR or newer devices) support the ARM64E architecture. developer.apple.com
class_data_bits_t bits; : is equivalent to the class_rw_t pointer with rr/alloc flags. Bits are used to store information such as methods, attributes, and protocols of a class. Inside class_data_bits_t, there is a method called data() that writes bits and returns a class_rw_t structure
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
Copy the code
Class_rw_t, inside, you can see that there are methods method_array_t,property_array_t,protocol_array_t, and so on, and then we’re going to come up with a method that goes here;
We could have gotten the address of LGPerson, the address of the class, which is actually the first address in the current memory structure, and we could have gotten the bits in the class by moving them in memory;
Get the memory structure of the class
X /4gx qlyPerson. class is used to view the memory structure of the class.
We need to read inside bits, so we can rely on a memory shift to do that, but the problem is, we don’t know how many bytes to shift; We need to analyze the internal structure to figure out how many bytes we need to shift;
Cache_t Memory size
When we look at the internal structure of cache_t, ah ~~~~, crazy. There’s too much code in there. How the hell is this sizing?
Inside cache_t, there are methods, global variables; We know that the method does not occupy the size of the structure, so, ha ~ ~, turn the method upside down, I don’t look at it. Global variables also do not occupy our memory structure, it exists in the global area, so, dry it;
Finally, we get the shift position: 8+8+8+8 = 32; And then we’re going to shift the address
Data () Reads information
We finally get data (), but we don’t see any member variables in data (), so we have to keep looking for them.
In class_rw_t, there are methods such as method_array_t, property_array_t, protocol_array_t, and so on. So we can read the data that we need through these methods;
We print the properties() function to get deeper data, but this is not what we need. We need to print the name property, but we know that the name is stored in the list_array_tt internal structure.
List_array_tt: list_array_tt: list_array_TT: list_array_TT: list_array_TT
Now that we can read the elements inside, we can use methods to read the data inside the array; When we print the data for properties(), we see a list inside.
At this point, we print the property perfectly;
To make it harder, let’s try adding methods and member variables to QLYPerson and see if we can print that out as well
Next, we try to get the method
Look at the source code and see what’s happening inside the property and method layers;
Inside the property, we get the data from the prpperty_list_t structure, which stores the data without doing anything to it;
In fact, the data that we’re reading out is the value of property_tAs you can see, the property_t structure, there is no internal business processing, it is directly read in the way of member variables;So if we look at method_list_t, method_T, we see that the system is going to do some changes to the data that it’s storing
Inside method_t, there are no direct member variables, so we can’t read them directly with get();
⚠️⚠️⚠️ note: But we can see that inside the struct big, there are member variables; So, we can just get this big right here, and you can see that it provides a way to get the big.
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 Struct big {// ⚠️⚠️⚠️ SEL name; const char *types; MethodListIMP imp; }; . big &big() const { ASSERT(! isSmall()); return *(struct big *)this; }...Copy the code
Note that if you are an M1 computer like me, you will not be able to output big through the above steps
Because at the bottom of the source code, there is a very important judgment ——> ASSERT(! isSmall()); M1 computer, let this claim be true, so we can’t get big, instead get small();
struct small { // The name field either refers to a selector (in the shared // cache) or a selref (everywhere else). RelativePointer<const void *> name; RelativePointer<const char *> types; RelativePointer<IMP> imp; bool inSharedCache() const { return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)this)); }};Copy the code
As you can see, inside small(), just like big, there are names, types, and IMP;