IOS bottom layer exploration – alloc & init

Time:2021-7-8

IOS bottom layer exploration - alloc & init

Exploration of alloc & init

AsiOS  Developers, the most we deal with every day should be objects. From the perspective of object-oriented design, object creation and initialization are the most basic content. So, let’s explore it together todayiOS  Most commonly used in  alloc  andinit  How to realize the bottom layer of it.

1、 How to explore the bottom

For the third-party open source framework, we have certain methods and routines to analyze the internal principles and details. And foriOS  The bottom, especiallyOC  At the bottom, we may need to use some methods that are not commonly used in development.

The main purpose of our series is to explore the bottom, so we asiOS  Developers need to pay attention to the process from application startup to application deploymentkill  Delete the content of the whole life cycle. We might as well start from the most familiarmain  Function, generally speaking, we start withmain.m  When a breakpoint is made in the file, the call stack view on the left should be as shown in the following figure:

IOS bottom layer exploration - alloc & init

To get such a call stack, there are two points to note:

  • Need to closeXcode  leftDebug  At the bottom of the areashow only stack frames with debug symbols and between libraries

IOS bottom layer exploration - alloc & init

  • We need to add one_objc_initThe end point of the symbol

IOS bottom layer exploration - alloc & init

We can easily get a simple and rough loading process structure from the call stack information above

IOS bottom layer exploration - alloc & init

We now have such a simple process structure in mind. When we analyze the bottom layer in the later stage, we will go back and sort out the whole startup process.

Next, let’s start the actual exploration process.

Let’s just open itXcode  New oneSingle View App  Engineering, and then we’re inViewController.m  Call in filealloc  method.

NSObject *p = [NSObject alloc];

We follow the normal way to explore the source code, directly press and holdCommand + Control  Come on inalloc  Internal implementation, but the result is not as we wish, we came to a header file, onlyalloc  Method, there is no corresponding implementation. At this time, we will fall into deep doubt. In fact, at this time, we only need to remember the following three common ways of exploration to solve the problem

1.1 code breakpoints directly

The specific operation mode is as followsControl + in 

IOS bottom layer exploration - alloc & init  therein  It refers to the button in the red part of the picture on the left. In fact, the operation here is called  Step into instruction 。 We can come here

IOS bottom layer exploration - alloc & init

It’s not hard for us to see what we’re looking forlibobjc.A.dylib  This dynamic link library.

1.2 open disassembly display

The specific operation mode is openDebug  Under the menuDebug Workflow  NextAlways Show Disassembly 

IOS bottom layer exploration - alloc & init

Next, let’s go to the next code breakpoint, and then step by step debugging will come to the following figure:

IOS bottom layer exploration - alloc & init

1.3 lower symbol breakpoint

Let’s choose firstSymbolic Breakpoint, and then enterobjc_alloc , As shown in the figure below:

IOS bottom layer exploration - alloc & init IOS bottom layer exploration - alloc & init

IOS bottom layer exploration - alloc & init

So far, we’ve got italloc  Implementation inlibObjc  This dynamic library, and apple has just opened up this part of the code, so we can   Apple open source official website latest version 10.14.5   Download it. abreast of the timeslibObc  756.

IOS bottom layer exploration - alloc & init

2、 ExplorelibObjc  Source code

We downloaded itlibObjc  Source code to our computer is not directly run, we need to be configured to achieve source tracking process. This piece of content is not within the scope of this article, readers can refer to IOS_ Objc4-756.2 latest source code compilation and debugging.

Well configuredlibObjc  After that, we create a new command line project and run the following code:

NSObject *myObj = [NSObject alloc];

2.1 objc_alloc

Then we go straight down to the symbolic breakpointobjc_alloc , And then step by step debugging, first came isobjc_alloc 

// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

2.2 the first call alloc

And then it will comecallAlloc  Method. Note that the third parameter here isfalse 

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    //Judge whether the incoming checknil is empty
    if (slowpath(checkNil && !cls)) return nil;

    //If the current compilation environment is OC 2.0
#if __OBJC2__
    //There is no custom allocwithzone for the current class
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        //If you don't implement alloc or allocwithzone, you will come here and directly open up the memory.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        //To fix a class without metaclasses, in other words, it does not inherit from nsobject
        //Judge whether the current class can quickly open up memory. Note that it will never be called here, because canallocfast is internal
        //Returns false
        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];
}

2.3 _objc_rootAlloc

Because we’re hereobjc_init  The third parameter passed in fromallocWithZone  yestrue , And ourcls  byNSObject , That is to say, they will come here directlyreturn [cls alloc] 。 We’ll come down nextalloc  method:
 

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

And then we’re going in_objc_rootAlloc  Methods: internal data were collected

// 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*/);
}

2.4 the second call alloc

Isn’t it a little similar? Yes, we entered the first stepobjc_init  Also calledcallAlloc  Method, but there are two different parameters, the second parametercheckNil  Whether it is necessary to judge the air and pass it directlyfalse , From the system point of view, we have called it for the first timecallAlloc  When the air was judged, there was no need to judge it again. The third parameterallocWithZone  It is said thattrue , For this method, I refer to the Apple Developer’s documentation, which is explained as follows:

Do not override allocWithZone: to include any initialization code. Instead, class-specific versions of init... methods.
This method exists for historical reasons; memory zones are no longer used by Objective-C.
Don’t overloadallocWithZone  And fill in any initialization code inside it. Instead, you shouldinit...  Inside the class initialization operation.
This method exists for a historical reason, memoryzone  It’s no longer being usedObjective-C  Used.

