【面试题】runtime 相关

一个 NSObject 对象占用多少内存?

objc4 源码

+ (id)alloc {
    return _objc_rootAlloc(self);
    }
    
// 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*/);
}
// 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__
    if (slowpath(checkNil && !cls)) return nil;
    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));
}
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);
}
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
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);
}
inline 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;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}
#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;
}
static inline size_t word_align(size_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

https://unix.org/version2/whatsnew/lp64_wp.html
LP64 (also known as 4/8/8) denotes long and pointer as 64 bit types

// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
    ASSERT(isRealized());
    return data()->ro()->instanceSize;
}
void setInstanceSize(uint32_t newSize) {
    ASSERT(isRealized());
    ASSERT(data()->flags & RW_REALIZING);
    auto ro = data()->ro();
    if (newSize != ro->instanceSize) {
        ASSERT(data()->flags & RW_COPIED_RO);
        *const_cast<uint32_t *>(&ro->instanceSize) = newSize;
    }
    cache.setFastInstanceSize(newSize);
}
static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)
{
    runtimeLock.assertLocked();

    class_ro_t *cls_ro_w, *meta_ro_w;
    class_rw_t *cls_rw_w, *meta_rw_w;
    
    cls_rw_w   = objc::zalloc<class_rw_t>();
    meta_rw_w  = objc::zalloc<class_rw_t>();
    cls_ro_w   = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
    meta_ro_w  = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
    
    cls->setData(cls_rw_w);
    cls_rw_w->set_ro(cls_ro_w);
    meta->setData(meta_rw_w);
    meta_rw_w->set_ro(meta_ro_w);
    
    // Set basic info
    
    cls_rw_w->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
    meta_rw_w->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING | RW_META;
    
    cls_ro_w->flags = 0;
    meta_ro_w->flags = RO_META;
    if (superclass) {
        uint32_t flagsToCopy = RW_FORBIDS_ASSOCIATED_OBJECTS;
        cls_rw_w->flags |= superclass->data()->flags & flagsToCopy;
        cls_ro_w->instanceStart = superclass->unalignedInstanceSize();
        meta_ro_w->instanceStart = superclass->ISA()->unalignedInstanceSize();
        cls->setInstanceSize(cls_ro_w->instanceStart);
        meta->setInstanceSize(meta_ro_w->instanceStart);
    } else {
        cls_ro_w->flags |= RO_ROOT;
        meta_ro_w->flags |= RO_ROOT;
        cls_ro_w->instanceStart = 0;
        meta_ro_w->instanceStart = (uint32_t)sizeof(objc_class);
        cls->setInstanceSize((uint32_t)sizeof(id));  // just an isa
        meta->setInstanceSize(meta_ro_w->instanceStart);
    }
    
    cls_ro_w->name.store(strdupIfMutable(name), std::memory_order_release);
    meta_ro_w->name.store(strdupIfMutable(name), std::memory_order_release);
    
    cls_ro_w->ivarLayout = &UnsetLayout;
    cls_ro_w->weakIvarLayout = &UnsetLayout;
    
    meta->chooseClassArrayIndex();
    cls->chooseClassArrayIndex();
    
    // This absolutely needs to be done before addSubclass
    // as initializeToEmpty() clobbers the FAST_CACHE bits
    cls->cache.initializeToEmpty();
    meta->cache.initializeToEmpty();

#if FAST_CACHE_META
    meta->cache.setBit(FAST_CACHE_META);
#endif
    meta->setInstancesRequireRawIsa();
    
    // Connect to superclasses and metaclasses
    cls->initClassIsa(meta);
    
    if (superclass) {
        meta->initClassIsa(superclass->ISA()->ISA());
        cls->setSuperclass(superclass);
        meta->setSuperclass(superclass->ISA());
        addSubclass(superclass, cls);
        addSubclass(superclass->ISA(), meta);
    } else {
        meta->initClassIsa(meta);
        cls->setSuperclass(Nil);
        meta->setSuperclass(cls);
        addRootClass(cls);
        addSubclass(cls, meta);
    }
    
    addClassTableEntry(cls);
}
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

总结

  • NSObject 对象只有一个 isa 指针成员变量
  • LP64 指针类型内存中占 8 个字节
  • 8 个字节 word_align 后返回 8 个字节
  • 而 instanceSize 方法中限制所有对象至少 16 字节(CF requires all objects be at least 16 bytes)
  • 所以一个 NSObject 对象占用 16 字节内存(但只使用了 8 字节存储 isa 指针)

验证

代码

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];

        // 获得 NSObject 类的实例对象的成员变量所占用的大小
        size_t instanceSize = class_getInstanceSize([NSObject class]);
        // 获得 obj 指针所指向内存大小
        size_t mallocSize = malloc_size((__bridge const void *)(obj));

        NSLog(@"instance size:%zu", instanceSize);  // 8
        NSLog(@"malloc size:%zu", mallocSize);      // 16
    }
    return 0;
}

