iOS OC 类原理一
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
,如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4iOVL2t-1586242928963)(https://user-gold-cdn.xitu.io/2019/12/28/16f4cc767683200b?w=745&h=355&f=png&s=43820)]
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
由打印结果看出,对象方法
存在于类
中,不存在于元类
中,类方法
存在于元类
中,不存在于类
中。