前言
每个对象都会有一个它所属的类,这是面向对象的基本概念。但是在OC
中,这对所有数据结构有效。任何数据结构,只要在恰当的位置具有一个指针指向一个Class
,那么,它都可以被认为是一个对象。
在之前的文章中,笔者也简单分析了一下OC
对象的原理,那么带着同样的问题我们来反洗一下什么是类,这里先抛出几个问题:
什么是类,元类;
OC类的本质是什么;
OC类里面有什么成员变量;
类里面方法、协议、 扩展优势如何存储的;
在之前对象的分析过程中,我们通过LLDB
的一些命令(比如:x/4gx p
)获取了一个对象的内存结构,然后通过和ISA_MASK
取与(&)操作得到了我们的类。
那么在这里有没有这样一个问题,如果我再对这个类进行相同的操作会有什么样的结果呢?
类的探究分析
LLDB调试
- 那我们按照对象的思路继续探索类的下面是什么,依然先创建一个类
CDPerson
。
@interface CDPerson : NSObject
{
NSString *_nickName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (void)sayNB;
+ (void)sayHello;
@end
@implementation CDPerson
- (void)sayNB {
}
+ (void)sayHello {
}
@end
//然后初始化一下
CDPerson *person = [CDPerson alloc];
按照对象的思路去打印相关的isa的内存地址
从上图可以看到,$1
和 $3
虽然都是 CDPerson
,但是内存地址确实截然不同的。那到底哪个才是我们的 实例对象 person
的类呢?
这里我们用结果来说明。
CDPerson *p = [CDPerson alloc];
Class cls1 = p.class;
Class cls2 = object_getClass(p);
Class cls3 = [CDPerson class];
Class cls4 = [[CDPerson alloc] class];
Class cls5 = [CDPerson class];
NSLog(@"\ncls1-%p\ncls2-%p\ncls3-%p\ncls4-%p\ncls5-%p\n", cls1, cls2, cls3, cls4, cls5);
//结果如下
cls1-0x1000083a0
cls2-0x1000083a0
cls3-0x1000083a0
cls4-0x1000083a0
cls5-0x1000083a0
从这个结果验证来看,0x00000001000083a0
才是我们的 实例对象 person
的类,那么这个 0x0000000100008378
又是什么那?
然而我们通过打印 NSObject
的类
p/x [NSObject class]
(Class) $5 = 0x00000001efaa4f48 NSObject
很明显这个 $3
也不是 NSObject
- 这里借助一个二进制分析工具
MachOView
分析工具分析一些我们的可执行文件,里面也可以很直观的看到谁才是我们的CDPerson
类。
CDPerson 类的真正地址
同理,CDStudent
也是一样的
- 小结:到这里我们就得到一个结论
对象 --isa--> 类
;类 --isa--> 元类
;
既然上面都有了原类的概念了,那么我们的元类的isa
又该指向谁呢。这里还是用一个例子来解释。
Class cls1 = [CDPerson class];
Class metaCls1 = object_getClass(cls1);
Class rootCls1 = object_getClass(metaCls1);
Class rootRootCls1 = object_getClass(rootCls1);
///都知道,NSObject 就是我们的根类
Class cls2 = [NSObject class];
Class cls3 = object_getClass(cls2);
Class cls4 = object_getClass(cls3);
NSLog(@"\n类:%p \n元类:%p \n根元类:%p \n根根元类%p \n根类:%p \n根元类:%p \n根根元类:%p", cls1, metaCls1, rootCls1, rootRootCls1, cls2, cls3, cls4);
打印的结果如下:
打印
从这个打印也可以得出结论:
非根类的isa 指向元类,然后元类的isa指向根元类,而根元类的isa依然指向根元类;
根类的isa 指向根元类;
更具这个结论就得到如下的一张isa 的指向图。
isa 指向图
源码分析
那么接下来我们就看看源码到底是如何定义的。
//这里可以看到我们平时的Class 就是一个 objc_class 的结构体指针
typedef struct objc_class *Class;
//这里可以知道,objc_class 也是继承自我们的 objc_object,所以类也是一个对象(类对象)
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
//这儿是一个隐藏参数isa,来自objc_object 这个结构体的唯一成语啊变量。
// Class ISA;
//这里有一个指针(Class)的superclass,从之前的类的内存问题也可以看到,第一个8字节是isa,第二个8字节是个NSObject 之类的。
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
......
//此处省略一堆方法
}
从上面的源码定义里面就可以知道,我们的类也是一个对象,只是里面比对象多了几个成员变量 superclass
、cache
、bits
;那接下来我们就来看看 cache
和 bits
到底是什么。
-
superclass
这个就是指向父类的指针。 -
cache (cache_t)
通过分析cache_t de
结构,我们可以得出cache_t
这个占用的内存空间为16字节。所以按照内存平移的规则我们可以知道class_data_bits_t
和类首地址之间的差距是32 字节(isa:8字节,superclass:8字节,cache:16字节
)。 -
bits(class_data_bits_t)
在之前对象里面我们也有一个 bits 的成员变量;
typedef unsigned long uintptr_t;
uintptr_t bits;
但是类里面的 bits
和 对象里面的 bits
不是一个概念。
先来看看 class_data_bits_t
这个结构
struct class_data_bits_t {
//友元类:C++中的friend关键字其实做这样的事情:在一个类中指明其他的类(或者)函数能够直接访问该类中的private和protected成员。
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit) const
{
return bits & bit;
}
......
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
// Use a store-release fence because there may be concurrent
// readers of data and data's contents.
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
......
}
可以看到,在里面有一个 bits
成员(Values are the FAST_ flags above)
,还有一个 class_rw_t*
返回值的 data()
方法。
再看看 class_rw_t
这个结构的定义
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
private:
......
public:
......
//
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
// 返回值为 method_array_t 的一个方法列表
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
// 属性列表
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
// 协议列表
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
}
然后通过 LLDB
调试一下这个 class_data_bits_t
这个数据类型。看看里面到底是不这样。
-
首先:通过
LLDB打印元类LLDB
找到元类
-
打印
class_data_bits_t
数据类型
(lldb) p (class_data_bits_t *)0x00000001007277f4
(class_data_bits_t *) $4 = 0x00000001007277f4
- 获取
class_rw_t
并且查看具体内容
p $4->data()
(class_rw_t *) $5 = 0x0000000101a0d1f0
(lldb) p *$5
(class_rw_t) $6 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000920
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
-
查看
class_rw_t 的属性、方法和协议class_rw_t
的相成员
-
继续查看一个属性到底是不是我们定义的
(lldb) p $7.list.ptr
(property_list_t *const) $8 = 0x0000000100008488
(lldb) p *$8
(property_list_t) $9 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p *($8)
(property_list_t) $10 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $10.get(0)
(property_t) $9 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $10.get(1)
(property_t) $10 = (name = "age", attributes = "Tq,N,V_age")
从上面的调试结果来看,的确也得到了我们的属性,和相关的编码等都可以看到
同理也可以查看方法、和协议等
类的继承关系
上面我们分析了 isa
的问题,那么继承的问题呢?接下来我们就来分析一下继承的问题。
...... 待完善,先放一张结论图
类方法,ivar 在哪里
成员变量ivar
从上面的 properties()
里面我们发现这个CDPerosn
类只有两个,那么我们的成员变量又在什么地方呢?
于是乎我们在当前文件里面搜索了一下 ivars
,发现了在 class_ro_t
里面有这么一个定义 const ivar_list_t * ivars;
struct class_ro_t {
......
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
......
}
然后在回到我们的 class_rw_t
里面去发现了这么一段定义
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
}
return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
于是乎,按照 properties()
相同的思路去调试
然后继续打印看结果,果然我们的成员变量和属性对应的成员变量都在这里。
(lldb) p $15.ivars
(const ivar_list_t *const) $16 = 0x0000000100008420
(lldb) p $16.get(0)
(ivar_t) $17 = {
offset = 0x00000001000084f8
name = 0x0000000100003eaf "_nickName"
type = 0x0000000100003f6e "@\"NSString\""
alignment_raw = 3
size = 8
}
Fix-it applied, fixed expression was:
$16->get(0)
(lldb) p $16.get(1)
(ivar_t) $18 = {
offset = 0x0000000100008500
name = 0x0000000100003e77 "_name"
type = 0x0000000100003f6e "@\"NSString\""
alignment_raw = 3
size = 8
}
Fix-it applied, fixed expression was:
$16->get(1)
类方法
因为前面我们分析了,类方法存在元类里面,所以我们就先拿到我们的当前类的元类在去分析。
-
通过
获取元类的class_rw_tLLDB
获取元类的class_rw_t
-
通过
class_rw_t
结构,获取方法列表。
(lldb) p $4.methods()
(const method_array_t) $5 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008338
}
arrayAndFlag = 4295000888
}
}
}
(lldb) p $5.list.ptr
(method_list_t *const) $6 = 0x0000000100008338
(lldb) p *($6)
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $7.get(0)
(method_t) $8 = {}
(lldb) p $8.big
(method_t::big) $9 = {
name = "sayHello"
types = 0x0000000100003f66 "v16@0:8"
imp = 0x0000000100003d10 (KCObjcBuild`+[CDPerson sayHello])
}
Fix-it applied, fixed expression was:
$8.big()
总结
通过上面的分析和调试,我们得出如下结论:
1、实例变量(对象)的
isa
指向class
;
2、类Class
也是一个对象;
3、类的isa
指向元类;
4、类里面存放了属性、成员变量、实例方法、协议等;
5、类方法存放在元类列面,和对象与类的关系一模一样。
最直观的就是下面这张图
isa流程图
补充
friend 友元类
对于一个没有定义public访问权限的类,能够让其他的类操作它的私有成员往往是有用的。例如你写了一段binary tree的代码,Node是节点类,如果能够让连接多个节点的函数不需要调用public方法就能够访问到Node的私有成员的话,一定是很方便的。
C++中的friend关键字其实做这样的事情:在一个类中指明其他的类(或者)函数能够直接访问该类中的private和protected成员。
struct str_a {
int area_length;
struct str_b *sb;
int testFunc();
};
struct str_b {
//如果这里没有这个friend,那么str_a 的sb 成员无法访问private 成员变量。
friend str_a;
private:
int age;
public:
double height;
};
int str_a::testFunc() {
if (sb->age > 10) {
return 20;
}
return 10;
}
网友评论