This is the fourth day of my participation in the More text Challenge. For details, see more text Challenge

The nature and extension of objects

What is a Clang

Clang is a lightweight compiler for C, C++, and Objective-C. The source code is distributed under the BSD protocol. Clang will support its normal lambda expressions, simplified handling of return types, and better handling of constexpr keywords. Clang is an Apple-led, LLVM-based C/C++/Objective-C/Objective-C++ compiler. It is almost completely compatible with the GNU C specification and adds additional syntactic features such as C function overloading (which decorates functions with __attribute__((overloadable)), one of its goals being to surpass GCC.

Clang command

Next, let’s use a few Clang commands

0, preparation

Create a new class Person as follows:

@interface Person : NSObject
@property (nonatomic.copy) NSString *nickName;
@end

@implementation Person

@end
Copy the code

1. Clang command

Run the command in the directory where the person. m file resides

clang -rewrite-objc Person.m -o Person.cpp

When done, a person.cpp file will be generated in the directory

2. Complex clang command

Using this command again, we generate the CPP file for the viewController.m file. The result is as follows:

Error: UIKit library has been introduced in viewController.m file, so Clang command is not sufficient. In this case, we need the following command

Clang – rewrite – objc – fobjc – arc – fobjc – runtime = ios 14.5 -isysroot/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14. 5.sdk/ ViewController.m

To generate the CPP file for the viewController.m file

3. Run the xcrun command

XcodeIt was installed along with the installationxcrunCommand,xcrunCommand inclangBased on the encapsulation, more easy to use

The xcrun command for the emulator

xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

The real machine xcrun command

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64-iphoneos.cpp

Both xcrun commands generate CPP files corresponding to.m files

The CPP file

Let’s take a look at the CPP file generated by Person.m

The underlying nature of the object

We look for the location of the nickName property in the CPP file, and we locate the code

#ifndef _REWRITER_typedef_Person
#define _REWRITER_typedef_Person
typedef struct objc_object Person;
typedef struct {} _objc_exc_Person;
#endif

extern "C" unsigned long OBJC_IVAR_$_Person$_nickName;
struct Person_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString * _Nonnull _nickName;
};
Copy the code

We can see that the essence of the Person class is a struct, in this case a pseudo-inheritance of a struct, which in C++ can be inherited.

At the OC level, Person is inherited from NSObject, and at the lower level, it’s a struct of type ObjC_object

We continue to search for the NSObject_IMPL and find the code here:

struct NSObject_IMPL {
	Class isa;
};
Copy the code

We find that the NSObject_IMPL is isa in Person parent NSObject:

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
Copy the code

Let’s continue to look at the underlying implementation of Class:

typedef struct objc_class *Class;
Copy the code

Class at the bottom is a pointer to a structure of type Objc_class

typedef struct objc_object *id;
typedef struct objc_selector *SEL;
Copy the code

Our usual id,SEL, etc., are Pointers to structs

“NickName” = “nickName”, “getter”, “setter” = “nickName”

// @implementation Person


static NSString * _Nonnull _I_Person_nickName(Person * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool.bool);

static void _I_Person_setNickName_(Person * self, SEL _cmd, NSString * _Nonnull nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _nickName), (id)nickName, 0.1); }
// @end
Copy the code

We find that both the getter and setter methods have two more arguments self and _cmd, which are hidden arguments to the methods

Consortium bit field

The structure of the body

Let’s look at a simple structure:

struct Car {
    BOOL front;
    BOOL back;
    BOOL left;
    BOOL right;
};

struct Car car1;
NSLog(@"1--->%lu".sizeof(car1));
Copy the code

By running print, we find that the structure takes up 4 bytes of size (one byte for each BOOL)

0000 0000 0000 0000 0000 0000 0000 0000

In such a 4-byte, 32-bit space, we only need to store front,back,left and right values, which greatly causes a waste of space. So do we have any way to optimize the space? For us, the 4-bit space can already meet our judgment of the four directions. But since memory is always allocated at least one byte, is there any way that we can optimize this struct for one byte?

Next we introduce the concept of bit fields

A domain

We will transform the above structure as follows:

struct Car {
    BOOL front: 1; // Specify that front occupies only one digit
    BOOL back: 1; // Specify that the back occupies only 1 bit
    BOOL left: 1; // Specify that the left occupies only 1 bit
    BOOL right: 1; // Specify that "right" occupies only one digit
};
Copy the code

Next, let’s print it again:

We have successfully optimized the structure from 4 bytes of memory to 1 byte

0000, 0000,

That’s the bit field

But since all four directions of the car can’t be true at the same time, and only one of them can ever be true, is there a way that only one of them can be true and none of the others can be true?

Next we introduce the concept of a union

A consortium

Let’s look at a combination

union Person {
    char *name;
    int age;
    double height;
};
Copy the code

After parsing, we create a union and assign values to the members in turn:

Assign a value to name:

Age and height are not used in memory, are dirty memory, and the values are dirty data

