1. Address and memory of the alloc object

Let’s start with an example:

SSLPerson *p1 = [SSLPerson alloc];
SSLPerson *p2 = [p1 init];
SSLPerson *p3 = [p2 init];
    
SSLPerson *p4 = [SSLPerson 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);
Copy the code

To view the print:

<SSLPerson: 0x600000eef1a0>-0x600000eef1a0-0x7ffeeec48448
<SSLPerson: 0x600000eef1a0>-0x600000eef1a0-0x7ffeeec48440
<SSLPerson: 0x600000eef1a0>-0x600000eef1a0-0x7ffeeec48438
<SSLPerson: 0x600000eef1b0>-0x600000eef1b0-0x7ffeeec48430
Copy the code
  • p1,p2,p3Pointing to the same memory space:0x600000eef1a0;
  • p4Point to memory space:0x600000eef1b0;
  • p1,p2,p3,p4The pointer addresses of are:0x7ffeeec48448,0x7ffeeec48440,0x7ffeeec48438,0x7ffeeec48430.

Analysis:

  • allocThere is open memory,initNo memory is allocated;
  • The memory address is0x6Is stored on the heap, and the pointer address is0x7At the beginning, it’s stored on the stack;
  • Pointer address tail48,40,38.30They’re off by 8, which means they’re contiguous memory addresses,48>40>38>30, indicating that the stack is high address to low address storage;
  • b0 > a0, indicating that the heap is low address to high address storage.

So, as we can see from the above, object memory addresses are created by alloc, so let’s see how alloc works.

Click on the alloc method to go to nsobject. h:

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

When we get to nsobject. h we find that there is no implementation of alloc, so here are three ways to explore the underlying.

2. Three methods of low-level exploration

2.1 Symbolic breakpoints

  1. inallocCall the position break point, run the program, when the break point breaks, holdcontrolKey, clickstep intoGo to the next step;
  2. Find the method in assemblyobjc_alloc;
  3. forobjc_allocMethod to add a symbolic breakpoint;
  4. Find the underlying library and method.

The detailed steps are as follows:

2.2 Assembly and process

  1. inallocCall the position break point, run the program;
  2. When the break point is broken,Xcode -> Debug -> Debug Workflow -> Always Show Disassembly, enter the compilation;
  3. In the assemblycallqFunction call, that’s how you find a method call;
  4. Find a method call that you can passstep intoOr continue debugging as a symbolic breakpoint to find the underlying libraries and methods.

The detailed steps are as follows:

2.3 The position is broken through the symbolic breakpoint under the known function

  1. inallocCall the position break point, run the program;
  2. addallocBreak point, click the break point jump button;
  3. Find the underlying library and method.

The detailed steps are as follows:

3. Compile the source code and explore the underlying layer

The three methods described above helped us find the underlying method, objc_alloc, and the underlying library, LibobJC. Let’s get the OBJC source code up and running for a deeper dive.

3.1 Download source code

Apple open source: opensource.apple.com/tarballs/

The download steps are as follows:

Download down the source code can not be directly compiled debugging, you can go to the configuration method, we use the configuration of the code below to explore, the version is 818.2.

3.2 source breakpoint debugging

According to the source debugging, the alloc call process is as follows:

Break point to enter alloc method:

+ (id)alloc {
    return _objc_rootAlloc(self);
}   
Copy the code

Break to enter the _objc_rootAlloc method:

id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
Copy the code

Break point to callAlloc method:

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)); }Copy the code

Break to _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

Here, the breakpoint goes to the core method _class_createInstanceFromZone:

static ALWAYS_INLINE id _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); 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

There are three core sub-methods in this method:

  • cls->instanceSize(extraBytes): Calculate the memory required;
  • (id)calloc(1, size): open up memory, return address pointer;
  • obj->initInstanceIsa(cls, hasCxxDtor): initializes a pointer that is associated with a class.

InstanceSize: Calculates the required memory

Break to 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

Breakpoint to cache. 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); }}Copy the code

Break to align16:

To get the required space size:

(x + size_t(15)) & ~size_t(15))

Explanation:

  • Let’s simplify the code:(x + 15) & ~15;
  • (x + 15)“Yes” means rightxPerform an upgrade ifxnot16, then step up to the next multiple level and wait for the next calculation;
  • ~ 15“Yes” means right15The not get1111, 0000,;
  • The last&When I do that, I get16A multiple of theta, and we’re left with a deficit16The erase.

Note: This algorithm is the same as >> 4 << 4

Take x = 23 as an example:

0010 0110 15:00 00 1111 ~ 15:1111 0000 38 & ~15 0010 0110 & 1111 0000 0010 0000 or 32

Why is 16-byte alignment needed

  1. Faster access:cpuInstead of storing data in bytes, it will treat memory as a block, each read is a fixed overhead, reducing memory access times to improve application performance.
  2. More safety:iOSEach of the objects in theisaPointer,8Bytes of memory, if8Byte alignment, objects are all right next to each other and there’s no space between them, so a single error or offset in memory access can cause memory chaos, so we pick8A multiple of16To align.

Now the required memory size has been calculated, and the next step is to open up the memory space according to the size.

Calloc: Opens up memory and returns an address pointer

Breakpoint to continue viewing:

You can see we didn’t assign a value to obj, but it already has an address, which means it’s a dirty address, which doesn’t really mean anything.

Check after calloc call:

You can find that the OBJ address has changed, indicating that the memory has been opened up.

InitInstanceIsa: Initializes the pointer that is associated with the class

Break point to enter initInstanceIsa method:

inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { ASSERT(! cls->instancesRequireRawIsa()); ASSERT(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); }Copy the code

Print oJB after initInstanceIsa call:

The print result is:

, by which time the pointer and class have been associated, the alloc exploration is complete.