As an iOS developer, every day is object-oriented development, so to speak, with objects crisscrossing our engineering projects, jumping at the tap of a fingertip. Usually very casual, suddenly, came a soul of torture, what is the object?

The brain suddenly buzzing…

When I first learned iOS, I created a Person object… I don’t seem to have any impression, 😁, so what is the object to explore, that is, the essence of the object to explore

Nature of objects

Before putting objects together, let’s look at a tool called —–clang (for looking at the underlying source code structure)

The underlying object is the structure

1.1 what isclang

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.

In other words, Clang is an Apple-led, LLVM-based C/C++/Objective-C compiler.

Ok, so much for Clang, biu, biu, biu

1.2 How to Obtain.cppfile

Let’s get to the point. As usual, code goes a wave 😄

#import <Foundation/Foundation.h> #import <objc/runtime.h> @interface TestPerson : NSObject @property (nonatomic, copy) NSString *test_nickName; @end @implementation TestPerson @end int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!" ); } return 0; }Copy the code

Create a TestPerson object and give it a test_nickName member variable

After creating a good project on thisshow in finder, and then open the terminal as follows:

Clang -rewrite-objc main.m -o main. CPP to compile an object file into c++ (in this case, the object file is main.m to main.cpp)

Note: If we want to compile a viewcontroller.m file into a c++ file in our own iOS project, we're going to get a UIKit error. If we get a UIKit error, we're going to run this command, we're going to change the iOS version, And the final filename (main.m) clang-rewrite-objc-fobjc-arc - fobjc - runtime = ios - 13.0.0 - isysroot/Applications/Xcode. The app/Contents/Developer/Platforms/iPhoneSimulator platform/Develope R/SDKs/iPhoneSimulator13.0. Main SDK. M or directly use xcrun command installed xcode when passing the xcrun command, command on the basis of the clang xcrun for some of the packaging, M -o main-arm64. CPP (emulator) xcrun-sdk Iphoneosclang -arch arm64-rewrite-objc main.m-o main-arm64.cppCopy the code

Then performclangAfter you command it, you get onemain.cppfile

1.3 At the bottom of an object is a structure

Open themain.cppFile, full-text index created beforeTestPersonobject

As you can see, the TestPerson object is made up of a structure.

From this, we can see that the object is essentially a structure at the bottom.

