Runtime Series
Runtime Principle Exploration (I) — An in-depth understanding of ISA (Apple isa optimization)
An in-depth analysis of the Class structure
OC Class cache_t
Runtime principle exploration (4) — To investigate the bottom message mechanism
Exploring the Runtime principle (5) — The nature of Super
Runtime theory exploration (6) — Runtime in the interview question
☕️☕️ The length of this article is quite long. The purpose of creation is not to brush praise and read quantity in Jane’s book, but to review knowledge for myself in the future. If you are lucky enough to find this article and arouse your interest in reading, please have a full rest, calm down, and start reading with sufficient energy. I hope this article can help you. If you find any mistake, please leave a message to correct, thank you. ☕ ️ ☕ ️
How to understand the dynamic nature of Objective-C?
In many static programming languages, code is written, then compiled and linked to produce an executable file that can be run on a computer.
Take C as an example
void test(a) {
printf("Hello World");
}
int main(a) {
test();
}
Copy the code
After the above code is compiled, the main function must call test(), and the implementation of test() must be the same as written in the code. This is determined at compile time, and will not change during execution. C is a typical static language.
Objective-c, by contrast, allows you to modify some of the functions and methods identified during compilation prior to runtime.
************************main.m*************************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLPerson *person = [[CLPerson alloc] init];
[person test];
}
return 0; } * * * * * * * * * * * * * * * * * * * * * * *CLPerson.h************************
#import <Foundation/Foundation.h>
@interface CLPerson : NSObject
- (void)test;
@end* * * * * * * * * * * * * * * * * * * * * * *CLPerson.m************************
#import "CLPerson.h"
@implementation CLPerson
- (void)test {
NSLog(@"%s", __func__);
}
- (void)abc {
}
@end
Copy the code
As shown above, [Person test]; This code, at run time, can call the test method of CLPerson or, through the dynamic nature of OC, eventually call another method, such as ABC, or even call another class method. In addition, OC can also add methods to the class during the program running phase, which is the so-called dynamic feature.
The Runtime profile
- Objective-c is a highly dynamic programming language, which is quite different from root C and C++
- The dynamics of Objective-C is underpinned by the Runtime API
- Runtime API interface is basically C language, source code written by C/C++/ assembly language
Isa,
Before we dive into the Runtime, we need to address one of the more important concepts — ISA. In the early Runtime, the ISA pointer pointed directly to the address of the class/meta-class object. Isa was a normal pointer.
Later, Apple optimized ISA from the ARM 64-bit architecture, defining it as a common body (UNION) structure, combining the concept of bit fields and the way of bit operations to store more class-related information. The ISA pointer requires a binary & with a value called ISA_MASK (the mask) to get the address of the real class/meta-class object. Next, let’s take a closer look at how Apple optimized it.
First from the source point of view, compare the changes before and after isa optimization
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id;
Copy the code
Above is the pre-64-bit definition of objc_Object as shown above, where ISA points directly to objc_class.
Let’s look at the optimized definition of objc_Object
struct objc_object {
private:
isa_t isa;
public:
Copy the code
From arm64, isa type changed to ISA_t, what the hell is that? This is the focus of the next discussion, first take a look at its source code
union isa_t
{
isa_t() {}isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
# else
# error unknown architecture for packed isa
# endif
// SUPPORT_PACKED_ISA
#endif
#if SUPPORT_INDEXED_ISA
# if __ARM_ARCH_7K__ >= 2
# define ISA_INDEX_IS_NPI 1
# define ISA_INDEX_MASK 0x0001FFFC
# define ISA_INDEX_SHIFT 2
# define ISA_INDEX_BITS 15
# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS)
# define ISA_INDEX_MAGIC_MASK 0x001E0001
# define ISA_INDEX_MAGIC_VALUE 0x001C0001
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t indexcls : 15;
uintptr_t magic : 4;
uintptr_t has_cxx_dtor : 1;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 7;
# define RC_ONE (1ULL<<25)
# define RC_HALF (1ULL<<6)
};
# else
# error unknown architecture for indexed isa
# endif
// SUPPORT_INDEXED_ISA
#endif
};
Copy the code
The above code is the essence of Apple’s ISA optimization, in order to understand the above code, first need to start with some basic knowledge.
Scenario Requirement Analysis
First, define a class CLPerson. First, add several properties and member variables to CLPerson
@interface CLPerson : NSObject
{
BOOL _tall;
BOOL _rich;
BOOL _handsome;
}
@property (nonatomic.assign.getter=isRich) BOOL rich;
@property (nonatomic.assign.getter=isTall) BOOL tall;
@property (nonatomic.assign.getter=isHandsome) BOOL handsome;
Copy the code
Needless to say, their use is as follows
#import <Foundation/Foundation.h>
#import "CLPerson.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLPerson *person = [[CLPerson alloc] init];
person.rich = YES;
person.tall = NO;
person.handsome = YES;
NSLog(@"%zu", class_getInstanceSize([CLPerson class]));
}
return 0;
}
Copy the code
From the Runtime, we can see the memory usage of CLPerson class objects
201907 -- 16 13:15:04.083828+0800OC the underlying Runtime [2509:80387] 16
Program ended with exit code: 0
Copy the code
From my previous analysis of the memory layout of objects, the following conclusions can be drawn:
isa
It takes 8 bytes_rich
,_tall
,_handsome
These three member variables take up 1 byte each- Class objects take up 16 bytes of memory space because of memory alignment and bucketSized factors.
🐞🐞🐞 But _rich, _tall and _handsome can only have two values, YES/NO, that is, 0 and 1, which can be represented by a binary bit. The three bits together only occupy three binary bits, not even half a byte. Is there any way to achieve this memory saving requirement? 🐞 🐞 🐞
If you use attributes directly, you will automatically generate underlined member variables, so you can’t slim down memory. So you need to manually implement getter/setter methods in place of properties.
#import <Foundation/Foundation.h>
@interface CLPerson : NSObject
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
@end
Copy the code
Then inside the.m file, use a char _tallRichHandsome; (one byte) to store tall/rich/handsome information.
#import "CLPerson.h"
@interface CLPerson(a)
{
char _tallRichHandsome; // 0b 0000 0000
}
@end
@implementation CLPerson
- (void)setTall:(BOOL)tall {
}
- (void)setRich:(BOOL)rich {
}
- (void)setHandsome:(BOOL)handsome {
}
- (BOOL)isTall {
}
- (BOOL)isRich {
}
- (BOOL)isHandsome {
}
@end
Copy the code
If I want to use the last three words of _tallRichHandsome to store the three information of tall, rich and handsome respectively, what method can I do it?
The values
So first we’re going to solve the getter method, the value problem. How do you get a value out of a particular bit? That’s right — & (bitwise and operation).
Suppose we stipulate that
- tallwith
_tallRichHandsome
The right of the first1
Who said, - richwith
_tallRichHandsome
The right of the first2
Who said, - handsomewith
_tallRichHandsome
The right of the first3
Who said, - andtall=
YES
.rich=NO
.handsome=YES
.
The value of _tallRichHandsome should be 0000 0101
Tall (YES) | Rich (NO) | Handsome (YES) | |
---|---|---|---|
_tallRichHandsome | 0000, 0101, | 0000, 0101, | 0000, 0101, |
Mask code (for value) | 0000 & 0001 | & 0000 0010 | & 0000100 |
The result is obtained by the & operation | 0000, 0001, | 0000, 0000, | 0000, 0100, |
According to the characteristics of & operation, if you want to extract the value above a specific bit, you only need to set the corresponding bit in the mask code to 1, because the original value & 1 = the original value, and set the other bits in the mask code to 0, you can mask the value above the other bits except the specific bit, because the original value & 0 = 0, this should be very easy to understand. There are many ways to convert the extracted value to the desired value (in this case YES/NO). Okay, so let’s go to the code and implement it. As shown below.
*************************main.m*****************************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLPerson *person = [[CLPerson alloc] init];
NSLog(@"tall-%d, rich-%d, handsome%d", person.isTall, person.isRich, person.isHandsome);
}
return 0; } * * * * * * * * * * * * * * * * * * * * * * * * *CLPerson.m*****************************
#import "CLPerson.h"
@interface CLPerson(a)
{
char _tallRichHandsome;
}
@end
@implementation CLPerson
- (instancetype)init
{
self = [super init];
if (self) {
_tallRichHandsome = 0b00000101;// Set an initial value
}
return self;
}
/* Mask mask: binary 0b 00000001 --> decimal 1 Rich mask: binary 0B 00000010 --> decimal 2 handsome Binary 0B 00000100 --> decimal 4 */
- (BOOL)isTall {
return!!!!! (_tallRichHandsome &1);
}
- (BOOL)isRich {
return!!!!! (_tallRichHandsome &2);
}
- (BOOL)isHandsome {
return!!!!! (_tallRichHandsome &4);
}
@end* * * * * * * * * * * * * * * * * * * * * * * * * * operation result * * * * * * * * * * * * * * * * * * * * * * * * * *201907 -- 16 17:54:32.915636+0800OC the underlying Runtime [2828:156639]
tall = 1,
rich = 0,
handsome = 1
Program ended with exit code: 0
Copy the code
In the above solution, I am through!! (_tallRichHandsome & mask value); _tallRichHandsome & mask is either 0 or an integer greater than 0, so it passes twice! That gives us BOOL values, 0 for NO, and numbers greater than 0 for YES.
The value of the mask code can be expressed in binary or decimal, but in the specific use, a large number of annotation codes are needed to explain the meaning of the mask code. Therefore, a better way to deal with it is to define them as macros, and express the required meaning through the name of the macro. Rewrite as follows:
#define CLTallMask 1
#define CLRichMask 2
#define CLHandsomeMask 4
- (BOOL)isTall {
return!!!!! (_tallRichHandsome &CLTallMask);
}
- (BOOL)isRich {
return!!!!! (_tallRichHandsome &CLRichMask);
}
- (BOOL)isHandsome {
return!!!!! (_tallRichHandsome &CLHandsomeMask);
}
Copy the code
However, there is another problem. From the macro definition, it is not easy to see which bit value the mask code is to extract. Therefore, it is better to change the binary representation, as follows
#define CLTallMask 0b00000001
#define CLRichMask 0b00000010
#define CLHandsomeMask 0b00000100
Copy the code
But it’s still not perfect, any developer who doesn’t have a compulsion to write a bunch of binaries like this, it’s too much trouble, so we have a more sophisticated way of doing it, yes, with displacement operators, as follows
#define CLTallMask (1 << 0)
#define CLRichMask (1 << 1)
#define CLHandsomeMask (1 << 2)
Copy the code
1 represents 0b00000001, which is the binary 1. 1<< 0 means to move 0 bits to the left, which is not to move, which means to go to the lowest value on the right. Similarly, 1<< 1, 1<< 2 means to take the second and third values from the right, respectively.
Why MASK?
When I first started programming, I used to wonder why the string of binary code used to get the content of a particular bit was called in Englishmask
themask
Why translate to mask? I don’t know if you’re confused. And then I was thinking about it, and then I had an Epiphany. Thismask
It’s used to get a value for a particular position, which is to see where you want to see it.mask
The meaning of the word hasThe maskThe mask knows what it means. It’s down hereThe holes in the mask are for the eyes and the mouth, because when you go to a mask party, you only want to show the glasses and the mouth, and the rest of the mask is covered. The value of a mask code is the same as that of a mask code. But I personally think the Chinese translation is too stiff, translated intoMask codeWouldn’t it be better? Small sigh, English technical documents there are a lot of such strange translation of the noun, in fact, is cultural differences, foreigners from their cultural perspective to give some concepts vividly named, but to our side is really a terrible translation, just mass production Luo Yufeng ah!! So it’s important to learn English well, and some translations are really killing people.
Set the value
Let’s take a look at how to store external values to the corresponding bits without affecting other bits. (1) Set the value to 1. Just the bitwise operation or (|) can achieve the requirement. Let’s review the rules of or operations
- 0 | 0 = 0
- 0 | = 1
- 1 | 0 = 1
- 1 | = 1
Therefore, according to the above characteristics, the specific value can be set to the target bit by performing or operation (|) with the mask code. Because in the mask code, the corresponding target bit is 1, and the corresponding non-target bit is 0.
(2) Set the value to 0. The value of bit-and-operation (&) was also introduced above. Combined with the requirements here, it can be found that the finger positioning can be set to 0 only after the bit-inverse mask code is reversed and the and operation (&) is performed with the target object. The matching code is as follows
#import "CLPerson.h"
#define CLTallMask (1 << 0)
#define CLRichMask (1 << 1)
#define CLHandsomeMask (1 << 2)
@interface CLPerson(a)
{
char _tallRichHandsome;
}
@end
@implementation CLPerson
- (instancetype)init
{
self = [super init];
if (self) {
_tallRichHandsome = 0b00000101;
}
return self;
}
// Value operation
- (BOOL)isTall {
return!!!!! (_tallRichHandsome &CLTallMask);
}
- (BOOL)isRich {
return!!!!! (_tallRichHandsome &CLRichMask);
}
- (BOOL)isHandsome {
return!!!!! (_tallRichHandsome &CLHandsomeMask);
}
// Set the value operation
- (void)setTall:(BOOL)tall {
if(tall) {
_tallRichHandsome |= CLTallMask;
} else {
_tallRichHandsome &= ~CLTallMask; }} - (void)setRich:(BOOL)rich {
if(rich) {
_tallRichHandsome |= CLRichMask;
} else {
_tallRichHandsome &= ~CLRichMask; }} - (void)setHandsome:(BOOL)handsome {
if(handsome) {
_tallRichHandsome |= CLHandsomeMask;
} else {
_tallRichHandsome &= ~CLHandsomeMask; }}@end
Copy the code
Invokes and prints the results
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLPerson *person = [[CLPerson alloc] init];
person.rich = YES;
person.tall = YES;
person.handsome = YES;
NSLog(@"\ntall = %d, \nrich = %d, \nhandsome = %d", person.isTall, person.isRich, person.isHandsome);
}
return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *2019- 08. 11:36:49.081651+0800OC the underlying Runtime [1147:65497]
tall = 1,
rich = 1,
handsome = 1
Program ended with exit code: 0
Copy the code
You can see that the Settings are successful. Through the above attempts, the original need for 3 bytes to represent the information, stored in one byte, in order to achieve the purpose of space saving.
A domain
In the above space, we use | and & two bit operations to achieve the goal of memory saving, please think about it, is this perfect? A careful analysis will find the following deficiencies:
- Later maintenance, if we need to add a new attribute, then we need to add a corresponding
mask
Code, add the correspondingset
Method, add the correspondinggetter
Method is still relatively cumbersome, and the code size increases rapidly.- We’re through
char _tallRichHandsome;
It expresses three messagestall
,rich
,handsome
If you need to represent 10 pieces of information, you can expect a very long name, which is obviously not extensible and readable.
Now take a look at the following code
@interface CLPerson(a)
{
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
}_tallRichHandsome;
// char _tallRichHandsome;
}
@end
Copy the code
In the code, use the struct instead of the previous onechar _tallRichHandsome
The structure has three members —tall
,rich
,handsome
. After each member: 1.
Represents that the member occupies 1 bit. The type keyword in front of a member has no real effect, except that the syntax for defining variables requires a type keyword, which is written here for uniformitychar
The actual size of the memory used by the member is determined by the following one: X
X is the number of bits. This is the bitfield, about the specific content of this concept, you can view the BASIC knowledge of the C language. becausestruct
As an overall unit, the minimum unit of allocated memory is one byte, thentall
,rich
,handsome
The three members are arranged from right to left in the 8-bit space of a byte in the order defined in sequence.Accordingly, we need to adjust the correspondinggetter
/setter
methods
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *CLPerson.m*************************************
@implementation CLPerson
- (BOOL)isTall {
return _tallRichHandsome.tall;
}
- (BOOL)isRich {
return _tallRichHandsome.rich;
}
- (BOOL)isHandsome {
return _tallRichHandsome.handsome;
}
- (void)setTall:(BOOL)tall {
_tallRichHandsome.tall = tall;
}
- (void)setRich:(BOOL)rich {
_tallRichHandsome.rich = rich;
}
- (void)setHandsome:(BOOL)handsome {
_tallRichHandsome.handsome = handsome;
}
@end
******************************main.m*************************************
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLPerson *person = [[CLPerson alloc] init];
person.tall = NO;
person.rich = YES;
person.handsome = NO;
NSLog(@"\ntall = %d, \nrich = %d, \nhandsome = %d", person.isTall, person.isRich, person.isHandsome);
}
return 0; } * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * printing * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *2019- 08. 14:53:31.980516+0800OC the underlying Runtime [1333:126711]
tall = 0,
rich = - 1,
handsome = 0
Program ended with exit code: 0
Copy the code
As you can see from the output above, it looks likegetter
/setter
It looks like it’s working, but it looks likerich
I have a problem. Set it toYES
Finally, it was printed out- 1
, it should be1
That’s what we expect, and we’ll solve that problem later, but we can add a breakpoint, and we can look at the structure_tallRichHandsome
situationIt can also be obtained by command in the LLDB window
lldb) p/x person->_tallRichHandsome
((anonymous struct)) $0 = (tall = 0x00, rich = 0x01, handsome = 0x00)
(lldb)
Copy the code
The results clearly show that the values of tall, rich and handsome of the three members are indeed set correctly. In addition, you can directly view the memory of _tallRichHandsome to illustrate the result. Run the following command to obtain the memory address of _tallRichHandsome
(lldb) p/x &(person->_tallRichHandsome)
((anonymous struct) *) $1 = 0x00000001033025c8
Copy the code
Then run commands to check the memory information corresponding to the address
(lldb) x 0x00000001033025c8
0x1033025c8: 02 00 00 00 00 00 00 00 41 f0 2f 96 ff ff 1d 00. A./.....0x1033025d8: 80 12 00 00 01 00 00 00 06 00 05 00 05 00 00 00.Copy the code
How to look at the result? First of all, it should be known that this printing method is displayed in hexadecimal system, so every 2 numbers represents a byte. As mentioned above, _tallRichHandsome actually occupies a byte size, so its corresponding value should be the first 2 numbers 02 in the printed result. This value is 0000 0010 when translated into binary, and the corresponding values of the three members Tall, rich and handsome are 0, 1 and 0 respectively, which is consistent with our setting and proves that our getter/setter method has taken effect.
Back to the question we left above, why is it set toYES
The member of the memory verified that there is no problem, why is finally printed out is- 1
? And the reason is,getter
Method, a cast is performed when the value is returned. How do you understand thatWe use the following method validation to adjust Rich’s getter method as follows and add a breakpoint where it returns
- (BOOL)isRich {
BOOL ret = _tallRichHandsome.rich;
return ret;
}
Copy the code
The memory output of RET through LLDB is as follows
(lldb) p/x &ret
(BOOL *) $0 = 0x00007ffeefbff42f 255
(lldb) x 0x00007ffeefbff42f
0x7ffeefbff42f: ff bc 9e a9 7b ff 7f 00 00 70 e4 80 01 01 00 00. {... p......0x7ffeefbff43f: 00 80 f4 bf ef fe 7f 00 00 95 0c 00 00 01 00 00. (lldb)Copy the code
As you can see, the ret memory is ff, which is binary 11111111. Indeed, as we said above, the result is strong.
In fact, during the conversion, the value is filled according to the leftmost bit of the object value. Because NO corresponds to 0, one bit of binary 0 is converted to BOOL, and the other bits are filled with 0, so the final result will not be affected.
As for why 11111111 on a byte is printed as -1, please review the expression of signed numbers if in doubt. For the current problem, there are many solutions, we can use the previous two times! And you do that, and you get 1
- (BOOL)isRich {
return!!!!! _tallRichHandsome.rich;; }Copy the code
Alternatively, you can expand the number of bits required for the member information
@interface CLPerson(a)
{
struct {
char tall : 2;
char rich : 2;
char handsome : 2;
}_tallRichHandsome;
// char _tallRichHandsome;
}
@end
Copy the code
This way, if someone needs to set it to YES, the result will be 0b01 because it takes up 2 bits. According to the rules for filling bits, it should be 0b0000 0001, which does not affect the final value.
Summary: Using the optimization above, we streamlined the getter/setter code implementation and left out the mask code. The disadvantage is that the final value is not accurate enough due to the complement transformation in the value process (the first scheme does not have this problem by means of bit operation).
D share body
Next, let’s take a look at the optimizations Apple uses. Apple is actually building on the bit arithmetic of the first scheme, combined with the union/Commons technique.
So just to review the idea of union,
union Person {
char * name;// Takes 8 bytes
int age; // Take up 4 bytes
bool isMale ; // take 1 byte
};
Copy the code
The system allocates 8 bytes of space to the Union Person, which is shared by its three members. Compare the definition of struct
struct Person { char * name; // take up 8 bytes int age; // 4 bytes bool isMale; // take 1 byte};Copy the code
According to the memory versus its principle, the system isstruct Person
Allocate 16 bytes of memory, and its three members will have their own memory space to use independently. A graph summarizes it as follows
Back to the apple optimization question, let’s start with the following code
@interface CLPerson()
{
union {
char bits;
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
};
} _tallRichHandsome;
}
@end
Copy the code
_tallRichHandsome is defined as a union. The members defined in the union share the memory space. According to the above writing, char bits are used in the actual bit operation to achieve the getter/setter. Bits is a number of bits, defined by the type keyword before it. We need 8 bits, so char is used. Because the struct and char bits below; This struct is not used in real memory, but it can be used to explain the meaning of the bits. So the getter/setter changes as follows
@implementation CLPerson
- (BOOL)isTall {
return!!!!! (_tallRichHandsome.bits &CLTallMask);
}
- (BOOL)isRich {
return!!!!! (_tallRichHandsome.bits &CLRichMask);
}
- (BOOL)isHandsome {
return!!!!! (_tallRichHandsome.bits &CLHandsomeMask);
}
- (void)setTall:(BOOL)tall {
if(tall) {
_tallRichHandsome.bits |= CLTallMask;
} else {
_tallRichHandsome.bits &= ~CLTallMask; }} - (void)setRich:(BOOL)rich {
if(rich) {
_tallRichHandsome.bits |= CLRichMask;
} else {
_tallRichHandsome.bits &= ~CLRichMask; }} - (void)setHandsome:(BOOL)handsome {
if(handsome) {
_tallRichHandsome.bits |= CLHandsomeMask;
} else {
_tallRichHandsome.bits &= ~CLHandsomeMask; }}@end
*************************main.m***************************
int main(int argc, const char * argv[]) {
@autoreleasepool {
CLPerson *person = [[CLPerson alloc] init];
person.tall = NO;
person.rich = YES;
person.handsome = NO;
NSLog(@"\ntall = %d, \nrich = %d, \nhandsome = %d", person.isTall, person.isRich, person.isHandsome);
}
return 0; } * * * * * * * * * * * * * * * * * * * * * * * * the output * * * * * * * * * * * * * * * * * * * * * * * * * *2019- 08. 17:20:07.157392+0800OC the underlying Runtime [1673:197854]
tall = 0,
rich = 1,
handsome = 0
Program ended with exit code: 0
Copy the code
As you can see, the getter/setter requirement has been successfully implemented. In fact, the use of char _tallRichHandsome is exactly the same as that of our first solution, except that it is changed to _tallRichHandsome. Bits. The difference is that we use struct in union to enhance the readability of the code. If you omit the struct definition, you get exactly the same result
@interface CLPerson(a)
{
union {
char bits;
} _tallRichHandsome;
}
@end
Copy the code
It should be noted that in the first scheme, the position of tall, rich and handsome member information in memory is defined by structure, while in Apple’s scheme, it is controlled by mask code. The displacement of mask code represents the position of member information. The most important function of the struct in the union is to explain the information of the members inside the bits, which is to improve the readability and make it easy to understand. So in the future when reading the source code to see this kind of union, no longer have to be afraid, that’s what happened.
Apple ISA optimization summary
Now back to the source code for ISA at the beginning
struct objc_object {
private:
isa_t isa;
public:
Copy the code
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
};
#endif
};
Copy the code
Here, some compatibility code is trimmed down and only the code for iOS is retained. Based on some of the topics studied in this article, we can summarize Apple’s optimization of ISA as follows
Through bit operation, bit field and union technology, isa memory space is more fully utilized, the real address of the object is stored in the isa memory above 33 bits, the remaining 31 bits are used to store other information related to the object. Here’s what the other bits of ISA do
nonpointer
— 0 represents a common pointer, storing the memory address of class and meta-class objects. 1, which means optimized, using bitfields to store more informationhas_assoc
— Whether the associated object is set, if not, it will be faster when casthas_cxx_dtor
Is there a C++ sparse constructor? If not, it will be fastershiftcls
–This section stores the memory address information of the real Class and meta-class objectsAnd therefore passisa & ISA_MASK
In order to get the 33 bits here and get the real address of the object.magic
— Used to tell if an object is initialized while debuggingweekly_referenced
Is it pointed to by the weak drink pointer? If not, it will release fasterextra_rc
— The value stored inside isReference count – 1deallocating
— Whether the object is being releasedhas_sidtable_rc
Is the reference counter too large to be stored in isa? If it is, this is 1. The reference count is stored in an attribute of a class called SideTable.
Why has_assoc, has_cxx_dtor, weekly_referenced affect the speed of object release? Void *objc_destructInstance(id obj) is called when the object is freed
/*********************************************************************** * objc_destructInstance * Destroys an instance without freeing memory. * Calls C++ destructors. * Calls ARC ivar cleanup. * Removes associative references. * Returns `obj`. Does nothing if `obj` is nil. **********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
Copy the code
From the source code comments and implementation logic, it is easy to see that the program will
- According to the
obj->hasCxxDtor()
To determine whether to callobject_cxxDestruct(obj)
C++ destructor, - According to the
obj->hasAssociatedObjects()
To determine whether to call_object_remove_assocations(obj)
Remove the associated object reference.
inobj->clearDeallocating();
insideisa.weakly_referenced
andisa.has_sidetable_rc
Will decide whether to proceed weak_clear_no_lock(&table.weak_table, (id)this);
andtable.refcnts.erase(this);
Operation. Therefore, these values in ISA affect the speed at which objects are released.
The details of the ISA_MASK
I am inDetail the ISA&Superclass pointerIt was summarized as followsIn iOS (arm64),
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
Copy the code
The above ISA_MASK is represented in hexadecimal notation, which is not easy to see, so let’s use a scientific calculator to convert it
So you can see it clearly throughisa & ISA_MASK
I’m going to pick out which ones. And you can also notice a little detail, that the address of the object that you end up with is going to be 36 valid bits, and the last four bits are only going to be1000
or0000
In hexadecimal8
or0
, so the last bit of the object’s address (in hexadecimal) must be8
or0
. Experience it, and then go through the code
#import "ViewController.h"
#import <objc/runtime.h>
#import "CLPerson.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"ViewController class address: %p", [ViewController class]);
NSLog(@"ViewController metaclass address: %p", object_getClass([ViewController class]));
NSLog(CLPerson class object address: %p"[CLPerson class]);
NSLog(@"CLPerson metaclass object address: %p", object_getClass([CLPerson class]));
}
@end* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * a printout2019- 08- 05 10:49:42.408303+0800 iOS-Runtime[1276:57991] ViewController class object address:0x103590dc8
2019- 08- 05 10:49:42.408405+0800 iOS-Runtime[1276:57991] ViewController metaclass object address:0x103590df0
2019- 08- 05 10:49:42.408481+0800 iOS-Runtime[1276:57991] CLPersonClass object address:0x103590e90
2019- 08- 05 10:49:42.408565+0800 iOS-Runtime[1276:57991] CLPersonMetaclass object address:0x103590e68
(lldb)
Copy the code
That’s the end of the ISA discussion.
🦋🦋🦋 Portal 🦋 port 🦋
Runtime Principle Exploration (I) — An in-depth understanding of ISA (Apple isa optimization)
An in-depth analysis of the Class structure
OC Class cache_t
Runtime principle exploration (4) — To investigate the bottom message mechanism
Exploring the Runtime principle (5) — The nature of Super
Runtime theory exploration (6) — Runtime in the interview question