preface

In the OC development most basic is the creation of the object, also is to use alloc and init method to initialize the object, but in our daily developing this object to create the most fundamental and most simple operation we only know how to use, does not know what what is going on inside of the bottom, this is what this article needs the following introduction alloc underlying principle.

To better illustrate the following, we define a TestObject class

@interface TestObject : NSObject

@end

#import "TestObject.h"

@implementation TestObject

+ (void)initialize
{
    NSLog(@"%s",__func__);
}

+(instancetype)allocWithZone:(struct _NSzone *)zone{
    NSLog(@"%s",__func__);
    return [super allocWithZone:zone];
}
@end

Copy the code

I’ll start with a simple example

    TestObject *test = [TestObject alloc];
    TestObject *test1 = [test init];
    TestObject *test2 = [test1 init];
    NSLog(@"========= Output result ============");
    NSLog(@"%@---%p".test, &test);
    NSLog(@"%@---%p".test1, &test1);
    NSLog(@"%@---%p".test2, &test2); = = = = = = = = = the output = = = = = = = = = = = = 2020-04-28 09:45:04. 657448 + 0800 AllocDemo [3448-65473] < TestObject: 0x600002DFDF70 >-- 0x7ffEE4081188 2020-04-28 09:45:04.657608+0800 AllocDemo[3448:65473] <TestObject: 0x600002DFDF70 >-- 0x7ffEE4081180 2020-04-28 09:45:04.657769+0800 AllocDemo[3448:65473] <TestObject: 0x600002dfdf70>---0x7ffee4081178Copy the code

As you can see from the example, these three objects are the same object, but the address of the pointer is different, so the question is why these three objects are the same address? What’s going on underneath alloc and init?

1. Explore alloc through assembly

Xcode: Debug->Debug Workflow->Always show Disassembly

2. With the form of system breakpoint, you can see the process of each step of alloc layer step by step. In this way, the entry point to the underlying alloc is the objc_alloc underlying method

objc_alloc
libobjc.A.dylib
alloc

2. Source code analysis of alloc bottom layer

Ios_objc4-756.2 下载, 下载, 下载, 下载, 下载, 下载, 下载, 下载, 下载. Next, I explore the source code for ObjC4-756.2.

2.1 Entry to AlloC Objc_alloc

As you can see from the above compilation, objc_alloc is the underlying entry to alloc, but if we click on it from the source code, we will jump directly to it

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

This is a class method. Why is that? Because the first time alloc goes to objc_alloc, it only goes once. The reason is that SEL implements imp function address. In macho’s binding symbol sel_alloc, objc_alloc can be found

fixupMessageRef

So use the source code to put the breakpoint on the objc_alloc method and proceed to the next step

2.2 callAlloc

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if(slowpath(checkNil && ! cls))return nil;

#if __OBJC2__
    if(fastpath(! cls->ISA()->hasCustomAWZ())) { // No alloc/allocWithZone implementation. Go straight to the allocator. // fixme store hasCustomAWZin the non-meta class and 
        // add it to canAllocFast's summary if (fastpath(cls->canAllocFast())) { // No ctors, raw isa, etc. Go straight to the metal. bool dtor = cls->hasCxxDtor(); id obj = (id)calloc(1, cls->bits.fastInstanceSize()); if (slowpath(! obj)) return callBadAllocHandler(cls); obj->initInstanceIsa(cls, dtor); return obj; } else { // Has ctor or raw isa or something. Use the slower path. id obj = class_createInstance(cls, 0); if (slowpath(! obj)) return callBadAllocHandler(cls); return obj; } } #endif // No shortcuts available. if (allocWithZone) return [cls allocWithZone:nil]; return [cls alloc]; }Copy the code

The first entry is from objc_alloc, where checkNil is true and allocWithZone is false, because the first entry is fastPath (! CLS ->ISA()->hasCustomAWZ()) does not yet have a pointer to ISA, so it is false, Return [CLS alloc] directly, in which objc_msgSend calls the Initialize class method after the [CLS alloc] method is called, since this method will be called before the class is first initialized.

alloc–>objec_alloc–>callAlloc(Class cls, true, false)

#define fastpath(x) (__builtin_expect(bool(x), 1)) #define slowpath(x) (__builtin_expect(bool(x), 0)) slowpath(bool) and fastpath(bool) : often used if-else, can optimize the speed of judgment. GCC command __builtin_expect(EXP,N) is applied to optimize the code layout of the compiler at compile time and reduce the performance consumption brought by instruction jump.Copy the code

2.3 Alloc second process

Through the first return of callAlloc, the breakpoint is jumped to

+ (id)alloc {
    return _objc_rootAlloc(self);
}

// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
Copy the code

The callAlloc method is called again, but the arguments checkNil is false and allocWithZone is true. Since the initialize class was called, the bits value is now available, directly to if(FastPath (! CLS – > ISA () – > hasCustomAWZ ())).

2.3.1 hasCustomAWZ() method parsing

    bool hasCustomAWZ() {
        return ! bits.hasDefaultAWZ();
    }
Copy the code

The hasCustomAWZ method is used to determine whether a custom allocWithZone method is implemented, and if not, the system default allocWithZone method is called.

Because fastPath (CLS ->canAllocFast()) always returns false according to the source code

    bool canAllocFast() { assert(! isFuture());return bits.canAllocFast();
    }
#if FAST_ALLOC...#else... a boolcanAllocFast() {
        return false;
    }
