Class的结构
runtime的底层代码可详见:配置objc4-750
本文Demo代码见gitHubDemo
下面我们根据里面的代码分析的


int main(int argc, const char * argv[]) {
@autoreleasepool {
SEL sel1 = @selector(test);
SEL sel2 = sel_registerName("test");
NSLog(@"%p",sel1);
NSLog(@"%p",sel2);
NSLog(@"%p",sel_registerName("test"));
}
return 0;
}
打印:
05.SEL[39903:12173170] 0x7fff4b57ffdf
05.SEL[39903:12173170] 0x7fff4b57ffdf
05.SEL[39903:12173170] 0x7fff4b57ffdf
三个地址是一样的哟
Type Encoding

myPerson.h
#import <Foundation/Foundation.h>
@interface myPerson : NSObject
- (int)test:(int)age height:(float)height;
@end
myPerson.m
#import "myPerson.h"
@implementation myPerson
- (int)test:(int)age height:(float)height{
NSLog(@"%s", __func__);
return 0;
}
@end
【main】
#import <Foundation/Foundation.h>
#import "myPerson.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"%s", method_getTypeEncoding(class_getInstanceMethod([myPerson class], @selector(test:height:))));
}
return 0;
}
打印:
05.SEL[43843:12233135] i24@0:8i16f20
分析:
"i 24 @0 :8 i16 f20"
i->int (返回值类型)
24->总共24个字节 (整个方法参数占位的总长度)
@0 0 -> 从第0个字节开始 (表示在offset为0的地方有一个objective-c的对象,在objective-c method里,首个对象是self自身)
:8 ->从第8个字节开始(在offset为8的地方有一个SEL)
i->int 16->从第16个字节开始 (第一个参数的位置)
f->float 20->从第20个字节开始 (第二个参数的位置)
方法缓存
Class内部结构中会有一个方法缓存(cache_t),用散列表(哈希表)来缓存曾经使用过的方法,可以提高方法的查找速度
objc-runtime-new.h