Debug

Debug -> Debug Workflow -> View Memory

其它

将 Objective-C 代码转换为 C\C++ 代码

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源文件 -o 输出文件

如果需要链接其它框架,使用 -framework 参数:-framework UIKit

常用 LLDB 指令

# 打印
print
p
# 打印对象
po

# 读取内存
# memory read/数量格式字节数 内存地址
# x/数量格式字节数 内存地址
x/3xw 0x10011001

# 写入内存(修改内存中的值)
# memory write 内存地址 数值
memory write 0x10011001 10

# 格式
# x: 16 进制
# d: 10 进制
# f: 浮点
# s: 字符串

# 字节大小(单位)
# b: byte       1 字节
# h: half word  2 字节
# w: word       4 字节
# g: giant word 8 字节

对象的 isa 指针指向哪里?

OC 中对象分为:

  • 实例对象(instance object)
  • 类对象(class object)
  • 元类对象(meta-class object)

其中:

  • 实例对象的 isa 指针指向类对象;
  • 类对象的 isa 指针指向元类对象;
  • 元类对象的 isa 指针指向基类的元类对象。

证明

执行代码:

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

/*
 objc4 源码中 isa.h L57-L113 宏定义(省略部分)
 https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/isa.h#L57-L113
 */
# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL
#   endif
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# else
#   error unknown architecture for packed isa
# endif

@interface JQObject : NSObject

@end

@implementation JQObject

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // 创建实例对象
        JQObject *obj = [[JQObject alloc] init];
        // 获取类对象
        Class class = object_getClass(obj);
        // 获取元类对象
        Class metaClass = objc_getMetaClass("JQObject");
        // 获取基类的元类对象
        Class rootMetaClass = objc_getMetaClass("NSObject");

//        'obj->isa' 方法已过期,使用 'object_getClass(obj)' 替代获取 isa 指针
//        Direct access to Objective-C's isa is deprecated in favor of object_getClass()
//        Replace 'obj->isa' with 'object_getClass(obj)'
//        long isa = (long)obj->isa;
        
        // 将指针地址转换为长整型供计算
        long objIsaAddress = (long)object_getClass(obj);
        long classAddress = (long)class;
        long classIsaAddress = (long)object_getClass(class);
        long metaClassAddress = (long)metaClass;
        long metaClassIsaAddress = (long)object_getClass(metaClass);
        long rootClassAddress = (long)rootMetaClass;
        
        // OC 运行时会对 isa 指针进行 ISA_MASK 与操作
        // https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc-object.h#L260
        if ((objIsaAddress & ISA_MASK) == classAddress) {
            NSLog(@"实例对象的 isa 指针指向类对象");
        }
        if ((classIsaAddress & ISA_MASK) == metaClassAddress) {
            NSLog(@"类对象的 isa 指针指向元类对象");
        }
        if ((metaClassIsaAddress & ISA_MASK) == rootClassAddress) {
            NSLog(@"元类对象的 isa 指针指向基类的元类对象");
        }
    }
    return 0;
}

输出:

示例项目地址:
https://github.com/Coder-ZJQ/demos/tree/master/interview/oc-isa-to

OC 的类信息存放在哪里?

objc4 源码

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
struct class_data_bits_t {
// ......
public:

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
// ......
}
struct class_rw_t {

    // ......
    
    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }
    
    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }
    
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
        }
    }
    
    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
        }
    }
    
    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
};
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };
    
    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    
    // ......

}

UML

classDiagram

class Class
class objc_class {
  Class superclass
  cache_t cache
  class_data_bits_t bits
}
class objc_object {
  Class isa
}
class NSObject {
  Class isa
}
class class_data_bits_t {
  class_rw_t* data
}
class class_rw_t {
  class_ro_t *ro
  method_array_t methods
  property_array_t properties
  protocol_array_t protocols
}
class class_ro_t {
  uint32_t instanceStart
  uint32_t instanceSize
  ivar_list_t * ivars
}

NSObject..>Class
objc_object..>Class
objc_class..>Class
Class-->objc_class : typedef
objc_class--|>objc_object
objc_class..>class_data_bits_t
class_data_bits_t..>class_rw_t
class_rw_t..>class_ro_t

link NSObject "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/NSObject.h#L53-L58"
link Class "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc.h#L37-L38"
link objc_class "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc-runtime-new.h#L1688-L1696"
link objc_object "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc.h#L40-L43"
link class_data_bits_t "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc-runtime-new.h#L1609-L1611"
link class_rw_t "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc-runtime-new.h#L1458-L1574"
link class_ro_t "https://github.com/Coder-ZJQ/objc4-818.2/blob/main/runtime/objc-runtime-new.h#L1037-L1171"


cache_t 方法缓存

cache_t 相关快捷方法

struct cache_t {
    struct bucket_t *buckets() const; // 散列表
    unsigned capacity() const;        // 散列表的长度
    mask_t mask() const;              // 散列表的长度 - 1
    mask_t occupied() const;          // 已缓存的方法数量
}

bucket_t 相关数据结构及方法

struct bucket_t {
private:
    explicit_atomic<uintptr_t> _imp;
    explicit_atomic<SEL> _sel;

public:
    inline SEL sel() const { return _sel.load(memory_order_relaxed); }
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
#define MAYBE_UNUSED_ISA
#else
#define MAYBE_UNUSED_ISA __attribute__((unused))
#endif
    inline IMP rawImp(MAYBE_UNUSED_ISA objc_class *cls) const {
        uintptr_t imp = _imp.load(memory_order_relaxed);
        if (!imp) return nil;
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
        imp ^= (uintptr_t)cls;
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
#else
#error Unknown method cache IMP encoding.
#endif
        return (IMP)imp;
    }
    
    inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls) const {
        uintptr_t imp = _imp.load(memory_order_relaxed);
        if (!imp) return nil;
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
        SEL sel = _sel.load(memory_order_relaxed);
        return (IMP)
            ptrauth_auth_and_resign((const void *)imp,
                                    ptrauth_key_process_dependent_code,
                                    modifierForSEL(base, sel, cls),
                                    ptrauth_key_function_pointer, 0);
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
        return (IMP)(imp ^ (uintptr_t)cls);
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
        return (IMP)imp;
#else
#error Unknown method cache IMP encoding.
#endif
    }

}

向散列表中插入方法

void cache_t::insert(SEL sel, IMP imp, id receiver)
{
    runtimeLock.assertLocked();

    // Never cache before +initialize is done
    if (slowpath(!cls()->isInitialized())) {
        return;
    }
    
    if (isConstantOptimizedCache()) {
        _objc_fatal("cache_t::insert() called with a preoptimized cache for %s",
                    cls()->nameForLogging());
    }

#if DEBUG_TASK_THREADS
    return _collecting_in_critical();
#else
#if CONFIG_USE_CACHE_LOCK
    mutex_locker_t lock(cacheUpdateLock);
#endif

    ASSERT(sel != 0 && cls()->isInitialized());
    
    // Use the cache as-is if until we exceed our expected fill ratio.
    mask_t newOccupied = occupied() + 1;
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
    if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
        // Cache is less than 3/4 or 7/8 full. Use it as-is.
    }
#if CACHE_ALLOW_FULL_UTILIZATION
    else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
        // Allow 100% cache utilization for small buckets. Use it as-is.
    }
#endif
    else {
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);
    }
    
    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;
    
    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot.
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));
    
    bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}

散列表 hash 算法实现

// Class points to cache. SEL is key. Cache buckets store SEL+IMP.
// Caches are never built in the dyld shared cache.

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
    value ^= value >> 7;
#endif
    return (mask_t)(value & mask);
}

class_rw_t 与 class_ro_t

结构

通过阅读 obj4 源码可以得出 class_rw_tclass_ro_t 结构(部分省略),如下:
(objc4 源码版本:objc4-818.2)

flowchart LR

subgraph class_rw_t
    A["class_ro_t *ro()"]
    B["method_array_t methods()"]
    C["property_array_t properties()"]
    D["protocol_array_t protocols()"]
    DD["......"]
end

subgraph class_ro_t
    E["uint32_t instanceSize;"]
    F["method_list_t *baseMethods()"]
    G["property_list_t *baseProperties;"]
    H["protocol_list_t *baseProtocols;"]
    I["const ivar_list_t * ivars;"]
    II["......"]
end

J["method_array_t"]
K["property_array_t"]
L["protocol_array_t"]
M[method_list_t]
N[property_list_t]
O[protocol_list_t]
P[ivar_list_t]

subgraph method_t
    direction LR
    U["SEL name;"]
    V["const char *types;"]
    W["IMP imp;"]
    WW["......"]
end

subgraph property_t
    direction LR
    Q["const char *name;"]
    R["const char *attributes;"]
end

subgraph protocol_t
    direction LR
    X["struct protocol_list_t *protocols;"]
    Y["method_list_t *instanceMethods;"]
    Z["method_list_t *classMethods;"]
    ZZ["property_list_t *instanceProperties;"]
    ZZZ["......"]
end

subgraph ivar_t
    direction LR
    S["const char *name;"]
    T["const char *type;"]
    TT["......"]
end

A-->class_ro_t
B-->J
C-->K
D-->L
F-->M
G-->N
H-->O
I-->P
J-->M
K-->N
L-->O
M-->method_t
N-->property_t
O-->protocol_t
P-->ivar_t

class_ro_t

  • class_ro_t 里面的 baseMethodList、baseProtocols、ivars、baseProperties 是一维数组,是只读的,包含了类的初始内容;
  • class_ro_t 编译时即确定;

class_rw_t

  • class_rw_t 里面的 methods、properties、protocols 是二维数组,是可读可写的,包含了类的初始内容、分类的内容;
  • class_rw_t 运行时才确定,且会先拷贝 class_ro_t 中的内容;
  • class_rw_t 中已包含 class_ro_t 的相关内容,所以访问类的方法属性协议等主要通过 class_rw_t,不通过 class_ro_t。

拷贝 class_ro_t 中内容至 class_rw_t 源码:

/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
  **********************************************************************/
  static void methodizeClass(Class cls, Class previously)
  {
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods()) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name()));
        }
        ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
    }
#endif
}

objc_msgSend 执行流程

flowchart TB
 A(objc_msgSend)
 B{消息接收者是否为nil}
 C([退出])
 D{在 cache 中查找}
 E{在 class_rw_t 中查找}
 F{在 cache 中查找}
 G{在 class_rw_t 中查找}
 H{superclass == nil}
 I{是否动态方法解析}
 J([调用方法结束查找])
 K([调用方法结束查找并将方法缓存至 cache])
 L[+resolveInstanceMethod:]
 M[+resolveClassMethod:]
N{类对象还是实例对象}
O{+/- forwardingTargetForSelector:}
P{+/- methodSignatureForSelector:}
Q[调用 +/- forwardInvocation: 方法]
R[调用 doesNotRecognizeSelector: 方法抛出异常]
S["调用 objc_msgSend(返回值, selector)"]

 subgraph 消息发送
   subgraph "消息接受者的类对象(isa)"
		D
		E
   end
   subgraph "消息接受者的父类对象(superclass)"
		F
		G
   end
   B
   C
   H
   J
   K
 end
 subgraph 动态方法解析
   I
   L
   M
   N
 end
 subgraph 消息转发
   O
   P
   Q
   R
   S
 end
 A-->B
 B-->|是|C
 B-->|否|D
 D-->|未找到|E
 E-->|未找到|H
 H-->|否|F
 H-->|是|I
 F-->|未找到|G
 G-->|未找到|H
 D-->|找到了|J
 E-->|找到了|K
 F-->|找到了|J
 G-->|找到了|K
 I-->|否|N
 I-->|是|O
 N-->|实例对象|L
 N-->|类对象|M
 L-->|返回YES|D
 M-->|返回YES|D
 O-->|返回值不为nil|S
 O-->|返回值为nil|P
 P-->|返回值不为nil|Q
 P-->|返回值为nil|R

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

@interface JQForwardObject : NSObject

- (void)test;
+ (void)test;

@end

@implementation JQForwardObject

- (void)test {
    NSLog(@"\nfunc: %s\nreceiver: %@\nsel: %@", __func__, self, NSStringFromSelector(_cmd));
}

+ (void)test {
    NSLog(@"\nfunc: %s\nreceiver: %@\nsel: %@", __func__, self, NSStringFromSelector(_cmd));
}

@end

@interface JQObject : NSObject

- (void)test;
+ (void)test;

@end

@implementation JQObject
/* 1. 动态消息解析
+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(test)) {
        Method met = class_getInstanceMethod(self, @selector(resolveMethod));
        class_addMethod(object_getClass(self), sel, method_getImplementation(met), method_getTypeEncoding(met));
        return YES;
    }
    return [super resolveClassMethod:sel];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        class_addMethod(self, sel, (IMP)resolveMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

- (void)resolveMethod {
    NSLog(@"\nfunc: %s\nreceiver: %@\nsel: %@", __func__, self, NSStringFromSelector(_cmd));
}

void resolveMethod(id self, SEL _cmd) {
    NSLog(@"\nfunc: %s\nreceiver: %@\nsel: %@", __func__, self, NSStringFromSelector(_cmd));
}
*/

/*
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        return [[JQForwardObject alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

+ (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        return [JQForwardObject class];
    }
    return [super forwardingTargetForSelector:aSelector];
}
*/

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
//    anInvocation getReturnValue:<#(nonnull void *)#>
//    anInvocation getArgument:<#(nonnull void *)#> atIndex:<#(NSInteger)#>
//    anInvocation invoke
//    [anInvocation invokeWithTarget:<#(nonnull id)#>];
    NSLog(@"%@", anInvocation);
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%@", anInvocation);
}

@end



int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [[[JQObject alloc] init] test];
        [JQObject test];
    }
    return 0;
}