python corrupt extra field_iOS Runtime详解及应用场景

一 runtime简介

二 Class的结构

三 isa指针详解

四 method详解

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

六 runtime常用api

七 runtime开发中具体应用

一 runtime简介

定义:所谓运行时, 就是尽可能地把决定从编译器推迟到运行期, 就是尽可能地做到动态. 只是在运行的时候才会去确定对象的类型和方法的. 因此利用Runtime机制可以在程序运行时动态地修改类和对象中的所有属性和方法.

Objective-C中调用对象的方法时, 会向该对象发送一条消息, runtime根据该消息做出反应.

Runtime是一套比较底层的纯C语言的API, Objective-C是运行在Runtime上的, 因此在Runtime中动态添加和实现一些非常强大的功能也就不足为奇了.

在Objective-C代码中使用Runtime, 需要引入

总结说明:

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

(lldb) po person2

(lldb) po person3

(lldb) po person4

instance对象在内存中存储的信息包括

isa指针,及它自己的成员变量

3.2.2 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

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]);

NSLog(@"p1class:%p\n p2class:%p",p1metaclass,p2metaclass);

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

p2class:0x100002180

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

{

typedef list_array_tt Super;

public:

method_list_t **beginCategoryMethodLists() {

return beginLists();

}

method_list_t **endCategoryMethodLists(Class cls);

method_array_t duplicate() {

return Super::duplicate();

}

};

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

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

#include "Person.h"

#import

#import

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:];

}

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:];

}

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

消息处理流程

动态解析过后,会重新走“消息发送”的流程

“从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);

}

// 调用 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 *)touches withEvent:(UIEvent *)event {

// TODO

}

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

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

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可断开所有关联, 把对象恢复至原始状态.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值