We can’t get around objects in programming, so what is an object? What is the nature of the object? An object is an abstraction of an Objective thing. An object can define properties and methods. Objective-c is a superset of C, C++, and assembly language. To analyze Objective-C objects, you need to convert Objective-C into C++ through Clang or XCRun, and then analyze the composition of objects.

The nature of Objective-C objects

Definitions of Clang and XCRun

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 compilerCopy the code
xcrun
Xcrun is xcode installed along with the 'xcrun' command, 'xcrun' command on the basis of 'clang' encapsulation, more convenient to useCopy the code

Compile the following Objective-C file using Clang and XCRun

//
//  main.m
//
//  Created by Atom on 2021/6/15.
//

#import <Foundation/Foundation.h>

@interface ATPerson : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation ATPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
    }
    return 0;
}
Copy the code
compile

Go to the main.m directory and run the compile command

// Clang clang -rewrite-objc main.m -o main.cpp xcrun 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-arm64Copy the code

Source code analysis

After compiling, the main. CPP file will be generated, which will generate so much code that I don’t know where to start. We can search through the custom object ATPerson to analyze the corresponding structure in the.

struct ATPerson_IMPL { struct NSObject_IMPL NSObject_IVARS; NSString *_name; // Define the name attribute}; Struct NSObject_IMPL {Class isa; struct NSObject_IMPL {Class isa; }; typedef struct objc_class *Class; Static NSString * _I_ATPerson_name(ATPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_ATPerson$_name)); Static void _I_ATPerson_setName_(ATPerson * self, SEL _cmd, NSString *name) {objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ATPerson, _name), (id)name, 0, 1); }Copy the code

From the above code snippet, we know that the underlying ATPerson is a structure type, and the structure ATPerson contains onenameProperty, one of which is structNSObject_IMPL NSObject_IVARS, by looking for the NSObject_IMPL containsClassThe type ofisaAnd Class isobjc_classStructure pointer to, so NSObject_IVARS corresponds toisaPointer; In the objective-C object definition of the properties, we can see from the source automatically generatedgetandsetThe get method finds the address of the property name by finding the address of self(i.e. Isa) pointer, finds the address of the property name by the address offset, and then obtains the value of the property. The set method also finds the address of the property name by finding the address of the pointer isa, finds the address of the property name by the pointer offset, and then assigns the value to name.

Second, the nonPointerIsa

define

Before we get to nonPointerIsa, let’s take a look at the federated bit fields, and then analyze why Apple designed nonPointerIsa. What’s the difference between nonPointerIsa and regular ISA?

Combination field

Struct S1 {BOOL front; BOOL back; BOOL left; BOOL right; };Copy the code

throughAnalysis of internal alignment of structuresArticle analysis, it can be learned that S1 takes up 4 bytes, we have to run the code to verify;

4 bytes is equal to 4 * 8 = 32bit, but for the design of direction and position, it does not need 32 bits to store, which causes a huge waste of space. For each position, only 1 bit is needed to mark, so only 4 bits is half a byte, because the smallest unit is 1 byte. That is, 1 byte can meet our requirements, and there is no need to design it as 4 bytes. Can we further optimize it? Please see the following definition:

Struct S2 {BOOL front: 1; struct S2 {BOOL front: 1; BOOL back : 1; BOOL left : 1; BOOL right: 1; }; Run print input size = 1 byte, greatly optimize the storage spaceCopy the code

Let’s look at the following two definitions. What’s the difference? Let’s also define two objects, T1 and U1, assign values to member variables and print them and see what the difference is?

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

union U1 {
    char        *name;
    int         age;
    double      height ;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        struct T1 t1;
        t1.name = "Atom";
        t1.age = 18;
        
        union U1 u1;
        u1.name = "Atom";
        u1.age = 18;
    }
    return 0;
}
Copy the code

By running the breakpoint to print and view one by one, it can be seen that the member variables of struct type T1 are all null after initialization, and the corresponding variables will update the assigned values after assignment. The member variables of union type U1 are also null after initialization, and the name is updated to the new value after assignment to U1. But the age and height of u1 are also going to be values, it’s just going to be weird values, and when the breakpoint goes one step further down and I assign age, age is what we normally assign 18, but name and height are not, so that’s the difference between a struct and a union.

Conclusion: the struct type is coexist, as long as the initialization will give all variables set up memory space, and a consortium union is mutually exclusive, when for a variable assignment, other state variable is not used, to a value of dirty data, so we can update in the design of different scenarios to define different types, can provide efficiency to save memory space.

nonPointerIsa

NonPinterIsa is literally an impaler ISA. An ISA takes up 8 bytes of memory (64 bits), but the actual pointer address does not hold that much memory

// ARM64 architecture # if __arm64__ # define ISA_MASK 0x0000000ffffff8ull # define ISA_MAGIC_MASK 0x000003f000000001ULL # define  ISA_MAGIC_VALUE 0x000001a000000001ULL # 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 deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 19 # define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18) # define ISA_MASK 0x00007ffffffffff8ULL # define ISA_MAGIC_MASK 0x001f800000000001ULL # define ISA_MAGIC_VALUE 0x001d800000000001ULL # 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 deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ Uintptr_t extra_RC: 8 # define RC_ONE (1ULL<<56) # define RC_HALF (1ULL<<7) nonpointer: Enable pointer optimization has_ASsoc: Has_cxx_dtor: is there a destructor for C++ or Objc? If there is a destructor, then the destructor logic needs to be done. If there is no destructor, then the object shiftcls: stores the value of the class pointer. Weakly_referenced: 33 bits in the ARM64 architecture are used to store the class pointer magic when pointer optimization is enabled: space used by the debugger to determine if the current object is a real object or not initialized weakly_referenced: Deallocating: Indicates whether the object is freeing memory. Has_sidetable_rc: When the object reference technique is greater than 10, you need to borrow this variable to store extra_rc: When representing the reference count value of this object, the reference count value is actually reduced by 1. For example, if the object's reference count is 10, extra_rc is 9. If the reference count is greater than 10, the following has_sideTABLE_rc is usedCopy the code

Through the above analysis of isa pointer structure, iOS can be followed by the underlying principle of alloc primary exploration of the third point of the binding object for further understanding, the following is the source of isa pointer associated object

inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { ASSERT(! isTaggedPointer()); if (! Isa = isa_t((uintptr_t) CLS); } else { ASSERT(! DisableNonpointerIsa); ASSERT(! cls->instancesRequireRawIsa()); // If it is nonpointer, the parts need to be assigned. The following part of the assignment is the structure inside isa pointer above isa_t newisa(0); #if SUPPORT_INDEXED_ISA ASSERT(cls->classArrayIndex() > 0); newisa.bits = ISA_INDEX_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.indexcls = (uintptr_t)cls->classArrayIndex(); #else newisa.bits = ISA_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.shiftcls = (uintptr_t)cls >> 3; #endif // This write must be performed in a single store in some cases // (for example when realizing a class because other threads // may simultaneously try to use the class). // fixme use atomics here to guarantee single-store and to //  guarantee memory order w.r.t. the class index table // ... but not too atomic because we don't want to hurt instantiation isa = newisa; }}Copy the code
Example Verification (x86_64 as an example)
  1. X /4gx P outputs the object ISA address 0x011D8001000080e9
  2. p/x 0x011d8001000080e9 & 0x00007ffffffffff8ULL // isa & ISA_MASK
  3. P /x atPerson. class, prints the address of ATPerson

It turns out that the output of 2 and 3 is the same, both can print the address of the ATPerson object.

Here’s a look at the distribution of nonPointerIsa using a memory diagram:The address of nonPointerIsa is mainly stored in 3-46 bits of memory. Therefore, we can use translation method to calculate the address of isa. We still use x/4gx method to get the first address of isa. Then move 20 bits to the left and 17 bits to the right to restore the original class, as verified by the following codeThe above is combined with the source code analysis of the actual code run to get the same result.

conclusion

  1. Understand Clang and XCRUN two kinds of compilation tools, and through these two kinds of compilation methods output class source files, the OC object source code analysis shows that the essence of class is structure.
  2. Through the example to further understand the union bit domain, and in memory can be optimized according to the demand, improve the utilization of memory.
  3. Know what nonPointerIsa is and why Apple designed it this way, and analyze the internals with actual code examples.