1. 联合体、位域
1.1 结构体
先看下面的代码:
struct SSLCar {
BOOL front;
BOOL back;
BOOL left;
BOOL right;
}sslCar;
NSLog(@"sslCar:%lu",sizeof(sslCar));
打印结果:sslCar:4
我们看到一个SSLCar
结构体是4
个字节,也就是32
位(0000 0000 0000 0000 0000 0000 0000 0000
),而SSLCar
结构体中4
个BOOL
值只需要4
位(1111
)就可以存储,这造成了非常大的空间浪费。
1.2 位域
通过位域指定每个成员变量占1
位:
struct SSLCar2 {
BOOL front: 1;
BOOL back : 1;
BOOL left : 1;
BOOL right: 1;
}sslCar2;
NSLog(@"sslCar2:%lu",sizeof(sslCar2));
打印结果:sslCar2:1
此时内存空间已经得到了不错的优化。
1.3 联合体
创建联合体:
union SSLPerson {
char *name;
int age;
};
为联合体赋值,并打印:
通过打印结果,发现联合体只有后赋值的变量才有值。
1.4 结构体和联合体的区别
结构体(struct
)中所有变量是“共存”的——优点
是“有容乃大”,全面;缺点
是struct
内存空间的分配是粗放的,不管用不用,全分配。
联合体(union
)中是各变量是“互斥”的——缺点
就是不够“包容”; 但优点
是内存使用更为精细灵活,也节省了内存空间。
2. 对象本质
在main.m
中添加如下代码:
@interface SSLPerson : NSObject
@property (nonatomic, copy) NSString *sslName;
@end
@implementation SSLPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
通过clang
命令编译main.m
(更多clang
信息点这里):
clang -rewrite-objc main.m -o main.cpp
得到main.cpp
文件,在main.cpp
中我们可以看到有关对象的信息:
typedef struct objc_class *Class; // 7658行
struct objc_object { // 7661 行
Class _Nonnull isa __attribute__((deprecated));
};
typedef struct objc_object *id; // 7666行
typedef struct objc_object SSLPerson; // 111788行
struct SSLPerson_IMPL { // 111793行
struct NSObject_IMPL NSObject_IVARS;
NSString *_sslName;
};
由此我们可以得出,对象是一个objc_object
类型的结构体,内部有一个Class
类型的isa
指针。
打开源码,查看objc_object
:
struct objc_object {
private:
isa_t isa;
...
}
3. isa 结构分析
我们在 OC 对象原理探索(一) 中,提到过obj->initIsa(cls)
,它的作用是将指针和类进行关联,下面我们用源码进行详细的分析。
initIsa
函数:
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
ASSERT(!isTaggedPointer());
isa_t newisa(0); // isa初始化
if (!nonpointer) {
newisa.setClass(cls, this);//如果是纯指针 isa被直接cls赋值
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#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;
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
isa = newisa;
}
点击查看isa_t
;
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_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)
看一下isa
的内存分布图:
-
nonpointer
:表示是否对isa
指针开启指针优化。0
:纯isa
指针,1
:不止是类对象地址,isa
中包含了类信息、对象的引用计数等。 -
has_assoc
:关联对象标志位,0
没有,1
存在。 -
has_cxx_dtor
:该对象是否有C++
或者Objc
的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象。 -
shiftcls
:存储类指针的值。开启指针优化的情况下,在arm64
架构中有33
位用来存储类指针。 -
magic
:用于调试器判断当前对象是真的对象还是没有初始化的空间。 -
weakly_referenced
:标志对象是否被指向或者曾经指向一个ARC
的弱变量,没有弱引用的对象可以更快释放。 -
deallocating
:标志对象是否正在释放内存。 -
extra_rc
:表示该对象的引用计数值,如果不够存储,has_sidetable_rc
的值就会变为 1; -
has_sidetable_rc
:如果为1
,代表引用计数过大无法存储在isa
中,那么超出的引用计数会存储在SideTable
的RefCountMap
中。所以,如果isa
是nonpointer
,则对象的引用计数存储在它的isa_t
的extra_rc
中以及SideTable
的RefCountMap
中。
总结:
-
isa
分为nonpointer
类型和非nonpointer
类型。非nonpointer
类型是一个纯指针,nonpointer
类型还包含了引用计数、关联对象等信息。 -
isa
采用联合体
+位域
的方式存储信息。因为在iOS
中万物皆对象,对象中都用到了isa
,这样可以大大的节省内存空间。
4. isa 关联类
先创建一个SSLperson
类,初始化[SSLPerson alloc]
,在initIsa
函数中断点调试:
如上图,创建了一个联合体newisa
,里边的变量目前都是0
,没有赋值。
继续向下运行:
如上图,newisa.bits = ISA_MAGIC_VALUE
,ISA_MAGIC_VALUE
是一个宏
=0x001d800000000001
。被赋值的变量有bits
=8303511812964353
,cls
=0x001d800000000001
,nonpointer
=1
,magic
=59
。转化成二进制表示:
-
0x001d800000000001
=8303511812964353
,它们的二进制表示相同。 -
nonpointer
的二进制表示是1
,magic
的二进制表示是111011
,它们包含在bits
的二进制表示中。
断点进入setClass
:
- 这里,
isa
和SSLPerson
类进行了关联。 - 目前是
nonpointer
,所以并不会给cls
赋值。 -
SSLPerson
的类地址>>3
进行10
进制转换,然后赋值给shiftcls
。- 为什么要右移
3位
呢,因为shiftcls
在isa
中是从第四位开始存储的,这样isa & ISA_MASK
才能得到准确的类地址
。
- 为什么要右移
继续向下运行:
-
shiftcls
,extra_rc
也被成功赋值。 - 这里我一直有个疑惑
cls
明明没有被赋值,为什么却是SSLPerson
呢,而且联合体
也应该会把cls
覆盖掉才对,所以我后期有做了一个调试:-
- 打印了
cls
的值,它确实是整个isa
的值,至于为什么显示SSLPerson
,应该是系统做了处理。
-
到此,isa
和类
关联的探索基本完成,以后如有更深入理解会持续更新。