从源码objc_runtime-new.h 中可以看到
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; //方法缓存
class_data_bits_t bits; //用于获取具体的类信息
class_rw_t *data() {
return bits.data();
}
}
// 我们来看 bits*data 这个方法的实现
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// bits & FAST_DATA_MASK 返回一个class_rw_t 的结构体指针
接着我们来窥探下 class_rw_t 都存储来哪些东西?
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容
struct class_rw_t {
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_ro_t 如下
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //instance对象的size
#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是一维数组,是只读的,包含了类的初始内容。
然后我来在来看看method_t 吧。
struct method_t {
SEL name; //方法选择器(方法名)
const char *types; //编码(返回值类型,参数类型)
/*
ios提供了一个@encode的指令,可以将具体的类型变成字符串编码。
假如有这样一个方法:
-(int)test:(int)Age Height:(CGFloat)height;
type 为:i24@:08i16f20
其中i为返回值类型,24 所有参数的字节总数(id,SEL,int,float),@是id,:代表SEL,i参数int,f参数float.另外的数字代表参数 开始的字节位数
*/
IMP imp;//方法的实现
}
SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似,想要验证也很简单。如下:
char *str = "init";
NSLog(@"%s-%@",str,NSStringFromSelector(@selector(init)));
//打印如下
2018-10-31 15:52:10.218499+0800 关联对象[3274:207551] init-init
可以通过sel_getName()和NSStringFromSelector()转成字符串
不同类中相同名字的方法,所对应的方法选择器是相同的
继续往下看:Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。(很重要)
struct cache_t {
struct bucket_t *_buckets; //散列表
mask_t _mask; //散列表的长度-1
mask_t _occupied;//已经缓存的方法数量
}
struct bucket_t {
private:
cache_key_t _key; // SEL作为key
IMP _imp; //函数的内存地址,可理解为value
}
//简述下原理:对象调用方法是通过isa 是找到类对象,
然后在类对象通过方法缓存去找到对应的方法缓存列表_buckets,
一旦发现调用的SEL与缓存列表中的key 相同,就指出取出函数的地址,
通过地址直接调用函数方法。
原理如下:
0 | NULL |
1 | NULL |
2 | NULL |
4 | bucket_t (_key=@select(test),_imp) |
...... | ..... |
假如散列表如上,对象的方法名叫test。他会拿对象方法的地址 & _mask(上面介绍过了是散列表的长度-1) 假如为4,他就会去坐标为4的散列表找方法,(直接去找坐标为4的方法列表,前面的列表位置留空)假如方法存在直接调用,假如不存在就直接缓存进去。假如两个不同的地址值 & _mask得出来的索引值相同,那会怎么做呐?来看下源码
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
/*
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
*/
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
/*
cache_next 的实现
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
*/
} 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);
}
发现相同之后,吧得到的下标-1 缓存到得到的下标里去。如果直接减到0的时候直接赋值mask,然后在-1 慢慢的找,直到找到空余的位置缓存。以空间换时间。大家可以自己参考objc_cache.mm文件中。
//一个类的内部结构-供参考:一个c++文件
// XZClassInfo.h
// TestClass
#import <Foundation/Foundation.h>
#ifndef XZClassInfo_h
#define XZClassInfo_h
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;
#if __arm__ || __x86_64__ || __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
#else
#error unknown architecture
#endif
struct bucket_t {
cache_key_t _key;
IMP _imp;
};
struct cache_t {
bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
IMP imp(SEL selector)
{
mask_t begin = _mask & (long long)selector;
mask_t i = begin;
do {
if (_buckets[i]._key == 0 || _buckets[i]._key == (long long)selector) {
return _buckets[i]._imp;
}
} while ((i = cache_next(i, _mask)) != begin);
return NULL;
}
};
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
};
struct method_t {
SEL name;
const char *types;
IMP imp;
};
struct method_list_t : entsize_list_tt {
method_t first;
};
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
uint32_t alignment_raw;
uint32_t size;
};
struct ivar_list_t : entsize_list_tt {
ivar_t first;
};
struct property_t {
const char *name;
const char *attributes;
};
struct property_list_t : entsize_list_tt {
property_t first;
};
struct chained_property_list {
chained_property_list *next;
uint32_t count;
property_t list[0];
};
typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
uintptr_t count;
protocol_ref_t list[0];
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // instance对象占用的内存空间
#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;
};
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_list_t * methods; // 方法列表
property_list_t *properties; // 属性列表
const protocol_list_t * protocols; // 协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
#define FAST_DATA_MASK 0x00007ffffffffff8UL
struct class_data_bits_t {
uintptr_t bits;
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
};
/* OC对象 */
struct mj_objc_object {
void *isa;
};
/* 类对象 */
struct mj_objc_class : mj_objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
public:
class_rw_t* data() {
return bits.data();
}
mj_objc_class* metaClass() {
return (mj_objc_class *)((long long)isa & ISA_MASK);
}
};
#endif /* XZClassInfo_h */
用法如下:
mj_objc_class *goodStuderntClass = (__bridge mj_objc_class *)[XZGoodStudent class];
cache_t cache = goodStuderntClass->cache;
bucket_t *buckets = goodStuderntClass->cache._buckets;
bucket_t bucket =buckets [(long long)@selector(goodStudentTest) & cache._mask];
NSLog(@"%s",bucket._key);