OC中类的结构
类和元类的创建时机
类和元类是在编译时就已经创建的的,来下边验证一下
创建一个mac工程,在main函数打断点
我们
p/x JSPerson.class
image.png
在还没有进入main函数时就发现已经存在了指针了,证明这个时候已经存在JSPerson
的类了
然后我们拿到类的 isa
指针,也就是类的第一个属性0x001d8001000010d9
,我们和ISA_MASK进行&运算
$1
即为JSPerson的元类
这个方法可能不足以证明是在编译时产生的,那么我们利用 machoView
,来验证,我们command + b
会在Products
文件夹里生成一个可执行文件,我们将这个可执行文件拖到machoView
里,
在
section64
的 _objc_data
里看到已经存在JSPerson
的类通过
lldb
和command+b
+machoView
两种方式验证由此证明类和元类是在编译时期就创建的.
指针补充
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 普通指针
// 值拷贝
int a = 10; //
int b = 10; //
NSLog(@"%d -- %p",a,&a);
NSLog(@"%d -- %p",b,&b);
// 10 -- 0x7ffeefbff49c
// 10 -- 0x7ffeefbff498
//对象 - 指针拷贝
Person *p1 = [Person alloc];
Person *p2 = [Person alloc];
NSLog(@"%@ -- %p",p1,&p1);
NSLog(@"%@ -- %p",p2,&p2);
// <Person: 0x1020455f0> -- 0x7ffeefbff490
// <Person: 0x102037dc0> -- 0x7ffeefbff488
}
return 0;
}
image.png
a不是一个指针,内存中有个值10,然后有一个变量名字叫a
,然后把10给了a
,10本身就在内存中的,属于值拷贝,
People
讲过两次alloc
生成两个不同的内存空间,p1
,p2
分别指向了两个不同的内存空间
&p1
,&p2
相当于分别指向p1
,p2
指针的指针
//数组指针
int c[4] = {1,2,3,4};
int *d = c;
NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
// 0x7ffeefbff4b0 - 0x7ffeefbff4b0 - 0x7ffeefbff4b4
// 首地址就是数组的第一个元素
NSLog(@"%p - %p - %p",d,d+1,d+2);
// 0x7ffeefbff4b0 - 0x7ffeefbff4b4 - 0x7ffeefbff4b8
// 因为数组里边放的是int类型,占4个字节,所以内存地址相差4
// 地址+1 就是偏移,偏移位置就是一个所在元素的位数
for (int i = 0; i<4; i++) {
// int value = c[I];
int value = *(d+i);
// d+1 就是元素所在位置,*就是取出当前地址的值
NSLog(@"%d",value);
// 1,2,3,4
}
image.png
类的结构
可以clang rewrite 生成cpp文件,或者直接拿到源码分析,
class其实就是一个objc_class
结构体,objc_class
继承 与objc_object
struct objc_class : objc_object {
//隐藏属性,继承与objc_object
// Class ISA; // 8,
Class superclass; // 8
cache_t cache; // 16 不是8 // 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);
}
...
...
}
我们先来探索属性的存储
struct class_data_bits_t {
...
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
...
}
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;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
void setFlags(uint32_t set)
{
OSAtomicOr32Barrier(set, &flags);
}
void clearFlags(uint32_t clear)
{
OSAtomicXor32Barrier(clear, &flags);
}
// set and clear must not overlap
void changeFlags(uint32_t set, uint32_t clear)
{
assert((set & clear) == 0);
uint32_t oldf, newf;
do {
oldf = flags;
newf = (oldf | set) & ~clear;
} while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
}
};
bits
是一个class_data_bits_t
类型,里边有一个class_rw_t*data()
的函数得到的是一个class_rw_t
类型, class_rw_t
也是一个结构体,里边有properties
,那么这个肯定是我们要研究的重点了
打印当前内存段,1.代表当前类的isa,2,代表当前类的superclass . 3代表catch,4代表当前类的bits
4当前为0x000显然没值,所以我们拿到当前isa内存地址进行内存偏移,偏移位数,isa位数为8,superclass 8 位,catch为catch_t类型,为一个结构体,所占位数由内部属性决定(这里为结构体不为结构体指针(8) 所以为16字节)
struct cache_t {
struct bucket_t *_buckets; // 8字节
mask_t _mask; // uint32_t 4字节
mask_t _occupied; // 4
所以总的偏移位数为 8+8+16 = 32
16进制32表示b
进两位为d
所以0x1000023b0 偏移32位之后的内存地址为0x1000023d0
然后我们将结果强转 p (class_data_bits_t *)0x1000023d0
得到bits
然后调用bits.data()方法得到一个内存地址0x0000000100fa6660
然后我们打印 p * $10
image.png
我们要看的是person的 hobby和nickName
@interface JSPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)sayHappy;
@end
打印他的properties
会有一个新的类型
list_array_tt
点进去会发现可以遍历
const iterator& operator ++ () {
assert(m != mEnd);
m++;
if (m == mEnd) {
assert(lists != listsEnd);
lists++;
if (lists != listsEnd) {
m = (*lists)->begin();
mEnd = (*lists)->end();
}
}
return *this;
}
然后打印他的第一个元素发现什么也没有
image.png
那么我们打印他的list
image.png
有一个新的类型 property_list_t ,然后我们在点进去会发现一个first image.png
然后打印p $13.first
那么hobby去哪里了啊,尝试一下他的下一个元素
image.png
也没有,其实这里是打印不出来hobby的,因为他没有放在rw里边,那么我们不去properties.我们去尝试取一下ro
image.png
然后取里边的ivars
诶我们发现hobby打印了
那么nickName有没有在这里边么,我们打印下baseProperties
image.png
变量和属性是有区别的,属性会自动生成一个
_nickName
的成员变量
方法
image.png
静态方法存在metaClass
中
我们可以用runtime的api来进行打印
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
//0x100002250-0x0-0x0-0x1000021e8
//0x0表示没有得到此方法
//我们发现元类里边存在一个`sayHappy`的实例方法
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy)); // ?
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
//0x0-0x0-0x1000021e8-0x1000021e8
这里我们发现类和元类都打印了函数指针,那么就有问题了,不是说在元类么,不急,先看下class_getClassMethod
的方法实现
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
是获取元类的实例方法
cache_t
struct objc_class : objc_object {
// Class ISA; // 8
Class superclass; // 8
cache_t cache; // 16 不是8
}
cache字面意思是缓存,缓存什么东西呢,接下来探索一下
首先看一下他到底是个什么东西
struct cache_t {
struct bucket_t *_buckets; // 8
mask_t _mask; // 4
mask_t _occupied; // 4
public:
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
mask_t capacity();
bool isConstantEmptyCache();
bool canBeFreed();
static size_t bytesForCapacity(uint32_t cap);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void expand();
void reallocate(mask_t oldCapacity, mask_t newCapacity);
struct bucket_t * find(cache_key_t key, id receiver);
static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};
里边有一个buckets,也是一个结构体
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__
MethodCacheIMP _imp;
cache_key_t _key;
#else
cache_key_t _key;
MethodCacheIMP _imp;
#endif
public:
inline cache_key_t key() const { return _key; }
inline IMP imp() const { return (IMP)_imp; }
inline void setKey(cache_key_t newKey) { _key = newKey; }
inline void setImp(IMP newImp) { _imp = newImp; }
void set(cache_key_t newKey, IMP newImp);
};
imp 函数的实现
key 缓存的key
下边验证下是否缓存,打一断点
打断点
然后我们x/4xg pClass
,拿到首地址,便宜16字节 打印p (cache_t*) 0x1000023c8
然后p *$1
取出地址的值,拿到catch_t
,然后p $2._buckets
打印buckets
,发现什么也没有,
然后断点继续走让方法调用,再次打印,发现imp
和key
都有值了
打印buckets
buckets是一个结构体数组,换一种用角标访问的形式
以角标方式访问
由此证明,catch在方法执行过一次之后进行缓存,下次调用直接在缓存中进行查找,节约了内存加快了响应速度