本文仅记录笔者的学习过程,只代表笔者个人的理解,如果有错的地方,欢迎各位指正!
OC对象的本质
对象继承与NSObject
翻看Objc源码可以看到
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
可以看到Class
类型的isa
继续跟进去可以看到
typedef struct objc_class *Class;
可以得治Class
其实是个结构体,继续找objc_class
结构体的定义
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
从上面的源码中我们得知对象的本质是个结构体,且结构体中国呢的第一个参数是一个isa
isa是什么
这个isa也就是将要探索的内容。在笔者OC对象之alloc探索-源码探索的三种方式这篇博文中可以看到有这么一个初始化
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)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
//创建isa
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
其中有obj->initInstanceIsa(cls, hasCxxDtor)
跟踪这个创建过程
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
断点定位
image.png
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
- 我们可以发现,其实isa_t是一个联合体位域结构,采用这种结构的原因也是基于内存优化的考虑(即二进制中每一位均可表示不同的信息)。
- 通常来说,isa指针占用的内存大小是8字节,即64位,已经足够存储很多的信息了,这样可以极大的节省内存,以提高性能。
- 在
isa_t
这个粘合体中可以发现内部嵌套了结构体,结构体内部是哥宏定义ISA_BITFIELD
image.png
至此isa的结构清晰了,我们就来讲解一下,他每一个字段代表的含义吧:
-
nonpointer
:表示是否对isa
指针开启指针优化,0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等。 -
has_assoc
:关联对象标志位,0没有,1存在。 -
has_cxx_dtor
:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。 -
shiftcls
:存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位⽤来存储类指针。 -
magic
:⽤于调试器判断当前对象是真的对象还是没有初始化的空间。 -
weakly_referenced
:志对象是否被指向或者曾经指向⼀个 ARC 的弱变量,没有弱引⽤的对象可以更快释放。 -
deallocating
:标志对象是否正在释放内存。 -
has_sidetable_rc
:当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位。 -
extra_rc
:当表示该对象的引⽤计数值,实际上是引⽤计数值减 1,例如,如果对象的引⽤计数为 10,那么 extra_rc 为 9。如果引⽤计数⼤于 10,则需要使⽤到上⾯的 has_sidetable_rc。
image.png
位域与联合体
通过探索可以知道isa_t
是一个联合体位域结构那么什么是粘合体位域?
-
位域: 位域就是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作——这样就可以
把几个不同的对象用一个字节的二进制位域来表示
。位域是C语言一种数据结构
。 - 使用位域的好处是:
- 有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态,用一位二进位即可。这样节省存储空间,而且处理简便。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。
- 可以很方便的利用位域把一个变量给按位分解。比如只需要4个大小在0到3的随即数,就可以只
rand()
一次,然后每个位域取2个二进制位即可,省时省空间。
位域的使用
C语言中,位域的生命和结构体类似不同是:
- 在声明时,
位域成员必须是整形或枚举类型(通常是无符号类型)
- 在
成员名的后面是一个冒号和一个整数,整数规定了成员所占用的位数
。- 位域
不能是静态类型。不能使用&对位域做取地址运算
,因此不存在位域的指针,编译器通常不支持位域的引用(reference)
分别创建位域和结构体对比:
struct Stuct {
// (数据类型 元素);
int a;// 4字节 0 1 2 3
char b;// 1字节 4。不够8字节补齐
}Stuct1;
struct Uni{
// (数据类型 位域名: 位域长度);
int a : 1;
long b : 1;
}Uni1;
来看下效果:
image.png
image.png
联合体
-
联合体
与结构体
的区别: 结构体中每个成员都会独立一块内存,相互不影响!而联合体所有成员公用一块内存!牵一而动全身!
1.1 联合体特征
union PPP {
int a; //4个字节
short b; //2个字节
char c; //1个字节
} p;
NSLog(@"union size: %lu - %lu",sizeof(p), sizeof(union PPP));
结果:
union size: 4 - 4
union PPP {
int a; //4个字节
short b; //2个字节
char c; //1个字节
long d; //8个字节
} p;
结果
union size: 8 - 8
可见:联合体的内存是成员所需的最大内存那个。
1.2 联合体特征
p.a = 2;
NSLog(@"%d---%d---%c---%d",p.a,p.b,p.c,p.d);//2---2---�---2
p.b = 4;
NSLog(@"%d---%d---%c---%d",p.a,p.b,p.c,p.d);//4---4---�---4
p.c = 'c';
NSLog(@"%d---%d---%c---%d",p.a,p.b,p.c,p.d);//99---99---c---99
可见:每次改变联合体中的成员,其他成员会受到影响、即联合体成员之间是相互互斥的
网友评论