First, the role of alloc

As oc developers, we all know how to create an instance object, that’s alloc. So how does alloc get called at the bottom? Let’s explore it. Code signing

    LGPersion *p = [LGPersion alloc];
    LGPersion *p1 = [p init];
    LGPersion *p2 = [p init];
    
    NSLog(@"p : %@---%p----%p",p,p,&p);
    NSLog(@"p1: %@---%p----%p",p1,p1,&p1);
    NSLog(@"p2: %@---%p----%p",p2,p2,&p2);
Copy the code

Print the result

p : <LGPersion: 0x6000006c0160>---0x6000006c0160----0x7ffee9ae3c58
p1: <LGPersion: 0x6000006c0160>---0x6000006c0160----0x7ffee9ae3c50
p2: <LGPersion: 0x6000006c0160>---0x6000006c0160----0x7ffee9ae3c48
Copy the code

From the above code and the printed result, it can be seen that the object address of P, P1 and P2 is the same, but the pointer address is different, so p, P1 and P2 point to the same memory space, and their performance in memory is as shown in the figure:

Conclusion: Alloc opens up the object’s memory space, init does not operate on the space

2. Methods of exploring alloc

But how does alloc open up memory? How do we find out? Next, we explore it in three ways:

1, setp into + symbol breakpoint

Add a breakpoint before alloc, then hold down Control and click SETp into

Enter the

Then add the symbol breakpointobjc_alloc

Release breakpoint entrylibobjc.A.dylib objc_alloc:

2, assembly

Assembly mode: debug -> Debug Workflow ->Always Show Disassembly Usage: Enter the breakpoint and follow the preceding steps to enter assembly code. As shown in figure:

1, the breakpoint

2. Enter assembly

Assembly:

3, step into

Hold down thecontrolClick on thestep intoEnter the alloc

3, direct symbol breakpoint

We all know that this method is alloc, so we can just use the symbol breakpoint. The process is as follows: first break the point breakpoint before alloc and then as shown

Release the current breakpoint directly after adding the symbol

Then it looks like this

Alloc source tracking

Open Source Browser and search for Objc, as shown below:

Download objC4-818.2.tar. gz and unzip it

Command + left alloc

Jump straight to

Continue to command

To continue,Enter the_objc_rootAllocWithZone

Continue to enter the_class_createInstanceFromZone(Core code)

As shown here (the main part has been marked)

CLS ->instanceSize(extraBytes

Calloc (1, size) opens up space

InitInstanceIsa (CLS, hasCxxDtor) associates the created space with the class

A fine product is really the core thing, so the following began to explore.

1. Calculate the size

Let’s start with the instanceSize diagram

There is a judgment first, here to determine whether there is a cache, if there is a direct fetch return (save time)

Into the cache. FastInstanceSize

Command Displays the value of FAST_CACHE_ALLOC_MASK as 0x1FF8

As shown in the dotted break, we need to enter ALIGN16

So let’s go through this equation

Binary x = 0000 1001 15:0000 1111 9 + 15:0001 1000 ~ 15:1111 0000 9 + 15) & ~15: (17 + 15) &~ 15 Binary x = 0001 1000 15:0000 1111 9 + 15:0010 0111 ~15: 1111 0000 9 + 15) & ~ 15:0010 0000 32Copy the code

So align16 is an alignment method that returns a multiple of 16, 16 if less than 16, 16* (n +1) if x>16*n && x<16 * (n +1), the same as >> 4 <<4. This method is called byte alignment, what does byte alignment mean?

Improving CPU read speed: Reading data with a fixed length greatly improves CPU work efficiency

Next look at the normal method Command to enter alignedInstanceSize

Continue to enter theunalignedInstanceSize(Not alignedInstanceSize)

So this method returns the size of the object (before alignment). How do we determine the size of the object? Let’s think about it. What’s in the object? Properties, methods, and protocols; But the size of the object depends only on the member variables. This method returns the instanceSize of ro in data, that is, the size of the instance variable. The method’s comments also mention that May be unaligned depending on class’s ivars. Size of a member variable.

Then word_align

WORD_MASKIs 7, so the method is 8-byte aligned.

Finally, return to instanceSize

2. Open up space

This diagram breakpoint is just declared. Although there is an address, it is dirty and unavailable.

After passing the open method, I found that the address has changed, but AFTER Po obj, I found that it is only an address, which is not quite the same as the usual object

Because the next steps are still being taken,classThe binding

3, the binding

Enter the initInstanceIsa

Enter the initIsa

Ok, so I’m going to go back to _class_createInstanceFromZone and Po, and OBj is normal as usual, so I’m done with alloc.

4, summarize

Through the above exploration, a simple alloc flow chart is drawn below:

5, modify,

In callAlloc, we find three returns, all with breakpoints:

Then close the breakpoint and run the program to enterallocBefore opening breakpoints:

Go straight tocallAlloc, did not enterallocIn the? What’s going on here? Let’s take a closer lookreturnThe thingsobjc_msgSendSend a message with the parameterclsandallocAt the moment, the message isallocMethod? What the hell is going on? After god reminds andllvmFind, find in callallocMethods whenllvmThe method exchange, the code is too long screenshot is not convenient to directly on the code:

/// The ObjC runtime may provide entrypoints that are likely to be faster /// than an ordinary message send of the appropriate selector. /// /// The entrypoints are guaranteed to be equivalent to just sending the /// corresponding message. If the entrypoint is implemented naively as just a /// message send, using it is a trade-off: it sacrifices a few cycles of /// overhead to save a small amount of code. However, it's possible for /// runtimes to detect and special-case classes that use "standard" /// behavior; if that's dynamically a large proportion of all objects, using /// the entrypoint will also be faster than using a message send. /// /// If the runtime does support a required entrypoint, then this method will /// generate a call and return the resulting value. Otherwise it will return /// None and the caller can generate a msgSend instead. static Optional<llvm::Value *> tryGenerateSpecializedMessageSend(CodeGenFunction &CGF, QualType ResultType, llvm::Value *Receiver, const CallArgList& Args, Selector Sel, const ObjCMethodDecl *method, bool isClassMessage) { auto &CGM = CGF.CGM; if (! CGM.getCodeGenOpts().ObjCConvertMessagesToRuntimeCalls) return None; auto &Runtime = CGM.getLangOpts().ObjCRuntime; switch (Sel.getMethodFamily()) { case OMF_alloc: if (isClassMessage && Runtime.shouldUseRuntimeFunctionsForAlloc() && ResultType->isObjCObjectPointerType()) { // [Foo alloc] -> objc_alloc(Foo) or // [self alloc] -> objc_alloc(self) if (Sel.isUnarySelector() && Sel.getNameForSlot(0) ) return CGF.EmitObjCAlloc(Receiver, CGF.ConvertType(ResultType)); // [Foo allocWithZone:nil] -> objc_allocWithZone(Foo) or // [self allocWithZone:nil] -> objc_allocWithZone(self) if (Sel.isKeywordSelector() && Sel.getNumArgs() == 1 && Args.size() == 1 && Args.front().getType()->isPointerType() && Sel.getNameForSlot(0) == "allocWithZone") { const llvm::Value* arg = Args.front().getKnownRValue().getScalarVal(); if (isa<llvm::ConstantPointerNull>(arg)) return CGF.EmitObjCAllocWithZone(Receiver, CGF.ConvertType(ResultType)); return None; } } break; case OMF_autorelease: if (ResultType->isObjCObjectPointerType() && CGM.getLangOpts().getGC() == LangOptions::NonGC && Runtime.shouldUseARCFunctionsForRetainRelease()) return CGF.EmitObjCAutorelease(Receiver, CGF.ConvertType(ResultType)); break; case OMF_retain: if (ResultType->isObjCObjectPointerType() && CGM.getLangOpts().getGC() == LangOptions::NonGC && Runtime.shouldUseARCFunctionsForRetainRelease()) return CGF.EmitObjCRetainNonBlock(Receiver, CGF.ConvertType(ResultType)); break; case OMF_release: if (ResultType->isVoidType() && CGM.getLangOpts().getGC() == LangOptions::NonGC && Runtime.shouldUseARCFunctionsForRetainRelease()) { CGF.EmitObjCRelease(Receiver, ARCPreciseLifetime); return nullptr; } break; default: break; } return None; }Copy the code

Clip figure again:

case OMF_alloc:
            if (isClassMessage &&
                Runtime.shouldUseRuntimeFunctionsForAlloc() &&
                ResultType->isObjCObjectPointerType()) {
                // [Foo alloc] -> objc_alloc(Foo) or
                // [self alloc] -> objc_alloc(self)
                if (Sel.isUnarySelector() && Sel.getNameForSlot(0) )
                    return CGF.EmitObjCAlloc(Receiver, CGF.ConvertType(ResultType));
                // [Foo allocWithZone:nil] -> objc_allocWithZone(Foo) or
                // [self allocWithZone:nil] -> objc_allocWithZone(self)
                if (Sel.isKeywordSelector() && Sel.getNumArgs() == 1 &&
                    Args.size() == 1 && Args.front().getType()->isPointerType() &&
                    Sel.getNameForSlot(0) == "allocWithZone") {
                    const llvm::Value* arg = Args.front().getKnownRValue().getScalarVal();
                    if (isa<llvm::ConstantPointerNull>(arg))
                        return CGF.EmitObjCAllocWithZone(Receiver,
                                                         CGF.ConvertType(ResultType));
                    return None;
                }
            }
            break;
Copy the code

Continue to streamline:

if (Sel.isUnarySelector() && Sel.getNameForSlot(0) )
                    return CGF.EmitObjCAlloc(Receiver, CGF.ConvertType(ResultType));
Copy the code

Here, the alloc is returned to Calloc, so it directly enters the Calloc method and then returns to alloc after a series of judgments, so the modified alloc is shown in the figure: