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:

  • isaIt takes 8 bytes
  • _rich,_tall,_handsomeThese 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_tallRichHandsomeThe right of the first1Who said,
  • richwith_tallRichHandsomeThe right of the first2Who said,
  • handsomewith_tallRichHandsomeThe right of the first3Who 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 EnglishmaskthemaskWhy translate to mask? I don’t know if you’re confused. And then I was thinking about it, and then I had an Epiphany. ThismaskIt’s used to get a value for a particular position, which is to see where you want to see it.maskThe 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 correspondingmaskCode, add the correspondingsetMethod, add the correspondinggetterMethod is still relatively cumbersome, and the code size increases rapidly.
  • We’re throughchar _tallRichHandsome;It expresses three messagestall,rich,handsomeIf 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 _tallRichHandsomeThe 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 uniformitycharThe actual size of the memory used by the member is determined by the following one: XX 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. becausestructAs an overall unit, the minimum unit of allocated memory is one byte, thentall,rich,handsomeThe 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/settermethods

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *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/setterIt looks like it’s working, but it looks likerichI have a problem. Set it toYESFinally, it was printed out- 1, it should be1That’s what we expect, and we’ll solve that problem later, but we can add a breakpoint, and we can look at the structure_tallRichHandsomesituationIt 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 toYESThe member of the memory verified that there is no problem, why is finally printed out is- 1? And the reason is,getterMethod, 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 PersonAllocate 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 information
  • has_assoc— Whether the associated object is set, if not, it will be faster when cast
  • has_cxx_dtorIs there a C++ sparse constructor? If not, it will be faster
  • shiftclsThis section stores the memory address information of the real Class and meta-class objectsAnd therefore passisa & ISA_MASKIn 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 debugging
  • weekly_referencedIs it pointed to by the weak drink pointer? If not, it will release faster
  • extra_rc— The value stored inside isReference count – 1
  • deallocating— Whether the object is being released
  • has_sidtable_rcIs 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 theobj->hasCxxDtor()To determine whether to callobject_cxxDestruct(obj)C++ destructor,
  • According to theobj->hasAssociatedObjects()To determine whether to call_object_remove_assocations(obj)Remove the associated object reference.

inobj->clearDeallocating();insideisa.weakly_referencedandisa.has_sidetable_rcWill 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_MASKI’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 be1000or0000In hexadecimal8or0, so the last bit of the object’s address (in hexadecimal) must be8or0. 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