OC对象(一)-- alloc和init底层到底在干嘛
OC对象(二)-- 内存对齐和calloc中的16字节对齐
OC对象(三)-- isa结构分析
开场白
本文主要讲解isa结构和isa的赋值过程
1、isa
实例对象在内存中首地址就是isa,其实就是用来表示对象的类是谁。
DZPerson *obj = [[DZPerson alloc] init];
obj.name = @"DZ";
NSLog(@"%@", obj.name);
通过lldb打印obj
的内存情况:
通过调试,po
一下obj
首地址中的第一个值是DZPerson
。说明isa中存储着实例对象对应的归宿类。
2、isa结构
通过objc源码查看isa是一个union位域的形式,源码如下:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD) //ISA_BITFIELD存在
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
条件编译命令中的ISA_BITFIELD
是存在的,所以后面的代码是会被编译进去的。接下来看看ISA_BITFIELD
是如何定义的
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
- 这里使用条件编译命令区分
__arm64__
代表iOS。__x86_64__
代表MAC -
ISA_MASK
:掩码,用于直接获取isa中类信息的。也就是通过掩码可以直接得到shiftcls
-
nonpointer
:表示是否对isa指针开启指针优化- 0:isa就是一个指针
- 1:isa不只是一个简单的指针,里面还包含了类信息,引用计数等等信息。通常自定义的类实例对象都是这个类型。
-
has_assoc
:是否有关联对象 -
has_cxx_dtor
:是否有c++或Objc析构器 -
shiftcls
:存储类指针的值。注意iOS和Mac中占用的位数不一样 -
magic
:⽤于调试器判断当前对象是真的对象还是没有初始化的空间 -
weakly_referenced
:对象有没有被若引用 -
deallocating
:对象是否正在释放中 -
has_sidetable_rc
:是否有引用计数散列表 -
extra_rc
:提供给引用计数使用的。
扩展 - union位域
union位域可以节省内存空间的开辟,举个例子:
定义一个DZPeront
类,里面含有四个属性,分别代表走路的四个方向
@interface DZPerson : NSObject
@property (assign, nonatomic) BOOL front;
@property (assign, nonatomic) BOOL back;
@property (assign, nonatomic) BOOL left;
@property (assign, nonatomic) BOOL right;
@end
这四个属性都是BOOL类型,一个BOOL类型占用1个字节。因此需要内存开辟4字节的空间。BOOL类型其实就是两种值YES
or NO
,对应的二进制是0000 0001
or 0000 0000
,如图:
如果把前面的几位利用上,就可以达到减少内存占用。使用union位域就可以实现这个目的。
union {
char bits;
struct{
char front :1;
char back :1;
char left :1;
char right :1;
};
} _walkDirection;
联合体(union)中还包含了一个结构体(struct),这个结构体中的成员后面跟着的冒号和数字,数字就是代表占用的位数(语法要求,这个要死记硬背),这个就是位域
需要注意:
-
案例中的数字“1”,代表占用1位。1位可以表达的两种情况:1和0。如果想表达“0-7”的值,那么就需要定义为“3”
-
位域中成员定义的位是按从低到高的方式存储的
nonuse nonuse nonuse nonuse right left back front 0 0 0 0 1 1 1 1
上面的DZPerson
可以修改成:
@interface DZPerson : NSObject{
union {
char bits;
struct{
char front :1;
char back :1;
char left :1;
char right :1;
};
} _walkDirection;
}
- (void)setFront:(BOOL)isFront;
- (BOOL)isFront;
- (void)setBack:(BOOL)isBack;
- (BOOL)isBack;
@end
@implementation DZPerson
- (instancetype)init
{
self = [super init];
if (self) {
_walkDirection.bits = 0b00000000;
}
return self;
}
- (void)setFront:(BOOL)isFront {
if (isFront) {
_walkDirection.bits |= kPersonWalkDirectionFrontMask;
} else {
_walkDirection.bits |= ~kPersonWalkDirectionFrontMask;
}
}
- (BOOL)isFront {
return _walkDirection.front;
}
- (void)setBack:(BOOL)isBack {
_walkDirection.back = isBack;
}
- (BOOL)isBack {
return _walkDirection.back;
}
@end
//调用
DZPerson *obj = [[DZPerson alloc] init];
[obj setFront:YES];
[obj setBack:YES];
NSLog(@"%@", obj.isFront ? @"YES": @"NO");
查看obj内存情况,如图:
此处内存中十六进制是0x3
,因为调用代码设置的front
和back
,二进制表示:0b11
3、isa的赋值过程
实例alloc有重要的三步骤(不清楚的可以翻看之前的博客)
- 计算需要分类的空间大小:
size = cls->instanceSize(extraBytes);
- calloc分配内存:
obj = (id)calloc(1, size);
- 实例对象的初始化:
obj->initInstanceIsa(cls, hasCxxDtor);
此处主要聊聊第三部做了什么:
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
...//省略不关心代码
size = cls->instanceSize(extraBytes);
...//省略不关心代码
obj = (id)calloc(1, size);
...//省略不关心代码
//核心入口,加星标记🦍🦍🦍🦍🦍🦍🦍🦍
obj->initInstanceIsa(cls, hasCxxDtor);
//🦍🦍🦍🦍🦍🦍🦍🦍
...//省略不关心代码
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
⏬⏬⏬
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
⏬⏬⏬
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {//传入参数是true,不会走这个分支
......
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
//核心入口 👨💻
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
isa = newisa;
}
}
核心研究的地方就是对isa中的位进行赋值,注意类的相关信息赋值在newisa.shiftcls位中。
扩展 - 获取isa中的类信息
runtime的api中提供了获取对象类方法object_getClass
,接下来研究一下源码的实现:
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
⏬⏬⏬
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) 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中shiftcls的值(ISA_MASK上面有讲过,是个宏,在不同操作系统定义的值也不同)。 - 得到的值进行Class类型强转,这也是为什么我们在看相关源码中,isa都是定义成Class类型原因。
网友评论