Within the current structure, another structure is nested, equivalent to a pseudo-inheritance (not true inheritance, but inc++It’s ok inside), it inheritsNSObject_IMPL, thenNSObject_IVARSIt’s a member variableisa(Full-text indexing is available here NSObject_IMPL {You can find the structurestruct)

At this point, you can determine that the TestPerson object has a pointer to ISA in it, along with its own declared member variables.

We’re going to look at this screenshot where TestPerson is a structure, and we’re going to look at line 113495, and we’re going to look at this: Typedef struct objc_object TestPerson; You can see that TestPerson is essentially a class of type objC_Object, and what we see at the OC level, TestPerson inherits from NSObject, but underneath it, it’s just objC_object.

In the same way, it’s likeclassType, we can indexclass {You can find

His bottom is actually withobjc_And then the indexobjc_ class You can see it hereclassType, in fact, isobjc_class *Type, meaning currentclassIs a pointer to a structure, andClassIt’s just an alias

Again for instanceidType, useidWhen you quote, you don’t need to add*Yes, why?Because it isobjc_object *

So by this point, it’s pretty clear.

1.4 setterMethods andgetterThe bottom of the method

Here, back to ourTestPersonUp here, inmain.cppIn the file, you can also find member variablestest_nickNamethegetterMethods andsetterMethods.113514Rows and113517Yeah, well, it’s justtest_nickNamethegetterMethods andsetterMethods.

So for example, this getter method on line 113514, when we call it from the top, we don’t show the arguments (TestPerson * self, SEL _cmd), so those arguments, in fact, are the hidden arguments of this method. Return (*(NSString **)((char *)self + OBJC_IVAR_$_TestPerson$_test_nickName)); Through the self (TestPerson) plus a test_nickName pointer address offset value, to obtain test_nickName value, and then through (* * *) (nsstrings reduction of type string. The following sketch is even more vivid:

The process of the value is: first get the address of the current member variable, and then go to the address stored in the value.

Like the setter method on line 113517, the value is stored in the same way.

Analysis of here, is there a sense of suddenly, the bottom of my heart want to shout:

Uh-huh, uh-huh, uh-huh… Calm down, don’t be impatient, sit still ~ ~ ~ ~ 😁, wonderful continue biu ~

We know from our analysis that the TestPerson object structure has an ISA pointer in addition to test_nickName. What’s in this ISA pointer? , and then the isa show (ad-free link 😁)

2 ISAPointer to the

2.1 Structure, union and bit domain

We are familiar with the structure, so what is the combination and bit field? Hey hey, directly on the code:As you can see from the print, thisTestCar1The memory size of the structure is4, because the memory size of each Boolean is1, so the total memory is4.

Note: There is a difference between this and member variable byte internal alignment, as usual8Byte alignment is about member variables inOCLayer alignment. And in the struct, is the largest member variable. So, structs and member variables are not the same.

Structure TestCar1, a total of 4 bytes, each byte 8, a total of 32 bit (bit), such as: 0000 0000 0000 0000 1111, open up 32 bits, but only use 4 bits for storage, so the remaining 28 bits are empty, which is wasteful. Only half a byte of space is needed, which is 0000 1111. Then you need to optimize, which leads to the word bitfield.

2.1.1 bit-field

Since the system automatically allocates this large amount of memory, what if you specify the location size of the Boolean variable in the structure? Will it be optimized? Take a look at the chart below:As you can see from the print, compared to the structureTestCar1The structureTestCar2Takes up less memory space, which is:0000, 1111,This greatly optimizes the memory space.

Let’s test it againTestCar2toThe printed result is:

The structure of the bodyTestCar2Take up2Byte memory, we’re looking at it in binary4The positions occupied by the Boolean values:

Note: The maximum number of positions for a single variable can only be 8 (bit).

Here’s an example: When using a proxy:

@protocol TestProtocol <NSObject>

@optional

- (void)methodA;
- (void)methodB;
- (void)methodC;

@end
Copy the code

When we call, we usually check whether the proxy object already implements the method, as follows:

- (void)methodA_CallBack { if (_delegate && [_delegate respondsToSelector:@selector(methodA)]) { [_delegate performSelector:@selector(methodA)]; }}Copy the code

The performance problem is that the delegate has to implement the method each time through the messaging mechanism. Although the OC’s messaging mechanism caches the method implementation to the method cache of the class object, it can affect performance if the method implementation is called frequently. So we can improve on that with bit fields.

Improved code:

@interface TestClass ()
{
    struct {
        unsigned int methodAFlag : 1;
        unsigned int methodBFlag : 1;
        unsigned int methodCFlag : 1;
    } _delegateFlags;
}

@end

@implementation TestClass

- (void)setDelegate:(id<TestProtocol>)delegate {
    _delegate = delegate;
    _delegateFlags.methodAFlag = [_delegate respondsToSelector:@selector(methodA)];
    _delegateFlags.methodBFlag = [_delegate respondsToSelector:@selector(methodB)];
    _delegateFlags.methodCFlag = [_delegate respondsToSelector:@selector(methodC)];
}

- (void)methodA_CallBack {
    if (_delegateFlags.methodAFlag) {
        [_delegate performSelector:@selector(methodA)];
    }
}

- (void)methodB_CallBack {
    if (_delegateFlags.methodBFlag) {
        [_delegate performSelector:@selector(methodB)];
    }
}

- (void)methodC_CallBack {
    if (_delegateFlags.methodCFlag) {
        [_delegate performSelector:@selector(methodC)];
    }
}
@end
Copy the code

In this way, the method determination is performed only once when the proxy is set, and the value is stored in the cache (bitfield), saving many times of method query and improving efficiency.

2.1.2 consortium

finishedA domain“And then introduce another point:A consortium. To create aTestTeacher1Structure:through3The breakpoint, and the print at the breakpoint, if you look at it, it starts with a value of zero0Of, and then do the assignment.

Next, create using a consortiumTestTeacher2 The same3A breakpoint when executing toBreakpoint 1.And whenTestTeacher1The situation is similar, but fromBreakpoint is 2.In the beginning, there was a relatively big difference. I’m just giving younameAssign the value,ageandheightNeither, but they do have values, and these values are what we call dirty data (placeholders). However, to implementBreakpoint is 3..ageIt’s assigned, butnameIs set to null.

This is the property of a union: variables are mutually exclusive.

In struct (struct) : all variables are "co-existing" the advantage is "tolerant", comprehensive; The disadvantage is that struct memory space allocation is extensive, whether used or not, all allocation. In a union, the disadvantage of "mutually exclusive" variables is that they are not "inclusive"; But the advantage is that the memory use is more delicate and flexible, but also save memory space;Copy the code

Seeing here, maybe some old iron will ask: is it not isa’s performance? How also talk to us about commonwealth, bit-domain. Isn’t that trying to sell dog meat? (Boycott black business 😄😄)

No, they promise: that’s because the ISA covers it.

2.2 isaPointer to the

2.2.1 nonPointerIsaThe analysis of the

For nonPointerlsa analysis, go to the objc source code (source download address, explore the underlying iOS ——–alloc article). Earlier, when we talked about the underlying source of alloc, Alloc –> _objc_rootAlloc –> callAlloc –> _objc_rootAllocWithZone –> _class_createInstanceFromZone

in_class_createInstanceFromZoneThere is another important point, that isObj - > initIsa (CLS);This code, it will apply from the heap memoryStructure pointerAnd the currentclsBind together. Look at the underlying code:

Enter theinitIsaMethods the insideAt the end of this method:

Here it is, it appearsisaisisa_tType of, in entryisa_t

As you can see, isa isa consortium.

A common expression of the address of a class is the word nonPointerIsa. For example, a class can be used as a pointer, and there are many things that can be stored on the class. The pointer to the class is 8 bytes, 8 bytes * 8 bit = 64 bit (64 bits). It would be wasteful to just store a pointer, since each class has an ISA pointer. Apple has done to the isa optimization, put some content which is closely linked, and class, such as: is released, the reference count, weak, connection object, the destructor, etc. (so, OC in ground floor, which is a c + +, as the release of the OC, is not really a release, but the lower level of c + + release, is the real release). It’s all class-dependent, so you can store it in the 64 bits. Then there’s nonPointerIsa. NonPointerIsa is not a simple address either. We can see what’s in it by looking at the bit field of isa_t.

inX86_64In:

In arm64:

#if__arm64__ #define ISA_MASK 0x0000000 o ff8ULL #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; 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)Copy the code

Nonpointer: indicates whether pointer optimization is enabled for the ISA pointer. 0: pure ISA pointer. 1: Isa contains more than the address of a class object.

Has_assoc: associated object flag bit, 0 does not exist, 1 exists;

Has_cxx_dtor: whether the object has a C++ or Objc destructor. If so, the destructor logic is needed. If not, the object can be released faster.

Shiftcls: Stores the value of the class pointer. With pointer optimization enabled, 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: Specifies whether an object is or has been referenced to an ARC weak variable. Objects without a weak reference can be released faster.

Deallocating: indicates whether the object is freeing memory;

Has_sidetable_rc: If the object reference technique is greater than 10, this variable needs to be borrowed to store carries.

Extra_rc: When representing the object’s reference count, you’re actually subtracting the reference count 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 used.

For example, in the arm64 heap, 8 bytes are aligned, 8 bytes * 8 bits = 64 bits (64 bits).

Nonpointer [1], Has_assoc [2], Has_Cxx_dtor [3], Shiftcls [4 ~ 36], Magic [37~42], Weakly_referenced is in [43] position, deallocating is in [44] position, has_sidetable_RC is in [45] position, extra_RC is in [46 ~ 64] position.

2.2.2 isaThe bit operations

In order toX86_64As reference:According to theisaThe body of the class is stored inshiftclsStudent: In position, so inshiftclsOn one side of the position segment3And on the other side17A location. Want to seeshiftclsWhat is inside, we can find by translation, and we can show it by a graph:Then according to this step, operate in the project:You can say,shiftclsIt’s an object storeTestMan.classAll the information we have.

3 initandnew

3.1 initMethods the underlying

inobjcIn the source code ofmainFile, initialize oneTestPersonClass:TestPerson *p = [[TestPerson alloc] init] ;, then you can clickinitInto its underlying source code. Because it’s already been thereallocSo it’s an object

Then enter the_objc_rootInitmethodsIt seemed as if he had done nothing but return to himself.

So what does init actually do? Init it is an initialization method, factory design pattern, and constructor that can be overridden by subclasses. In our normal development, we often override init methods. The initialization method can be reconstructed according to different cases. It is to provide an interface for easy extension.

3.2 newThe bottom of the method

The same way, or search directly in the source codenew {, you can find the underlying implementation of new, as shown below:In fact,newisalloc + init;

For example, create a TestPerson class:

inmain.mIn the file, separatelyalloc + initWay andnewMode initializationTestPerson.The print, whether it’s an address, whether it’s an assignment, is the same,new = alloc + init.

The same goes for assembly debugging:

inobjcSearch in source codeobjc_opt_new Same thing.

At this point, oh yeah, the work is done, the exploration of the nature of the object is completed, do you have any harvest, (no no <( ̄▽ ̄)/)

Thank you for coming to ~ ~ ~ ~ ~ ~