This blog mainly talks about the process of OC object alloc, mainly divided into two steps: 1: how to explore the execution process of alloc method 2: analyze the execution process of alloc function

The following is a flow chart of OC object alloc

1. How to explore the execution process of alloc method

As shown above, there are some methods called in the alloc flow of an OC object.

  1. We can set the symbol breakpoint directly by using xcode symbol breakpoint with Xcode (hold command, step into)

Disable the symbol breakpoint on the line of code where we want to view an object instantiation. At this point we can see that alloc calls objc_alloc internally, and then we can set the symbolic breakpoint directly. Intercept the objc_alloc function to see what method is called internally, and so on

2. View the call chain through disassembly debugging

Select Always Show Disassembly to quickly locate the objc_Alloc binding symbol breakpoint, hold down Command, and click Step into.

3. We directly through the source analysis, we through the above symbol breakpoint and disassembly debugging, know objc_alloc is libobjC.a.dylib function, we go to Apple Open Source download source code compiled, step by step debugging.

Two: analyze the alloc function execution process

When instantiating an instance of the NSObject class, disassembly + source debugging shows that the NSObject class method alloc is not executed. Instead, objc_alloc is executed directly

1. alloc

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

Copy the code

2. objc_alloc

id objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*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

In the callAlloc method, you’ll find that if (fastPath (! cls->ISA()->hasCustomAWZ()))

inline Class objc_object::ISA(bool authenticated) { ASSERT(! isTaggedPointer()); return isa.getDecodedClass(authenticated); } // class or superclass has default alloc/allocWithZone: implementation // Note this is is stored in the metaclass. #define FAST_CACHE_HAS_DEFAULT_AWZ (1<<14) bool hasCustomAWZ() const { return ! cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ); }Copy the code

If there ISA cache in the cache, execute _objc_rootAllocWithZone. If there ISA cache in the cache, execute _objc_rootAllocWithZone. If you execute objc_msgSend and continue to run the program, you’ll see that NSObject is already cached in the cache, so _objc_rootAllocWithZone is executed directly

_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

_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

_objc_rootAllocWithZone -> _class_createInstanceFromZone Creates an instance of the class in three steps:

1. CLS ->instanceSize calculates how much memory the object needs

2. CLS ->calloc

3. CLS ->initInstanceIsa Associates an object with a class through ISA

And then it goes straight back to OBj

Add a custom type call to alloc

It is found that p1 instance is in the alloc execution flow, and there is no cache in the cache, using runtimeobjc_msgSendThe function calls the alloc method, and then calls again_objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone -> instanceSize -> calloc -> initInstanceIsaThen skip p1, and you’ll see that p2’s alloc does the same thing as NSObject, and doesn’t pass through the Runtimeobjc_msgSendcallallocMethod, because at this point there’s already a cache of LGPerson in the cache_objc_rootAllocWithZoneFollow the following process

conclusion

A procedure for customizing class alloc: By calling objc_alloc->callAlloc, there are two cases of callAlloc cache and no cache. If there is no cache, go to _objc_rootAllocWithZone -> _class_createInstanceFromZone -> instanceSize -> calloc -> initInstanceIsa. If no cache exists, go to _objc_rootAllocWithZone -> _class_createInstanceFromZone -> instanceSize -> calloc -> initInstanceIsa CallAlloc is called internally via objc_msgSend, and _objc_rootAlloc -> callAlloc is executed internally. The following steps are the same as above.