1. 类和元类的创建时机
前面简单提到类和元类的创建时机是在编译器,今天我们通过一下两种方法来验证一下:
1.1 打印 类和元类的指针
首先看下面代码:
在main函数之前打印断点,
通过p/x 打印类指针,如果能获得指针, 说明已经在内存中申请了内存空间
然后x/4gx打印类的内存结构,得到类 的isa,然后isa & 掩码 ISA_MASK获得元类的isa,如果这个过程中能正常打印出相应的指针,则能简单验证类和元类的创建是在编译期创建的,打印结果如下:
1.2 command + B生成可执行文件,然后使用 MachoView 打开程序二进制可执行文件查看
由此,可以验证类和元类是在编译期创建的,在运行项目alloc之前已经被创建出来了
2. 指针偏移
2.1 普通指针 值拷贝
int a = 10; //
int b = 10; //
LGNSLog(@"%d -- %p",a,&a);
LGNSLog(@"%d -- %p",b,&b);
// KC打印: 10 -- 0x7ffeefbff45c
// KC打印: 10 -- 0x7ffeefbff458
2.2 指针拷贝
// 对象 - 指针拷贝
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [LGPerson alloc];
LGNSLog(@"%@ -- %p",p1,&p1);
LGNSLog(@"%@ -- %p",p2,&p2);
// KC打印: <LGPerson: 0x100753be0> -- 0x7ffeefbff450
// KC打印: <LGPerson: 0x10074e740> -- 0x7ffeefbff448
2.3 指针偏移
// 数组指针
int c[4] = {1,2,3,4};
int *d = c;
NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
NSLog(@"%p - %p - %p",d,d+1,d+2);
for (int i = 0; i<4; i++) {
// int value = c[i];
int value = *(d+i);
LGNSLog(@"%d",value);
}
NSLog(@"指针 - 内存偏移");
// 0x7ffeefbff470 - 0x7ffeefbff470 - 0x7ffeefbff474
// 0x7ffeefbff470 - 0x7ffeefbff474 - 0x7ffeefbff478
// KC打印: 1
// KC打印: 2
// KC打印: 3
// KC打印: 4
首地址是数组的第一个元素的地址,&c[0] 和 &c[1],相差一个元素的大小,指针d + 1,相当于偏移一个所占位数的元素的大小
3. 类的结构
3.1 类的结构是什么?
通过clang查看看下面代码在c++文件中的编译:
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
NSLog(@"%@ - %p",person,pClass);
}
return 0;
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// id, SEL
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
Class pClass = object_getClass(person);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5s_4100t0cd5rn_d7gx0n5wqh8w0000gn_T_main_60f7a3_mi_9,person,pClass);
}
return 0;
}
我们探究的类的结构,就是Class,在cpp文件中不难发现类结构是:
typedef struct objc_class *Class;
可以看出,类是 objc_class类型的 结构体。
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
我们知道万物皆对象,objc_class 继承自objc_object,那么我们通过下图方法查看objc_class的源码:
struct objc_class : 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();
}
··· // 方法和函数
}
源码中可以看到,有个隐藏的Class isa(为什么有个隐藏的Class isa?),
隐藏的属性必然是来自于继承,继承自objc_object ,看objc_object源码:
object源码:
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
那么NSObject的定义是什么样的呢?
其实NSObject的定义是结构体的一种仿写:
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
问: 为什么isa是Class类型?
答:万物皆对象,Clss本身继承自object,用来接收isa可以的,早期调用isa就是为了返回类,
后期优化了 nonpointer isa
问:objc_class 和NSObject的关系? objc_object和NSObject的关系?
NSObject 是一种objc_class 的类型,NSObject也是一个类class,底层也是objc_class;
OC底层封装的C,objc_object是NSObject底层编译的写法。
objc_object和objc_class是底层的实现,对应当前NSObject(Class)和NSObject。
3.2 类的结构分析
通常我们会在类中定义属性、成员变量和方法,
@interface LGPerson : NSObject{
NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)sayHappy;
@end
那么在类中是如何存储这些定义的属性 成员变量 方法的呢?
接下来我们来研究一下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
NSLog(@"%@ - %p",person,pClass);
}
return 0;
}
通过x/4gx pClass打印类结构,通过前面的查看源码得知如下图:
objc_class中 Class ISA和Class superclass分别占8字节,
cache_t cache占16字节
struct cache_t {
struct bucket_t *_buckets; // 8
mask_t _mask; // 4 uint32_t mask_t
mask_t _occupied; // 4
public: // 下面是函数,函数不占内存
struct bucket_t *buckets();
// 方法
···
};
因为objc_class中cache_t cache是结构体,而不是结构体指针占(结构体指针占8字节), 所以cache_t cache占内存8 + 4 + 4 = 16字节。
猜测:属性 成员变量存储在class_data_bits_t bits中,通过指针偏移(偏移原理类比为数组),偏移32字节获取class_data_bits_t bits。
探索如下:
对pClass首地址0x100001278 + 32 得到 0x100001298(16进制)即bits,通过bits.data() 得到class_rw_t *data(),打印如下:
而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;
#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));
}
};
打印*data():
属性应该存储在properties中,打印properties,然后并打印其中list:
methods,一系列操作后如下:
由此我们探究出了属性 方法的存储位置,那么成员变量存储在什么地方呢?
通过查看struct class_rw_t中的const class_ro_t *ro,
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;
}
};
里面分别有method_list_t * baseMethodList property_list_t *baseProperties const ivar_list_t * ivars,我们猜测类的方法 属性 成员变量分别存储在对应的变量中,打印ro结果如下:
由此可以看出LGPerson仅有的一个成员变量 nickName存储在bit.data()中的ro中baseProperties中,
那么为什么bit.data()中property_array_t properties也等打印出成员变量呢?暂时先抛出个问题。
接下来我们用同样的方法分别打印ivars baseMethodList,如图:
baseMethodList打印:
打印出count = 2,分别打印ivars中成员变量,分别为hobby _nickName,再次验证了@property生成的属性,在系统底层会自动生成_属性的成员变量,并且会自动生成setter getter。
问题:从baseMethodList中并未打印出类方法 sayHappy,那么类方法存储在什么地方呢?
猜测:
实例方法存在 类中,那么其实 类也是元类创建出来的类对象,类的类方法应该存在元类中。
通过下面代码,分别在类和元类中打印对象方法和类方法:
void testInstanceMethod_classToMetaclass(Class pClass){
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);
NSLog(@"%s",__func__);
}
打印结果
2019-12-29 12:28:17.714554+0800 LGTest[799:13098] 0x100002198-0x0-0x0-0x100002130
2019-12-29 12:28:17.715541+0800 LGTest[799:13098] testInstanceMethod_classToMetaclass
由打印结果看出,对象方法存在于类中,不存在于元类中,类方法存在于元类中,不存在于类中。