参阅文章‘https://www.jianshu.com/p/a3fb0919877c’
第一章
1.简介
Object-C是一门动态性比较强的编程语言,跟C,C++有着很大的不
编写->编译->运行
Objective-C的动态性是由Runtime API来支撑的
Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写
2.isa详解
要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针
在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
OC对象的isa指针并不是直接指向类对象或者元类对象,而是需要&ISA_MASK通过位运算才能获取到类对象或者元类对象的地址。为什么需要&ISA_MASK才能获取到类对象或者元类对象的地址,以及这样的好处。
// 截取objc_object内部分代码
struct objc_object {
private:
isa_t isa;
}
isa指针其实是一个isa_t类型的共用体,来到isa_t内部查看其结构
isa_t是union类型,union表示共用体。可以看到共用体中有一个结构体,结构体内部分别定义了一些变量,变量后面的值代表的是该变量占用多少个二进制位,也就是位域技术。
// 精简过的isa_t共用体
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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)
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
// 1代表优化后的使用位域存储更多的信息。0代表普通的指针,存储着Class,Meta-Class对象的内存地址。
uintptr_t nonpointer : 1;/
uintptr_t has_assoc : 1;//是否有设置过关联对象,如果没有,释放时会更快
uintptr_t has_cxx_dtor : 1; //是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS
0x7fffffe00000
//存储着Class、Meta-Class对象的内存地址信息,由于要进行位运算,所以地址值末三位都为000(此情况为二进制)
uintptr_t magic : 6; //用于在调试时分辨对象是否未完成初始化
uintptr_t weakly_referenced : 1; //是否有被弱引用指向过,如果没有,释放时会更快
uintptr_t deallocating : 1; //对象是否正在释放
uintptr_t has_sidetable_rc : 1; //引用计数器是否过大无法存储在isa中 ,如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
uintptr_t extra_rc : 8; //里面存储的值是引用计数器减1
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
# else
# error unknown architecture for packed isa
# endif
#endif
第二章
1.对class对象简单回顾
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
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
}
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
bits & FAST_DATA_MASK位运算之后,可以得到class_rw_t,而class_rw_t中存储着方法列表、属性列表以及协议列表
2.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、property_array_t、protocol_array_t其实都是二维数组,来到method_array_t、property_array_t、protocol_array_t内部看一下。这里以method_array_t为例,method_array_t本身就是一个数组,数组里面存放的是数组method_list_t,method_list_t里面最终存放的是method_t
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>();
}
};
class property_array_t :
public list_array_tt<property_t, property_list_t>
{
typedef list_array_tt<property_t, property_list_t> Super;
public:
property_array_t duplicate() {
return Super::duplicate<property_array_t>();
}
};
class protocol_array_t :
public list_array_tt<protocol_ref_t, protocol_list_t>
{
typedef list_array_tt<protocol_ref_t, protocol_list_t> Super;
public:
protocol_array_t duplicate() {
return Super::duplicate<protocol_array_t>();
}
};
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容
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;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容。数组里面分别存放的是类的初始信息,以method_list_t为例,method_list_t中直接存放的就是method_t,但是是只读的,不允许增加删除修改。
一开始类的方法,属性,成员变量属性协议等等都是存放在class_ro_t中的,当程序运行的时候,需要将分类中的列表跟类初始的列表合并在一起的时,就会将class_ro_t中的列表和分类中的列表合并起来存放在class_rw_t中,也就是说class_rw_t中有部分列表是从class_ro_t里面拿出来的。并且最终和分类的方法合并。
###4.realizeClass部分源码
static Class realizeClass(Class cls)
{
runtimeLock.assertWriting();
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
if (!cls) return nil;
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
// 最开始cls->data是指向ro的
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// rw已经初始化并且分配内存空间
rw = cls->data(); // cls->data指向rw
ro = cls->data()->ro; // cls->data()->ro指向ro
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// 如果rw并不存在,则为rw分配空间
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); // 分配空间
rw->ro = ro; // rw->ro重新指向ro
rw->flags = RW_REALIZED|RW_REALIZING;
// 将rw传入setData函数,等于cls->data()重新指向rw
cls->setData(rw);
}
}
从上述源码中就可以发现,类的初始信息本来其实是存储在class_ro_t中的,并且ro本来是指向cls->data()的,也就是说bits.data()得到的是ro,但是在运行过程中创建了class_rw_t,并将cls->data指向rw,同时将初始信息ro赋值给rw中的ro。最后在通过setData(rw)设置data。那么此时bits.data()得到的就是rw,之后再去检查是否有分类,同时将分类的方法,属性,协议列表整合存储在class_rw_t的方法,属性及协议列表中。
###5.method_t
######method_t是对方法\函数的封装
struct method_t {
SEL name; // 函数名
const char *types; // 编码(返回值类型,参数类型)
IMP imp; // 指向函数的指针(函数地址)
};
######IMP代表函数的具体实现
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull,...);
######SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似
可以通过@selector()和sel_registerName()获得
可以通过sel_getName()和NSStringFromSelector()转成字符串
不同类中相同名字的方法,所对应的方法选择器是相同的
typedef struct objc_selector *SEL;
######type包含了函数返回值,参数编码的字符串
######Type Encoding
apple为了能够清晰的使用字符串表示方法及其返回值,制定了一系列对应规则
将types的值同表中的一一对照查看types的值v16@0:8 代表什么
- (void) test;
v 16 @ 0 : 8
void id SEL
// 16表示参数的占用空间大小,id后面跟的0表示从0位开始存储,id占8位空间。
// SEL后面的8表示从第8位开始存储,SEL同样占8位空间
- (int)testWithAge:(int)age Height:(float)height
{
return 0;
}
i 24 @ 0 : 8 i 16 f 20
int id SEL int float
// 参数的总占用空间为 8 + 8 + 4 + 4 = 24
// id 从第0位开始占据8位空间
// SEL 从第8位开始占据8位空间
// int 从第16位开始占据4位空间
// float 从第20位开始占据4位空间
iOS提供了@encode的指令,可以将具体的类型转化成字符串编码。
NSLog(@"%s",@encode(int));
NSLog(@"%s",@encode(float));
NSLog(@"%s",@encode(id));
NSLog(@"%s",@encode(SEL));
// 打印内容
Runtime-test[25275:9144176] i
Runtime-test[25275:9144176] f
Runtime-test[25275:9144176] @
Runtime-test[25275:9144176] :
###6.方法缓存
Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度
######cache_t的内部结构
struct cache_t {
struct bucket_t *_buckets; // 散列表 数组
mask_t _mask; // 散列表的长度 -1
mask_t _occupied; // 已经缓存的方法数量
};
######bucket_t是以数组的方式存储方法列表的,看一下bucket_t内部结构
struct bucket_t {
private:
cache_key_t _key; // SEL作为Key
IMP _imp; // 函数的内存地址
};
######从源码中可以看出bucket_t中存储着SEL和_imp,通过key->value的形式,以SEL为key,函数实现的内存地址 _imp为value来存储方法
————————————————
#第二章补充 散列表算法
###散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
###Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度
struct cache_t {
struct bucket_t *_buckets;//散列表
mask_t _mask;//散列表长度 - 1
mask_t _occupied;//已经缓存的方法数量
}
struct bucket_t {
cache_key_t _key;//SEL作为key
IMP _imp;//函数的内存地址
}
###散列函数及散列表原理
###1.cache_fill 及 cache_fill_nolock 函数
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();
// 如果没有initialize直接return
if (!cls->isInitialized()) return;
// 确保线程安全,没有其他线程添加缓存
if (cache_getImp(cls, sel)) return;
// 通过类对象获取到cache
cache_t *cache = getCache(cls);
// 将SEL包装成Key
cache_key_t key = getKey(sel);
// 占用空间+1
mask_t newOccupied = cache->occupied() + 1;
// 获取缓存列表的缓存能力,能存储多少个键值对
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// 如果为空的,则创建空间,这里创建的空间为4个。
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// 如果所占用的空间占总数的3/4一下,则继续使用现在的空间
}
else {
// 如果占用空间超过3/4则扩展空间
cache->expand();
}
// 通过key查找合适的存储空间。
bucket_t *bucket = cache->find(key, receiver);
// 如果key==0则说明之前未存储过这个key,占用空间+1
if (bucket->key() == 0) cache->incrementOccupied();
// 存储key,imp
bucket->set(key, imp);
}
###2.reallocate 函数
######reallocate函数负责分配散列表空间
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
{
// 旧的散列表能否被释放
bool freeOld = canBeFreed();
// 获取旧的散列表
bucket_t *oldBuckets = buckets();
// 通过新的空间需求量创建新的散列表
bucket_t *newBuckets = allocateBuckets(newCapacity);
assert(newCapacity > 0);
assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
// 设置Buckets和Mash,Mask的值为散列表长度-1
setBucketsAndMask(newBuckets, newCapacity - 1);
// 释放旧的散列表
if (freeOld) {
cache_collect_free(oldBuckets, oldCapacity);
cache_collect(false);
}
}
enum {
INIT_CACHE_SIZE_LOG2 = 2,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2)
};
###3.expand ()函数
######当散列表的空间被占用超过3/4的时候,散列表会调用expand ()函数进行扩展
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) {
newCapacity = oldCapacity;
}
// 调用reallocate函数,重新创建存储空间
reallocate(oldCapacity, newCapacity);
}
###4.find 函数
######最后来看一下散列表中如何快速的通过key找到相应的bucket呢?我们来到find函数内部
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
// 获取散列表
bucket_t *b = buckets();
// 获取mask
mask_t m = mask();
// 通过key找到key在散列表中存储的下标
mask_t begin = cache_hash(k, m);
// 将下标赋值给i
mask_t i = begin;
// 如果下标i中存储的bucket的key==0说明当前没有存储相应的key,将b[i]返回出去进行存储
// 如果下标i中存储的bucket的key==k,说明当前空间内已经存储了相应key,将b[i]返回出去进行存储
do {
if (b[i].key() == 0 || b[i].key() == k) {
// 如果满足条件则直接reutrn出去
return &b[i];
}
// 如果走到这里说明上面不满足,那么会往前移动一个空间重新进行判定,知道可以成功return为止
} 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);
}
######函数cache_hash (k, m)用来通过key找到方法在散列表中存储的下标,来到cache_hash (k, m)函数内部
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
######当第一次使用方法时,消息机制通过isa找到方法之后,会对方法以SEL为keyIMP为value的方式缓存在cache的_buckets中,当第一次存储的时候,会创建具有4个空间的散列表,并将_mask的值置为散列表的长度减一,之后通过SEL & mask计算出方法存储的下标值,并将方法存储在散列表中。举个例子,如果计算出下标值为3,那么就将方法直接存储在下标为3的空间中,前面的空间会留空。
当散列表中存储的方法占据散列表长度超过3/4的时候,散列表会进行扩容操作,将创建一个新的散列表并且空间扩容至原来空间的两倍,并重置_mask的值,最后释放旧的散列表,此时再有方法要进行缓存的话,就需要重新通过SEL & mask计算出下标值之后在按照下标进行存储了。
如果一个类中方法很多,其中很可能会出现多个方法的SEL & mask得到的值为同一个下标值,那么会调用cache_next函数往下标值-1位去进行存储,如果下标值-1位空间中有存储方法,并且key不与要存储的key相同,那么再到前面一位进行比较,直到找到一位空间没有存储方法或者key与要存储的key相同为止,如果到下标0的话就会到下标为_mask的空间也就是最大空间处进行比较。
当要查找方法时,并不需要遍历散列表,同样通过SEL & mask计算出下标值,直接去下标值的空间取值即可,同上,如果下标值中存储的key与要查找的key不相同,就去前面一位查找。这样虽然占用了少量控件,但是大大节省了时间,也就是说其实apple是使用空间换取了存取的时间。
#第三章objc_msgSend
####1.OC中的方法调用,其实都是转换为objc_msgSend函数的调用
objc_msgSend的执行流程可以分为3大阶段
消息发送:负责从类及父类的缓存列表及方法列表查找方法
动态方法解析:如果消息发送阶段没有找到方法,则会进入动态解析阶段,负责动态的添加方法实现。
消息转发:如果也没有实现动态解析方法,则会进行消息转发阶段,将消息转发给可以处理消息的接受者来处理
####2.objc_msgSend执行流程 – 源码跟读
objc-msg-arm64.s文件中
//ENTRY 入口
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
//判断是否为0,x0为objcd的第一个参数,receiver
//x0称之为寄存器:消息接受者,
cmp x0, #0 // nil check and tagged pointer check
//b指令为跳转 le为跳转的条件 LNilOrTagged内部会执行LReturnZero->return 0
b.le LNilOrTagged // (MSB tagged pointer looks negative)
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
//得到isa
LGetIsaDone:
//查找缓存
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
//b 指令为跳转
b.le LNilOrTagged
CacheLookup NORMAL
//macro 为枚举标示
//CacheLookup 内部对方法缓存列表进行查找
.macro CacheLookup
//命中
CacheHit $0 // call or return imp
//或没有命中
CheckMiss $0 // miss if bucket->sel == 0
.macro CheckMiss //失败,执行下面方法
STATIC_ENTRY __objc_msgSend_uncached
//调用下面函数
__objc_msgSend_uncached
//查找
MethodTableLookup //方法列表查找
__class_lookupMethodAndLoadCache3
汇编方法名字 __class_lookupMethodAndLoadCache3
c语言方法名字 _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
_objc_msgForward_impcache
objc-msg-arm64.s
STATIC_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
Core Foundation
__forwarding__(不开源)
#####3._class_lookupMethodAndLoadCache3 函数
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
#####4.lookUpImpOrForward 函数
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
// initialize = YES , cache = NO , resolver = YES
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 缓存查找, 因为cache传入的为NO, 这里不会进行缓存查找, 因为在汇编语言中CacheLookup已经查找过
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.read();
if (!cls->isRealized()) {
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}
retry:
runtimeLock.assertReading();
// 防止动态添加方法,缓存会变化,再次查找缓存。
imp = cache_getImp(cls, sel);
// 如果查找到imp, 直接调用done, 返回方法地址
if (imp) goto done;
// 查找方法列表, 传入类对象和方法名
{
// 根据sel去类对象里面查找方法
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
// 如果方法存在,则缓存方法,
// 内部调用的就是 cache_fill 上文中已经详细讲解过这个方法,这里不在赘述了。
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
// 方法缓存之后, 取出imp, 调用done返回imp
imp = meth->imp;
goto done;
}
}
// 如果类方法列表中没有找到, 则去父类的缓存中或方法列表中查找方法
{
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.");
}
// 查找父类的缓存
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 在父类中找到方法, 在本类中缓存方法, 注意这里传入的是cls, 将方法缓存在本类缓存列表中, 而非父类中
log_and_fill_cache(cls, imp, sel, inst, curClass);
// 执行done, 返回imp
goto done;
}
else {
// 跳出循环, 停止搜索
break;
}
}
// 查找父类的方法列表
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 同样拿到方法, 在本类进行缓存
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
// 执行done, 返回imp
goto done;
}
}
}
// ---------------- 消息发送阶段完成 ---------------------
// ---------------- 进入动态解析阶段 ---------------------
// 上述列表中都没有找到方法实现, 则尝试解析方法
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
triedResolver = YES;
goto retry;
}
// ---------------- 动态解析阶段完成 ---------------------
// ---------------- 进入消息转发阶段 ---------------------
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
// 返回方法地址
return imp;
}
#####5.getMethodNoSuper_nolock 函数
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// cls->data() 得到的是 class_rw_t
// class_rw_t->methods 得到的是methods二维数组
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
// mlists 为 method_list_t
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
######源码中getMethodNoSuper_nolock函数中通过遍历方法列表拿到method_list_t最终通过search_method_list函数查找方法
#####6.search_method_list函数
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
// 如果方法列表是有序的,则使用二分法查找方法,节省时间
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// 否则则遍历列表查找
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
return nil;
}
#####7.findMethodInSortedMethodList函数内二分查找实现原理
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
assert(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
// >>1 表示将变量n的各个二进制位顺序右移1位,最高位补二进制0。
// count >>= 1 如果count为偶数则值变为(count / 2)。如果count为奇数则值变为(count-1) / 二分查找
for (count = list->count; count != 0; count >>= 1) {
// probe 指向数组中间的值
probe = base + (count >> 1);
// 取出中间method_t的name,也就是SEL
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// 取出 probe
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
// 返回方法
return (method_t *)probe;
}
// 如果keyValue > probeValue 则折半向后查询
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
####3.objc_msgSend执行流程02-动态方法解析
开发者可以实现以下方法,来动态添加方法实现
+resolveInstanceMethod:
+resolveClassMethod:
动态解析过后,会重新走“消息发送”的流程
“从receiverClass的cache中查找方法”这一步开始执行
######以实例对象为例通过代码来看一下动态解析的过程
@implementation Person
- (void) other {
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// 动态的添加方法实现
if (sel == @selector(test)) {
// 获取其他方法 指向method_t的指针
Method otherMethod = class_getInstanceMethod(self, @selector(other));
// 动态添加test方法的实现
//获取IMP method_getImplementation(otherMethod)
//获取type method_getTypeEncoding(otherMethod)
class_addMethod(self, sel,method_getImplementation(otherMethod),method_getTypeEncoding(otherMethod));
// 返回YES表示有动态添加方法
return YES;
}
NSLog(@"%s", __func__);
return [superresolveInstanceMethod:sel];
}
@end
######本类和父类cache和class_rw_t中都找不到方法时,就会进行动态解析的方法,也就是说会自动调用类的resolveInstanceMethod:方法进行动态查找。因此我们可以在resolveInstanceMethod:方法内部使用class_addMethod动态的添加方法实现。
/**
第一个参数: cls:给哪个类添加方法
第二个参数: SEL name:添加方法的名称
第三个参数: IMP imp: 方法的实现,函数入口,函数名可与方法名不同(建议与方法名相同)
第四个参数: types :方法类型,需要用特定符号,参考API
*/
class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types)
class_addMethod用来向具有给定名称和实现的类添加新方法,class_addMethod将添加一个方法实现的覆盖,但是不会替换已有的实现
######Method是objc_method类型结构体,可以理解为其内部结构同method_t结构体相同,上文中提到过method_t是代表方法的结构体,其内部包含SEL、type、IMP
struct method_t {
SEL sel;
char *types;
IMP imp;
};
- (void) other {
NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// 动态的添加方法实现
if (sel == @selector(test)) {
// Method强转为method_t
struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));
NSLog(@"%s,%p,%s",method->sel,method->imp,method->types);
// 动态添加test方法的实现
class_addMethod(self, sel, method->imp, method->types);
// 返回YES表示有动态添加方法
return YES;
}
NSLog(@"%s", __func__);
return [superresolveInstanceMethod:sel];
}
######动态解析的方法
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.
triedResolver = YES;
goto retry;
}
######_class_resolveMethod函数内部,根据类对象或元类对象做不同的操作
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
//不是元类对象
if (! cls->isMetaClass()) {
// try [clsresolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClassresolveClassMethod:sel]
// and [clsresolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
######无论我们是否实现了动态解析的方法,系统内部都会执行retry对方法再次进行查找,那么如果我们实现了动态解析方法,此时就会顺利查找到方法,进而返回imp对方法进行调用。如果我们没有实现动态解析方法。就会进行消息转发。
####4.消息转发
######消息转发:将消息转发给别人
_cache_addForwardEntry(cls, sel);
methodPC = _objc_msgForward_impcache;
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band register %edx is nonzero for stret, zero otherwise
// Check return type (stret or not)
testl %edx, %edx
jnz __objc_msgForward_stret
jmp __objc_msgForward
END_ENTRY _objc_msgForward_impcache
ENTRY __objc_msgForward
// Non-struct return version
// Get PIC base into %edx
call L__objc_msgForward$pic_base
L__objc_msgForward$pic_base:
popl %edx
// Call user handler, if any
movl L_forward_handler-L__objc_msgForward$pic_base(%edx),%ecx
movl (%ecx), %ecx
testl %ecx, %ecx // if not NULL
je 1f // skip to default handler
jmp *%ecx // call __objc_forward_handler
1:
// No user handler
// Push stack frame
pushl %ebp
movl %esp, %ebp
// Die if forwarding "forward::"
movl (selector+4)(%ebp), %eax
movl _FwdSel-L__objc_msgForward$pic_base(%edx),%ecx
cmpl %ecx, %eax
je LMsgForwardError
// Call [receiverforward:sel:margs]
subl $8, %esp // 16-byte align the stack
leal (self+4)(%ebp), %ecx
pushl %ecx // &margs
pushl %eax // sel
movl _FwdSel-L__objc_msgForward$pic_base(%edx),%ecx
pushl %ecx // forward::
pushl (self+4)(%ebp) // receiver
call _objc_msgSend
movl %ebp, %esp
popl %ebp
ret
######当本类没有实现方法,并且没有动态解析方法,就会调用forwardingTargetForSelector函数,进行消息转发,我们可以实现forwardingTargetForSelector函数,在其内部将消息转发给可以实现此方法的对象。
######如果forwardingTargetForSelector函数返回为nil或者没有实现的话,就会调用methodSignatureForSelector方法,用来返回一个方法签名,这也是我们正确跳转方法的最后机会。
######如果methodSignatureForSelector方法返回正确的方法签名就会调用forwardInvocation方法,forwardInvocation方法内提供一个NSInvocation类型的参数,NSInvocation封装了一个方法的调用,包括方法的调用者,方法名,以及方法的参数。在forwardInvocation函数内修改方法调用对象即可。
######如果methodSignatureForSelector返回的为nil,就会来到doseNotRecognizeSelector:方法内部,程序crash提示无法识别选择器unrecognized selector sent to instance。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
// 返回能够处理消息的对象
if (aSelector == @selector(driving)) {
// 返回nil则会调用methodSignatureForSelector方法
return nil;
// return [[Car alloc] init];
}
return [superforwardingTargetForSelector:aSelector];
}
// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(driving)) {
// return [NSMethodSignature signatureWithObjCTypes: "v@:"];
// return [NSMethodSignature signatureWithObjCTypes: "v16@0:8"];
// 也可以通过调用Car的methodSignatureForSelector方法得到方法签名,这种方式需要car对象有aSelector方法
return [[[Car alloc] init] methodSignatureForSelector: aSelector];
}
return [supermethodSignatureForSelector:aSelector];
}
######NSInvocation
######methodSignatureForSelector方法中返回的方法签名,在forwardInvocation中被包装成NSInvocation对象,NSInvocation提供了获取和修改方法名、参数、返回值等方法,也就是说,在forwardInvocation函数中我们可以对方法进行最后的修改。
同样上述代码,我们为driving方法添加返回值和参数,并在forwardInvocation方法中修改方法的返回值及参数。
#import "Car.h"
@implementation Car
- (int) driving:(int)time
{
NSLog(@"car driving %d",time);
return time * 2;
}
@end
#import "Person.h"
#import
#import "Car.h"
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector
{
// 返回能够处理消息的对象
if (aSelector == @selector(driving)) {
return nil;
}
return [superforwardingTargetForSelector:aSelector];
}
// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(driving:)) {
// 添加一个int参数及int返回值type为 i@:i
return [NSMethodSignature signatureWithObjCTypes: "i@:i"];
}
return [supermethodSignatureForSelector:aSelector];
}
//NSInvocation 封装了一个方法调用,包括:方法调用者,方法名,方法的参数
//anInvocation.target 方法调用者
//anInvocation.selector 方法方法名
//[anInvocation getArgument: NULL atIndex: 0] 方法的参数
//方法参数顺序:receiver,selector,other arguments
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
int time;
// 获取方法的参数,方法默认还有self和cmd两个参数,因此新添加的参数下标为2
[anInvocation getArgument: &time atIndex: 2];
NSLog(@"修改前参数的值 = %d",time);
time = time + 10; // time = 110
NSLog(@"修改前参数的值 = %d",time);
// 设置方法的参数 此时将参数设置为110
[anInvocation setArgument: &time atIndex:2];
// 将tagert设置为Car实例对象
[anInvocation invokeWithTarget: [[Car alloc] init]];
// 获取方法的返回值
int result;
[anInvocation getReturnValue: &result];
NSLog(@"获取方法的返回值 = %d",result); // result = 220,说明参数修改成功
result = 99;
// 设置方法的返回值 重新将返回值设置为99
[anInvocation setReturnValue: &result];
// 获取方法的返回值
[anInvocation getReturnValue: &result];
NSLog(@"修改方法的返回值为 = %d",result); // result = 99
}
#import
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
// 传入100,并打印返回值
NSLog(@"[person driving: 100] = %d",[person driving: 100]);
}
return 0;
}
#######只要来到forwardInvocation方法中,我们便对方法调用有了绝对的掌控权,可以选择是否调用方法,以及修改方法的参数返回值等等。
类方法的消息转发
类方法消息转发同对象方法一样,同样需要经过消息发送,动态方法解析之后才会进行消息转发机制。我们知道类方法是存储在元类对象中的,元类对象本来也是一种特殊的类对象。需要注意的是,类方法的消息接受者变为类对象。
当类对象进行消息转发时,对调用相应的+号的forwardingTargetForSelector、methodSignatureForSelector、forwardInvocation方法,需要注意的是+号方法仅仅没有提示,而不是系统不会对类方法进行消息转发。
类方法的消息转发机制。
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Person driving];
}
return 0;
}
#import "Car.h"
@implementation Car
+ (void) driving;
{
NSLog(@"car driving");
}
@end
#import "Person.h"
#import
#import "Car.h"
@implementation Person
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
// 返回能够处理消息的对象
if (aSelector == @selector(driving)) {
// 这里需要返回类对象
return [Car class];
}
return [superforwardingTargetForSelector:aSelector];
}
// 如果forwardInvocation函数中返回nil 则执行下列代码
// 方法签名:返回值类型、参数类型
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(driving)) {
return [NSMethodSignature signatureWithObjCTypes: "v@:"];
}
return [supermethodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation invokeWithTarget: [Car class]];
}
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)。方法调用过程中也就是objc_msgSend底层实现分为三个阶段:消息发送、动态方法解析、消息转发。本文主要对这三个阶段相互之间的关系以及流程进行的探索。
第一章 super本质
#######MJStudent->MJPerson->NSObject
- (instancetype)init
{
if (self = [super init]) {
NSLog(@"[self class] = %@", [self class]); // MJStudent
NSLog(@"[self superclass] = %@", [self superclass]); // MJPerson
NSLog(@"--------------------------------");
// objc_msgSendSuper({self, [MJPerson class]}, @selector(class));
//super的消息接受者让然是子类对象,只不过是从父类开始查找
NSLog(@"[super class] = %@", [super class]); // MJStudent
NSLog(@"[super superclass] = %@", [super superclass]); // MJPerson
}
return self;
}
#######解析方法
[super run];
//转化为底层源码内部其实调用的是objc_msgSendSuper函数
((void (*)(__rw_objc_super *, SEL))(void
*)objc_msgSendSuper)((__rw_objc_super){(id)self,
(id)class_getSuperclass(objc_getClass("MJStudent"))},
sel_registerName("run"));
简化后
//objc_msgSendSuper函数内传递了两个参数
//__rw_objc_super结构体和sel_registerName("run")方法名
objc_msgSendSuper((__rw_objc_super){
(id)self,
(id)class_getSuperclass(objc_getClass("MJStudent"))}, sel_registerName("run"))
进一步简化
struct __rw_objc_super arg = {
self,
class_getSuperclass(objc_getClass("MJStudent"))}
objc_msgSendSuper(arg, @selector(run));
//objc_msgSendSuper中传入的结构体是objc_super
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
通过源码查找objc_super结构体查看其内部结构
// 精简后的objc_super结构体
struct objc_super {
__unsafe_unretained _Nonnull id receiver;// 消息接受者
__unsafe_unretained _Nonnull Class super_class;// 消息接受者的父类
/* super_class is the first class to search */
// 父类是第一个开始查找的类
};
//super_class的消息接受者让然是子类对象,只不过是从父类开始查找
//super调用方法的消息接受者receiver仍然是self,只是从父类的类对象开始去查找方法
########class内部实现是根据消息接受者返回其对应的类对象,最终会找到基类的方法列表中,而self和super的区别仅仅是self从本类类对象开始查找方法,super从父类类对象开始查找方法,因此最终得到的结果都是相同的
######## class及superClass代码实现
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
########其实super底层真正调用的函数时objc_msgSendSuper2函数我们可以通过查看super调用方法转化为汇编代码来验证这一说法
########super底层其实调用的是objc_msgSendSuper2函数,我们来到源码中查找一下objc_msgSendSuper2函数的底层实现,我们可以在汇编文件中找到其相关底层实现。
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
MESSENGER_START
ldp x0, x16, [x0] // x0 = real receiver, x16 = class
ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
CacheLookup NORMAL
END_ENTRY _objc_msgSendSuper2
########通过上面汇编代码我们可以发现,其实底层是在函数内部调用的class->superclass获取父类,并不是我们上面分析的直接传入的就是父类对象
########其实_objc_msgSendSuper2内传入的结构体为objc_super2
struct objc_super2 {
id receiver;
Class current_class;
};
###总结
[super message]的底层实现
1.消息接收者仍然是子类对象
2.从父类开始查找方法的实现
第二章. isKindOfClass 与 isMemberOfClass
- (BOOL)isMemberOfClass:(Class)cls {
// 直接获取实例类对象并判断是否等于传入的类对象
return [self class] == cls;
}
- (BOOL)isKindOfClass:(Class)cls {
// 向上查询,如果找到父类对象等于传入的类对象则返回YES
// 直到基类还不相等则返回NO
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
// 判断元类对象是否等于传入的元类元类对象
// 此时self是类对象 object_getClass((id)self)就是元类
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
// 向上查找,判断元类对象是否等于传入的元类对象
// 如果找到基类还不相等则返回NO
// 注意:这里会找到基类
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
第三章.奇怪问题
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)test;
@end
// Person.m
#import "Person.h"
@implementation Person
- (void)test
{
//栈中内存地址分配从高位到低位,则cls在高位,obj在最低位,
//self.name== self->name
//self则为isa指针地址,self->name则isa指针位置下移8位,地址后向高地址位取8个字节地址空间存储的值
NSLog(@"test print name is : %@", self.name);
}
@end
// ViewController.m
@implementation ViewController
- (void)viewDidLoad {
//栈中内存地址分配从高位到低位,则UIVewController在高位,self在最低位
[super viewDidLoad];//这句调用完后
//cls在低位,self在高位
//cls中存储person类对象地址值
id cls = [Person class];
//obj中存储cls的地址
void *obj = &cls;
//p/x obj (MJPerson *) $0 = 0x00007ffee1eee888 地址
// x 0x00007ffee1eee888 //地址内容
//前面8字节为cls(c8 0f d1 0d 01 00 00 00)
0x7ffee1eee888: c8 0f d1 0d 01 00 00 00 c0 be 70 1f ef 7f 00 00 ..........p.....
0x7ffee1eee898: 00 0f d1 0d 01 00 00 00 87 d6 32 12 01 00 00 00 ..........2.....
//x/4g 0x00007ffee1eee888 //打印四个数据,每个数据八个字节 4:四个数据 g:八个字节
//打印出了 cls八个字节 self八个字节 viewController八个字节
0x7ffee1eee888: 0x000000010dd10fc8 0x00007fef1f70bec0
0x7ffee1eee898: 0x000000010dd10f00 0x000000011232d687
// p (Class)0x000000010dd10fc8
(Class) $1 = MJPerson
//po 0x00007fef1f70bec0
<ViewController: 0x7fef1f70bec0>
//p (Class)0x000000010dd10f00
(Class) $4 = ViewController
[(__bridge id)obj test];//test print name is : <ViewController: 0x7f95514077a0>
//person中存储实例对象地址,实例对象的第一块地址是isa指针,因此person中存储的地址其实和isa指针地址相同
//isa指针中存储Person类对象地址
Person *person = [[Person alloc] init];
[person test];//test print name is : (null)
}
####1.obj为什么可以正常调用方法
person调用方法时首先通过isa指针找到类对象进而查找方法并进行调用。而person实例对象内实际上是取最前面8个字节空间也就是isa并通过计算得出类对象地址。obj在调用test方法时,也会通过其内存地址找到cls,而cls中取出最前面8个字节空间其内部存储的刚好是Person类对象地址。因此obj是可以正常调用方法的
####2.为什么self.name打印内容为ViewController对象
1. super的底层本质为调用objc_msgSendSuper2函数,传入objc_super2结构体,结构体内部存储消息接受者和当前类,用来告知系统方法查找从父类开始。
2. 局部变量分配在栈空间,并且从高地址向低地址连续分配。先创建的局部变量分配在高地址,后续创建的局部变量连续分配在较低地址。
3. 方法调用的消息机制,通过isa指针找到类对象进行消息发送。
4. 指针存储的是实例变量的首字节地址,上述例子中person指针存储的其实就是实例变量内部的isa指针的地址。
5. 访问成员变量的本质,找到成员变量的地址,按照成员变量所占的字节数,取出地址中存储的成员变量的值。
什么是Runtime?平时项目中有用过么?
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
平时编写的OC代码,底层都是转换成了Runtime API进行调用
具体应用
利用关联对象(AssociatedObject)给分类添加属性
遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
交换方法实现(交换系统的方法)
利用消息转发机制解决方法找不到的异常问题
第四章.r运行时相关API
#######类相关API
1. 动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
2. 注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls)
3. 销毁一个类
void objc_disposeClassPair(Class cls)
示例:
void run(id self , SEL _cmd) {
NSLog(@"%@ - %@", self,NSStringFromSelector(_cmd));
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建类 superclass:继承自哪个类 name:类名 size_t:格外的大小,创建类是否需要扩充空间
// 返回一个类对象
Class newClass = objc_allocateClassPair([NSObject class], "Student", 0);
// 添加成员变量
// cls:添加成员变量的类 name:成员变量的名字 size:占据多少字节 alignment:内存对齐,最好写1 types:类型,int类型就是@encode(int) 也就是i
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_height", 4, 1, @encode(float));
// 添加方法
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
// 注册类
objc_registerClassPair(newClass);
// 创建实例对象
id student = [[newClass alloc] init];
// 通过KVC访问
[student setValue:@10 forKey:@"_age"];
[student setValue:@180.5 forKey:@"_height"];
// 获取成员变量
NSLog(@"_age = %@ , _height = %@",[student valueForKey:@"_age"], [student valueForKey:@"_height"]);
// 获取类的占用空间
NSLog(@"类对象占用空间%zd", class_getInstanceSize(newClass));
// 调用动态添加的方法
[student run];
}
return 0;
}
// 打印内容
// Runtime应用[25605:4723961] _age = 10 , _height = 180.5
// Runtime应用[25605:4723961] 类对象占用空间16
// Runtime应用[25605:4723961] <Student: 0x10072e420> - run
注意
类一旦注册完毕,就相当于类对象和元类对象里面的结构就已经创建好了。
因此必须在注册类之前,添加成员变量。方法可以在注册之后再添加,因为方法是可以动态添加的。
创建的类如果不需要使用了 ,需要释放类。
4. 获取isa指向的Class,如果将类对象传入获取的就是元类对象,如果是实例对象则为类对象
Class object_getClass(id obj)
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
NSLog(@"%p,%p,%p",object_getClass(person), [Person class],
object_getClass([Person class]));
}
return 0;
}
// 打印内容
Runtime应用[21115:3807804] 0x100001298,0x100001298,0x100001270
5. 设置isa指向的Class,可以动态的修改类型。例如修改了person对象的类型,也就是说修改了person对象的isa指针的指向,中途让对象去调用其他类的同名方法。
Class object_setClass(id obj, Class cls)
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person run];
object_setClass(person, [Car class]);
[person run];
}
return 0;
}
// 打印内容
Runtime应用[21147:3815155] -[Person run]
Runtime应用[21147:3815155] -[Car run]
最终其实调用了car的run方法
6. 用于判断一个OC对象是否为Class
BOOL object_isClass(id obj)
// 判断OC对象是实例对象还是类对象
NSLog(@"%d",object_isClass(person)); // 0
NSLog(@"%d",object_isClass([person class])); // 1
NSLog(@"%d",object_isClass(object_getClass([person class]))); // 1
// 元类对象也是特殊的类对象
7. 判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)
8. 获取类对象父类
Class class_getSuperclass(Class cls)
#######成员变量相关API
1. 获取一个实例变量信息,描述信息变量的名字,占用多少字节等
Ivar class_getInstanceVariable(Class cls, const char *name)
2. 拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
3. 设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
4. 动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
5. 获取成员变量的相关信息,传入成员变量信息,返回C语言字符串
const char *ivar_getName(Ivar v)
6. 获取成员变量的编码,types
const char *ivar_getTypeEncoding(Ivar v)
示例:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 获取成员变量的信息
Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
// 获取成员变量的名字和编码
NSLog(@"%s, %s", ivar_getName(nameIvar), ivar_getTypeEncoding(nameIvar));
Person *person = [[Person alloc] init];
// 设置和获取成员变量的值
object_setIvar(person, nameIvar, @"xx_cc");
// 获取成员变量的值
object_getIvar(person, nameIvar);
NSLog(@"%@", object_getIvar(person, nameIvar));
NSLog(@"%@", person.name);
// 拷贝实例变量列表
unsigned int count ;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i ++) {
// 取出成员变量
Ivar ivar = ivars[i];
NSLog(@"%s, %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
}
return 0;
}
// 打印内容
// Runtime应用[25783:4778679] _name, @"NSString"
// Runtime应用[25783:4778679] xx_cc
// Runtime应用[25783:4778679] xx_cc
// Runtime应用[25783:4778679] _name, @"NSString"
#######属性相关AIP
1. 获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)
2. 拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
3. 动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
4. 动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
5. 获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
#######方法相关API
1. 获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
2. 方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)
3. 拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
4. 动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
5. 动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
6. 获取方法的相关信息(带有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)
7. 选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
8. 用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)