Files
knowledge-kit/Chapter1 - iOS/1.87.md
2026-02-22 22:12:36 +01:00

4.6 KiB

Objective-C Under the Hood

  1. Objects and classes in Objective-C are primarily implemented using structs from C/C++.

Method 1:

You can verify with clang. clang -rewrite-objc main.m -o main.cpp
To target a specific platform: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

struct NSObject_IMPL {
    Class isa;
};

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

So Class is a pointer to a struct, which occupies 8 bytes on a 64-bit system and 4 bytes on a 32-bit system.

NSObject as a struct therefore occupies 8 bytes.

[images omitted]

You notice class_getInstanceSize and malloc_size results differ.

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

class_getInstanceSize returns the size of a class instance's member variables (rounded up to pointer-size boundary, so not the exact allocated size).

extern size_t malloc_size(const void *ptr);
/* Returns size of given ptr */

malloc_size returns the actual size allocated for the pointer.

[image omitted]

Conclusions:

  • If a class inherits from NSObject and has no additional properties, the class occupies 16 bytes. class_getInstanceSize returns 8, malloc_size returns 16.
  • If a class inherits from NSObject and has additional properties, the class occupies 16 bytes. class_getInstanceSize returns 16, malloc_size returns 16.

Method 2: Verify from source code (top-down)

// NSObject.mm
// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}

// objc-class-old.mm
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;

    if (fastpath(!zone)) {
        obj = class_createInstance(cls, 0);
    } else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }

    if (slowpath(!obj)) obj = _objc_callBadAllocHandler(cls);
    return obj;
}

// objc-class-old.mm
/***********************************************************************
* _class_createInstance.  Allocate an instance of the specified
* class with the specified number of bytes for indexed variables, in
* the default zone, using _class_createInstanceFromZone.
**********************************************************************/
static id _class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone (cls, extraBytes, nil);
}



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;

    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

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

    if (!zone && fast) {
        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);
}
// objc-runtime-new.h
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t  alignedInstanceSize() const {
  return word_align(unalignedInstanceSize());
}
// objc-runtime-new.h
size_t instanceSize(size_t extraBytes) const {
  if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
    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;
}

"CF requires all objects be at least 16 bytes." The system allocates at least 16 bytes for NSObject objects, but it uses 8 bytes for storing ivars (on 64-bit systems).

  1. How memory is allocated for a class that inherits from NSObject