The most commonly used method in iOS development is the alloc method, and today we’re going to explore what’s going on underneath alloc. First of all, we need to figure out what library the alloc method is in

From the breakpoint, you can see that the alloc method comes from LibobJC. Let’s download the objC library source code to explore further.

Alloc underlying code exploration

  • By searching thealloc {The symbol, we found itallocThe underlying source code implementation calls a function in the method_objc_rootAlloc
+ (id)alloc {
    return _objc_rootAlloc(self);
}
Copy the code
  • Enter the_objc_rootAllocIt is found that another function has been called internallycallAlloc
id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/.true/*allocWithZone*/);
}
Copy the code
  • callAllocThere are two cases, if yesOBJC2And there’s no customizationalloc/allocWithZone:Method is called_objc_rootAllocWithZoneThe delta function, that’s the same function that we’re going to take
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && ! cls))return nil;
    // 👇 see if the custom class has a custom alloc/allocWithZone method. If not, use the default alloc implementation, that is, _objc_rootAllocWithZone
    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
  • _objc_rootAllocWithZoneAnother function is still called inside the function_class_createInstanceFromZone._class_createInstanceFromZoneFunction is also the system’s final object creation function
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
  • The internal implementation of _class_createInstanceFromZone consists of three main steps

    • size = cls->instanceSize(extraBytes)Gets how much memory the instance object needs to allocate
    • obj = (id)calloc(1, size)callcallocGo to the heap to open up memory space
    • obj->initInstanceIsa(cls, hasCxxDtor)Associate the new memory space with the CLS
    • The specific source code is shown below, and the key parts have been annotated
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
    // 👇 whether there is a c++ constructor, custom pure OC class returns false
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(a);// 👇 whether there is a c++ destructor, custom pure OC class returns false
    bool hasCxxDtor = cls->hasCxxDtor(a);// 👇 returns basically true in OBJC2
    bool fast = cls->canAllocNonpointer(a);size_t size;

    // 👇 calculates the amount of memory to be allocated
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // 👇 allocates memory space and assigns a pointer to obj
        obj = (id)calloc(1, size);
    }
    if (slowpath(! obj)) {if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    The difference between 👇 and else is whether nonpointer is true or false when calling initIsa
    if(! zone && fast) {// 👇 is here to associate CLS with obj, that is, to assign CLS to 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;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}
Copy the code

This is the main process for creating instance objects, but there are three key areas where the code is not clear

Gets the size of memory required to create the instance object
  • instanceSizeIs a function used to get the size of memory required to create an instance objectfastInstanceSizefunction
// 👇 gets the size of memory needed to create the instance object
inline size_t instanceSize(size_t extraBytes) const {
    // 👇 Custom classes go fastPath
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
        // 👇 Calculate the required memory size by using the following functions
        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
  • fastInstanceSizeThe implementation of the following is visible internally with 16 bytes of memory alignment
size_t fastInstanceSize(size_t extra) const
{
    ASSERT(hasFastInstanceSize(extra));

    if (__builtin_constant_p(extra) && extra == 0) {
        return _flags & FAST_CACHE_ALLOC_MASK16;
    } else {
        // 👇 gO to the else process in iOS, size is the required memory space calculated according to the structure
        size_t size = _flags & FAST_CACHE_ALLOC_MASK;
        // remove the FAST_CACHE_ALLOC_DELTA16 that was added
        // by setFastInstanceSize
        👇 also requires 16-byte memory alignment in the align16 function
        return align16(size + extra - FAST_CACHE_ALLOC_DELTA16); }}Copy the code
callcallocGo to the heap to open up memory space
  • As we hold downcommandThen click thecallocThere is no way to jump to the function implementation, so we add another one in the projectcallocIs visible from the breakpointcallocThe implementation of thelibsystem_mallocIn the

Note that this is not libsystem, it is malloc library, so let’s download malloc source code

  • inmallocSource code we searchcalloc(size_tCame tocallocSource code implementation,callocThe implementation of is relatively simple, inferred frommalloc_zone_callocWhen the functioncallocThe main implementation of
👇 calloc source code implementation
void * calloc(size_t num_items, size_t size)
{
	void *retval;
	// 👇 extrapolating this line from the return value is the focus of the entire function
	retval = malloc_zone_calloc(default_zone, num_items, size);
	if (retval == NULL) {
		errno = ENOMEM;
	}
	return retval;
}
Copy the code
  • malloc_zone_callocInside the function we can also get a key line of codeptr = zone->calloc(zone, num_items, size)But here if I clickcallocContinue to go down will find unable to continue with the source. You can put a breakpoint here if the project is going to compile,lldbThe inputp zone->callocI’ll get the underlying calldefault_zone_callocfunction
void * malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
	MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);

	void *ptr;
	if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
		internal_check(a); }// 👇 here we also get this line of code as the key code according to the principle of backsliding,
	// 👉 Calloc called here will eventually call default_zone_calloc
	ptr = zone->calloc(zone, num_items, size);
	
	if (malloc_logger) {
		malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
				(uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
	}

	MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
	return ptr;
}
Copy the code
callinitInstanceIsaassociatedclass
  • initInstanceIsaInternally calledinitIsaThe isa function initializes isa
inline void  objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(! cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}
Copy the code
  • initIsaThe implementation of the code we saw created newisa_t newisa(0)fornewisaThe key thing to do is to assignnewisa.setClass(cls, this)
inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    👇 nonpointer 0 indicates that isa isa pure isa pointer. Isa stores only class information
    if(! nonpointer) { newisa.setClass(cls, this);
    👇 nonpointer 1 indicates that isa is an optimized ISA pointer. The class information is stored in the Shift class of ISA
    } else {
        ASSERT(! DisableNonpointerIsa);ASSERT(! cls->instancesRequireRawIsa());

👇 iOS SUPPORT_INDEXED_ISA is not false, so go to the else process
#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex(a) >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(a);#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        // 👇 set the class information in ISA to associate ISA with CLS
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    // 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
  • setClassWe can clearly see that in the functionshiftclsThe value is assigned, and at the time of the assignment, theclsThe three to the right are becauseisaThe lower three digits ofnonpointer,has_assocandhas_cxxdtor
inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
    // Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#   if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
    // No signing, just use the raw pointer.
    uintptr_t signedCls = (uintptr_t)newCls;

#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
    // We're only signing Swift classes. Non-Swift classes just use
    // the raw pointer
    uintptr_t signedCls = (uintptr_t)newCls;
    if (newCls->isSwiftStable())
        signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
    // We're signing everything
    uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

#   else
#       error Unknown isa signing mode.
#   endif

    shiftcls_and_sig = signedCls >> 3;

#elif SUPPORT_INDEXED_ISA
    // Indexed isa only uses this method to set a raw pointer class.
    // Setting an indexed class is handled separately.
    cls = newCls;
// 👇 Since our custom classes are basically nonpointer ISA, we assign the newCls variable to the shiftcls variable directly by moving it three places right
// 👇 moves three Spaces to the right because the first three Spaces are nonpointer, has_assoc, has_cxx_dtor flags respectively
#else // Nonpointer isa, no ptrauth
    shiftcls = (uintptr_t)newCls >> 3;
#endif
}
Copy the code