The road of Montana is heavy and long, never give up!!

1. Learn clues

We usually think of the main function as the entry to the application, is that right? We add a breakpoint at the entrance to main. Bt looked at the running stack information and found some clues!

There is also a start before main, and the start is from libdyld.dylid. Note that the main function is not the entry point of the application, and there are some application startup processes before this. This is strange. Let’s start with the simple one. Let’s add a symbolic breakpoint here, alloc, and pass the breakpoint on main.

Such as UIKitCore, CoreFoundation, libDispach. Dylib, etc., the most commonly used content in our development process, such as UI, NSObject, GCD, etc., are all from these libraries.

Start with the familiar creation object!!

A preliminary study of alloc principle

Creating objects during development is the most common code we write. GFPerson * gf = [[GFPerson alloc] init]; So that leaves us with the question, what does alloc do, what does init do?

1. Explore case studies

Let’s introduce a case where we initialize an object gF1 using alloc method, create gf2 and gf3 by calling init method gF1, and output the object, object address, and pointer address, respectively.

According to the run result, it is found that the object output of the three is the same, the object address is the same, the pointer address is different.

Case Summary:

  1. Three Pointers to the same object, which is<GFPerson: 0x600000fbc1f0>;
  2. The addresses of the three Pointers areContinuous storage in the stack area, andOpen up memory space from high to low;

  1. Pointer to theOccupied space is8 bytes;
  2. lg1throughAlloc methodThe memory space is opened up and the object is created. throughThe init methodThe initialization of thelg2andlg3No memory space is created, that is, no object is created.

So, how does alloc open up memory to create an object? So what does init do?

2. Underlying exploration methods

In our development project, Jump to Definition can only see the alloc method declaration, not the actual source code implementation process.

+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
Copy the code

Here are three ways to explore the bottom line.

1. Add the form of symbolic breakpoints directly to the process

We’re going to study the alloc method, so we’re going to add a symbolic breakpoint alloc. The following figure shows how to add symbol breakpoints:

Since we are only studying the GFPerson class’s initialization process for the time being, we will avoid the interference caused by alloc calls from other classes by setting alloc breakpoints to Disable breakpoints at runtime. In the program run to GFPerson * gf1 = [GFPerson alloc]; Then set the alloc Breakpoint to Enable Breakpoint. The running results are shown in the following figure:

According to the figure above, [GFPerson alloc] eventually goes to +[NSObject alloc]; Why NSObject? Because LGPerson inherits from NSObject, there’s no alloc method in the subclass, and you end up calling the alloc method in the superclass. And we found an important clue, [NSObject alloc], from the Libobjc.A. dylib dynamic library.

2. 通过按住control – step into

In GFPerson * gf1 = [GFPerson alloc]; This line of code adds a breakpoint, runs the program, and when you reach the breakpoint, hit Control-step into. See the following figure for the operation process:

You go in and you see that objc_alloc is called, and then you add a symbolic breakpoint objc_alloc, and you run the program.

An important clue is that objc_alloc also comes from the libobjc.a. dylib dynamic library.

3. Compile the view and process

Setting mode: Debug -> Debug Workflow -> Always Show Disassembly

In GFPerson * gf1 = [GFPerson alloc]; This line of code adds a breakpoint and runs the program. Enter the assembly process, as shown in the following figure:

You can’t read the assembly, but you can get a clue by looking at the key words, and you’ll also go to the objc_alloc method, and continue to look for the source using symbolic breakpoints.

4. Explore and summarize

The above exploration leads us to the alloc method, which is a dynamic library: libobjc.a. dylib. There is also a question: the initialization calls the alloc method, but the assembly trace above shows that it calls the objc_alloc method. What’s the relationship between the two methods? To learn more about the alloc implementation, download the objC4 source code. Source download address: objC4 source download address

Three. Combined with source analysis of alloc

