OC底层探究 - Runtime

936 阅读20分钟

Runtime简介

Runtime 又叫运行时,是一套底层的 C 语言 API,是 iOS 系统的核心之一。Runtime API 提供的接口基本都是C语言的,源码由C\C++\汇编语言编写。开发者在编码过程中,可以给任意一个对象发送消息,在编译阶段只是确定了要向接收者发送这条消息,而接受者将要如何响应和处理这条消息,那就要看运行时来决定了。

C语言中,在编译期,函数的调用就会决定调用哪个函数。而OC的函数,属于动态调用过程,在编译期并不能决定真正调用哪个函数,只有在真正运行时才会根据函数的名称找到对应的函数来调用。

Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。

Objc 在三种层面上与 Runtime 系统进行交互:

runtime交互

  1. 通过 Objective-C 源代码

    一般情况开发者只需要编写 OC 代码即可,Runtime 系统自动在幕后把我们写的源代码在编译阶段转换成运行时代码,在运行时确定对应的数据结构和调用具体哪个方法。

  2. 通过 Foundation 框架的 NSObject 类定义的方法

    在OC的世界中,除了NSProxy类以外,所有的类都是NSObject的子类。在Foundation框架下,NSObject和NSProxy两个基类,定义了类层次结构中该类下方所有类的公共接口和行为。NSProxy是专门用于实现代理对象的类,这个类暂时本篇文章不提。这两个类都遵循了NSObject协议。在NSObject协议中,声明了所有OC对象的公共方法。

    在NSObject协议中,有以下5个方法,是可以从Runtime中获取信息,让对象进行自我检查。

    - (Class)class OBJC_SWIFT_UNAVAILABLE("use 'anObject.dynamicType' instead");
    - (BOOL)isKindOfClass:(Class)aClass;
    - (BOOL)isMemberOfClass:(Class)aClass;
    - (BOOL)conformsToProtocol:(Protocol *)aProtocol;
    - (BOOL)respondsToSelector:(SEL)aSelector;
    
    • -class 方法返回对象的类;
    • -isKindOfClass: -isMemberOfClass: 方法检查对象是否存在于指定的类的继承体系中(是否是其子类或者父类或者当前类的成员变量);
    • -respondsToSelector: 检查对象能否响应指定的消息;
    • -conformsToProtocol: 检查对象是否实现了指定协议类的方法;

    在NSObject的类中还定义了一个方法:

    - (IMP)methodForSelector:(SEL)aSelector; // 返回指定方法实现的地址IMP
    
  3. 通过对 Runtime 库函数的直接调用

    关于库函数可以在 Objective-C Runtime Reference 中查看 Runtime 函数的详细文档。

isa详解

要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针:

  • 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
  • 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

  	// 所有值加起来刚好是64位
    struct {
        uintptr_t nonpointer        : 1;	// 是否优化过,使用位域存储更多信息
        uintptr_t has_assoc         : 1;	// 是否设置过关联对象
        uintptr_t has_cxx_dtor      : 1;	// 是否有C++的析构函数
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };
}
  • nonpointer

    • 0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
    • 1,代表优化过,使用位域存储更多的信息
  • has_assoc

    是否有设置过关联对象,如果没有,释放时会更快

  • has_cxx_dtor

    是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快

  • shiftcls

    存储着Class、Meta-Class对象的内存地址信息,占用33位

  • magic

    用于在调试时分辨对象是否未完成初始化

  • weakly_referenced

    是否有被弱引用指向过,如果没有,释放时会更快

  • deallocating

    对象是否正在释放

  • extra_rc

    里面存储的值是引用计数器减1

  • has_sidetable_rc

    • 0,引用计数器是否过大无法存储在isa中
    • 1,那么引用计数会存储在一个叫SideTable的类的属性中

在64位架构中,不管是类对象还是元类对象,它们的地址值的后三位一定是0

位运算

#import "MJPerson.h"

// &可以用来取出特定的位,
// 按位与(&),两位都是1结果才会是1
// 按位或(|),只要有一位是1结果就是1

// 0000 0111      0000 0111
//&0000 0100		 |0000 0100
// ---------   		---------
// 0000 0100			0000 0111

// 掩码,一般用来按位与(&)运算的
//#define MJTallMask 1
//#define MJRichMask 2
//#define MJHandsomeMask 4

//#define MJTallMask 0b00000001
//#define MJRichMask 0b00000010
//#define MJHandsomeMask 0b00000100

#define MJTallMask (1<<0) // 1向左移0位 == 0b00000001
#define MJRichMask (1<<1) // 1向左移1位 == 0b00000010
#define MJHandsomeMask (1<<2) // 1向左移2位 == 0b00000100

@interface MJPerson()
{
  	/*
  		char 占用1个字节的内存,这样做可以节省内存
  		原本三个bool属性,每个都要占用4个字节,而现在只占用1个字节中的1位
  	*/
    char _tallRichHansome;
}
@end

@implementation MJPerson

- (instancetype)init
{
    if (self = [super init]) {
        _tallRichHansome = 0b00000100;
    }
    return self;
}

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHansome |= MJTallMask;
    } else {
        _tallRichHansome &= ~MJTallMask; // ~ 按位取反
    }
}

- (BOOL)isTall
{
    return !!(_tallRichHansome & MJTallMask); // 只要有值且不为0,连续取反,结果为true
}

@end

另一种方式,使用共用体(union):

#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)

@interface MJPerson()
{
  	// 共用体表示,里面的数据会共用同一块内存
    union {
        int bits;
        
      	/*
      		这里这个struct并没有实际意义,只是为了说明
      		某一位的值是代表什么意义
      	*/
        struct {
            char tall : 1;
            char rich : 1;
            char handsome : 1;
        };
    } _tallRichHandsome;
}
@end

@implementation MJPerson

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHandsome.bits |= MJTallMask;
    } else {
        _tallRichHandsome.bits &= ~MJTallMask;
    }
}

- (BOOL)isTall
{
    return !!(_tallRichHandsome.bits & MJTallMask);
}

@end

再来看一种情况,枚举采用位运算:

//typedef enum {
//    MJOptionsOne = 1,   // 0b0001
//    MJOptionsTwo = 2,   // 0b0010
//    MJOptionsThree = 4, // 0b0100
//    MJOptionsFour = 8   // 0b1000
//} MJOptions;

typedef enum {
//    MJOptionsNone = 0,    // 0b0000
    MJOptionsOne = 1<<0,   // 0b0001
    MJOptionsTwo = 1<<1,   // 0b0010
    MJOptionsThree = 1<<2, // 0b0100
    MJOptionsFour = 1<<3   // 0b1000
} MJOptions;

@implementation ViewController

/*
 0b0001
 0b0010
 0b1000
 ------
 0b1011
&0b0100
-------
 0b0000
 */
- (void)setOptions:(MJOptions)options
{
    if (options & MJOptionsOne) {
        NSLog(@"包含了MJOptionsOne");
    }
    
    if (options & MJOptionsTwo) {
        NSLog(@"包含了MJOptionsTwo");
    }
    
    if (options & MJOptionsThree) {
        NSLog(@"包含了MJOptionsThree");
    }
    
    if (options & MJOptionsFour) {
        NSLog(@"包含了MJOptionsFour");
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setOptions: MJOptionsOne | MJOptionsTwo | MJOptionsFour];
    // 因为值特殊,所以用 + 号也可以实现,但是不专业,不推荐
    [self setOptions: MJOptionsOne + MJOptionsTwo + MJOptionsFour];
}

@end

Class详解

objc_class

在Objc2.0之前,objc_class源码如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
    
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
    
} OBJC2_UNAVAILABLE;

然后在2006年苹果发布Objc 2.0之后,objc_class的定义就变成下面这个样子

typedef struct objc_class *Class;
typedef struct objc_object *id;

@interface Object { 
    Class isa; 
}

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

struct objc_object {
private:
    isa_t isa;
}

struct objc_class : objc_object {
    // 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
}

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
}

把源码的定义转化成类图,就是下图的样子

objc_class类图

从上述源码中,我们可以看到,Objective-C 对象都是 C 语言结构体实现的,在objc2.0中,所有的对象都会包含一个 isa_t 类型的结构体。

objc_object 被源码 typedef 成了id类型,这也就是我们平时遇到的id类型。这个结构体中就只包含了一个isa_t类型的结构体。

objc_class 继承于 objc_object。所以在 objc_class 中也会包含 isa_t 类型的结构体 isa。至此,可以得出结论:Objective-C 中类也是一个对象。在 objc_class 中,除了isa之外,还有3个成员变量,一个是父类的指针,一个是方法缓存,最后一个用来存储类的具体信息。

Object 类和 NSObject 类里面分别都包含一个 objc_class 类型的 isa。

class结构

class_rw_t

class_rw_t 里面的 methods、properties、protocols 是二维数组,是可读可写的,包含了类的初始内容、分类的内容。

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;				// 方法列表
    property_array_t properties;	// 属性列表
    protocol_array_t protocols;		// 协议列表

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
};

class_rw_t

class_ro_t

class_ro_t 里面的 baseMethodList、baseProtocols、ivars、baseProperties 是一维数组,是只读的,包含了类的初始内容。

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;						// 实例对象占用的内存空间
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;								// 类名
    method_list_t * baseMethodList;		// 初始方法列表
    protocol_list_t * baseProtocols;	// 初始协议列表
    const ivar_list_t * ivars;				// 成员变量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;	// 初始属性列表
};

class_ro_t

method_t

method_t 是对方法 / 函数的封装

struct method_t {
    SEL name;						// 方法/函数名
    const char *types;	// 编码(返回值类型、参数类型)
    IMP imp;						// 指向函数的指针(函数地址)
};
  • SEL 代表方法 / 函数名,一般叫做选择器,底层结构跟 char * 类似

    • 可以通过 @selector()sel_registerName() 获得
    • 可以通过 sel_getName()NSStringFromSelector() 转成字符串
    • 不同类中相同名字的方法,所对应的方法选择器是相同的
  • IMP 代表函数的具体实现,实际上就是一个指向函数地址的指针

    typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
    
  • types 包含了函数返回值、参数编码的字符串,iOS中提供了一个叫做 @encode 的指令,可以将具体的类型表示成字符串编码。

    NSLog(@"%s", @encode(SEL));  // @
    
    /*
    	types = "i24@0:8i16f20" 
    	i 表示返回值为 int,24表示所有数据占用的内存
    	@ 表示id或object,OC方法默认有两个参数,self 和 sel,0表示从第几个字节开始存储
    	: 表示SEL,8表示从第8个字节开始存储,因为前面的self已经占用了8个字节
    	......
    */
    - (int)test:(int)age height:(float)height;
    

cache_t

Class 内部结构中有个方法缓存(cache_t),用**散列表(哈希表)**来缓存曾经调用过的方法,可以提高方法的查找速度。

#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct cache_t {
    struct bucket_t *_buckets;	// 方法缓存散列表
    mask_t _mask;								// 散列表的长度-1
    mask_t _occupied;						// 已经缓存的方法数量
};

struct bucket_t {
private:
    cache_key_t _key;						// SEL作为key
    IMP _imp;										// IMP为值
};

cache_t

// objc-cache.mm

void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
    mutex_locker_t lock(cacheUpdateLock);
    cache_fill_nolock(cls, sel, imp, receiver);
#else
    _collecting_in_critical();
    return;
#endif
}
// 装填
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
    cacheUpdateLock.assertLocked();

    // Never cache before +initialize is done
    if (!cls->isInitialized()) return;

    // Make sure the entry wasn't added to the cache by some other thread 
    // before we grabbed the cacheUpdateLock.
    if (cache_getImp(cls, sel)) return;

    cache_t *cache = getCache(cls);
    cache_key_t key = getKey(sel);

    // Use the cache as-is if it is less than 3/4 full
    mask_t newOccupied = cache->occupied() + 1;
    mask_t capacity = cache->capacity();
    if (cache->isConstantEmptyCache()) {
        // Cache is read-only. Replace it.
        cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
    }
    else if (newOccupied <= capacity / 4 * 3) {
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        // Cache is too full. Expand it.
        cache->expand();
    }

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot because the 
    // minimum size is 4 and we resized at 3/4 full.
    bucket_t *bucket = cache->find(key, receiver);
    if (bucket->key() == 0) cache->incrementOccupied();
    bucket->set(key, imp);
}
// 查找
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
    assert(k != 0);

    bucket_t *b = buckets();
    mask_t m = mask();
    mask_t begin = cache_hash(k, m);
    mask_t i = begin;
    do {
        if (b[i].key() == 0  ||  b[i].key() == k) {
            return &b[i];
        }
    } while ((i = cache_next(i, m)) != begin);

    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)k, cls);
}
// 获取哈希值
static inline mask_t cache_hash(cache_key_t key, mask_t mask) 
{
    return (mask_t)(key & mask);
}
// 寻找下一个空余空间,不同架构下方法不同
#if __arm__  ||  __x86_64__  ||  __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}

#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}
// 扩容
void cache_t::expand()
{
    cacheUpdateLock.assertLocked();
    
    uint32_t oldCapacity = capacity();
    uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;

    if ((uint32_t)(mask_t)newCapacity != newCapacity) {
        // mask overflow - can't grow further
        // fixme this wastes one bit of mask
        newCapacity = oldCapacity;
    }

    reallocate(oldCapacity, newCapacity);
}
// 重新分配内存
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
{
    bool freeOld = canBeFreed();

    bucket_t *oldBuckets = buckets();
    bucket_t *newBuckets = allocateBuckets(newCapacity);

    // Cache's old contents are not propagated. 
    // This is thought to save cache memory at the cost of extra cache fills.
    // fixme re-measure this

    assert(newCapacity > 0);
    assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);

    setBucketsAndMask(newBuckets, newCapacity - 1);
    
    if (freeOld) {
      	// 将之前的内容释放掉
        cache_collect_free(oldBuckets, oldCapacity);
        cache_collect(false);
    }
}

objc_msgSend

OC中的方法调用,其实都是转换为 objc_msgSend 函数的调用。

objc_msgSend 的执行流程可以分为3大阶段:

  • 消息发送
  • 动态方法解析
  • 消息转发

objc_msgSend 如果找不到合适的方法进行调用,会报错 unrecognized selector sent to instance

objc_msgSend源码解析

	// ENTYR 表示入口
	ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
	MESSENGER_START
	// x0寄存器:消息接受者,receiver
	cmp	x0, #0			// nil check and tagged pointer check
	// b 表示调整,b.le 表示在条件 le 成立的时候进行跳转,跳转至 LNilOrTagged
	// le 表示小于等于,即 x0 <= #0,也就是判断receiver是否为nil,一旦为nil,就跳转到 LReturnZero
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
	// ldr 把数据从内存中读取到寄存器中,这里x13寄存器里保存的是isa指针
	ldr	x13, [x0]		// x13 = isa
	// 将isa与ISA_MASK按位与,并将结果保存在x16寄存器中,x16寄存器保存的是class地址
	and	x16, x13, #ISA_MASK	// x16 = class	
LGetIsaDone:
	// 获取到class地址以后,开始查找方法缓存
	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached

LNilOrTagged:
	b.eq	LReturnZero		// nil check

	// tagged
	mov	x10, #0xf000000000000000
	cmp	x0, x10
	b.hs	LExtTag
	adrp	x10, _objc_debug_taggedpointer_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
	ubfx	x11, x0, #60, #4
	ldr	x16, [x10, x11, LSL #3]
	b	LGetIsaDone

LExtTag:
	// ext tagged
	adrp	x10, _objc_debug_taggedpointer_ext_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
	ubfx	x11, x0, #52, #8
	ldr	x16, [x10, x11, LSL #3]
	b	LGetIsaDone
	
LReturnZero:
	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	MESSENGER_END_NIL
	ret # 相当于OC里的return

	END_ENTRY _objc_msgSend

objc_msgSend执行流程

  • objc-msg-arm64.s

    • ENTRY _objc_msgSend
    • b.le LNilOrTagged
    • CacheLookup NORMAL
    • .macro CacheLookup
    • .macro CheckMiss
    • STATIC_ENTRY __objc_msgSend_uncached
    • .macro MethodTableLookup
    • __class_lookupMethodAndLoadCache3
  • objc-runtime-new.mm

    • _class_lookupMethodAndLoadCache3
    • lookUpImpOrForward
      • getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
      • cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
      • _class_resolveMethod (从这里开始进入动态方法解析)
        • _class_resolveClassMethod (如果是元类)
        • _class_resolveInstanceMethod (如果不是元类)
      • _objc_msgForward_impcache (从这里开始进入消息转发)
  • objc-msg-arm64.s

    • STATIC_ENTRY __objc_msgForward_impcache
    • ENTRY __objc_msgForward
  • Core Foundation

    • __forwarding__(不开源)

lookUpImpOrForward

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.read();

    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    
 retry:    
    runtimeLock.assertReading();

    // Try this class's cache.
		// 判断缓存中是否存在,存在,就跳转至done
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
  	// 缓存中不存在,就去类的方法列表中找
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
          	// 找到以后,添加至缓存中,然后跳转至done
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.
  	// 去superclass的缓存和方法列表中找
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                  	// 找到以后,添加至缓存中(添加至当前Class的缓存,不是superclass),然后跳转至done
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
              	// 找到以后,添加至缓存中(添加至当前Class的缓存,不是superclass),然后跳转至done
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.
		// 没有找到方法实现,开始尝试动态方法解析
  	// 先查看是否已经进行过动态方法解析,没有则进入,有则跳过
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
      	// 开始进行动态方法解析
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
      	// 将标记改为已经进行了动态方法解析,然后回到retry,重复消息发送流程
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
		// 没有找到方法实现,动态方法解析也没用,则进入消息转发
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

消息发送

消息发送

动态方法解析

动态方法解析

@implementation MJPerson
// C函数
void c_other(id self, SEL _cmd)
{
    NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}
// 动态添加类方法
+ (BOOL)resolveClassMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 第一个参数是object_getClass(self)
        class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
        return YES;
    }
    return [super resolveClassMethod:sel];
}
// 动态添加实例方法,也可以添加C函数
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 动态添加test方法的实现
        class_addMethod(self, sel, (IMP)c_other, "v16@0:8");

        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

- (void)other
{
    NSLog(@"%s", __func__);
}
// 动态添加实例方法,可以添加其他OC方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 获取其他方法
        Method method = class_getInstanceMethod(self, @selector(other));

        // 动态添加test方法的实现
        class_addMethod(self, sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));

        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end

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

消息转发

消息转发,顾名思义就是把自己无法处理的消息转发给别的对象去处理。

通过分析报错信息,我们发现了一个 __forwarding__ 函数,但是这个函数是不开源的,万幸的是国外有网友通过汇编对代码进行了分析,猜测的写了份伪代码,总结如下:

// 伪代码
int __forwarding__(void *frameStackPointer, int isStret) {
    id receiver = *(id *)frameStackPointer;
    SEL sel = *(SEL *)(frameStackPointer + 8);
    const char *selName = sel_getName(sel);
    Class receiverClass = object_getClass(receiver);

    // 调用 forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if (forwardingTarget && forwardingTarget != receiver) {
            if (isStret == 1) {
                int ret;
                objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
                return ret;
            }
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }

    // 僵尸对象
    const char *className = class_getName(receiverClass);
    const char *zombiePrefix = "_NSZombie_";
    size_t prefixLen = strlen(zombiePrefix); // 0xa
    if (strncmp(className, zombiePrefix, prefixLen) == 0) {
        CFLog(kCFLogLevelError,
              @"*** -[%s %s]: message sent to deallocated instance %p",
              className + prefixLen,
              selName,
              receiver);
        <breakpoint-interrupt>
    }

    // 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature) {
            BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
            if (signatureIsStret != isStret) {
                CFLog(kCFLogLevelWarning ,
                      @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.",
                      selName,
                      signatureIsStret ? "" : not,
                      isStret ? "" : not);
            }
            if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
                NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

                [receiver forwardInvocation:invocation];

                void *returnValue = NULL;
                [invocation getReturnValue:&value];
                return returnValue;
            } else {
                CFLog(kCFLogLevelWarning ,
                      @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
                      receiver,
                      className);
                return 0;
            }
        }
    }

    SEL *registeredSel = sel_getUid(selName);

    // selector 是否已经在 Runtime 注册过
    if (sel != registeredSel) {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
              sel,
              selName,
              registeredSel);
    } // doesNotRecognizeSelector
    else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }
    else {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
              receiver,
              className);
    }

    // The point of no return.
    kill(getpid(), 9);
}

消息转发

  • 开发者可以在 forwardInvocation: 方法中自定义任何逻辑
  • 以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)
@implementation MJPerson
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        // objc_msgSend([[MJCat alloc] init], aSelector)
      	// 如果这里没有做任何处理,或者返回nil,就进入methodSignatureForSelector:方法
        return [[MJCat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
      	// 如果返回为nil,则直接调用doesNotRecognizeSelector:方法
      	// 如果返回一个合理的方法签名,则会调用forwardInvocation:方法
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
      	// return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}

// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
// anInvocation.target 方法调用者
// anInvocation.selector 方法名
// [anInvocation getArgument:NULL atIndex:0]
// 在这里你可以做你想做的一切,哪怕什么都不写,只是实现了这个方法,也不会报错
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//    anInvocation.target = [[MJCat alloc] init];
//    [anInvocation invoke];

    [anInvocation invokeWithTarget:[[MJCat alloc] init]];
  
  	  // 参数顺序:receiver、selector、other arguments
//    int age;
//    [anInvocation getArgument:&age atIndex:2];
//    NSLog(@"%d", age + 10);
  
//  	int ret;
//    [anInvocation getReturnValue:&ret];
//    NSLog(@"%d", ret);
}

@end

RuntimeAPI

类相关

  • 动态创建一个类(参数:父类,类名,额外的内存空间)

    Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
    
  • 注册一个类(要在类注册之前添加成员变量)

    void objc_registerClassPair(Class cls)
    
  • 销毁一个类

    void objc_disposeClassPair(Class cls)
    
  • 获取isa指向的Class

    Class object_getClass(id obj)
    
  • 设置isa指向的Class

    Class object_setClass(id obj, Class cls)
    
  • 判断一个OC对象是否为Class

    BOOL object_isClass(id obj)
    
  • 判断一个Class是否为元类

    BOOL class_isMetaClass(Class cls)
    
  • 获取父类

    Class class_getSuperclass(Class cls)
    

示例:

void run(id self, SEL _cmd)
{
    NSLog(@"_____ %@ - %@", self, NSStringFromSelector(_cmd));
}

void testClass()
{
    // 创建类
    Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
    class_addIvar(newClass, "_age", 4, 1, @encode(int));
    class_addIvar(newClass, "_weight", 4, 1, @encode(int));
    class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
    // 注册类
    objc_registerClassPair(newClass);
    
    //        MJPerson *person = [[MJPerson alloc] init];
    //        object_setClass(person, newClass);
    //        [person run];
    
    //        id dog = [[newClass alloc] init];
    //        [dog setValue:@10 forKey:@"_age"];
    //        [dog setValue:@20 forKey:@"_weight"];
    //        [dog run];
    //
    //        NSLog(@"%@ %@", [dog valueForKey:@"_age"], [dog valueForKey:@"_weight"]);
    
    // 在不需要这个类时释放
    objc_disposeClassPair(newClass);
}

void test()
{
    MJPerson *person = [[MJPerson alloc] init];
    [person run];
    
    object_setClass(person, [MJCar class]);
    [person run];
    
    NSLog(@"%d %d %d",
          object_isClass(person),
          object_isClass([MJPerson class]),
          object_isClass(object_getClass([MJPerson class]))
          );
    
    //        NSLog(@"%p %p", object_getClass([MJPerson class]), [MJPerson class]);
}

成员变量

  • 获取一个实例变量信息

    Ivar class_getInstanceVariable(Class cls, const char *name)
    
  • 拷贝实例变量列表(最后需要调用free释放)

    Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
    
  • 设置和获取成员变量的值

    void object_setIvar(id obj, Ivar ivar, id value)
    id object_getIvar(id obj, Ivar ivar)
    
  • 动态添加成员变量(已经注册的类是不能动态添加成员变量的)

    BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
    
  • 获取成员变量的相关信息

    const char *ivar_getName(Ivar v)
    const char *ivar_getTypeEncoding(Ivar v)
    

示例:

void testIvars()
{
    // 获取成员变量信息
//    Ivar ageIvar = class_getInstanceVariable([MJPerson class], "_age");
//    NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
    
    // 设置和获取成员变量的值
//    Ivar nameIvar = class_getInstanceVariable([MJPerson class], "_name");
//    MJPerson *person = [[MJPerson alloc] init];
//    object_setIvar(person, nameIvar, @"123");
  	// 数字不能直接转成id类型,指针变量就是存值的,所以可以转为指针变量,可以将指针变量转为id类型
//    object_setIvar(person, ageIvar, (__bridge id)(void *)10);
//    NSLog(@"%@ %d", person.name, person.age);
    
    // 成员变量的数量
    unsigned int count;
    Ivar *ivars = class_copyIvarList([MJPerson class], &count);
    for (int i = 0; i < count; i++) {
        // 取出i位置的成员变量
        Ivar ivar = ivars[i];
        NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
    }
    free(ivars);
}

属性

  • 获取一个属性

    objc_property_t class_getProperty(Class cls, const char *name)
    
  • 拷贝属性列表(最后需要调用free释放)

    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
    
  • 动态添加属性

    BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
    
  • 动态替换属性

    void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
    
  • 获取属性的一些信息

    const char *property_getName(objc_property_t property)
    const char *property_getAttributes(objc_property_t property)
    

方法

  • 获得一个实例方法、类方法

    Method class_getInstanceMethod(Class cls, SEL name)
    Method class_getClassMethod(Class cls, SEL name)
    
  • 方法实现相关操作

    IMP class_getMethodImplementation(Class cls, SEL name) 
    IMP method_setImplementation(Method m, IMP imp)
    // 交换方法实现
    void method_exchangeImplementations(Method m1, Method m2) 
    
  • 拷贝方法列表(最后需要调用free释放)

    Method *class_copyMethodList(Class cls, unsigned int *outCount)
    
  • 动态添加方法

    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    
  • 动态替换方法

    IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
    
  • 获取方法的相关信息(带有copy的需要调用free去释放)

    SEL method_getName(Method m)
    IMP method_getImplementation(Method m)
    const char *method_getTypeEncoding(Method m)
    unsigned int method_getNumberOfArguments(Method m)
    char *method_copyReturnType(Method m)
    char *method_copyArgumentType(Method m, unsigned int index)
    
  • 选择器相关

    const char *sel_getName(SEL sel)
    SEL sel_registerName(const char *str)
    
  • 用block作为方法实现

    IMP imp_implementationWithBlock(id block)
    id imp_getBlock(IMP anImp)
    BOOL imp_removeBlock(IMP anImp)
    

Runtime应用

查看并设置私有成员变量

- (void)viewDidLoad {
    [super viewDidLoad];
    
    unsigned int count;
    Ivar *ivars = class_copyIvarList([UITextField class], &count);
    for (int i = 0; i < count; i++) {
        // 取出i位置的成员变量
        Ivar ivar = ivars[i];
        NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
    }
    free(ivars);
    
    self.textField.placeholder = @"请输入用户名";
    
    [self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
    
//    UILabel *placeholderLabel = [self.textField valueForKeyPath:@"_placeholderLabel"];
//    placeholderLabel.textColor = [UIColor redColor];
    
//    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
//    attrs[NSForegroundColorAttributeName] = [UIColor redColor];
//    self.textField.attributedPlaceholder = [[NSMutableAttributedString alloc] initWithString:@"请输入用户名" attributes:attrs];
}

字典转模型

@implementation NSObject (Json)
// 只是简单演示,离成型的字典转模型框架还差得远
+ (instancetype)mj_objectWithJson:(NSDictionary *)json
{
    id obj = [[self alloc] init];
    
    unsigned int count;
    Ivar *ivars = class_copyIvarList(self, &count);
  	// 利用Runtime遍历所有的属性或者成员变量
    for (int i = 0; i < count; i++) {
        // 取出i位置的成员变量
        Ivar ivar = ivars[i];
        NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
        [name deleteCharactersInRange:NSMakeRange(0, 1)];
        
        id value = json[name];
        if ([name isEqualToString:@"ID"]) {
            value = json[@"id"];
        }
      	// 利用KVC设值
        [obj setValue:value forKey:name];
    }
    free(ivars);
    
    return obj;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 字典转模型
        NSDictionary *json = @{
                               @"id" : @20,
                               @"age" : @20,
                               @"weight" : @60,
                               @"name" : @"Jack"
                               };
        
        MJPerson *person = [MJPerson mj_objectWithJson:json];
        NSLog(@"123");
    }
    return 0;
}

替换方法实现(Method Swizzle)

@implementation MJPerson
- (void)run
{
    NSLog(@"%s", __func__);
}

- (void)test
{
    NSLog(@"%s", __func__);
}

@end
  
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *person = [[MJPerson alloc] init];
        
        Method runMethod = class_getInstanceMethod([MJPerson class], @selector(run));
        Method testMethod = class_getInstanceMethod([MJPerson class], @selector(test));
        method_exchangeImplementations(runMethod, testMethod);

        [person run];
    }
    return 0;
}

这种应用对于我们自己编写的类其实用处不大,一般都是用来交换系统或者第三方库里面的方法,添加上一些我们自己的实现。

方法交换的底层逻辑是,将两个方法底层数据结构 Method_t 中的 IMP 进行交换。

@implementation NSMutableArray (Extension)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(mj_insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)mj_insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if (anObject == nil) return;
    /*
    	调用系统原来的实现,因为方法的IMP已经交换了,
    	所以要调用系统原来的实现,就要调用我们自己的方法
    */
    [self mj_insertObject:anObject atIndex:index];
}

@end

Method Swizzle注意事项

  1. 避免交换父类方法
  2. 交换方法应该在 +load 方法
  3. 交换方法应该放到 dispatch_once 中执行
  4. 交换的分类方法应该添加自定义前缀,避免冲突
  5. 交换的分类方法应调用原实现

在每次的方法交换时都应该注意以上几点,最终我们可以将其封装到NSObject的分类中,方便在调用:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface NSObject (Swizzling) 
 
+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector
                         swizzledSelector:(SEL)swizzledSelector;
@end
#import "NSObject+Swizzling.h"
@implementation NSObject (Swizzling)
 
+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector{
    Class class = [self class];
 
    //原有被交换方法
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
 
    //要交换的分类新方法
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
 
    //避免交换到父类的方法,先尝试添加被交换方法的新实现IMP
    BOOL didAddMethod = class_addMethod(class,originalSelector,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
 
    if (didAddMethod) {//添加成功:则表明没有实现IMP,将源SEL的IMP替换到交换SEL的IMP
        class_replaceMethod(class,swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {//添加失败:表明已实现,则可以直接交换实现IMP
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
@end

扩展

Super的本质

[super message] 的消息接收者仍然是子类对象,只不过在查找方法实现的时候从父类开始查找,但是找到方法实现后,还是向子类发送消息。

super调用,底层会转换为 objc_msgSendSuper2 函数的调用,接收2个参数:

  • struct objc_super2

    struct objc_super2 {
    		id receiver;					// 消息接收者,子类对象
    		Class current_class;	// 消息接收者的Class对象
    }
    
  • SEL

在 objc_msgSendSuper2 方法内部,还是会根据 current_class -> superclass 来查找方法实现的。

isMemberOfClass 和 isKindOfClass

// 返回当前对象的类对象是否是传入的对象
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
// 返回当前对象的类对象是否是传入对象的子类
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
// 返回当前对象的元类是否是传入的对象
+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}
// 返回当前对象的元类是否是传入对象的子类
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

// 这句代码的方法调用者不管是哪个类(只要是NSObject体系下的),都返回YES
NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0
NSLog(@"%d", [MJPerson isKindOfClass:[MJPerson class]]); // 0
NSLog(@"%d", [MJPerson isMemberOfClass:[MJPerson class]]); // 0

LLVM中间代码

Objective-C在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation),扩展名为 .ll ,可以使用以下命令行指令生成中间代码:

clang -emit-llvm -S main.m

我们之前在分析底层的时候,都是将OC代码转成C++代码,实际上是有些不准确的,只不过绝大多数时候都是比较准确的,而且C++语法和OC比较类似,易于我们分析底层知识,如果想准确的了解底层到底做了什么,可以将其转成中间代码之后再分析。

语法简介

  • @ - 全局变量
  • % - 局部变量
  • alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
  • i32 - 32位4字节的整数
  • align - 对齐
  • load - 读出,store 写入
  • icmp - 两个整数值比较,返回布尔值
  • br - 选择分支,根据条件来转向label,不根据条件跳转的话类似 goto
  • label - 代码标签
  • call - 调用函数

具体可以参考官方文档:llvm.org/docs/LangRe…

面试题

1.讲一下OC的消息机制

OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)

objc_msgSend底层有3大阶段:消息发送(当前类、父类中查找)、动态方法解析、消息转发

2.消息转发流程

3.什么是Runtime,平时项目中用到过吗?

  • OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
  • OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
  • 平时编写的OC代码,底层都是转换成了Runtime API进行调用

具体应用:

  • 利用关联对象(AssociatedObject)给分类添加属性
  • 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
  • 交换方法实现(交换系统的方法)
  • 利用消息转发机制解决方法找不到的异常问题

4.以下代码能不能执行成功?如果可以,打印结果是什么?

@interface MJPerson : NSObject
@property (copy, nonatomic) NSString *name;

- (void)print;
@end

@implementation MJPerson
- (void)print
{
    NSLog(@"my name is %@", self->_name);
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];

    id cls = [MJPerson class];
    void *obj = &cls;
    [(__bridge id)obj print];
}
@end

可以调用成功,打印:

my name is <ViewController: 0x7fcfac804e00>
  1. 为什么可以调用成功?

    因为obj调用方法和person对象调用方法在底层结构,本质上是一样的。

    person对象在调用方法时,首先是找到自己的isa指针,然后根据指针指向的地址找到自己的类对象,从中找到方法实现进行调用,而查找isa指针的过程,其实就是取出自己指向的内存的前八个字节内的数据,因为我们知道isa在对象内存中永远是第一位,而且占用八个字节。

    obj在调用print方法时,也是先找到自己指向的内存,也就是cls的内存,而cls里刚好只有8个字节的数据,用来存储指向 [MJPerson class] 的指针,所以obj也找到了类对象,并可以从中找到方法实现并调用。

  2. 为什么self.name变成了viewcontroller等其他内容?

    这里是涉及到栈空间地址分配的问题,我们在使用person对象打印name时,首先要拿到name的值,而取值的时候,底层最根本的其实是找到person对象的isa指针的内存地址,再往后8个字节的内存地址就是name的内存地址。

    而obj在调用print方法时,把cls当成是isa指针,所以取name值时,也是找到在cls的内存地址的基础上再加8个字节后的内存地址所指向的数据。

    // 局部变量分配在栈空间
    // 栈空间分配,从高地址到低地址
    void test()
    {
        long long a = 4; // 0x7ffee638bff8
        long long b = 5; // 0x7ffee638bff0
        long long c = 6; // 0x7ffee638bfe8
        long long d = 7; // 0x7ffee638bfe0
        Abc e = { @"123", 4 };
        
        NSLog(@"%p %p %p %p %p", &a, &b, &c, &d, &e);
      	// 0x7ffee7bd9c18 0x7ffee7bd9c10 0x7ffee7bd9c08 0x7ffee7bd9c00 0x7ffee7bd9bf0
    }
    

    内存

    [super viewDidLoad] 内部会创建一个 super_obj 的结构体,也是存放在栈空间,这个结构体中有两个成员变量,self 和 [UIViewController Class],所以 cls 的内存地址再加8个字节,刚好是存储的 self,所以打印出来的内容为 <ViewController: 0x7fcfac804e00>