Write in front: iOS underlying principle exploration is my usual development and learning in the accumulation of a section of advanced road. Record my continuous exploration of the journey, I hope to be helpful to all readers.Copy the code

The directory is as follows:

  1. IOS underlying principles of alloc exploration
  2. The underlying principles of iOS are explored

Summary of the above column

  • Summary of iOS underlying principles of exploration

Writing in the front

Previously we analyzed the principle of alloC underlying processes and structures for memory alignment. So, today we’re going to look at what the nature of an object is.

As we all know, OC language is based on C and C++ language to add a layer to the object, so, we will start from OC object, in C and C++ in the bottom of the implementation code to begin today’s exploration of the road.

To prepare

  • 1, Clang

Clang is a lightweight compiler for C, C++, and Objective-C. Source code is published 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 compiler. The common commands are as follows:

Compile the object file into a c++ file, clang-rewrite-objc main.m -o main.cppCopy the code

Xcode is installed with the xcrun command, which is a bit more wrapped around clang to make it easier to use

Xcrun - SDK iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (emulator) xcrun - SDK iphoneOS clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (handset)Copy the code
  • 2, the union

Union let’s use an example to understand and understand unions and the difference between a union and a struct

struct SMTeacher3 {
    char        *name;
    int         age;
    double      height ;
};

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

So just to summarize,

Each variable in the consortium is “mutually exclusive”, the disadvantage is not enough “inclusive”; The advantage is that memory usage is more delicate and flexible, and also saves memory space. (The printed T4 takes up 8 bytes of memory space, 3 times less than the structure’s 24 bytes. As the contents of T4 are printed for the second and third time, you can see that after each assignment, the other attributes become dirty data and are no longer readable);

Structure is all the variables are “co-existing”, the advantage is enough “inclusive” comprehensive, the disadvantage is that the memory management is extensive, no matter whether full allocation.

start

First, in our new project, in our main.h file, we add a SMPerson object:

@interface SMPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) NSInteger height;
@property (nonatomic, copy) NSString *like;

@end

@implementation SMPerson

@end
Copy the code

H file to c++ file main. CPP with the command line tool:

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

You can see that a main.cpp file is generated under the current project path. Our SMPerson class is a structure at the bottom that implements the following:

struct SMPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
	NSInteger _age;
	NSInteger _height;
	NSString *_like;
};
Copy the code

Struct NSObject_IMPL NSObject_IVARS; What is it? Yes, he was Isa,

typedef struct objc_class *Class;

struct NSObject_IMPL {
	Class isa;
};
Copy the code

The following is a low-level implementation of the SET and GET methods of the SMPerson attribute

static NSString * _I_SMPerson_name(SMPerson * self, SEL _cmd) {
    
    return (*(NSString **)((char *)self + OBJC_IVAR_$_SMPerson$_name));
}
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_SMPerson_setName_(SMPerson * self, SEL _cmd, NSString *name) {
    
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SMPerson, _name), (id)name, 0, 1);
}

static NSInteger _I_SMPerson_age(SMPerson * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_SMPerson$_age)); }
static void _I_SMPerson_setAge_(SMPerson * self, SEL _cmd, NSInteger age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_SMPerson$_age)) = age; }

static NSInteger _I_SMPerson_height(SMPerson * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_SMPerson$_height)); }
static void _I_SMPerson_setHeight_(SMPerson * self, SEL _cmd, NSInteger height) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_SMPerson$_height)) = height; }

static NSString * _I_SMPerson_like(SMPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SMPerson$_like)); }
static void _I_SMPerson_setLike_(SMPerson * self, SEL _cmd, NSString *like) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SMPerson, _like), (id)like, 0, 1); }
Copy the code

We print out the memory information of p1 object through the debugging console.

It can be seen that p1 object is in memory and its attributes are laid out as follows:

Therefore, in the underlying implementation of set and GET methods, OBJC_IVAR_$_SMPerson$_name is the offset of the memory address corresponding to the attribute, and the system assigns and values it by the offset of each attribute.