Source download completed, using the keyword alloc {global search, in nsobject. mm file (objective-C ++ assembly) to find the method of alloc implementation.

1. Alloc process analysis

According to the alloc method implementation searched above, the trace key source code is as follows:

// alloc + (id)alloc { return _objc_rootAlloc(self); } // _objc_rootAlloc id _objc_rootAlloc(Class cls) { return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); } // callAlloc static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { #if __OBJC2__ if (slowpath(checkNil && ! cls)) return nil; if (fastpath(! cls->ISA()->hasCustomAWZ())) { return _objc_rootAllocWithZone(cls, nil); } #endif // No shortcuts available. if (allocWithZone) { return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil); } return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)); } // _objc_rootAllocWithZone id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused) { // allocWithZone under __OBJC2__ ignores the zone parameter return _class_createInstanceFromZone(cls, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC); }Copy the code

The following process can be obtained from the source code:

How do I verify that callAlloc goes to _objc_rootAllocWithZone or objc_msgSend? Yes, the above has learned to explore the way, here you can use assembly + add match breakpoints! Next set a symbol breakpoint in the program run to GFPerson * gf1 = [GFPerson alloc]; Then set the symbol Breakpoint to Enable Breakpoint to ensure that the GFPerson initialization process is being tracked. Breakpoint debugging, found that _objc_rootAllocWithZone flow.

Continue tracing the source code, _objc_rootAllocWithZone ->class_createInstanceFromZone, then the alloc process is shown below:

2. Process sorting

The alloc process tracing is complete, so it seems to be ok, but the question in the last chapter is not solved! The initialization calls the alloc method, but the assembly trace above shows that it calls the objc_alloc method. What’s the relationship between the two methods?

  • incontrol - step intoIn the way that the tracking code goes isObjc_alloc method.
  • Assembly view trace mode, also calledObjc_alloc method.

What’s the problem? Now that you have the source code, set breakpoints and track it! Set a breakpoint in the alloc method and objc_alloc method, respectively. Let’s see where it goes! The setting method is shown in the following figure:

Similarly, when the program goes to GFPerson * gf1 = [GFPerson alloc]; Then set the alloc method and the objc_alloc method to Enable Breakpoint to ensure that the GFPerson initialization process is tracked. Run the code:

And then something amazing happens, when you call the alloc method, you end up running into the objc_alloc method. Breakpoint debugging, trace discovery, will call callAlloc’s objc_msgSend method and send an Alloc message.

This is a bit of a long process, and it needs a little bit of a rehash, but the key thing is the callAlloc method, which branch of calloAlloc is going to take a little bit more research! The key code is:

static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { #if __OBJC2__ if (slowpath(checkNil && ! cls)) return nil; if (fastpath(! cls->ISA()->hasCustomAWZ())) { return _objc_rootAllocWithZone(cls, nil); } #endif // No shortcuts available. if (allocWithZone) { return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil); } return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)); } bool hasCustomAWZ() const { return ! cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ); }Copy the code

Code interpretation:

  1. Check whether there are custom files in the cachealloc/allocWithZoneLocal implementation, obviously the method is not cached in the first run class.
  2. What is CLS? The class? No! Take a look at the source definition:typedef struct objc_class *Class;. soCLS is a pointer to a structure, which is the Core Foundation class!
  3. Class initialization atread_imagesMethod is executed whileInstance objectsThe initialization of theallocFrom time to time.
  4. First execution((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));That will beSlow method lookupTo findThe alloc method of class NSObjectAnd,Put the method into the method cache.
  5. So except for the first callAlloc methodOutside, after the object initialization will go directly_objc_rootAllocWithZone method.Debug tracking verification can be performed! The authenticated!

In conclusion, the following flow chart is obtained:

So that’s basically what alloc does, but the question is why does ** calling alloc end up in objc_alloc? * * – LLVM! Decrypt later!

Iv.Alloc core method

The core process of the _class_createInstanceFromZone method is shown in the following figure:

1.cls->instanceSize

This process calculates how much memory space is needed. CLS ->instanceSize source code is implemented:

Compiler optimizations have been made to make it easier to execute the fastInstanceSize method in the cache to quickly calculate the required memory size.

You end up with the align16 method, which is the 16-byte alignment algorithm.

Process analysis:

  1. Initialized raw memory8 plus 15 is 23, 23 bits:0001, 0111,
  2. 15 Binary bits:0000, 1111,, take the reverse operation to get:1111, 0000,;
  3. Then the &operation is performed on the data obtained in 1 and 2, and the result is:0001, 0000 = 16;

Why is 16-byte alignment needed? There are the following:

  1. The CPU does not access data in bytes, but in blocks. Frequent access to misaligned bytes greatly reduces CPU performance, so the CPU overhead can be reduced by reducing the number of accesses.
  2. 16-byte alignmentIn an object, the first property ISA is 8 bytes (inherited from the parent class). Of course, an object must have other properties, when there is no property, 8 bytes will be reserved, i.e16-byte alignmentIf the isa of this object is not reserved, the ISA of this object is next to that of other objects, which may cause access confusion.
  3. 16-byte alignmentIn this way, the CPU reads faster and the access is more secure.

2.calloc

Apply to the system to open up memory, return address pointer. This process temporarily allocates a dirty memory, and the memory space allocated after calloc is the memory address where the object is created.

3.obj->initInstanceIsa

Associated with the corresponding class, the memory space to be opened up points to the class to be associated! The result shows that obj has only one memory address before obj->initInstanceIsa is called, and the object type is specified as LGPerson after the call.

V. Supplementary content

1. Compiler optimization

#define fastpath(x) (__builtin_expect(bool(x), 1))

#define slowpath(x) (__builtin_expect(bool(x), 0))

Fastpath: Can be understood as a fast process, more likely to execute the process to optimize the speed of operation;

Slowpath: Basic process, not optimized.

Fastest, Smallest, fastest, Smallest. Set to None for development builds for easy debugging!

2. Optimize memory

If the custom class does not define properties, and simply inherits from NSObject, then the actual size of the instance object is 8 bytes and the 8-byte alignment algorithm is used. The actual memory allocated by the system, using a 16-byte aligned algorithm.

Memory alignment can be understood as that when the memory size of the member in the structure is applied for, the minimum memory allocation of the system is 8 bytes, which is applied for by 8 bytes each time. If the memory size is not 8 bytes, the system also applies for 8 bytes. Then, the system applies again according to the order of the member variables in the structure until all the member variables can be put down.

  • Memory optimization (property rearrangement)

When the structure member memory is small in the front, memory will be wasted due to memory alignment. In order to solve this problem, Apple uses space for time, and rearranges the attributes in the class, so as to achieve the purpose of memory optimization.

3. InstanceSize doubt

Do I go to the cache or do I go down here?

It goes to the cache!

During the class implementation, whether the class is non-lazily loaded. If it is, it will precede the main function. If you load lazily, the first time a message is sent, the class will be initialized, that is, the implementation class! FastInstanceSize is set to the cache during the process.

So the actual size of memory allocated by the system, using a 16-byte aligned algorithm.

But I don’t quite understand what the 8-byte alignment returned below means!

4.NSObject alloc

Add to the [NSObject alloc] process, because when you call NSObject’s alloc method, the alloc is already in the cache. (When the system is initialized, the alloc is already in the cache by another class!) . So the NSObjec alloc process will call _objc_rootAllocWithZone directly.