前言
上一篇我们基本了解isa的存放着什么,和calloc是怎么分配内存的。最后我们遗留了几个问题,将会在本文逐一分析,这一章的内容,还是比较简单,来吧,一起看看吧。
对象与类的联系
对象isa
对象可以有多个,我们的类就只有1个。那对象和类是怎么联系的呢?
那我们就先创建一个对象,并一起看下对象的类。
Person *object = [Person alloc];
object_getClass(object);
我们点击进去object_getClass看下是什么?
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
我们可以看到重点obj->getIsa();
看这意思基本是和isa是有关系的,那我们再进去看看
inline Class objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
uintptr_t ptr = (uintptr_t) this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}
这里明显是return ISA(),那我们再进去看看
inline Class objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
有收获了,这里返回的是isa.bits & ISA_MASK。
我们看下ISA_MASK是什么
# define ISA_MASK 0x00007ffffffffff8ULL
这个是__x86_64_下的宏,由源码我们能得出一个结论:对象的isa指针 & ISA_MASK就是该对象的类,也可以叫做类对象。
isa & ISA_MASK
既然我们得出对象和类的联系了,那我们就试一下看下是不是真的是这样。
先创建一个对象
Person *p = [Person alloc];
NSLog(@"%p",p);
在NSLog那里打一个断点。
然后我们使用LLDB指令输出一下。
(lldb) x/4xg p
0x100595940: 0x011d8001000080e9 0x0000000000000000
0x100595950: 0x00007fff7c5d3d2c 0x00000000a893fef3
(lldb) p/x Person.class
(Class) $1 = 0x00000001000080e8 Person
(lldb) p/x 0x011d8001000080e9 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000080e8
(lldb)
我们先打印下p对象,可以知道前面8个字节0x011d8001000080e9就是isa指针。然后& 0x00007ffffffffff8ULL,可以发现最后结果和Person.class是一样的。
由这个,我们可以试下创建多个Person对象,然后看下该对象&isa,是不是同样的Person地址,由这个也可以得出结论:对象是有多个,类只有1个。也就是说类的内存地址只有1份。
isa的走位分析
我们分析了对象的isa是指向类,但是类里面也有isa,那有指向什么呢?
我们直接上图吧。
我们先看虚线的,由最下面由左往右开始看起。
Subclass对象的isa指向Subclass类。
Subclass类的isa指向Subclass的元类。
Subclass元类的isa指向NSObject的元类。
NSObject的元类的isa指向还是它自己。
简单来说就是对象 -> 类 -> 元类 -> 根元类 -> 根元类
那我们顺便再看下实线,这是个关于类继承关系的,我们就看下灰色区域,由最下往上看。
Subclass类的父类是SuperClass类。
SuperClass类的父类是RootClass,也就是NSObject类。
NSObject类的父类为 nil。
Subclass元类的父类是SuperClass元类。
SuperClass类的父类是RootClass元类,也就是NSObject元类。
NSObject元类的父类是 NSObject类。
有了这些东西,后面关于方法调用流程就会很清晰了,后续会开篇章讲到。
对象的本质
对象的本质是什么?
直接上源码吧。对象的本质就是结构体。
struct objc_class : 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();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
}
我们可以看到,前面8个字节是isa指针,再8位就是存放superClass父类。这个我就不去验证了。
这里有个印象就行,后面也会有文章说到里面的内容。
clang分析
现在要通过clang来分析对象的属性。
我们先在main文件里面添加我们的类。
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
@end
然后我们先把我们的main.m文件转为c++文件。
clang -rewrite-objc main.m -o main.cpp
我们就可以看到同目录输出了一个main.cpp文件。打开main.cpp文件。直接搜索int main。
我们就可以看到上面关于Person的信息。
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString _name;
};
我们可以看到Person有_name的成员变量。
static NSString * _I_Person_name(Person * self, SEL _cmd) { return** (*(NSString )((char *)self + OBJC_IVAR_$_Person$_name)); }
static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); }
这不就是name的set方法个get方法。
我们直接搜一下_I_Person_name。
{{(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_}}
我们根据SEL来,找到IMP,就是函数的实现的指针,从而实现set和get方法。
这里有一点要说明一下如果我们是成员变量,那么是不会生成set和get方法的。
总结
- 对象的
isa指针&ISA_MASK就是该类。 - 对象可以创建多个,类只有1个。
- 对象的本质是结构体。
下一章,我们更详细聊聊类的结构。