The nature of the object:

At the bottom, an object is a structure that stores instance variables of the class.

Object’s NSObject_IMPL inherits from NSObject’s ISA.

NSObject has only one member variable isa.

What is the underlying implementation of ISA?

In alloc, we explore the underlying flow of alloc-like methods. Among them in

Obj ->initInstanceIsa(CLS, hasCxxDtor);Copy the code

This process contains the initial isa process. To explore the underlying implementation of ISA, we will start with the creation of ISA:

InitIsa reads as follows:

We can see that an instance of newisa of type ISA_t is created within the method. After the assignment, newisa is returned. So, let’s take a closer look at the underlying implementation of isa_t.

Isa_t contents are as follows:

So ISA_t isa union with two member variables: bits and CLS. We know that the variables in a union are mutually exclusive, which has the advantage of more delicate and flexible memory usage. So, in other words, there are two ways to initialize isa_t:

  • bitsAssigned,clsNo value or value overwritten;
  • clsAssigned,bitsNo value or value overwritten.

Another member variable in ISA_t is the structure ISA_BITFIELD. This macro definition corresponds to __arm64__ and __x86_64__, both iOS and MacOS implementations. ISA_BITFIELD stores information through the bit domain as follows:

# 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)

# else
#   error unknown architecture for packed isa
# endif

// SUPPORT_PACKED_ISA
#endif
Copy the code

To get a more intuitive understanding of the information stored in the ISA_BITFIELD above, let’s draw a diagram to parse the long code above.

Isa in arm64 architecture:

Isa in x86_64 architecture:

The meaning of each variable is:

  • nonpointer0: indicates the pure ISA pointer. 1: indicates that the ISA contains not only the address of the class object, but also the class information and reference count of the object.
  • has_assoc: Flag bit of the associated object, 0 does not exist, 1 exists.
  • has_cxx_dtor: does the object have a destructor in C++ or Objc? If it has a destructor, it needs to do the destructor logic. If it does not, it can release the object faster.
  • shiftcls: Stores the value of the class pointer. With pointer optimization turned on, 33 bits are used to store class Pointers in the ARM64 architecture.
  • magic: used by the debugger to determine whether the current object is a real object or has no space to initialize.
  • weakly_referenced: whether the object is or was referred to an ARC weak variable,
  • Objects without weak references can be released faster.
  • deallocating: indicates whether the object is freeing memory.
  • has_sidetable_rc: When the object reference technique is greater than 10, the variable is borrowed to store the carry.
  • extra_rcThe extra_rc value is 9 if the object’s reference count is 10. If the reference count is greater than 10, the following has_sideTABLE_rc is used.

Summary of isa underlying implementation

  • isaThere are two typesnonpointerThe types and thenonpointerType,nonpointerA type contains data such as class information, object reference counts, etc. nonnonpointerA type is just a pure pointer.
  • isauseA consortiumA domainTo implement. In this wayIt saves a lot of memory(As you can see, Apple has done a lot of optimization in the underlying implementation); In development, a lot of objects will have oneisaPointers, so many of themisaIt takes up a lot of memory,A consortiumThe mutually exclusive feature of the member variables saves some memory space. Coupled with theA domainThe use of, but also save memory space at the same time, the object class information and other information for optimized storage, so thatisaThe memory space occupied by Pointers is maximized.

extension

# if __arm64__
#     define ISA_MASK        0x007ffffffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
Copy the code

The above four lines of code are taken from the ISA_BITFIELD structure. Here ISA_MASK is a mask. We know that in isa’s 8-byte to 64-bit storage space, the value of the arm64 (shiftcls) class pointer occupies the first 33 bits of memory from the fourth bit; On x86_64, or the computer side, shiftcls stores the value of the class pointer in the first 44 bits of memory. Our debugging desk prints out the memory address of an object, and takes the mask corresponding to the isa address & stored in the first place, which is the value of the object’s class pointer. Here, verify: