Write in front:
OC is a very dynamic programming language, and its dynamic is the Runtime BASED API. Runtime plays an important role in our actual development, and we often encountered runtime-related interview questions in the interview process. In previous installtions of exploration and analysis, we also often looked at the underlying source code of Runtime to see the relevant implementation. Runtime is very important for iOS developers. To learn and master Runtime technologies, you need to start with some common data structures at the bottom of Runtime. Master its underlying structure, we can learn to get twice the result with half the effort. Today we’ll start with ISA.
isa
The underlying structure of each OC object contains an ISA pointer:
struct NSObject_IMPL {
Class isa;
};
Copy the code
Before arm64, ISA was just a pointer that held the memory address of a Class or meta-class. After ARM64, Apple optimized ISA into an ISA_T union structure. Also use bitfields to store more information:
That is, the OC object that we are familiar withisa
Pointers do not directly point to the memory address of a class object or metaclass object, but rather need to&ISA_MASK
Bits are used to obtain the address of a class object or metaclass object.
Now you might be wondering, what is a Commons? What is a bitfield? What is a bit operation? Don’t worry, the next one for you to answer.
1, a domain
Bit-fields are information stored in one or more binary bits rather than a complete byte. For example, the light switch in life, it only “on”, “off” two states, then we can use 1 and 0 respectively to represent these two states, so we only use a binary bit to save the state of the switch. This not only saves storage space, but also makes processing easier.
2. Bit operators
In computer language, there are many arithmetic operators besides addition, subtraction, multiplication, division and so on. Here is a brief explanation of bitwise operators. Bitwise operators operate on bits. Of course, operands can only be integer and character data. Six kinds of an operator in the C language: & bitwise and, | bitwise or, ^ bitwise exclusive or, ~, < < left and > > moves to the right. We still refer to the light switch theory above, except now we have two switches, 1 for on and 0 for off.
1) by bit and &
There’s 0 out of 0, all 1’s are 1’s.
We can understand that in bitwise and operation, the two switches are in series. If we want to light up, we need to turn on both switches to light up, so1 & 1 = 1
. If either switch is not turned on, the light will not come on, so all other operations will be 0.
2) bitwise or |
I have 1 out 1, all 0 out 0.
In bitwise or operation, we can understand that the two switches are in parallel, that is, when a switch is on, the lamp will be on. Only when both switches are turned off does the light go off.
3) xor ^ by bit
Equal to 0, different to 1.
4) not ~
Either operation or inverse operation, in binary 1 becomes 0, 0 becomes 1. For example, 110101 is 001010 after non-calculation, that is, 1010.
5) left < <
The left shift operation is to move all the binary digits of the operand on the left of << several bits to the left. The number of digits moved is the value of the number on the right of <<. The high digit is discarded and the low digit is filled with 0. To the left n is 2 to the n. For example: A <<4 means to move each binary of a four to the left. For example, if a=00000011(decimal 3), the value is 00110000(decimal 48).
6) moves to the right > >
The right shift operation is to move all the binaries of the >> left operand several right bits, and the >> right number specifies the number of digits to move. For example, if a=15, a>>2 indicates that 00001111 is right-shifted to 00000011(decimal 3).
With a brief introduction to bitwise operators, here are two scenarios in which bitwise operators can be used.
Use of bitwise operators
1, the value of
You can use bitwise and & operation to take out the value of the specified bit. The specific operation is to take out the value of which bit will be 1, the other bits are 0, and then carry out bitwise and calculation with the original data, you can take out the specific bit.
Example: 0000 0011 Take the third from last value
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000Copy the code
In the example above, we value from 00000011, so 00000011 is called source. The 0000 0100 that is set by bit and operation is called the mask
2, set value
Bitwise or | operators will be one of the value is set to 1 or 0. The specific operation is: if you want to set the value of a certain bit to 1, then the value of the corresponding bit in the mask is set to 1, and the other bits of the mask are 0, and the source code and the mask can be operated by bit or.
Example: Change the third from last of 0000 0011 to 1
/ / change the value of the third from bottom to mask the bottom third of value set to 1, the other one is 0, with source code bitwise or operation 0011 | 0000 0100-0000 -- -- -- -- -- -- -- -- -- -- - 0000/0111 / third from bottom in the source code can be to change the value of 1Copy the code
If you want to set the value of a certain bit to 0, then set the value of the corresponding bit in the mask to 0, the other bits of the mask to 1, and the source code and the mask by bit or operation.
Example: Change the second-to-last value of 0000 0011 to 0
/ / change the value of the second from bottom to mask the bottom the second set to 0, the value of the other bits to 1, with source code bitwise or operation 0011 | 1111 1101-0000 -- -- -- -- -- -- -- -- -- -- - 0000/0001 / second from bottom in the source code can be to change the value of 0Copy the code
Here I believe that we have a certain understanding of the bit operator, let’s go through an example of OC code, to apply the bit operator to the actual code development.
We declare a Man class, which has three attributes of BOOL type, namely tall, rich and handsome. We judge whether the Man is tall, rich and handsome by these three attributes.
And then let’s look at oneMan
The size of memory occupied by class objects:
We see oneMan
The object of class is 16 bytes. Including oneisa
Pointer and threeBOOL
Type attribute, 8+1+1+1=11, according toMemory alignmentPrinciple so oneMan
The object of class is 16 bytes.
We know that there are only two cases where a BOOL takes up one byte of memory :0 or 1. There are eight bits in a byte of memory, and the bits are only zeros or ones, so we can use one bit to represent a BOOL. This means that the three BOOL values we declared above only end up using three bits, which saves memory. So how do we do that?
To implement three BOOL values in one byte, we can use char as a member variable. The char type takes up one byte of memory space, which is eight binary bits. You can use the last three bits to store three BOOL values.
Of course, we can’t write char as an attribute, because once we write it as an attribute, the system automatically adds member variables and implements set and GET methods for us.
@interface Man()
{
char _tallRichHandsome;
}
Copy the code
If we assign _tallRichHansome to 1, that is, 0b 0000 0001, we only use 0 or 1 in the last 3 binary bits to represent tall, rich and handsome values respectively. Then the states of tall, rich and handsome are as follows:
Combined with the six median operators we described above and the usage scenarios, we can declare them separatelytall
,rich
,handsome
To facilitate the next bit operation value and assignment:
#define Tall_Mask 0b00000100 // This binary number corresponds to the decimal number 4 #define Rich_Mask 0b00000010 // This binary number corresponds to the decimal number 2 #define Handsome_Mask 0b00000001 // The binary number corresponds to the decimal number 1Copy the code
With an understanding of the bitwise operators’ left shift << and right shift >>, we can optimize the above code to:
#define Tall_Mask (1<<2) // 0b00000100
#define Rich_Mask (1<<1) // 0b00000010
#define Handsome_Mask (1<<0) // 0b00000001
Copy the code
The custom set method is as follows:
- (void) setTall: (BOOL) tall {the if (tall) {/ / if you need to value set to 1, the source and mask for bitwise or _tallRichHandsome | = Tall_Mask; }else{// If you need to set the value to 0 // bitwise sum the source code and the bitwise inverse mask _tallRichHandsome &= ~Tall_Mask; } } - (void)setRich:(BOOL)rich { if (rich) { _tallRichHandsome |= Rich_Mask; }else{ _tallRichHandsome &= ~Rich_Mask; } } - (void)setHandsome:(BOOL)handsome { if (handsome) { _tallRichHandsome |= Handsome_Mask; }else{ _tallRichHandsome &= ~Handsome_Mask; }}Copy the code
The custom GET methods are as follows:
- (BOOL)isTall { return !! (_tallRichHandsome & Tall_Mask); } - (BOOL)isRich { return !! (_tallRichHandsome & Rich_Mask); } - (BOOL)isHandsome { return !! (_tallRichHandsome & Handsome_Mask); }Copy the code
Note here that in the code! Is the logical operator is not, because after the _tallRichHandsome & Tall_Mask code is executed, it must return an integer number. For example, when TALL is YES, it indicates that the binary number is 0B 0000 0100 and the corresponding decimal number is 4. Then, after a logical non-operation,! The value of (4) is 0, perform another logical non operation on 0! (0), the result becomes 1, so it corresponds to tall is YES. So there are two logical non-operations here!! .
And, of course, implement the initialization method:
- (instancetype)init
{
if (self = [super init]) {
_tallRichHandsome = 0b00000100;
}
return self;
}
Copy the code
By testing, we have completed the evaluation and assignment:
Optimize code using structural position fields
We talked about bit-fields earlier, so we can use structural bit-fields to optimize our code. This eliminates the need to declare the mask part of the code above. The format of the bit-field declaration is bit-domain: bit-field length. Note the following when using bitfields:
1. If a byte does not have enough space for another bit field, store the bit field from the next cell. 2. The length of the bit field cannot be greater than the length of the data type itself. For example, the length of an int type cannot be greater than 32 bits binary. 3. The bit domain can have no bit domain name. In this case, it is only used for filling or adjusting position. Nameless bit fields are not available.
After using bit-field optimization:
So let’s test it out and see if it’s correct, and this time we’re going totall
Set toYES
,rich
Set toNO
,handsome
Set toYES
:
Assignments and values are still done.
However, after the code is optimized in this way, we remove the mask and initialization code, which is very poor readability, so we continue to use common optimization:
Optimize code with Commons
We can use more efficient bit operations for assignment and value, and use union Commons for data storage. This not only increases reading efficiency, but also improves code readability.
#define Tall_Mask (1<<2) // 0b00000100 #define Rich_Mask (1<<1) // 0b00000010 #define Handsome_Mask (1<<0) // 0b00000001 @interface Man() { union { char bits; Struct {char tall: 1; char rich : 1; char handsome : 1; }; }_tallRichHandsome; } @end @implementation Man - (void)setTall:(BOOL)tall { if (tall) { _tallRichHandsome.bits |= Tall_Mask; }else{ _tallRichHandsome.bits &= ~Tall_Mask; } } - (void)setRich:(BOOL)rich { if (rich) { _tallRichHandsome.bits |= Rich_Mask; }else{ _tallRichHandsome.bits &= ~Rich_Mask; } } - (void)setHandsome:(BOOL)handsome { if (handsome) { _tallRichHandsome.bits |= Handsome_Mask; }else{ _tallRichHandsome.bits &= ~Handsome_Mask; } } - (BOOL)isTall { return !! (_tallRichHandsome.bits & Tall_Mask); } - (BOOL)isRich { return !! (_tallRichHandsome.bits & Rich_Mask); } - (BOOL)isHandsome { return !! (_tallRichHandsome.bits & Handsome_Mask); }Copy the code
The _tallRichHandsome share occupies only one byte, because tall, rich and handsome in the structure occupy only one bit of binary space, so the structure occupies only one byte, and bits of the char type also occupy only one byte. They are all in the share. So you can share one byte of memory. Moreover, the assignment and value in the set and GET methods are enhanced by bit operations using masks, and the overall logic is clear.
However, if we write code like this in daily development, we will probably get killed by our colleagues. While the code is clear, the overall reading is very difficult. We’ll learn about bit operations and Commons here, but more so that we can read the underlying OC code. Let’s return to the subject of this article and take a look at the source code for the ISA_T Commons.
Isa_t sharing body
What we found was thatisa_t
Share macros for internal useISA_BITFIELD
Define the bitfield, we enter the bitfield to view the source code:
And we see that internally, they’re defined separatelyarm64
An architecture andx86_64
The mask and bitfield of the schema. We only analyzearm64
Part of the content in the bit architecture (highlighted in red).
You can see it clearlyISA_BITFIELD
The contents of the bitfield and the maskISA_MASK
Value:0x0000000ffffffff8ULL
.
Let’s focus on thatuintptr_t shiftcls : 33;
In theshiftcls
Which stores the memory address information of class objects and metaclass objectsisa
Pointers need to be the same asISA_MASK
It takes a bitwise and operation to get the actual address of the class object. So we’re going toISA_MASK
The value of the0x0000000ffffffff8ULL
Convert to binary numbers for analysis:
You can see that in the pictureISA_MASK
The value of is converted to binary with 33 bits1
The bitwise and operation mentioned above can take out the value of the 33 bits. So that means the sameISA_MASK
Bitwise and operations can be performed to retrieve the memory address information of class and metaclass objects.
Let’s continue to analyze the meanings of other contents in the structural posture domain:
Struct {// 0 represents a pointer to a class object or a metaclass object. // 1 represents the optimized use of bitfields to store more information. uintptr_t nonpointer : 1; // Uintptr_t has_assoc: 1; Uintptr_t has_cxx_dtor: 1; // Uintptr_t shiftcls: 33; // Uintptr_t magic: 6; // If there is a weak reference to point to. uintptr_t weakly_referenced : 1; // Whether the object is releasing the Uintptr_t dealLocating: 1; Uintptr_t has_sideTABLE_rc: 1; Uintptr_t extra_rc: 19; };Copy the code
Now we have a new understanding of isa Pointers. After __arm64__, isa Pointers don’t just store the memory addresses of class and metaclass objects. Instead, they store more information in a shared way, shiftcls stores the memory addresses of class and metaclass objects. The memory address value can be extracted by bitwise ampersand operation with ISA_MASK.
The original [address] (www.jianshu.com/p/a3fb09198.)