Welcome to the iOS Exploration series.

  • IOS explores the alloc process
  • IOS explores memory alignment &malloc source code
  • IOS explores ISA initialization & pointing analysis
  • IOS exploration class structure analysis
  • IOS explores cache_T analysis
  • IOS explores the nature of methods and the method finding process
  • IOS explores dynamic method resolution and message forwarding mechanisms
  • IOS explores the dyLD loading process briefly
  • The loading process of iOS explore classes
  • IOS explores the loading process of classification and class extension
  • IOS explores isa interview question analysis
  • IOS Explore Runtime interview question analysis
  • IOS explores KVC principles and customization
  • IOS explores KVO principles and customizations
  • IOS explores the principle of multithreading
  • IOS explores multi-threaded GCD applications
  • IOS explores multithreaded GCD underlying analysis
  • IOS explores NSOperation for multithreading
  • IOS explores multi-threaded interview question analysis
  • IOS Explore the locks in iOS
  • IOS explores the full range of blocks to read

Writing in the front

OC is an object language, so it is essential to understand object creation and memory. This article will explore the specific steps of alloc at the bottom

The official source

Cooci Driver OBJC4-756.2 debugging scheme (Xcode11 temporarily unable to break source code)

First, explore the direction

In the source code, we can use Command+ click/right click ->Jump to Defintion to call the alloc method. Without the source code, how do we know what underlying method it calls?

Place a breakpoint at the code where the object is created, and when it reaches the breakpoint use the following methods:

  • Control+Step into
  • Symbol breakpoint
  • Menu bar Debug->Debug Workflow->Always Show Disassembly

All three methods tell you that the objc_alloc method is called

Two, start exploring

//
// main.m
// FXTest
//
// Created by mac on 2019/12/19.
//

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "FXPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *object1 = [NSObject alloc];
        FXPerson *object2 = [FXPerson alloc];
    }
    return 0;
}
Copy the code

Not surprisingly, you can all come to the following method

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

But the rest of the source code will make you dizzy and not want to read it

// 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 hasCustomAWZ in 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);
            returnobj; }}#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}
Copy the code

There are so many if-else logic forks in the road that many people will close Xcode

See what not good-looking source code, be disrelish oneself hair too exuberant?

Don’t worry, I’ve already cut your hair.

Alloc source code process

1. Pits — objc_alloc, alloc silly not clear

This pit is harmless enough to understand; OC object alloc->alloc

I don’t know if you’ve noticed anything strange about this. In section 2, the underlying objc_alloc method is called. Why does the source code go to the alloc method when creating an object?

The function call stack is also slightly problematic

Xcode11->objc_alloc, Xcode10->alloc

The current argument for this is that the source code is not open enough. The following code is not called, but the logic is intriguing. The approximate guess is that this Method is equal to exchanging Method Swizzling once.

The comment tells you that [CLS alloc] will be invoked.

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

Another point: fixMessageRef if something goes wrong (breakpoints will not be called)

2. Alloc and _objc_rootAlloc methods

The two methods mentioned earlier

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

3. CallAlloc method

// 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 hasCustomAWZ in 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);
            returnobj; }}#endif

    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}
Copy the code

1) if (slowpath (checkNil &&! CLS) judgment

Fastpath (x) indicates that x is probably not 0 and expects the compiler to optimize; Slowpath (x) means that x is likely to be 0 and the compiler is expected to optimize — here it means that CLS is likely to have a value and the compiler can avoid reading return nil instructions every time

(2) if (fastpath (! CLS – > ISA () – > hasCustomAWZ ()))

HasCustomAWZ actually means hasCustomAllocWithZone — there is no implementation of alloc/allocWithZone (true only for classes that do not inherit from NSObject/NSProxy)

(3) if (fastpath (CLS – > canAllocFast ()))

An internal call to bits.canAllocFast defaults to false

④id obj = class_createInstance(cls, 0)

Internal call _class_createInstanceFromZone(CLS, extraBytes, nil)

I have an ID obj here, so let’s try to print it on the console

Now that we have the result we want, let’s explore how the _class_createInstanceFromZone method creates obj

4. _class_createInstanceFromZone method

/***********************************************************************
* class_createInstance
* fixme
* Locking: none
**********************************************************************/

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 class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    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;
        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

1) hasCxxCtor ()

HasCxxCtor () is an implementation of the. Cxx_construct constructor that determines whether the current class or superclass has one

(2) hasCxxDtor ()

HasCxxDtor () is an implementation of the. Cxx_destruct method that determines whether the current class or superclass has one

(3) canAllocNonpointer ()

AnAllocNonpointer () specifies whether a class supports optimized ISA

(4) instanceSize ()

InstanceSize () gets the size of the class (the size of the extra bytes passed)

If zone=false and fast=true, then (! zone && fast)=true

(5) calloc ()

To dynamically open up memory, no specific implementation code, the next article will talk about malloc source code

6 initInstanceIsa ()

An internal call to initIsa(CLS, true, hasCxxDtor) initializes ISA

Now that you’ve initialized isa and set up memory, let’s see what instanceSize does

5. Align bytes

#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
    assert(isRealized());
    return data()->ro->instanceSize;
}

// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
    return word_align(unalignedInstanceSize());
}

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;
}
Copy the code

The following are explained in order of invocation

1) size_t instanceSize (size_t extraBytes)

Get the size of the class

(2) alignedInstanceSize ()

Gets the size of memory required by the class

(3) unalignedInstanceSize ()

Data ()->ro->instanceSize is the size of the memory for all attributes of this class. There is only one property, ISA, that inherits NSObject — it returns 8 bytes

(4) word_align

As the name implies, byte alignment – On 64-bit systems, object sizes are aligned with 8 bytes

⑤if (size < 16) size = 16

CoreFoundation requires all objects to add up to at least 16 bytes

6. Byte alignment algorithm

X + WORD_MASK = 9 + 7 = 16 WORD_MASK binary: 0000 0111 = 7 (4+2+1) ~WORD_MASK: 1111 1000 16 The binary number is 0001 0000 1111 1000 0001 0000 --------------- 0001 0000 = 16. Therefore, x = 16 is a multiple of 8, that is, 8-byte alignmentCopy the code

Conclusion: The object size is 16 bytes and must be a multiple of 8

Here’s a question: why use the 8-byte alignment algorithm?

I drew a simple diagram, the top is next to each other, and the bottom is an 8-byte grid. If the CPUS store data close to each other, the read length is constantly changing, so space for time is used

So why 8 bytes? Not 4 bytes or 16 bytes?

There are a lot of 8-byte Pointers in memory

7. Alloc actual flow chart

instanceSize

Calloc requests to open up memory — to build a house

InitInstanceIsa pointer to the associated object — the house writes the name

Init & new

Init does nothing but provide an interface for developers to use factory design patterns, right

// Replaced by CF (throws an NSException)
+ (id)init {
    return (id)self;
}

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

If (self = [super init]) returns nil if (self = [super init]) returns nil if (self = [super init]) returns nil

New is alloc first, init

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

Write in the back

Study source code is bound to be boring, but in the face of the source code do not be afraid, step by step to split it up to study, more use of official notes /Github god notes, slowly also gnawing down.

See others to learn with relish, as their hands actually play will be more harvest; You’ll never learn if you don’t practice. Maybe what you’re learning is someone else’s wrong theory