OC objects and Pointers

    GPerson *p1 = [GPerson alloc];
    GPerson *p2 = [p1 init];
    GPerson *p3 = [p1 init];
    
    LGPrint(@"%@ - %p - %p", p1, p1, &p1); //<GPerson: 0x6000022f4160> - 0x6000022f4160 - 0x7ffee7a11078
    LGPrint(@"%@ - %p - %p", p2, p2, &p2); //<GPerson: 0x6000022f4160> - 0x6000022f4160 - 0x7ffee7a11070
    LGPrint(@"%@ - %p - %p", p3, p3, &p3); //<GPerson: 0x6000022f4160> - 0x6000022f4160 - 0x7ffee7a11068
Copy the code

Three pointer variables point to the same memory space, p1, P2, p3 are placed in the stack space, so each pointer variable address is different;

Because it’s allocated in the stack space, the stack space goes from high to low;

Because of 64-bit devices, the pointer size is 8 bytes, so subtract 0x8 from 0x7FFEE7a11078.

%p -- p1 is the address of the printed object

%p -- &p1 prints the address of P1 in the address space


0x00 Prepare the source code project

  • Download the objC4-781 source project from the official website

  • Let’s start with the flow chart of alloc

0x01 — Alloc explore

  1. inmainIn the functionallocThe method goes to the implementation section, and from there, work your way down to πŸ˜„
+ (id)alloc {
    return _objc_rootAlloc(self);
}
Copy the code
  1. Jump to the_objc_rootAllocThe implementation part of.
// 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*/);
}
Copy the code
  1. Jump to thecallAllocThe implementation part of the process, this time there is a branch, after the breakpoint debugging, here go_objc_rootAllocWithZone

In the objc-config.h file

// Define __OBJC2__ for the benefit of our asm files.
#ifndef __OBJC2__
#   ifTARGET_OS_OSX && ! TARGET_OS_IOSMAC && __i386__
        // old ABI
#   else
#       define __OBJC2__ 1
#   endif
#endif
Copy the code
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__ // macro conditions determine whether OBJC2 version is available
    if (slowpath(checkNil && ! cls))return nil;
  	// CLS ->ISA()->hasCustomAWZ()
    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

Compiler optimization

Fastpath and slowPath reference

#define fastpath(x) (__builtin_expect(bool(x), 1))

#define slowpath(x) (__builtin_expect(bool(x), 0))

__builtin_expect is used by GCC for programmers to provide “branch” information to the compiler so that the compiler can optimize the code to reduce the performance degradation caused by instruction jumps

In fact, the deletion of slowPath and Fastpath in the code does not affect the function of this code. The addition of slowPath and FastPath tells the compiler whether there are high or low probability events in the if conditional statement, so that the compiler can optimize the code.

 if (x)
       return 1;
   else 
       return 30;
Copy the code

Since the computer reads multiple instructions rather than one instruction at a time, return 1 will also be read when the if statement is read. If x value is 0, return 30 will be read again. Reread instructions are relatively time-consuming. If x has a high probability of being 0, the return 1 command will inevitably be read, but there is almost no chance to execute it, resulting in unnecessary command reread. Therefore, after defining two macros, FastPath (x) still returns x, only telling the compiler that x is not generally 0, so that compilation can be optimized. Similarly, the slowpath(x) flag that x is likely to have a value of 0 can be tuned at compile time.

  • In daily development, through theXcodeSet up theOptimization LeveltheDebugtheNoneSet tofastest, Smallest[-Os]; To achieve the purpose of performance optimization.
  1. Came to_objc_rootAllocWithZone
