preface

In the future, you will thank yourself for your efforts now. Every effort is the wealth of the future life road!

Pointer address and memory address

YYObjet *p1 = [YYObjet alloc];
YYObjet *p2 = [p1 init];
YYObjet *p3 = [p1 init];
YYObjet *p4 = [YYObjet alloc];

    
NSLog(@"%@-%p-%p",p1,p1,&p1);
NSLog(@"%@-%p-%p",p2,p2,&p2);
NSLog(@"%@-%p-%p",p3,p3,&p3);
NSLog(@"%@-%p-%p",p4,p4,&p4);

<YYObjet: 0x6000025ec780>-0x6000025ec780-0x7ffeef1a1068
<YYObjet: 0x6000025ec780>-0x6000025ec780-0x7ffeef1a1060
<YYObjet: 0x6000025ec780>-0x6000025ec780-0x7ffeef1a1058
<YYObjet: 0x6000025ec7b0>-0x6000025ec7b0-0x7ffeef1a1050

Copy the code

It can be obtained by comparison:

  • Objects P1, P2, and P3 have the same memory address but different pointer addresses
  • P4 and P1, P2, P3 object memory address and pointer address are different

Alloc opens up memory space, init does not open up memory operation, through init source can be seen intuitively.

Three ways to explore the bottom

1. Symbolic breakpoints

steps

  • Xcode comes with debugging tools:Ctrl + Step into

  • Add sign breakpointlibobjc.A.dylib objc_alloc:

results

2. Assembly

3. Symbol breakpoint determination unknown

  • Simply put: because we are sure that it will be calledalloc, so add a sign breakpoint directly to it

results

With the above three debugging methods, the breakpoint is broken at libobjc.a.dylib. You can download the corresponding open source code from apple’s open source website, and there are also many configuration processes on the engineering website. You can download debugging [drive link], and pay more attention to the creation process of alloc.

Alloc creation process

Following the process trace, _class_createInstanceFromZone was finally called

This can be expressed more clearly with a flow chart

_class_createInstanceFromZone

_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, int construct_flags = OBJECT_CONSTRUCT_NONE, bool cxxConstruct = true, size_t *outAllocatedSize = nil) { ASSERT(cls->isRealized()); // Read class's info bits all at once for performance bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); size_t size; size = cls->instanceSize(extraBytes); // Calculate memory size (compile time) if (outAllocatedSize) *outAllocatedSize = size; id obj; if (zone) { obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else { obj = (id)calloc(1, size); } if (slowpath(! obj)) { if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { return _objc_callBadAllocHandler(cls); } return nil; } if (! zone && fast) { obj->initInstanceIsa(cls, hasCxxDtor); Else {// Use raw pointer ISA on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } if (fastpath(! hasCxxCtor)) { return obj; } construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE; return object_cxxConstructFromClass(obj, cls, construct_flags); }Copy the code
  • instanceSize
inline size_t instanceSize(size_t extraBytes) const { if (fastpath(cache.hasFastInstanceSize(extraBytes))) { return cache.fastInstanceSize(extraBytes); Size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; Return size; }Copy the code
  • fastInstanceSize
size_t fastInstanceSize(size_t extra) const { ASSERT(hasFastInstanceSize(extra)); if (__builtin_constant_p(extra) && extra == 0) { return _flags & FAST_CACHE_ALLOC_MASK16; } else { size_t size = _flags & FAST_CACHE_ALLOC_MASK; // remove the FAST_CACHE_ALLOC_DELTA16 that was added // by setFastInstanceSize return align16(size + extra - FAST_CACHE_ALLOC_DELTA16); // byte alignment}}Copy the code
  • align1616-byte alignment
static inline size_t align16(size_t x) { return (x + size_t(15)) & ~size_t(15); } for example, 10: Align16 (10){=(10 + size_t(15)) &~ size_t(15) =(10 + 15) &~ 15 = 25&~ 15 = 11001&~ 01111 // Convert to binary = 11001&10000 = 10000 (decimal: 16)}Copy the code

But does it really follow the Jump to Definition steps? If you look at the GIF below, you can see that the breakpoint will break objc_alloc first, not _objc_rootAlloc first

alloc -> objc_alloc

fixupMessageRef(message_ref_t *msg) { msg->sel = sel_registerName((const char *)msg->sel); if (msg->imp == &objc_msgSend_fixup) { if (msg->sel == @selector(alloc)) { msg->imp = (IMP)&objc_alloc; Else if (MSG ->sel == @selector(allocWithZone:)) {MSG ->imp = (imp) &objc_allocwithzone; } else if (msg->sel == @selector(retain)) { msg->imp = (IMP)&objc_retain; } else if (msg->sel == @selector(release)) { msg->imp = (IMP)&objc_release; } else if (msg->sel == @selector(autorelease)) { msg->imp = (IMP)&objc_autorelease; } else { msg->imp = &objc_msgSend_fixedup; } } else if (msg->imp == &objc_msgSendSuper2_fixup) { msg->imp = &objc_msgSendSuper2_fixedup; } else if (msg->imp == &objc_msgSend_stret_fixup) { msg->imp = &objc_msgSend_stret_fixedup; } else if (msg->imp == &objc_msgSendSuper2_stret_fixup) { msg->imp = &objc_msgSendSuper2_stret_fixedup; } #if defined(__i386__) || defined(__x86_64__) else if (msg->imp == &objc_msgSend_fpret_fixup) { msg->imp = &objc_msgSend_fpret_fixedup; } #endif #if defined(__x86_64__) else if (msg->imp == &objc_msgSend_fp2ret_fixup) { msg->imp = &objc_msgSend_fp2ret_fixedup; } #endif }Copy the code

If sel == @selector(alloc), then IMP = (imp)&objc_alloc, where SEL and IMP are re-bound. The compiler LLVM is covered in more detail, but you can check out the LLVM article if you are interested.

Alloc flow in the LLVM compiler

  • Alloc -> objc_alloc(Receiver(message Receiver) flag) -> callAlloc -> objc_msgSend(CLS, @selector(alloc))
  • alloc -> objc_alloc -> callAlloc -> objc_msgSend(cls, @selector(alloc)) -> + (id)alloc -> _objc_rootAlloc

Since the Receiver is already marked on the first loop, the second loop does not go through the alloc method when rebinding occurs, which is why callAlloc goes through the breakpoint twice.