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 thecontrol
Click on thestep into
Enter 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_MASK
Is 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,class
The 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 enteralloc
Before opening breakpoints:
Go straight tocallAlloc
, did not enteralloc
In the? What’s going on here? Let’s take a closer lookreturn
The thingsobjc_msgSend
Send a message with the parametercls
andalloc
At the moment, the message isalloc
Method? What the hell is going on? After god reminds andllvm
Find, find in callalloc
Methods whenllvm
The 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: