iOS Runtime详解及应用场景

一 runtime简介
二 Class的结构
三 isa指针详解
四 method详解
五 方法调用及消息转发流程
六 runtime常用api
七 runtime开发中具体应用

一 runtime简介

定义:所谓运行时, 就是尽可能地把决定从编译器推迟到运行期, 就是尽可能地做到动态. 只是在运行的时候才会去确定对象的类型和方法的. 因此利用Runtime机制可以在程序运行时动态地修改类和对象中的所有属性和方法.
Objective-C中调用对象的方法时, 会向该对象发送一条消息, runtime根据该消息做出反应.
Runtime是一套比较底层的纯C语言的API, Objective-C是运行在Runtime上的, 因此在Runtime中动态添加和实现一些非常强大的功能也就不足为奇了.
在Objective-C代码中使用Runtime, 需要引入<#import <objc/runtime.h>

总结说明:
runtime是什么?

runtime是一组APi,我们平时开发也用到很多API,所以runtime并没啥稀奇的,只不过使用c语言写的,GCD也是C语言写的API库。

runtime API干啥用的?

程序运行时动态地修改类和对象中的所有属性和方法,就是用来在程序运行是改变类的属性和行为的,就像GCD就是用来操作线程的。


二 Class的结构

既然是一组API来操作类的,首先我们就先来了解下类的本质是什么,然后再看它是怎么操作它的。

2.1我们看下类的本质定义

objc 源码地址 objc-runtime-new.h

class 结构图

我们看出来,它实际上就是用一个结构体来定义的,我们想想一个类它有什么,它首先有,属性,方法,然后有父类,他自己的标示等等。这个结构体里面基本都包含了。
接下来让我们具体解释一下这个结构体里面的每一项内容

2.1.1 ISA
Class ISA;  

objc_class 中也有一个 isa 指针,这说明 Objc 类本身也是一个对象。为了处理类和对象的关系,Runtime 库创建了一种叫做 Meta Class(元类) 的东西,类对象所属的类就叫做元类。Meta Class 表述了类对象本身所具备的元数据。

我们所熟悉的类方法,就源自于 Meta Class。我们可以理解为类方法就是类对象的实例方法。每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。

当你发出一个类似 NSObject alloc 的消息时,实际上,这个消息被发送给了一个类对象(Class Object),这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类(Root Meta Class)的实例。所有元类的 isa 指针最终都指向根元类。 下一节我们再详细介绍。

2.1.2 cache_t
struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;

Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。
Runtime 系统会把被调用的方法存到 Cache 中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。就像计算机组成原理中 CPU 绕过主存先访问 Cache 一样。
这个cache主要是方法调用的优化,别的没啥。

2.1.3 class_rw_t
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里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容,二位数组存储是为的更好的扩展新加的方法属性或协议。类和分类的信息都存储在这个结构体里面

2.1.3 class_ro_t
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里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,类的成员变量是存放在这里。
类的基本结构大致就这些,接下来我们要详细了解一下ISA指针,很多方面的东西都牵扯到它。

三 isa指针详解
3.1

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

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

isa.h

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      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
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

ISA_BITFIELD ISA位域信息解释
nonpointer
0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
1,代表优化过,使用位域存储更多的信息
has_assoc
是否有设置过关联对象,如果没有,释放时会更快
has_cxx_dtor
是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
shiftcls
存储着Class、Meta-Class对象的内存地址信息
magic
用于在调试时分辨对象是否未完成初始化
weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快
deallocating
对象是否正在释放
extra_rc
里面存储的值是引用计数器减1
has_sidetable_rc
引用计数器是否过大无法存储在isa中
如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
我们可以看到arm64架构之后isa 不仅存储着Class、Meta-Class对象的内存地址还存储着其他更多信息, Class、Meta-Class对象的内存地址被存储在shiftcls 中占用33位
uintptr_t shiftcls : 33
所以我们从一个对象isa指针看到的并不是对象真实的内存地址,我们要做一下位运算才能得到真实的地址要 & MASK.
既然isa存储这几种对象的内存地址,那我们就来了解下oc的几种对象。

3.2 Objective-C中的对象

Objective-C中的对象,简称OC对象,主要可以分为3种
instance对象(实例对象)
class对象(类对象)
meta-class对象(元类对象)

3.2.1 实例对象 instance

instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象

    Person *person1 = [[Person alloc] init];
    Person *person2 = [[Person alloc] init];
    Person *person3 = [[Person alloc] init];
    Person *person4 = [[Person alloc] init];

person1、person2,person3,person4是Person类的instance对象(实例对象)
它们是不同的四个对象,分别占据着四块不同的内存

(lldb) po person1
<Person: 0x102805b70>
(lldb) po person2
<Person: 0x102805bc0>
(lldb) po person3
<Person: 0x102805bd0>
(lldb) po person4
<Person: 0x102805be0>

instance对象在内存中存储的信息包括
isa指针,及它自己的成员变量


3.2.2 class对象(类对象)
  Person *person1 = [[Person alloc] init];
Person *person2 = [[Person alloc] init];
//传入对象的类对象
Class p1class = [person1 class];
Class p2class = [person2 class];
NSLog(@"p1class:%p\n p2class:%p",p1class,p2class);

22:17:12.098954+0800 test[4172:228782]
p1class:0x100002180
p2class:0x100002180

p1class ,p2class都是Person的class对象(类对象)
它们是同一个对象。每个类在内存中有且只有一个class对象
class对象在内存中存储的信息主要包括
isa指针
superclass指针
类的属性信息(@property)、类的对象方法信息(instance method)//存的是动态方法,为空
类的协议信息(protocol)、类的成员变量信息(ivar) //存储的是空值
......

3.2.3 meta-class对象(元类对象)
  Person *person1 = [[Person alloc] init];
    Person *person2 = [[Person alloc] init];
    //传入对象的类对象获取元类对象
    Class p1metaclass = object_getClass([person1 class]);
    Class p2metaclass = object_getClass([person2 class]);
    Boolean ismetaclass = class_isMetaClass(p1metaclass);//判断是否是元类对象
    NSLog(@"p1class:%p\n p2class:%p",p1metaclass,p2metaclass);

22:10:13.933354+0800 test[4129:225264] p1class:0x1000021a8
 p2class:0x1000021a8


p1metaclass是Person的meta-class对象(元类对象)
每个类在内存中有且只有一个meta-class对象
meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括
isa指针
superclass指针
类的类方法信息(class method)存的是静态方法
......

3.3 isa的与三种对象之间的关系
  • 实例对象(instance)的isa指向类对象(class)
  • 类对象(class)的isa指向元类对象meta-class)

他们的关系图如下所示:


isa与三种对象的关系图

当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用
当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用

3.4 supperclass讲解

supperclass只存在于类对象与元类对象里,它的作用想必大家也能猜到,遇到继承关系的时候supperclass就派上用场了,下面我们就分别讲解一下,类对象里的supperclass以及元类对象里的supperclass

3.4.1 类对象里的supperclass
@interface Student : Person
@interface Person: NSObject

我们定义了两个对象的继承关系Student继承Person,Person继承NSObject,那么他们的类对象关系图如下:


类对象里的supperclass关系图

当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用

3.4.2 元类对象里的supperclass
@interface Student : Person
@interface Person: NSObject

我们定义了两个对象的继承关系Student继承Person,Person继承
NSObject,那么他们的类对象关系图如下:


元类对象里的supperclass关系图

当Student的class要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过superclass找到Person的meta-class,最后找到类方法的实现进行调用

3.5 isa、superclass总结
isa supperclass关系图总结

1 instance的isa指向class
2 class的isa指向meta-class
3 meta-class的isa指向基类的meta-class
4 class的superclass指向父类的class
如果没有父类,superclass指针为nil

5 meta-class的superclass指向父类的meta-class
基类的meta-class的superclass指向基类的class
6 instance调用对象方法的轨迹
isa找到class,方法不存在,就通过superclass找父类
7 class调用类方法的轨迹
isa找meta-class,方法不存在,就通过superclass找父类
这个图已经很经典了需要大家细细品味。

四 method详解
4.1.1 method_t

上面我们讲过的类的结构中,我们可以看到,一个类的一些方法,协议,属性等是存在class_rw_t这个结构体中的。

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;
}

平时用的最多的就是方法调用了,下面让我们来深入了解一下method_array_t methods; // 方法列表
先看一下method_array_t是什么
objc-runtime-new.h

class method_array_t : 
    public list_array_tt<method_t, method_list_t> 
{
    typedef list_array_tt<method_t, method_list_t> Super;

 public:
    method_list_t **beginCategoryMethodLists() {
        return beginLists();
    }
    
    method_list_t **endCategoryMethodLists(Class cls);

    method_array_t duplicate() {
        return Super::duplicate<method_array_t>();
    }
};

我们可以看出这个类结构大概是下面这种结构的


method类结构

class_rw_t里面的methods是二维数组,是可读可写的,包含了类的初始内容、分类的内容,那么mathod_t内部结构是什么样的,我们来看一下

struct method_t {
    SEL name;   // 函数名
    const char *types; //编码(返回值,参数)
    MethodListIMP imp;  //函数地址

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

由此结构我们可以看出method_t就是对一个函数的具体描述,包含函数的名称,函数的出参,入参,以及函数具体地址,那么我们就具体来说一下它这几个变量

MethodListIMP
// Method lists use process-independent signature for compatibility.
using MethodListIMP = IMP __ptrauth_objc_method_list_imp;

#else

using MethodListIMP = IMP;

#endif
objc.h

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

IMP 代表函数的具体实现

SEL
objc.h

typedef struct objc_selector *SEL;

1 SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似
2 可以通过@selector()和sel_registerName()获得
可以通过sel_getName()和NSStringFromSelector()转成字符串

 SEL speaksel = sel_registerName(@"speak");
 SEL speaksel2 = @selector(speak);

3 不同类中相同名字的方法,所对应的方法选择器是相同的

这个其实很好理解,同一个类的不同对象,它的类结构都是一样的,类结构存储一份就好了,所以它们方法描述也只有一份,所有这个类的对象共用一份方法描述,只是每个对象传的出参,入参,不一样罢了。

types
const char *types 

描述方法参数类型的字符数组,types包含了函数返回值、参数编码的字符串

描述方法参数类型的字符数组的第一个字符是代表返回值的类型,后面的字符依次代表参数的类型,因为Objective-C中的函数会包含两个隐式参数,也就是方法调用者和方法名,例如

+(void)method

实际应该是

void method(id self, SEL _cmd)

如果返回值为空,那么函数的类型编码的第一个字符是v,如果不为空,则为返回值类型对应的编码,详细的可以看下面的编码对应表
因为第一个参数是方法调用者,它的类型肯定是对象类型,所以类型编码的第二个字符一定是@

因为第二个参数是方法名的类型,第三个字符一定是 :

所以这个函数

void method(id self, SEL _cmd)        

的类型编码为 "v@:"

那么如果要添加的函数是一个set函数,类型编码是怎么样的呢?

-(void)setA:(NSString *)a

同理,set方法实际的函数是这样的:

void setA(id self, SEL _cmd, id a)  

与上面无参数的方法相比,只是多了一个参数,

所以类型编码为"v@:@",代码为

class_addMethod(self, @selector(setA:), (IMP)setA,"v@:@");

iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码


encoding type

encoding type

由此方法描述的几个字段我们就讲解完了,接下来我们来讲解一下方法缓存

4.2方法缓存

在本文第一个图例,类结构中有一个cache字段

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
}
   

cache_t cache 就是用来缓存方法的,下面我们来看一下cache_t的内部结构

objc-runtime-new.h

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


struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    uintptr_t _imp;    //方法实现地址
    SEL _sel;   //sel  作为key
#else
    SEL _sel;
    uintptr_t _imp;
#endif

cache以方法名为 key方法地址为value 缓存。

4.2.1缓存查找实现
bucket_t * cache_t::find(SEL s, id receiver)
{
    assert(s != 0);

    bucket_t *b = buckets();
    mask_t m = mask();
    mask_t begin = cache_hash(s, m);
    mask_t i = begin;
    do {
        if (b[i].sel() == 0  ||  b[i].sel() == s) {
            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)s, cls);
}

static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask; //找不到就+1再找 这是解决散列冲突最简单的一种实现方式
}

当去查找一个方法是否在缓存中时,就会那这个方法名通过一个散列函数计算出他在buckets数组中的位置然后在&mask就能拿到一个函数的实现地址,

4.2.2缓存添加实现
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);

    // 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(); // 缓存扩容,生成一个新的数组大小是现有数组2倍,清空当前方法缓存
    }

    // 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(sel, receiver);
    if (bucket->sel() == 0) cache->incrementOccupied();
    bucket->set<Atomic>(sel, imp);
}

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
}

添加缓存首先要查找缓存是否存在,存在则返回,无则添加,当缓存满了的时候就会扩容,扩容后并不会把现有缓存copy到新数组中,而是把现有数组清空。所以每当缓存满了的时候就会,扩容,以前调用的方法都需要重新调用时才会缓存。

下面我们总结一下方法调用的一个整体过程:
1.首先通过isa指针找到类对象然后去类对象的缓存cache中去查找方法,如果查找到该方法则直接调用

2.如果在类对象中未找到方法,则去类对象的方法列表寻找方法,如果找到方法,则调用该方法,同时缓存一份到cache中

3.如果在类对象的cache和方法列表中都没有找到该方法,则通过类对象的superClass指针到父类的类对象的cache中查找,如果找到,则调用该方法,同时缓存一份到自身的类对象的cache中

4.如果在自身的类对象的cache中,方法列表中,父类的cache中都没找到,则到父类的方法列表中查找,如果找到,则调用该方法,同时缓存一 份到父类类对象的cache中,也缓存一份到自己类对象的cache中.

5.如果在父类的方法列表里也找不到该方法,则重复执行4,层层向上查找,直到找到NSObject,如果NSObject都没有,那就会结束 报错,其实当一个方法找不到时并不会立即报错,它会进入消息转发流程,给你三次机会去处理,这下来我们就讲一下这个流程。

五 方法调用及消息转发流程

首先我们先看一段代码:

#include<stdio.h>
#include "Person.h"
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
 int main()
{
    Person *person = [[Person alloc] init];
    [person speak];
}

把它转成c++代码:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

 int main()
{
    Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("speak"));
}

我们发现objc_msgSend参与了对象创建以及方法调用,OC中的方法调用,其实都是转换为objc_msgSend函数的调用。OC的方法调用就是利用objc_msgSend这种消息机制,给方法调用者发送消息,它有两个参与内容:

1 消息接受者 (receiver):person
2 消息名称 :init ,speak

objc_msgSend的执行流程可以分为3大阶段
1 消息发送
2 动态方法解析
3 消息转发
下面我们从源码来分析objc_msgSend执行流程,先看一下大致的执行流程,然后我们在分析每个阶段的流程

objc-msg-arm64.s
ENTRY _objc_msgSend  // 1 进入消息流程
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_resolveInstanceMethod。  // 2 动态方法解析
_objc_msgForward_impcache

objc-msg-arm64.s
STATIC_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward   //3 进入消息转发
Core Foundation
__forwarding__(不开源)
5.1.1 消息发送
objc_msgSend 的源码实现
ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    cmp p0, #0          //1 消息接收者,receiver  nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        // 小于等于零跳转到 LNilOrTagged  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13     // p16 = class
LGetIsaDone:
    CacheLookup NORMAL      // 2 查找缓存,具体实现看 CacheLookup calls imp or objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero     // 为空跳转到 LReturnZero nil check

    // tagged
    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]
    adrp    x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
    add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
    cmp x10, x16
    b.ne    LGetIsaDone

    // 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
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    ret            //return 退出程序

    END_ENTRY _objc_msgSend

.macro CacheLookup
    // p1 = SEL, p16 = isa
    ldp p10, p11, [x16, #CACHE] // 3 查找方法缓存  p10 = buckets, p11 = occupied|mask
#if !__LP64__
    and w11, w11, 0xffff    // p11 = mask
#endif
    and w12, w1, w11        // x12 = _cmd & mask
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // 4 缓存查找到就返回函数地址 call or return imp
    
2:  // not hit: p12 = not-hit bucket  // 没有查找到缓存
    CheckMiss $0            // 5 没找到缓存CheckMiss    miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

3:  // wrap: p12 = first bucket, w11 = mask
    add p12, p12, w11, UXTW #(1+PTRSHIFT)
                                // p12 = buckets + (mask << 1+PTRSHIFT)

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // 6 没有找到则调用CheckMiss  miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

3:  // double wrap
    JumpMiss $0
    
.endmacro

.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz p9, LGetImpMiss
.elseif $0 == NORMAL
    cbz p9, __objc_msgSend_uncached    //7 调用__objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup  // 8 查找方法表
    TailCallFunctionPointer x17

    END_ENTRY __objc_msgSend_uncached


    STATIC_ENTRY __objc_msgLookup_uncached
    UNWIND __objc_msgLookup_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup
    ret

    END_ENTRY __objc_msgLookup_uncached


    STATIC_ENTRY _cache_getImp

    GetClassFromIsa_p16 p0
    CacheLookup GETIMP

.macro MethodTableLookup
    
    // push frame
    SignLR
    stp fp, lr, [sp, #-16]!
    mov fp, sp

    // save parameter registers: x0..x8, q0..q7
    sub sp, sp, #(10*8 + 8*16)
    stp q0, q1, [sp, #(0*16)]
    stp q2, q3, [sp, #(2*16)]
    stp q4, q5, [sp, #(4*16)]
    stp q6, q7, [sp, #(6*16)]
    stp x0, x1, [sp, #(8*16+0*8)]
    stp x2, x3, [sp, #(8*16+2*8)]
    stp x4, x5, [sp, #(8*16+4*8)]
    stp x6, x7, [sp, #(8*16+6*8)]
    str x8,     [sp, #(8*16+8*8)]

    // receiver and selector already in x0 and x1
    mov x2, x16
    bl  __class_lookupMethodAndLoadCache3  // 9 跳转调用__class_lookupMethodAndLoadCache3

    // IMP in x0
    mov x17, x0
    
    // restore registers and return
    ldp q0, q1, [sp, #(0*16)]
    ldp q2, q3, [sp, #(2*16)]
    ldp q4, q5, [sp, #(4*16)]
    ldp q6, q7, [sp, #(6*16)]
    ldp x0, x1, [sp, #(8*16+0*8)]
    ldp x2, x3, [sp, #(8*16+2*8)]
    ldp x4, x5, [sp, #(8*16+4*8)]
    ldp x6, x7, [sp, #(8*16+6*8)]
    ldr x8,     [sp, #(8*16+8*8)]

    mov sp, fp
    ldp fp, lr, [sp], #16
    AuthenticateLR

.endmacro
10 class_lookupMethodAndLoadCache3 定义
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);//cls 类对象, sel 类名, obj 消息接收者, 
}
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.lock();
    checkIsKnownClass(cls);

    if (!cls->isRealized()) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }

    if (initialize && !cls->isInitialized()) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock may have been dropped but is now locked again

        // 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.assertLocked();

    // Try this class's cache.

    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists. 查找类对象的方法列表
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists. 查找父类对象的方法列表
    {
        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.
                    log_and_fill_cache(cls, imp, sel, inst, curClass); //cls 将父类方法填充到类对像的缓存中
                    goto done;  //找到了跳转 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) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass); //添加缓存到当前类对象
                imp = meth->imp;
                goto done;    //找到了直接跳转done
            }
        }
    }

    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        resolveMethod(cls, sel, inst);   //进入消息转发流程
        runtimeLock.lock();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        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.unlock();

    return imp;  // 返回函数地址
}

objc_msgSend由于调用的频次非常频繁,为了提高执行效率,所以使用了汇编来实现,它的大致实现流程是
1.首先通过isa指针找到类对象然后去类对象的缓存cache中去查找方法,如果查找到该方法则直接调用

2.如果在类对象中未找到方法,则去类对象的方法列表寻找方法,如果找到方法,则调用该方法,同时缓存一份到cache中

3.如果在类对象的cache和方法列表中都没有找到该方法,则通过类对象的superClass指针到父类的类对象的cache中查找,如果找到,则调用该方法,同时缓存一份到自身的类对象的cache中

4.如果在自身的类对象的cache中,方法列表中,父类的cache中都没找到,则到父类的方法列表中查找,如果找到,则调用该方法,同时缓存一 份到父类类对象的cache中,也缓存一份到自己类对象的cache中.

5.如果在父类的方法列表里也找不到该方法,则重复执行4,层层向上查找,直到找到NSObject,如果NSObject都没有,那就会进入动态方法解析。执行流程图如下:


objc_msgSend 消息发送流程

讲完了objc_msgSend 消息发送流程,接下来我们讲下 动态方法解析流程。

5.1.2 动态方法解析

先看一下底层实现源码:

我们截取了上面lookUpImpOrForward中的一段代码
 if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;  //重新走查找方法流程
    }
static void resolveMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());

    if (! cls->isMetaClass()) { //如果不是元类对象
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(cls, sel, inst); //调用resolveInstanceMethod
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(cls, sel, inst); //否则调用resolveClassMethod
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            resolveInstanceMethod(cls, sel, inst);
        }
    }
}
static void resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());

    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); //那当前类对象调用resolveInstanceMethod

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
static void resolveClassMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, SEL_resolveClassMethod, sel);//拿当前元类对象调用resolveClassMethod

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

1 前面的消息发送没有找到要调用的方法就会调用 resolveMethod(cls, sel, inst)
2 resolveMethod(cls, sel, inst)内部做了议程判断
* 如果是元类对象则调用resolveClassMethod(cls, sel, inst);
*否则调用:resolveInstanceMethod(cls, sel, inst);
3 resolveClassMethod(cls, sel, inst); 内部则是拿当前的元类对象调用resolveClassMethod方法,所以我们要进行消息处理则要在类里实现这个方法

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if(sel == @selector(eat)){
        Method method = class_getInstanceMethod(self, @selector(other));
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        return YES;
        
    }
    return [super resolveInstanceMethod:<#sel#>];
}

4 resolveInstanceMethod(cls, sel, inst) 内部这是拿当前的类对象调用resolveInstanceMethod方法同理我们要进行消息处理则要在类里实现这个方法

+ (BOOL)resolveClassMethod:(SEL)sel{
    if(sel == @selector(eat)){
           Method method = class_getInstanceMethod(self, @selector(other));
           class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
           return YES;
           
       }
       return [super resolveClassMethod:<#sel#>];
}

最后看一下它的执行流程图:


消息处理流程

动态解析过后,会重新走“消息发送”的流程
“从receiverClass的cache中查找方法”这一步开始执行,如果开发者详细处理的两个方法都没有实现,会怎么办呢,那么他就会走接下来的消息转发流程。

5.1.3 消息转发

lookUpImpOrForward接近最后两行代码

  imp = (IMP)_objc_msgForward_impcache;  //执行__objc_msgForward_impcache
  cache_fill(cls, sel, imp, inst);
objc-msg-arm64.s

STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
    b   __objc_msgForward

    END_ENTRY __objc_msgForward_impcache

    
    ENTRY __objc_msgForward

    adrp    x17, __objc_forward_handler@PAGE
    ldr p17, [x17, __objc_forward_handler@PAGEOFF]  //执行__objc_forward_handler
    TailCallFunctionPointer x17
    
    END_ENTRY __objc_msgForward
STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
    b   __objc_msgForward //执行__objc_msgForward

    END_ENTRY __objc_msgForward_impcache
//为代码
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);
}

最终我们发现它会调用两个方法:
1 forwardingTargetForSelector: 返回yes 执行objc_msgSend(返回值, SEL )返回nil这执行第二步
2 methodSignatureForSelector //方法签名如果这个方法有返回值的话这会执行下一步
3 (void)forwardInvocation:(NSInvocation *)anInvocation //NSInvocation封装了一个方法调用,包括,调用者,方法名,方法参数,只要能进入到这个方法,它里面的实现可以随便写,哪怕打印一句话也行或者任何处理也没有也行。
消息转发调用流程图如下:


消息转发调用流程

1 苹果的文档里,讲述了这一个消息转发的出发点,其实是为了实现类似C多继承的功能。我们知道,在C中如果一个类想要具有多个类的功能,是可以直接继承多个类的。而Objective-C是单继承,如果想实现类似的功能,就用消息转发,将消息转发给有能力处理的类。苹果是这样描述他们的思想的:C的多继承,是加法,在多继承的同时,其实也增加了很多不需要的功能,而苹果通过消息转发,实现了减法的思想,只留有用的方法,而不去增加过多内容。
2 开发者可以在forwardInvocation:方法中自定义任何逻辑
3 以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if(aSelector == @selector(eat)) {
        return [[Person alloc] init];//将eat方法j转交给Person对象执行,person要实现eat方法
    }
    return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if(aSelector == @selector(eat)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    
}


+ (id)forwardingTargetForSelector:(SEL)aSelector{
    if(aSelector == @selector(eat)) {
        return [[Person alloc] init];//将eat方法j转交给Person对象执行,person要实现eat方法
    }
    return [super forwardingTargetForSelector:aSelector];
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if(aSelector == @selector(eat)) {
         NSMethodSignature *signature = [[[Person alloc] init] methodSignatureForSelector:@selector(eat)];
        return signature;
       }
       return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
    anInvocation.target;
    anInvocation.selector;
    [anInvocation getArgument:NULL atIndex:2];// 第三个参数才是你的方法参数,前两个是方法的默认参数
    [anInvocation invoke];
}
六 runtime常用api
6.1类
  • 动态创建一个类(参数:父类,类名,额外的内存空间)
    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)

6.2成员变量

*获取一个实例变量信息
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)

6.3属性

*获取一个属性
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)

6.4方法

*获得一个实例方法、类方法
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基本知识都以经讲完了,下面我们来看一下runtime在实际开发中的应用

七 runtime开发中具体应用

runtime的应用,主要有几种:

  • 1 AOP,切面编程,做打点
  • 2 method swizzling,黑魔法做崩溃等的保护
  • 3 利用关联对象(AssociatedObject)给分类添加属性
  • 4 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
  • 5 交换方法实现(交换系统的方法)
    我们下面举两个例子来讲一下
7.1 Method Swizzling

通过修改一个已存在类的方法, 来实现方法替换是比较常用的runtime技巧.

如在UIView的load方法中:

+ (void)load {
     Method origin = class_getInstanceMethod([UIView class], @selector(touchesBegan:withEvent:));
     Method custom = class_getInstanceMethod([UIView class], @selector(custom_touchesBegan:withEvent:));
     method_exchangeImplementations(origin, custom);
}

- (void)custom_touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
     // TODO
}

这样, 想要触发UIView的touchesBegan:withEvent:方法时, 实际调用的却是自定义的custom_touchesBegan:withEvent:方法.

另外, 关于runtime method swizzling的一个使用场景, 请参考博客:

iOS — 使用runtime解决3D Touch导致UIImagePicker崩溃的问题

7.2 关联对象

Objective-C中的Category无法向既有的类添加属性, 因此可以使用runtime的关联对象(associated objects)来实现.

如将一个字符串关联到一个数组:

static char overviewKey;
NSArray *array = [[NSArray alloc] initWithObjects:@"1", @"2", @"3", nil];
// 为了演示的目的,使用initWithFormat:来确保字符串可以被销毁
NSString *overview = [[NSString alloc] initWithFormat:@"@", @"first three numbers"];
objc_setAssociatedObject(array, &overviewKey, overview, OBJC_ASSOCIATION_RETAIN);

这样, 当overview被手动释放时, 却不会被销毁. 因为关联策略指明了数组array要保有相关联的对象.

而array也释放时, overview才会被销毁.

设置关联对象, 指定被关联对象array, 关联关键字overviewKey, 关联对象overview即可:

objc_setAssociatedObject(array, &overviewKey, overview, OBJC_ASSOCIATION_RETAIN);

获取关联对象, 需要传递被关联对象array和关联关键字overviewKey:

NSString *associatedObject = (NSString *)objc_getAssociatedObject(array, &overviewKey);

断开关联, 只需要设置关联对象为nil即可, 关联策略就无所谓了.

objc_setAssociatedObject(array, &overviewKey, nil, OBJC_ASSOCIATION_ASSIGN);

使用objc_removeAssociatedObjects可断开所有关联, 把对象恢复至原始状态.


相关面试题目
iOS开发之Runtime常用示例

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容