OC對(duì)象(一)-- alloc和init底層到底在干嘛
OC對(duì)象(二)-- 內(nèi)存對(duì)齊和calloc中的16字節(jié)對(duì)齊
OC對(duì)象(三)-- isa結(jié)構(gòu)分析
開(kāi)場(chǎng)白
本文主要講解isa結(jié)構(gòu)和isa的賦值過(guò)程
1割坠、isa
實(shí)例對(duì)象在內(nèi)存中首地址就是isa舌狗,其實(shí)就是用來(lái)表示對(duì)象的類(lèi)是誰(shuí)。
DZPerson *obj = [[DZPerson alloc] init];
obj.name = @"DZ";
NSLog(@"%@", obj.name);
通過(guò)lldb打印obj
的內(nèi)存情況:
通過(guò)調(diào)試收叶,po
一下obj
首地址中的第一個(gè)值是DZPerson
膝晾。說(shuō)明isa中存儲(chǔ)著實(shí)例對(duì)象對(duì)應(yīng)的歸宿類(lèi)赏迟。
2约素、isa結(jié)構(gòu)
通過(guò)objc源碼查看isa是一個(gè)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
是存在的贝攒,所以后面的代碼是會(huì)被編譯進(jìn)去的盗誊。接下來(lái)看看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)
- 這里使用條件編譯命令區(qū)分
__arm64__
代表iOS。__x86_64__
代表MAC -
ISA_MASK
:掩碼隘弊,用于直接獲取isa中類(lèi)信息的哈踱。也就是通過(guò)掩碼可以直接得到shiftcls
-
nonpointer
:表示是否對(duì)isa指針開(kāi)啟指針優(yōu)化- 0:isa就是一個(gè)指針
- 1:isa不只是一個(gè)簡(jiǎn)單的指針,里面還包含了類(lèi)信息长捧,引用計(jì)數(shù)等等信息嚣鄙。通常自定義的類(lèi)實(shí)例對(duì)象都是這個(gè)類(lèi)型。
-
has_assoc
:是否有關(guān)聯(lián)對(duì)象 -
has_cxx_dtor
:是否有c++或Objc析構(gòu)器 -
shiftcls
:存儲(chǔ)類(lèi)指針的值。注意iOS和Mac中占用的位數(shù)不一樣 -
magic
:?于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒(méi)有初始化的空間 -
weakly_referenced
:對(duì)象有沒(méi)有被若引用 -
deallocating
:對(duì)象是否正在釋放中 -
has_sidetable_rc
:是否有引用計(jì)數(shù)散列表 -
extra_rc
:提供給引用計(jì)數(shù)使用的酵紫。
擴(kuò)展 - union位域
union位域可以節(jié)省內(nèi)存空間的開(kāi)辟舵变,舉個(gè)例子:
定義一個(gè)DZPeront
類(lèi)咬崔,里面含有四個(gè)屬性矢棚,分別代表走路的四個(gè)方向
@interface DZPerson : NSObject
@property (assign, nonatomic) BOOL front;
@property (assign, nonatomic) BOOL back;
@property (assign, nonatomic) BOOL left;
@property (assign, nonatomic) BOOL right;
@end
這四個(gè)屬性都是BOOL類(lèi)型妓蛮,一個(gè)BOOL類(lèi)型占用1個(gè)字節(jié)侦香。因此需要內(nèi)存開(kāi)辟4字節(jié)的空間把敞。BOOL類(lèi)型其實(shí)就是兩種值YES
or NO
弥奸,對(duì)應(yīng)的二進(jìn)制是0000 0001
or 0000 0000
,如圖:
如果把前面的幾位利用上奋早,就可以達(dá)到減少內(nèi)存占用盛霎。使用union位域就可以實(shí)現(xiàn)這個(gè)目的。
union {
char bits;
struct{
char front :1;
char back :1;
char left :1;
char right :1;
};
} _walkDirection;
聯(lián)合體(union)中還包含了一個(gè)結(jié)構(gòu)體(struct)耽装,這個(gè)結(jié)構(gòu)體中的成員后面跟著的冒號(hào)和數(shù)字愤炸,數(shù)字就是代表占用的位數(shù)(語(yǔ)法要求,這個(gè)要死記硬背)掉奄,這個(gè)就是位域
需要注意:
案例中的數(shù)字“1”规个,代表占用1位。1位可以表達(dá)的兩種情況:1和0姓建。如果想表達(dá)“0-7”的值诞仓,那么就需要定義為“3”
-
位域中成員定義的位是按從低到高的方式存儲(chǔ)的
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
//調(diào)用
DZPerson *obj = [[DZPerson alloc] init];
[obj setFront:YES];
[obj setBack:YES];
NSLog(@"%@", obj.isFront ? @"YES": @"NO");
查看obj內(nèi)存情況,如圖:
此處內(nèi)存中十六進(jìn)制是0x3
速兔,因?yàn)檎{(diào)用代碼設(shè)置的front
和back
墅拭,二進(jìn)制表示:0b11
3、isa的賦值過(guò)程
實(shí)例alloc有重要的三步驟(不清楚的可以翻看之前的博客)
- 計(jì)算需要分類(lèi)的空間大谢凉贰:
size = cls->instanceSize(extraBytes);
- calloc分配內(nèi)存:
obj = (id)calloc(1, size);
- 實(shí)例對(duì)象的初始化:
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)
{
...//省略不關(guān)心代碼
size = cls->instanceSize(extraBytes);
...//省略不關(guān)心代碼
obj = (id)calloc(1, size);
...//省略不關(guān)心代碼
//核心入口,加星標(biāo)記????????????????
obj->initInstanceIsa(cls, hasCxxDtor);
//????????????????
...//省略不關(guān)心代碼
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) {//傳入?yún)?shù)是true帜矾,不會(huì)走這個(gè)分支
......
} 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;
}
}
核心研究的地方就是對(duì)isa中的位進(jìn)行賦值,注意類(lèi)的相關(guān)信息賦值在newisa.shiftcls位中屑柔。
擴(kuò)展 - 獲取isa中的類(lèi)信息
runtime的api中提供了獲取對(duì)象類(lèi)方法object_getClass
屡萤,接下來(lái)研究一下源碼的實(shí)現(xiàn):
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
???
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA();
......
//省略無(wú)關(guān)代碼
}
???
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
}
- 最后調(diào)用的是
isa.bits & ISA_MASK
,這一步獲取的就是isa中shiftcls的值(ISA_MASK上面有講過(guò)掸宛,是個(gè)宏死陆,在不同操作系統(tǒng)定義的值也不同)。 - 得到的值進(jìn)行Class類(lèi)型強(qiáng)轉(zhuǎn)唧瘾,這也是為什么我們?cè)诳聪嚓P(guān)源碼中措译,isa都是定義成Class類(lèi)型原因。