NEVER_INLINE
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
  1. Go straight to the core method_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()); // error tolerance

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(a);// check whether there is a c++ constructor
    bool hasCxxDtor = cls->hasCxxDtor(a);// check whether there is a c++ destructor
    bool fast = cls->canAllocNonpointer(a);size_t size;
		ExtraBytes = 0
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
      // Apply to the system to open memory, return memory address pointer
        obj = (id)calloc(1, size);
    }
  	// error tolerance
    if (slowpath(! obj)) {if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }
		
    if(! zone && fast) {// The ISA binding is associated with the corresponding class
        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

Three core approaches:

⚠️NOTE: The following series of articles will focus on the internal implementation of these functions:

  • size = cls->instanceSize(extraBytes);
  • obj = (id)calloc(1, size);
  • obj->initInstanceIsa(cls, hasCxxDtor);

Conclusion under_class_createInstanceFromZoneprocess

1 Open up the memory. Calculate the required memory space
2 Apply for memory. Apply for memory and return the address pointer
3 ISA binding is associated with the corresponding class

Analyze the first key functionsize = cls->instanceSize(extraBytes);

Go to the instanceSize method

    size_t instanceSize(size_t extraBytes) const {
      	// The compiler optimizes fast computation
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
          	// Execute here
            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

FastInstanceSize is called here

    size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));
				//Gcc's built-in function __builtin_constant_p is used to determine if a value is compile-time,
        The function returns 1 if EXP is a constant, 0 otherwise
        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
          // _flags
            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

Then call align16

    // 0010 0111 39 (15+24)
    / / 0000 ~ 1111
    / / 0010 0000
    / / 0000 1111 15
// 16-byte alignment algorithm
static inline size_t align16(size_t x) {
    return (x + size_t(15& ~))size_t(15);
}
Copy the code

The align16 method passes in 24 and then executes the alignment algorithm, 24+15 = 39. Binary 0010 0111 and 15 take the inverse 1111 0000 and press the bitfield

You get 0010, 0000 which is 32 in decimal;

You could move it right and then left, or you could set the end to 0

Why are bytes aligned?

  • Convenient CPU read, improve the read efficiency, because cross-byte access will affect the IO read efficiency; Typically space for time;
  • An object that defaults to the first propertyisaIf it’s 8 bytes, if it has no attributes, it’s going to leave 8 bytes with the other object’s, rightisaThere is a certain width, to ensure security, will not address out of bounds, illegal access to the content of other objects;
  • For example 🌰: now the implementation of garbage classification, is to facilitate recycling, convenient disposal of waste kitchen garbage, with byte alignment is also the same, are for convenience, high efficiency; This is convenient and efficient compared to humans, while byte alignment is safe and efficient compared to CPU machines;

In the byte alignment algorithm, objects are mainly aligned, and the underlying nature is struct objc_Object.

Member variables in a structure are stored consecutively in memory, so you can force the type of the structure.

Earlier versions were 8-byte aligned, now 16-byte aligned;

Memory alignment rules can be seen in my previous struct article alignment rules, because oc objects compiled into the underlying representation is also a struct objc_Object structure; Access the link

0x02 — callAlloc exploration

obj = (**id**)calloc(1, size);

With this line of code, obj will have a hexadecimal address.

In oc, print the address of an object

; Object name + pointer address. Why is it different here?

  1. It’s mostly opened upMemory addressThere is no binding associated with the class passed in.

0x03 — initInstanceIsa exploration

obj->initInstanceIsa(cls, hasCxxDtor);

Then start binding the address to the class association. Initialize the initIsa pointer.

So if I print two obj’s here, that’s an object pointer.

Summarize the ALLOC process

  • Mainly open up memory, 16 – byte alignment, the size of 16 integer times.
  • Memory Trilogy:Calculate the size of the object --> Apply for a memory address ----> Class and address association

0x04initMethod underlying implementation

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

Factory design methods, which provide an entry point for users to customize their own initialization methods; The reason for using id strong, because memory alignment, we can use type strong to cast to the type we want;

0x05 newThe underlying implementation of the

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

Apple doesn’t recommend using new to initialize an object. If you override init in a custom class, using new will go to this method.

Init - (instanceType)init {self = [super init]; if (self) { NSLog(@"come here"); } return self; } Demo *obj = [Demo new]; // Print come hereCopy the code

If you don’t override the init method, using new will override the init method of the parent class

In general, our custom initWithXXX method will use the super init method instead of the parent init method, and the initialization process will be more complete.

Source code download website

Apple open source collection

Apple open Source project download

Takes you through the OC object creation process

Welcome big brother message correction πŸ˜„, code word is not easy, feel good to give a thumbs-up πŸ‘ have any expression or understanding error please leave a message; Common progress;