Through the above assignment process, we can find that each assignment to a member will affect the values of other members. This is because the members of the union have the same address, occupy the same memory, and only one member can be used at the same time.

case

Let’s look at an example:

Car. H file

@interface Car : NSObject
@property (nonatomic.assign) BOOL front;
@property (nonatomic.assign) BOOL back;
@property (nonatomic.assign) BOOL left;
@property (nonatomic.assign) BOOL right;
@end
Copy the code

Car. M file

#define LGDirectionFrontMask    (1 << 0)
#define LGDirectionBackMask     (1 << 1)
#define LGDirectionLeftMask     (1 << 2)
#define LGDirectionRightMask    (1 << 3)

@interface Car(a){
    / / a consortium
    union {
        char bits;
        / / a domain
        struct {
            char front  : 1;
            char back   : 1;
            char left   : 1;
            char right  : 1;
        };
    } _direction;
}
@end

@implementation Car
- (instancetype)init {
    self = [super init];
    if (self) {
        _direction.bits = 0b0000000000; // Binary default initialization
    }
    return self;
}

- (void)setFront:(BOOL)isFront {
    if (isFront) {
        _direction.bits |= LGDirectionFrontMask;
    } else {
        _direction.bits |= ~LGDirectionFrontMask;
    }
    NSLog(@"%s",__func__);
}

- (BOOL)isFront{
    return _direction.front;
}

- (void)setBack:(BOOL)isBack {
    _direction.back = isBack;

    NSLog(@"%s",__func__);
}

- (BOOL)isBack{
    return _direction.back;
}
Copy the code

To better understand the union for the results of the run:

Structure and union summary

  • The structure of the bodystructAll of the variables arecoexistenceOf, can be understood as: tolerance;Disadvantages:isstructMemory space allocation is extensive, whether used or not, all allocation.
  • A consortiumunionThe variables in areThe mutexNot tolerant enough;advantagesisunionThe memory usage is more subtle and flexible, which also saves memory space.

The analysis of the nonPointerIsa

What is thenonPointerIsa?

We often use a class that has an object pointer that takes 8 bytes. 8 bytes equals 64 bits. If you put a pointer in that 64 bit space, it’s actually a big waste. Apple came up with TaggedPointer and NonpointerIsa. Use TaggedPointet for small objects to store their values. NonpointerIsa is used for large memory consuming objects to use ISA bit-by-bit, partly to store the actual object address and partly to store additional information.

nonPointerIsaThe definition of

Remember when we were looking at the source code for ObjC, how the in-memory CLS was bound to our Person class?

Person is bound to the in-memory generated CLS by obj->initIsa(CLS) in the _class_createInstanceFromZone method

Let’s look at a concrete implementation of initIsa

What’s important is isa_t, so let’s just look at the definition

Isa_t isa consortium, and in the consortium there’s an ISA_BITFIELD, and that’s what we’re looking for nonPointerIsa

Let’s look at the definition

# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD                                                      \
        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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   define ISA_BITFIELD                                                        \
      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 unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)
Copy the code

Here we use the __x86_64__ framework:

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 unused            : 1;                                         \
uintptr_t has_sidetable_rc  : 1;                                         \
uintptr_t extra_rc          : 8
Copy the code
  • nonpointer0: pure ISA pointer. 1: Isa contains more than the address of a class object. Isa contains the class information and reference count of an object
  • has_assocAssociated object flag bit, 0 does not exist, 1 exists
  • has_cxx_dtorDoes the object have a C++ or Objc destructor? If so, the destructor logic needs to be done. If not, the object can be freed faster
  • shiftclsStores the value of a pointer to a class. With pointer optimization enabled, 33 bits are used to store class Pointers in the ARM64 architecture
  • magicUsed by the debugger to determine whether the current object is a real object or has no space to initialize
  • weakly_referencedAn ARC weak variable that indicates whether or not an object is referred to or has ever been referred to. Objects without weak references can be freed faster
  • unusedIndicates whether the object is freeing memory
  • has_sidetable_rcWhen the object reference technique is greater than 10, you need to borrow this variable to store carries
  • extra_rcWhen you represent the reference count for that object, you’re actually subtracting the reference count by 1. For example, if the object’s reference count is 10, the extra_RC is 9. If the reference count is greater than 10, the above is usedhas_sidetable_rc

In this case, we can derive the Class from ISA

Isa derived class

Let’s use Car as an example in the above bit

0x01000001005C55d9 is ISA

Next, let’s do and with isa and ISA_MASK

The sum of isa and ISA_MASK is car.class

Bit operations for ISA

According to nonPointerIsa’s definition, the class pointer Shiftcls takes up 44 bits in its 64-bit space, with 3 bits of data on the right and 17 bits of data on the left (little enened mode, data is read from right to left).

Then we can go through the bit operation and empty all the left and right data, leaving only shiftCLs’ 44 bits in place.

Moves to the right

The left 20

Moves to the right of 17

Eventually we cleared out the rest of the data, leaving only the pointer to the class shiftcls

So how do we verify this? Let’s verify this in code: