Alloc source code process analysis

First throw up a flow chart,ocobjectallocThe general flow is shown in the figure below

XXXObject *object1 = [XXXObject alloc];
XXXObject *object2 = [XXXObject alloc]init];
XXXObject *object3 = [XXXObject new];
Copy the code

These are some of the most familiar lines of code. Creates and returns an instance of a class. So what is its internal process? What do alloc and init and new do? We’ll explore this next.

Environment: Xcode 11.5

Source: objc4-781

[XXXObject alloc]

As you can see from the source code, the alloc method should call NSObject’s alloc method

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

But the interruption point is found in the executionallocMethod will be entered firstobjc_allocMethod is then executed[NSObject alloc].Internal implementation is as follows:

// call [CLS alloc]. Objc_alloc (Class CLS) {&1 return callAlloc(CLS, true/*checkNil*/, false/*allocWithZone*/); }Copy the code

If we write the code as follows:

XXXObject *object2 = [XXXObject alloc]init];
Copy the code

So it’s going to enter firstobjc_alloc_initMethod, the stack looks like this:Internal implementation is as follows:

// Calls [[cls alloc] init].
id
objc_alloc_init(Class cls)
{
    return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init];
}
Copy the code

CallAlloc is called in the same way, and the arguments are identical, except that init is added.

The LLVM compiler is responsible for compiling the LLVM file.

/// Instead of '[[MyClass alloc] init]', try to generate
/// 'objc_alloc_init(MyClass)'. This provides a code size improvement on the
/// caller side, as well as the optimized objc_alloc.
Copy the code

LLVM optimizes code like [[MyClass alloc] init] or [MyClass alloc] during compilation.

Alloc, _objc_rootAlloc

Either objc_alloc or objc_alloc_init will eventually call the [NSObject alloc] method.

+ (id)alloc {return _objc_rootAlloc(self); } + (id)_objc_rootAlloc(Class CLS) {&2 return callAlloc(CLS, false/*checkNil*/, true/*allocWithZone*/); }Copy the code

callAlloc

static ALWAYS_INLINE id 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

As mentioned above, the objc_alloc and objC_alloc_init methods also call callAlloc, eventually performing the objc_msgSend message on the last line, and finally recalling the [NSObject alloc] method, Then the alloc method also calls the callAlloc method again, this time calling the _objc_rootAllocWithZone(CLS, nil) method.

A few points to note are:

  • The slowPath (x) and FastPath (x) macros are used for compilation optimization, which allows the compiler to adjust the order of instructions and increase execution efficiency.
#define fastPath (x) (__builtin_expect(bool(x), 1)) slowPath (__builtin_expect(bool(x)), 0)) means x is likely to be 0Copy the code
  • HasCustomAWZ () whether there is a customallocWithZoneMethods.
bool hasCustomAWZ() const { return ! cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ); }Copy the code

You can see that the return value of this function is related to the cache.

_objc_rootAllocWithZone

NEVER_INLINE id _objc_rootAllocWithZone(Class cls, // allocWithZone under __OBJC2__ ignores the zone parameter return _class_createInstanceFromZone(cls, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC); }Copy the code

_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) {// Avoid multithreading problems 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(); // isa type distinction for isa. If a class and instances of its parent cannot use isa of type isa_t, the return value is false, but in objective-c 2.0, most classes are supported. Size_t size = CLS ->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (zone) { obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); Obj = (id)calloc(1, size); } if (slowpath(! obj)) { if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { return _objc_callBadAllocHandler(cls); } return nil; } if (! Zone && fast) {// 3. Initialize the instance object's ISA 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; }}Copy the code

The core process is as follows:

  1. Gets the size of memory that the instance needs to open upsize
  2. According to thesizeClear the corresponding memory space and returnidPointer to type
  3. Initialize the correspondingisaPointer to the

InstanceSize calculates memory space

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

The fastInstanceSize function in cache_t is called

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

Finally, the align16 method is called to return the true memory size that needs to be turned on, which must be a multiple of 16.

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}
Copy the code

obj->initInstanceIsa

The initInstanceIsa method eventually calls initIsa.

inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { ASSERT(! cls->instancesRequireRawIsa()); ASSERT(hasCxxDtor == cls->hasCxxDtor()); initIsa(cls, true, hasCxxDtor); } inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { ASSERT(! isTaggedPointer()); if (! nonpointer) { isa = isa_t((uintptr_t)cls); } else { ASSERT(! DisableNonpointerIsa); ASSERT(! cls->instancesRequireRawIsa()); isa_t newisa(0); #if SUPPORT_INDEXED_ISA ASSERT(cls->classArrayIndex() > 0); newisa.bits = ISA_INDEX_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.indexcls = (uintptr_t)cls->classArrayIndex(); #else newisa.bits = ISA_MAGIC_VALUE; // isa.magic is part of ISA_MAGIC_VALUE // isa.nonpointer is part of ISA_MAGIC_VALUE newisa.has_cxx_dtor = hasCxxDtor; newisa.shiftcls = (uintptr_t)cls >> 3; #endif // This write must be performed in a single store in some cases // (for example when realizing a class because other threads // may simultaneously try to use the class). // fixme use atomics here to guarantee single-store and to //  guarantee memory order w.r.t. the class index table // ... but not too atomic because we don't want to hurt instantiation isa = newisa; }}Copy the code

Put the source code, the details will be analyzed next time.

init

Init is simpler and basically provides an interface for developers to override.

- (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;
}

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

New is alloc init, init init, alloc init, alloc init, init, etc.