What is the essence of an OC object

We can see this by compiling OC code into C\C++ code with clang-rewrite-objc main.c -o main.cpp:

// Define a Person class
@interface Person : NSObject
{
    int _age;
    int _height;
    NSString *_name;
}
@end
Copy the code

The compiled core code is as follows:

/// NSObject
struct NSObject_IMPL {
    Class isa; / / 8 bytes
};

/// Person
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS; / / 8 bytes
    int _age;  / / 4 bytes
    int _height; / / 4 bytes
    NSString *_name; / / 8 bytes
};
Copy the code

You can see that NSObject’s classes are all compiled into constructs. And the NSObject structure also has an ISA pointer

How much memory does the OC class occupy

We can print this through the Runtime and malloc library functions

// Check the memory usage of the class
#import <objc/runtime.h>
class_getInstanceSize(Class  _Nullable cls)

// View the actual space allocated by the object
#import <malloc/malloc.h>
malloc_size(const void *ptr)

// Output the result
NSObject:class_getInstanceSize: 8
NSObject: malloc_size: 16
Person: class_getInstanceSize: 16
Person: malloc_size: 16
Copy the code

First, the NSObject class takes up 8 bytes of space. We can also see from the compiled structure that an ISA pointer takes up 8 bytes. Similarly, the Person class takes up 8+4+4+8 = 24 bytes.

So why are the addresses actually assigned 16 and 32? Let’s look at the source code below, why

Alloc source code exploration

  1. Alloc function
/ / alloc function
+ (id)alloc {
    return _objc_rootAlloc(self);
}
Copy the code
  1. _objc_rootAlloc function
id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/.true/*allocWithZone*/);
}
Copy the code
  1. CallAlloc function
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if(slowpath(checkNil && ! cls))return nil;  Cls is passed false for the current Class checkNil
    if(fastpath(! cls->ISA()->hasCustomAWZ())) {// Determine whether to customize +allocWithZone implementation
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif
    // Check the allocWithZone argument, passing true above
    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

Fastpath and slowPath are two macro definitions, as follows:

#define fastPath (x) (__builtin_expect(bool(x), 1))(__builtin_expect(bool(x), 1))) It means that the code inside if has a high probability of executing

Slowpath (x) (__builtin_expect(bool(x), 0))(__builtin_expect(bool(x), 0)) Slowpath can be shortened to false value judgment indicating that code in if has a low probability of execution

  1. _objc_rootAllocWithZone 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
  1. _class_createInstanceFromZone function
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;
    // Calculate the required space in extraBytes with 0 in the previous step
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // Request space
        obj = (id)calloc(1, size);
    }
    if(slowpath(! obj)) {if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if(! zone && fast) {// associate CLS with obj pointer
        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 function is the core operation of alloc and accomplishes three main tasks:

  1. CLS ->instanceSize(extraBytes): Calculate how much space you need to create
  2. (id)calloc(1, size): applies for memory, returns the pointer address
  3. InitInstanceIsa (CLS, hasCxxDtor): Associates a class with an ISA pointer
  • 1.1 instanceSize Method for calculating space size
inline size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            // Quickly calculate memory size
            return cache.fastInstanceSize(extraBytes);
        }
        // Calculate all attribute size + extra space size
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        // Minimum 16 bytes
        if (size < 16) size = 16;
        return size;
}
Copy the code
  • 1.2 fastInstanceSize and align16, calculate space size and align16 bytes
// Calculate space size
size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            returnalign16(size + extra - FAST_CACHE_ALLOC_DELTA16); }}// 16-byte alignment algorithm
static inline size_t align16(size_t x) {
    return (x + size_t(15& ~))size_t(15);
}
Copy the code

Why memory byte alignment:

  1. The CPU accesses and writes data in blocks. Byte alignment can be used to reduce the number of accesses and the CPU overhead
  2. The ISA pointer of the OC object occupies 8 bytes. If there are no other attributes, using 8-byte alignment will cause the object to be next to the ISA pointer of other objects in memory, which will easily cause access chaos

Rules for byte alignment:

  1. The starting address of each member variable is offset an integer multiple of the size of the current member
  2. If a member variable is a structure, the starting address offset of the structure member is an integer multiple of the size of the substructure
  3. The total size of the structure must be an integer multiple of the largest internal member
  • 2 Apply for memory space based on the calculated memory size and assign a value to OBj. Obj points to the memory address
  • 3 Associate the pointer with ISA based on the obtained memory address and the current incoming CLS

Using the source code, we know why NSObject’s actual memory size is 16 bytes (minimum 16 bytes) and why Person is 32 bytes (16-byte alignment)

Init source code analysis

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

Init just returns the current self outside

Five. New source code analysis

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

As you can see, the new method calls callAlloc() directly and then calls init, so new= alloc + init

Calloc source code analysis

In objC4 calloc, we can’t see the source code further because calloc is in libmalloc source code, so we’ll explore the source code below

  1. Calloc function