下面我们根据objc-runtime-new.h和objc-cache.mm重新构造类信息,如下
myClassInfo.h
#ifndef myClassInfo_h
#define myClassInfo_h
//isa.h LINE-57
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
//objc-runtime-new.h LINE-27
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;
//objc-runtime-new.h LINE-505
#define FAST_DATA_MASK 0x00007ffffffffff8UL
//objc-runtime-new.h LINE-37
struct bucket_t {
public:
// 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__
MethodCacheIMP _imp;
cache_key_t _key;
#else
cache_key_t _key;
IMP _imp;
#endif
};
//objc-cache.mm LINE-155
#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
/* objc-runtime-new.h LINE-524
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[I];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
*/
//objc-runtime-new.h LINE-59
struct cache_t {
struct 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;
}
};
/* OC对象 */
struct my_objc_object {
void *isa;
};
//objc-runtime-new.h LINE-222
struct method_t {
SEL name;
const char *types;
IMP imp;
};
//objc-runtime-new.h LINE-100
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
};
//objc-runtime-new.h LINE-265
struct method_list_t : entsize_list_tt{
bool isFixedUp() const;
void setFixedUp();
};
//objc-runtime-new.h LINE-287
typedef uintptr_t protocol_ref_t; // protocol_t *, but unremapped
//objc-runtime-new.h LINE-343
struct protocol_list_t {
// count is 64-bit by accident.
uintptr_t count;
protocol_ref_t list[0]; // variable-size
};
struct property_list_t : entsize_list_tt{
};
//objc-runtime-new.h LINE-277
struct ivar_list_t : entsize_list_tt {
};
//objc-runtime-new.h LINE-553
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;
}
};
//objc-runtime-new.h LINE-802
class property_array_t{
};
//objc-runtime-new.h LINE-826
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_list_t *methods;
property_array_t properties;
protocol_list_t *protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
//objc-runtime-new.h LINE-870
struct class_data_bits_t {
uintptr_t bits;
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
};
struct my_objc_class : my_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();
}
my_objc_class* metaClass() {
return (my_objc_class *)((long long)isa & ISA_MASK);
}
};
#endif /* myClassInfo_h */
使用:
myPerson.h
#import <Foundation/Foundation.h>
@interface myPerson : NSObject
-(void)test;
@end
myPerson.m
#import "myPerson.h"
@implementation myPerson
-(void)test{
NSLog(@"%s",__FUNCTION__);
}
@end
myBoy.h
#import "myPerson.h"
@interface myBoy : myPerson
@end
myBoy.m
#import "myBoy.h"
@implementation myBoy
@end
main
#import <Foundation/Foundation.h>
#import "myClassInfo.h"
#import "myBoy.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
myPerson *p = [[myPerson alloc]init];
[p test];
[p test];
my_objc_class *myPersonClass = (__bridge my_objc_class *)[myPerson class];
cache_t cache = myPersonClass->cache;
NSLog(@"%s %p", @selector(test), cache.imp(@selector(test)));
NSLog(@"--------");
bucket_t *buckets = cache._buckets;
bucket_t bucket = buckets[(long long)@selector(test) & cache._mask];
NSLog(@"%s %p", bucket._key, bucket._imp);
NSLog(@"--------");
for (int i = 0; i <= cache._mask; i++) {
bucket_t bucket = buckets[I];
NSLog(@"%s %p", bucket._key, bucket._imp);
}
NSLog(@"-----------------Boy-----------------");
myBoy *boy = [[myBoy alloc]init];
[boy test];
[boy test];
my_objc_class *myBoyClass = (__bridge my_objc_class *)[myBoy class];
cache_t cache2 = myBoyClass->cache;
NSLog(@"%s %p", @selector(test), cache2.imp(@selector(test)));
NSLog(@"--------");
bucket_t *buckets2 = cache2._buckets;
bucket_t bucket2 = buckets2[(long long)@selector(test) & cache2._mask];
NSLog(@"%s %p", bucket2._key, bucket2._imp);
NSLog(@"--------");
for (int i = 0; i <= cache2._mask; i++) {
bucket_t bucket2 = buckets2[I];
NSLog(@"%s %p", bucket2._key, bucket2._imp);
}
}
return 0;
}
打印结果:
06.方法缓存[84860:13598078] -[myPerson test]
06.方法缓存[84860:13598078] -[myPerson test]
06.方法缓存[84860:13598078] test 0x100001890
06.方法缓存[84860:13598078] --------
06.方法缓存[84860:13598078] test 0x100001890
06.方法缓存[84860:13598078] --------
06.方法缓存[84860:13598078] (null) 0x0
06.方法缓存[84860:13598078] init 0x1003aae50
06.方法缓存[84860:13598078] (null) 0x0
06.方法缓存[84860:13598078] test 0x100001890
06.方法缓存[84860:13598078] -----------------Boy-----------------
06.方法缓存[84860:13598078] -[myPerson test]
06.方法缓存[84860:13598078] -[myPerson test]
06.方法缓存[84860:13598078] test 0x100001890
06.方法缓存[84860:13598078] --------
06.方法缓存[84860:13598078] test 0x100001890
06.方法缓存[84860:13598078] --------
06.方法缓存[84860:13598078] (null) 0x0
06.方法缓存[84860:13598078] init 0x1003aae50
06.方法缓存[84860:13598078] (null) 0x0
06.方法缓存[84860:13598078] test 0x100001890
发现:
1.方法名(_key)与_mask 得到的位置(索引值),就缓存到数组的下标位置处
objc-cache.mm
【bucket_t * cache_t::find(cache_key_t k, id receiver)】
--> 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);
}
发现:(key & mask)
2.左边的值不管怎么 & 都不会比右边的mask的值要大(【&】的性质)
3.有可能&_mask的值是一样的,但发现里面的key不是与的key,底层会 i-1再去找(往上找),到0还没有,会从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);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[I];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
【__arm64__架构】
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
4.数组的长度不够了,会扩容(原来的长度X2)(mask的值变化),此时会清除里面的所有的,重新开始缓存(比如一开始会分配10个,不够了可能会给20个空间)
myPerson.h
#import <Foundation/Foundation.h>
@interface myPerson : NSObject
-(void)test;
-(void)test2;
-(void)test3;
@end
myPerson.m
#import "myPerson.h"
@implementation myPerson
-(void)test{
NSLog(@"%s",__FUNCTION__);
}
-(void)test2{
NSLog(@"%s",__FUNCTION__);
}
-(void)test3{
NSLog(@"%s",__FUNCTION__);
}
@end



发现:
默认的_mask 是 3 ,说明槽位是 4 个,实际占用槽位是3个。然后我们再多调用一个方法,
发现_occupied数量等于_mask时,再次加入一个缓存方法时,槽位的总量会变大,槽位会变为原来的2倍
源码:
=======================
默认的 槽位
objc-cache.mm LINE--91
/* Initial cache bucket count. INIT_CACHE_SIZE must be a power of two. */
enum {
INIT_CACHE_SIZE_LOG2 = 2,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2)
};
objc-cache.mm LINE-544
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
if ((uint32_t)(mask_t)newCapacity != newCapacity) {
// mask overflow - can't grow further
// fixme this wastes one bit of mask
newCapacity = oldCapacity;
}
reallocate(oldCapacity, newCapacity);
}
(4-1,2)-->(4-1,3)->(2*4-1,1)
5.如果子类调用了父类的方法,此时会缓存一份到自己的方法缓存列表里面
方法缓存的作用:以空间换取时间
总结:
1、每个Class 里会有一个cache 方法缓存。
2、cache 本质是一个 Hash表。
3、hash 函数式 f(@selector()) = index, @selector() & _mask。
4、槽位如果不够,_mask 会变换,变为原来的2倍,并且扩展槽位的时候,会清空数组里原有的缓存内容。
5、子类没有实现方法会调用父类的方法,会将父类方法加入到子类自己的cache 里。
友情链接:
网友评论