欢迎阅读iOS探索系列(按序阅读食用效果更加)html
在介绍isa以前,先介绍一个位域和结构体的知识点bash
有些信息在存储时,并不须要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1两种状态,用1位二进位便可。为了节省存储空间并使处理简便,C语言
提供了一种数据结构,称为位域
或位段
数据结构
所谓位域
就是把一个字节中的二进位划分为几个不一样的区域,并说明每一个区域的位数。每一个域有一个域名,容许在程序中按域名进行操做——这样就能够把几个不一样的对象用一个字节的二进制位域来表示架构
位域的使用与结构体相仿,它自己也是结构体的一种ide
// 结构体
struct FXStruct {
// (类型说明符 元素);
char a;
int b;
} FXStr;
// 位域
struct FXBitArea {
// (类型说明符 位域名: 位域长度);
char a: 1;
int b: 3;
} FXBit;
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Struct:%lu——BitArea:%lu", sizeof(FXStr), sizeof(FXBit));
}
return 0;
}
复制代码
输出Struct:8——BitArea:4
函数
对位域
有兴趣的能够看下struct中位域的定义post
当多个数据须要共享内存或者多个数据每次只取其一时,能够利用联合体(union)
性能
结构体每一个成员依次存储,联合体中全部成员的偏移地址都是0,也就是全部成员是叠在一块儿的,因此在联合体中在某一时刻,只有一个成员有效——结构体内存大小取决于全部元素,联合体取决于最大那个
优化
在以前的iOS探索 alloc流程
中轻描淡写的提了一句obj->initInstanceIsa(cls, hasCxxDtor)
——只知道内部调用initIsa(cls, true, hasCxxDtor)
初始化isa,并无对isa进行细说ui
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = 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;
}
}
复制代码
①建立对象跟着断点不难发现nonpointer
为true
②if-else跳转了else流程——SUPPORT_INDEXED_ISA
表示isa_t
中存放的 Class
信息是Class 的地址
,仍是一个索引
(根据该索引可在类信息表中查找该类结构地址)
③isa_t newisa(0)
至关于初始化isa这个东西,new.
至关于给isa赋值属性
1.SUPPORT_INDEXED_ISA适用于WatchOS 2.isa做为联合体具备互斥性,而cls、bits是isa的元素,因此当!nonpointer=true时对cls进行赋值操做,为false是对bits进行赋值操做(反正都是一家人,共用一块内存地址)
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
是个联合体,拥有两个初始化方法
②isa
内部有个Class cls
——Class
和isa
有绑定关系——isa
指向类的结构
③isa采用联合体+位域
的形式来优化内存(ISA_BITFIELD
是个位域宏定义)
先初始化bits
决定联合体的长度,再对联合体内的位域ISA_BITFIELD
进行赋值
联合体全部属性共用内存,内存长度等于其最长成员的长度,使代码存储数据高效率的同时,有较强的可读性;而位域能够容纳更多类型
#if SUPPORT_PACKED_ISA
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
// nonpointer must be the LSB (fixme or get rid of it)
// shiftcls must occupy the same bits that a real class pointer would
// bits + RC_ONE is equivalent to extra_rc + 1
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
// future expansion:
// uintptr_t fast_rr : 1; // no r/r overrides
// uintptr_t lock : 2; // lock for atomic property, @synch
// uintptr_t extraBytes : 1; // allocated with extra bytes
# 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)
# else
# error unknown architecture for packed isa
# endif
// SUPPORT_PACKED_ISA
#endif
复制代码
不一样架构下isa所占内存均为8字节
——64位
,但内部分布有所不一样,arm64架构
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
除
shiftcls
以外了解便可
@interface FXPerson : NSObject
@end
@implementation FXPerson
@end int main(int argc, const char * argv[]) {
@autoreleasepool {
FXPerson *p = [[FXPerson alloc] init];
NSLog(@"%@",p);
}
return 0;
}
复制代码
从父类NSObject
结构中能够有个isa属性
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
复制代码
可是内存中属性的位置是会由于优化发生改变的,下面就来证明下内存中第一位必定是isa
①打印第一位内存
②二进制打印第一位内存的内存值
③由于模拟器是x86架构
的,由isa
位域结构可知,shiftcls
前面有3位——右移3位——抹去isa前3位
④shiftcls
后面有17位——左移17位——抹去isa后17位
⑤由于末尾的0都是咱们添加的——右移17位——获得shiftcls
⑥根据newisa.shiftcls = (uintptr_t)cls >> 3;
——shiftcls
等于class
地址右移3位
比对两组shiftcls
二进制,发现它们二进制同样(前面不同是由于没抹掉)
若是你听得云里雾里的话,请看第二种方法
①在<objc/runtime.h>
下使用object_getClass
方法
#import <objc/runtime.h>
@interface FXPerson : NSObject
@end
@implementation FXPerson
@end int main(int argc, const char * argv[]) {
@autoreleasepool {
FXPerson *p = [[FXPerson alloc] init];
object_getClass(p);
}
return 0;
}
复制代码
②跟进object_getClass
方法
/*********************************************************************** * object_getClass. * Locking: None. If you add locking, tell gdb (rdar://7516456). **********************************************************************/
Class object_getClass(id obj) {
if (obj) return obj->getIsa();
else return Nil;
}
复制代码
③跟进getIsa()
#if SUPPORT_TAGGED_POINTERS
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];
}
}
复制代码
④通常isTaggedPointer
都为false,跟进ISA()
#if SUPPORT_NONPOINTER_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
}
复制代码
⑤已知SUPPORT_INDEXED_ISA
适用于WatchOS,那么走return (Class)(isa.bits & ISA_MASK);
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
复制代码
⑥检验
打印出isa & mask
的值,与class
相比较(mask
取x86架构)
从上述两种方法都能得出
实例对象首地址必定 是isa
@interface FXPerson : NSObject
@end
@implementation FXPerson
@end int main(int argc, const char * argv[]) {
@autoreleasepool {
Class class1 = [FXPerson class];
Class class2 = [FXPerson alloc].class;
Class class3 = object_getClass([FXPerson alloc]);
Class class4 = [FXPerson alloc].class;
NSLog(@"\n%p\n%p\n%p\n%p",class1,class2,class3,class4);
}
return 0;
}
复制代码
0x1000024a0
0x1000024a0
0x1000024a0
0x1000024a0
复制代码
输出证实类在内存中只会存在一个,而实例对象能够存在多个(自行证实)
类
其实和实例对象
同样,都是由上级实例化出来的——类的上级叫作元类
咱们先用p/x
打印类的内存地址,再用x/4gx
打印内存结构取到对应的isa,再用mask
进行偏移获得isa指向的上级
(等同于object_getClass
)依次循环
FXPerson类
取得isa
②由FXPerson类
进行偏移获得FXPerson元类
指针,打印FXPerson元类
取得isa
③由FXPerson元类
进行偏移获得NSObject根元类
指针,打印NSObject根元类
取得isa
④由NSObject根元类
进行偏移获得NSObject根元类
自己指针
⑤打印NSObject根类
取得isa
⑥由NSObject根类
进行偏移获得NSObject根元类
指针
结论:
①实例对象-> 类对象 -> 元类 -> 根元类 -> 根元类(自己)
②NSObject(根类) -> 根元类 -> 根元类(自己)
③指向根元类的isa都是同样的
int main(int argc, const char * argv[]) {
@autoreleasepool {
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类
Class class = object_getClass(object1);
// NSObject元类
Class metaClass = object_getClass(class);
// NSObject根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
}
return 0;
}
复制代码
0x100660ba0 实例对象
0x7fffacd3d140 类
0x7fffacd3d0f0 元类
0x7fffacd3d0f0 根元类
0x7fffacd3d0f0 根根元类
复制代码
由于是NSObject
(根类)它的元类就是根元类
——输出可得根元类指向本身
①运行时伪证法
main
以前
FXPerson类
和
FXPerson元类
已经存在在内存中,不过此时程序已经在运行了,并无什么说服力
②查看MachO文件法
FXPerson
已经存在在MachO文件中
结论:
对象是程序猿根据类实例化的
类是代码编写的,内存中只有一份,是系统建立的
元类是系统编译时,系统编译器建立的,便于方法的编译
复制代码
实例对象-> 类对象 -> 元类 -> 根元类 -> 根元类(自己)
继承关系(实线):NSObject父类为nil,根元类的父类为NSObject
None[-O0]
: 不优化。在这种设置下, 编译器的目标是下降编译消耗,保证调试时输出指望的结果。程序的语句之间是独立的:若是在程序的停在某一行的断点出,咱们能够给任何变量赋新值抑或是将程序计数器指向方法中的任何一个语句,而且能获得一个和源码彻底一致的运行结果。Fast[-O1]
: 大函数所需的编译时间和内存消耗都会稍微增长。在这种设置下,编译器会尝试减少代码文件的大小,减小执行时间,但并不执行须要大量编译时间的优化。在苹果的编译器中,在优化过程当中,严格别名,块重排和块间的调度都会被默认禁止掉。Faster[-O2]
: 编译器执行全部不涉及时间空间交换的全部的支持的优化选项。在这种设置下,编译器不会进行循环展开、函数内联或寄存器重命名。和'Fast[-O1]'项相比,此设置会增长编译时间和生成代码的性能。Fastest[-O3]
: 在开启'Fast[-O1]'项支持的全部优化项的同时,开启函数内联和寄存器重命名选项。这个设置有可能会致使二进制文件变大。Fastest, Smallest[-Os]
: 优化大小。这个设置开启了'Fast[-O1]'项中的全部不增长代码大小的优化选项,并会进一步的执行能够减少代码大小的优化。Fastest, Aggressive Optimizations[-Ofast]
: 这个设置开启了'Fastest[-O3]'中的全部优化选项,同时也开启了可能会打破严格编译标准的积极优化,但并不会影响运行良好的代码。Debug默认不优化,Relese默认Fastest, Smallest[-Os]