#endif

#define FAST_ALLOC (1UL<<2)

Copy the code

So it’s not going to execute the if block, it’s just going to execute the else block

2.3.2 The impact of allocWithZone class method implemented in the class on the process

1. Fastpath (! CLS ->ISA()->hasCustomAWZ()) checks the code inside and finally returns class_createInstance

            id obj = class_createInstance(cls, 0);
            if(slowpath(! obj))return callBadAllocHandler(cls);
            return obj;
Copy the code
id class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}
Copy the code

So the process is: alloc->_objc_rootAlloc->callAlloc(CLS,false,true)->class_createInstance->_class_createInstanceFromZone

2. Classes that implement the allocWithZone method do not run FastPath (! CLS ->ISA()->hasCustomAWZ()

    if (allocWithZone) return [cls allocWithZone:nil];
Copy the code

And the allocWithZone method implemented in the class is executed

173680+0800 LGTest[2370:69449] +[TestObject allocWithZone:]Copy the code

The flow in the form of breakpoints looks like this: alloc->_objc_rootAlloc->callAlloc(cls,false,true)->allocWithZone->_objc_rootAllocWithZone->class_createInstance->_class _createInstanceFromZone

In both cases, the _class_createInstanceFromZone function is executed.

2.3.4 _class_createInstanceFromZone method

The source code for this method

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if(! cls)return nil;

    assert(cls->isRealized());

    // Read classCxx_construct implementation of the constructor bool hasCxxCtor = cls->hasCxxCtor(); CXX -- destruct destruct bool hasCxxDtor = CLS ->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); Size_t size = CLS ->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (! zone && fast) { obj = (id)calloc(1, size); if (! obj) return nil; // initialize the instance isa pointer obj->initInstanceIsa(CLS, hasCxxDtor); } else { if (zone) { obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size); } else { obj = (id)calloc(1, size); } if (! obj) return nil; // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } if (cxxConstruct && hasCxxCtor) { obj = _objc_constructOrFree(obj, cls); } return obj; }Copy the code

The following is an analysis of _class_createInstanceFromZone

1. CLS ->instanceSize(extraBytes)

size_t instanceSize(size_t extraBytes) {
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
}
    
uint32_t alignedInstanceSize() {
    return word_align(unalignedInstanceSize());
}
    
uint32_t unalignedInstanceSize() {
    assert(isRealized());
    return data()->ro->instanceSize;
}
    
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

#ifdef __LP64__
# define WORD_MASK 7UL
#else
# define WORD_MASK 3UL
#endif
Copy the code

The TestObject has no attributes in it, so I extraBytes 0. Not really, because there is an ISA

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
Copy the code

The flow shows the size of the instance compiled in the unalignedInstanceSize method. Since ISA is an object pointer, the size of this instance is 8 bytes. So the x passed to the word_align method is 8. WORD_MASK is 7 on 64-bit systems and 3 otherwise, so the word_align() method evaluates to 8 bytes alignment on 64-bit systems, which is equivalent to a multiple of 8. The size returned to the instanceSize() method is the size the object needs to be 8, because 16 is returned for anything less than 16.

2. The calloc function initializes the allocated memory.

3. The initInstanceIsa function initializes ISA and is associated with CLS, which will be covered later.

2.4 the init and new

1. After introducing alloc, init method through source code

- (id)init {
    return _objc_rootInit(self);
}

id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}
Copy the code

It’s actually going to return itself.

2. The new method is a combination of callAlloc and init methods

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}
Copy the code

3. The last

The underlying flow chart of the final ALLOC was obtained through the above process