According to Apple’s developer documentation, in factallocWithZone  Essentially andalloc  There’s no difference. It’s justObjective-C  In ancient times, programmers needed to useallocWithZone  To optimize the memory structure of an object, and at the moment, you actually writealloc  andallocWithZone  The first mock exam is at the bottom.

OK, that’s a long way off. Let’s move on to the topic againcallAlloc  Inside the method, the second timecallAlloc  If so, in!cls->ISA()->hasCustomAWZ()  Here we judgecls  No custom  allocWithZone  The judgment here is essentially rightcls  that isobject_class  In this structure, theclass_rw_t  Offlags  Same as previous macroRW_HAS_DEFAULT_AWZ 。 After the author’s test, in the first entrycallAlloc  Inside the method,flags  The value is 1, and then  flags  And up1<<16  The result is 0, back in the pastfalse , And then in thehasCustomAWZ  Here, after negation, what is returned istrue , And then reverse it again, and you’ll skip itif  The logic inside; And a second entrycallAlloc  Inside the method,flags  The value is a large integer, similar to the value above1<<16  The result is not zero, sohasDefaultAWZ  Will returntrue , thathasCustomAWZ  It’ll be back herefalse , So back tocallAlloc  You’ll get in when you’re inif  The logic inside has changed.

Let’s just say that in our OC class structure, there is a structure calledclass_rw_t , There is a structure calledclass_ro_t 。 amongclass_rw_t  You can expand classes at run time, including properties, methods, protocols, and so onclass_ro_t  Member variables, properties and methods are stored, but these are determined at compile time and cannot be modified at run time.

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

 bool hasDefaultAWZ() {
     return data()->flags & RW_HAS_DEFAULT_AWZ;
 }

And then we’ll comecanAllocFast  We continue to get inside the method

if (fastpath(cls->canAllocFast()))    
    bool canAllocFast() {
        assert(!isFuture());
        return bits.canAllocFast();
    }

    bool canAllocFast() {
        return false;
    }

It turns out, obviously, that there’s a lot to be done herecanAllocFast  It’s always backfalse  In other words, it will come directly to the following logic

id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;            

We’re in againclass_createInstance  Method internal

id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    //Empty CLS
    if (!cls) return nil;
    //Asserts whether CLS is implemented
    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    //Does CLS have a C + + initialization constructor
    bool hasCxxCtor = cls->hasCxxCtor();
    //Does CLS have C + + destructor
    bool hasCxxDtor = cls->hasCxxDtor();
    //Can CLS allocate nonpointer? If so, it means that memory optimization is turned on 
    bool fast = cls->canAllocNonpointer();
        
    //Here, the extrabytes passed in is 0, and then the instance memory size of CLS is obtained
    size_t size = cls->instanceSize(extraBytes);
    //Here outallocatedsize is the default value nil, skip
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    //Here, zone also passes in nil, while fast gets true, so it will enter the logic here
    if (!zone  &&  fast) {
        //Open up memory according to size
        obj = (id)calloc(1, size);
        //If the development fails, return nil
        if (!obj) return nil;
        //Pass CLS and C + + destructor to initinstanceisa to instantiate Isa
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        //If the zone is not empty, after testing by the author, generally speaking, calling alloc will not come here, only allocwithzone
        //Or copywithzone will come to the following logic
        if (zone) {
            //According to the given zone and size, open up memory
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            //Open up memory according to size
            obj = (id)calloc(1, size);
        }
        //If the development fails, return nil
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        //Initialize ISA
        obj->initIsa(cls);
    }

    //If there is C + + initialization constructor and destructor, optimize and speed up the whole process
    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    //Return to the final result
    return obj;
}

So far, ouralloc  After exploring the process, we still have some questions, such as how to determine the memory size of an object,isa  How is it initialized? It doesn’t matter. Let’s continue to explore in the next article. Here, I first give a picture of my own paintingalloc  Flow chart, limited to the author’s limited level, there are some mistakes

IOS bottom layer exploration - alloc & init

Brief analysis of 2.5 init

The analysis is overalloc  Then we analyze the processinit  The process of management. Compared toalloc  For example,init  The internal implementation is very simple_objc_rootInit , And then go straight backobj  It’s too late. In fact, this is an embodiment of the abstract factory design pattern, for exampleNSObject  Bring your owninit  In other words, nothing is done, but if you inherit fromNSObject  Then you can rewrite itinitWithXXX  And so on.

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

3、 Summary

There are some words in Xunzi’s persuasion in the pre Qin period

Every step is a thousand miles; If you don’t accumulate small streams, you can’t build a river or a sea.

We’re exploringiOS  The bottom principle, should also hold such a learning attitude, pay attention to the accumulation of bit by bit, start from a young age, a little makes a lot. In the next article, the author will answer the two questions left in this article

  • How is object initialization memory allocated?
  • How is isa initialized?

Recommended Today

Java Engineer Interview Questions

The content covers: Java, mybatis, zookeeper, Dubbo, elasticsearch, memcached, redis, mysql, spring, spring boot, springcloud, rabbitmq, Kafka, Linux, etcMybatis interview questions1. What is mybatis?1. Mybatis is a semi ORM (object relational mapping) framework. It encapsulates JDBC internally. During development, you only need to pay attention to the SQL statement itself, and you don’t need to […]