void * calloc(size_t num_items, size_t size)
{
	void *retval;
	retval = malloc_zone_calloc(default_zone, num_items, size);
	if (retval == NULL) {
		errno = ENOMEM;
	}
	return retval;
}
Copy the code
  1. Malloc_zone_calloc function
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();
	}
        // Set a breakpoint here to continue debugging 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
  1. Through the breakpoint, we find that calloc actually calls default_zone_calloc
static void * default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
	zone = runtime_default_zone(); / / create a zone
        // Second breakpoint
	return zone->calloc(zone, num_items, size); // 用zone calloc
}
Copy the code
  1. Runtime_default_zone function
static inline malloc_zone_t * runtime_default_zone(a) {
	return (lite_zone) ? lite_zone : inline_malloc_default_zone();
}
Copy the code
  1. Inline_malloc_default_zone function
static inline malloc_zone_t * inline_malloc_default_zone(void)
{
	_malloc_initialize_once();
	// malloc_report(ASL_LEVEL_INFO, "In inline_malloc_default_zone with %d %d\n", malloc_num_zones, malloc_has_debug_zone);
	return malloc_zones[0];
}
Copy the code
  1. Going back to the second breakpoint of default_zone_calloc, you see that the nano_calloc function was actually called
static void *
nano_calloc(nanozone_t *nanozone, size_t num_items, size_t size)
{
	size_t total_bytes;

	if (calloc_get_size(num_items, size, 0, &total_bytes)) {
		return NULL;
	}

	if (total_bytes <= NANO_MAX_SIZE) {
                // Core method
		void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
		if (p) {
			return p;
		} else {
			/* FALLTHROUGH to helper zone */}}malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
	return zone->calloc(zone, 1, total_bytes);
}
Copy the code
  1. _nano_malloc_check_clear function simple version code
static void * _nano_malloc_check_clear(nanozone_t *nanozone, size_t size, boolean_t cleared_requested)
{
        / / /...
        // 16-bit alignment
	size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
	mag_index_t mag_index = nano_mag_index(nanozone);
	nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]);
	ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next));
	if (ptr) {
        /// ....
	} else {
                // Get the memory pointer
		ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
	}

	if (cleared_requested && ptr) {
		memset(ptr, 0, slot_bytes); // TODO: Needs a memory barrier after memset to ensure zeroes land first?
	}
	return ptr;
}
Copy the code

The _nano_malloc_check_clear core has two operations

  1. segregated_size_to_fit(args…) Alignment: 16
  2. segregated_next_block(args…) : Gets the memory pointer
    1. Segregated_size_to_fit, 16-bit alignment by bit operation
static MALLOC_INLINE size_t segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
	size_t k, slot_bytes;
	if (0 == size) { // multiples of 16 aligned
		size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
	}
	k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
	slot_bytes = k << SHIFT_NANO_QUANTUM;							// multiply by power of two quanta size
	*pKey = k - 1;		
        // k = (size + 15) >> 4 << 4 // Zero-based!
	return slot_bytes;
}
Copy the code
    1. Segregated_next_block simplified code
static MALLOC_INLINE void *
segregated_next_block(nanozone_t *nanozone, nano_meta_admin_t pMeta, size_t slot_bytes, unsigned int mag_index)
{
     while (1) {
        // The end address of the current available memory
	uintptr_t theLimit = pMeta->slot_limit_addr; // Capture the slot limit that bounds slot_bump_addr right now
	// Offset to the next address
        uintptr_t b = OSAtomicAdd64Barrier(slot_bytes, (volatile int64_t *)&(pMeta->slot_bump_addr));
	// Subtract the offset
        b -= slot_bytes; // Atomic op returned addr of *next* free block. Subtract to get addr for *this* allocation.

	if (b < theLimit) {   // Did we stay within the bound of the present slot allocation?
	    // If the current address is still in range, return directly
            return (void *)b; // Yep, so the slot_bump_addr this thread incremented is good to go
	} else {
	    if (pMeta->slot_exhausted) { // exhausted all the bands availble for this slot?
		pMeta->slot_bump_addr = theLimit;
		return 0;				 // We're toast
	    } else {
		// One thread will grow the heap, others will see its been grown and retry allocation
		_malloc_lock_lock(&nanozone->band_resupply_lock[mag_index]);
		// re-check state now that we've taken the lock
		if (false / /...). ) {
			/ / /...
                } else if (segregated_band_grow(nanozone, pMeta, slot_bytes, mag_index)) {
			_malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]);
			continue; // ... the slot has been successfully grown by us. Now try again.
			} else {
			/ / /...
			}
		}
	 }
     }
}
Copy the code
    1. The segregated_band_grow function opens a new band
static boolean_t
segregated_band_grow(nanozone_t *nanozone, nano_meta_admin_t pMeta, size_t slot_bytes, unsigned int mag_index)
{
        / / /...
	return TRUE;
}
Copy the code

This part of the code is not fully understood and needs further interpretation