我们要进行isa的分析首先掌握的知识
1、联合体(共用体)
1.1、使用位运算 进行 存取 数据
1.2、位域 简介
1.3、结构体位域优化代码
1.4、联合体优化代码
2、clang
2.1 clang 简介
2.2 简单用法
2.3 clang 源码 查看线索
3、isa_t联合体
1、联合体union
联合体union
的定义方式与结构体一样,但是二者有根本区别。
什么是联合体(union)呢?联合体是一种特殊的类,也是一种构造类型的数据结构。完全就是共用一个内存首地址,并且各种变量名都可以同时使用,操作也是共同生效。所以也叫共用体。并且联合体(union)中是各变量是“互斥”的,但是内存使用更为精细灵活,也节省了内存空间。
在结构体��中各成员有各自的内存空间,一个结构变量
的总长度是各成员长度之和
。而在“联合
”中,各成员共享一段内存空间
,一个联合变量的长度等于各成员中最长的长度
。
1.1、使用位运算 进行 存取 数据
废话不多 我们定义一个LHCar
类 这个类 有up
down
left
right
四个代表方向的BOOL类型
的属性
@interface LHCar : NSObject
@property (nonatomic, assign) BOOL up;
@property (nonatomic, assign) BOOL down;
@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL right;
@end
进行打印
2020-09-09 23:41:04.086760+0800 isa结构分析[4982:341540] 16
输出为16字节 其中包括 isa
和4个BOOL类型的属性 共 8
+1
+1
+1
+1
= 12
内存对齐 为 16 字节
我们知道 BOOL
值 只有两种情况 0
或1
, 一个字节
有8个二进制位
,并且二进制 只有 0
或1
想到这里 那么我们完全可以使用一个二进制位来表示 一个BOOL
值 。也就是这四个BOOL
属性 我们可以用4个二进制位
来表示如下图 ,大大节省了内存空间
按照我们的臆想来实现代码,首先分别声明 up down left right 掩码 mask ,来方便我们位运算取值赋值
#define LHDirectionUpMask 0b00001000
#define LHDirectionDownMask 0b00000100
#define LHDirectionLeftMask 0b00000010
#define LHDirectionRightMask 0b00000001
定义 char 类型 的成员变量
@interface LHCar(){
char _upDownLeftRight;
}
@end
初始化
- (instancetype)init
{
self = [super init];
if (self) {
_upDownLeftRight = 0b00000001;
}
return self;
}
自定义setter
-(void)setUp:(BOOL)up
{
if (up) {
/// 如果需要将值置为1,将源码和掩码进行按位或运算
_upDownLeftRight |= LHDirectionUpMask;
}else{
/// 如果需要将值置为0 // 将源码和按位取反后的掩码进行按位与运算
_upDownLeftRight &= ~LHDirectionUpMask;
}
}
-(void)setDown:(BOOL)down
{
if (down) {
_upDownLeftRight |= LHDirectionDownMask;
}else{
_upDownLeftRight &= ~ LHDirectionDownMask;
}
}
- (void)setLeft:(BOOL)left
{
if (left) {
_upDownLeftRight |= LHDirectionLeftMask;
} else {
_upDownLeftRight &= ~LHDirectionLeftMask;
}
}
- (void)setRight:(BOOL)right
{
if (right) {
_upDownLeftRight |= LHDirectionRightMask;
} else {
_upDownLeftRight &= ~LHDirectionRightMask;
}
}
getter
-(BOOL)isUp
{
return !!(_upDownLeftRight & LHDirectionUpMask);
}
-(BOOL)isDown
{
return !!(_upDownLeftRight & LHDirectionDownMask);
}
-(BOOL)isLeft
{
return !!(_upDownLeftRight & LHDirectionLeftMask);
}
-(BOOL)isRight
{
return !!(_upDownLeftRight & LHDirectionRightMask);
}
按照图上示意 及我们的想法 这时候调用getter方法打印 初始化值
LHCar * car = [[LHCar alloc]init];
NSLog(@"up:%d down:%d left:%d right:%d",car.isUp,car.isDown,car.isLeft,car.isRight);
我们屏蔽之前定义的属性 调用自定义的setter方法进行赋值
LHCar * car = [[LHCar alloc]init];
[car setUp:YES];
[car setDown:NO];
[car setLeft:YES];
[car setRight:NO];
NSLog(@"up:%d down:%d left:%d right:%d",car.isUp,car.isDown,car.isLeft,car.isRight);
发现我们用一个字节的里的4个二进制位 就完成了 之前的 占有4个字节的 4个属性的读写
1.2、位域 简介
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有0
和1
两种状态,用1位
二进制位
即可。为了节省存储空间并使处理简便,C语言又提供了一种数据结构,称为"位域"或"位段"
所谓"位域"是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
位域的定义和位域变量的说明
struct 位域结构名
{
位域列表
};
例如:
struct bs
{
int a:8;
int b:2;
int c:6;
}data;
说明 data 为 bs 变量,共占两个字节,其中位域a占8位,位域b占2位,位域 c 占6位。
位域定义说明
1、 一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域
2、由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。(如果最大长度大于计算机的整数字长,一些编译器可能会允许域的内存重叠,另外一些编译器可能会把大于一个域的部分存储在下一个字中。)
3、位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的
1.3、结构体位域优化代码
我们了解了位域的基本信息那么我们可以用结构体位域来优化代码
#import "LHCar.h"
//#define LHDirectionUpMask 0b00001000
//#define LHDirectionDownMask 0b00000100
//#define LHDirectionLeftMask 0b00000010
//#define LHDirectionRightMask 0b00000001
@interface LHCar(){
//
// char _upDownLeftRight;
struct{
char up : 1;
char down : 1;
char left : 1;
char right: 1;
}_upDownLeftRight;
}
@end
@implementation LHCar
- (instancetype)init
{
self = [super init];
if (self) {
// _upDownLeftRight = 0b00000001;
}
return self;
}
-(void)setUp:(BOOL)up
{
// if (up) {
// /// 如果需要将值置为1,将源码和掩码进行按位或运算
// _upDownLeftRight |= LHDirectionUpMask;
// }else{
// /// 如果需要将值置为0 // 将源码和按位取反后的掩码进行按位与运算
// _upDownLeftRight &= ~LHDirectionUpMask;
// }
_upDownLeftRight.up = up;
}
-(void)setDown:(BOOL)down
{
// if (down) {
// _upDownLeftRight |= LHDirectionDownMask;
// }else{
// _upDownLeftRight &= ~ LHDirectionDownMask;
// }
_upDownLeftRight.down = down;
}
- (void)setLeft:(BOOL)left
{
// if (left) {
// _upDownLeftRight |= LHDirectionLeftMask;
// } else {
// _upDownLeftRight &= ~LHDirectionLeftMask;
// }
_upDownLeftRight.left = left;
}
- (void)setRight:(BOOL)right
{
// if (right) {
// _upDownLeftRight |= LHDirectionRightMask;
// } else {
// _upDownLeftRight &= ~LHDirectionRightMask;
// }
_upDownLeftRight.right = right;
}
-(BOOL)isUp
{
// return !!(_upDownLeftRight & LHDirectionUpMask);
return !!_upDownLeftRight.up;
}
-(BOOL)isDown
{
// return !!(_upDownLeftRight & LHDirectionDownMask);
return !!_upDownLeftRight.down;
}
-(BOOL)isLeft
{
// return !!(_upDownLeftRight & LHDirectionLeftMask);
return !!_upDownLeftRight.left;
}
-(BOOL)isRight
{
// return !!(_upDownLeftRight & LHDirectionRightMask);
return !!_upDownLeftRight.right;
}
@end
这样是可以正常存取 但是去掉了 掩码mask 和初始化代码 导致可读性非常差 这时联合体出来了
1.4、联合体优化代码
#import "LHCar.h"
#define LHDirectionUpMask 0b00001000
#define LHDirectionDownMask 0b00000100
#define LHDirectionLeftMask 0b00000010
#define LHDirectionRightMask 0b00000001
@interface LHCar(){
union{
char bits;
struct{
char up : 1;
char down : 1;
char left : 1;
char right: 1;
};
}_upDownLeftRight;
}
@end
@implementation LHCar
- (instancetype)init
{
self = [super init];
if (self) {
_upDownLeftRight.bits = 0b00000001;
}
return self;
}
-(void)setUp:(BOOL)up
{
if (up) {
_upDownLeftRight.bits |= LHDirectionUpMask;
}else{
_upDownLeftRight.bits &= ~LHDirectionUpMask;
}
}
-(void)setDown:(BOOL)down
{
if (down) {
_upDownLeftRight.bits |= LHDirectionDownMask;
}else{
_upDownLeftRight.bits &= ~ LHDirectionDownMask;
}
}
- (void)setLeft:(BOOL)left
{
if (left) {
_upDownLeftRight.bits |= LHDirectionLeftMask;
} else {
_upDownLeftRight.bits &= ~LHDirectionLeftMask;
}
}
- (void)setRight:(BOOL)right
{
if (right) {
_upDownLeftRight.bits |= LHDirectionRightMask;
} else {
_upDownLeftRight.bits &= ~LHDirectionRightMask;
}
}
-(BOOL)isUp
{
return !!(_upDownLeftRight.bits & LHDirectionUpMask);
}
-(BOOL)isDown
{
return !!(_upDownLeftRight.bits & LHDirectionDownMask);
}
-(BOOL)isLeft
{
return !!(_upDownLeftRight.bits & LHDirectionLeftMask);
}
-(BOOL)isRight
{
return !!(_upDownLeftRight.bits & LHDirectionRightMask);
}
@end
测试发现依旧可以 完成 存取
其中 _upDownLeftRight
联合体只 占用了一个字节 因为结构体中
up
、down
、left
、right
、都只占用一位二进制空间,这就是 4 个二进制空间 而 char
类型 bits
也只占用了一个字节 他们都在联合体中 因此 共用一个字节的内存
总结: 通过掩码进行位运算来增加 效率 通过联合体结构 可以 节省内存空间
2、clang用法
2.1、简介
Clang是一个C语言、C++、Objective-C、C++语言的轻量级编译器。源代码发布于BSD协议下。也是Xcode 第一的编译器
2.2 简单使用
clang -rewrite-objc main.m -o main.cpp 把目标文件编译成c++文件 UIKit报错问题
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m
xcode
安装的时候顺带安装了xcrun
命令,xcrun
命令在clang
的基础上进行了 一些封装,要更好用一些
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模拟器)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp (手机)
clang就说这么多 具体深究请自行查阅 注意:查看模拟器版本 以及 源文件
准备一段代码 clang 其本质
#import
@interface LHPerson : NSObject
{
NSString * nickName;
}
@property (nonatomic, copy) NSString *name;
@end
@implementation LHPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
clang过后 我们根据LHPerson
为深入线索,进行搜索
struct LHPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *__strong nickName;
NSString *__strong _name;
};
// @property (nonatomic, copy) NSString *name;
/* @end */
// @implementation LHPerson
static NSString * _I_LHPerson_name(LHPerson * self, SEL _cmd) { return (*(NSString *__strong *)((char *)self + OBJC_IVAR_$_LHPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LHPerson_setName_(LHPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LHPerson, _name), (id)name, 0, 1); }
// @end
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 0;
}
NSObject_IMPL
又是什么?
struct NSObject_IMPL {
__unsafe_unretained Class isa;
};
通过上面总结
1、一个类的声明或创建 底层实现 就是 一个结构体
2、Class
其实就是一个指针 指向了 objc_class
类型的结构体
3、LHPerson_IMPL
结构体中有3个成员变量 isa
和nickName
_name
4、属性自动生成 getter
setter
方法 并且帮我们转成 _name
5、nickName
并没有生成getter
setter
6、self
和SEL _cmd
为默认参数
3、isa_t联合体
union 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
};
由上面的介绍联合体概念可以知道,cls和bits之间是互斥的,即有cls就没有bits,有bits就没有cls。
initInstanceIsa
我们知道alloc的流程 最重要的 是 三部曲
1、 size = cls->instanceSize(extraBytes); ///计算 需要多少内存空间
2、 obj = (id)calloc(1, size); // alloc 开辟内存的地方
3、 obj->initInstanceIsa(cls, hasCxxDtor);///关联对应的类
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) {
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;
}
}
这就很好地解释了为什么上面的源码在初始化isa的时候会用nonpointer来区分开。所以isa的大小占8个字节,64位。其中这64位中分别存储了什么呢?通过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)
# else
# error unknown architecture for packed isa
# endif
这两种是分别在arm64和x86系统架构下的,但是都是64位的,本文的说明是在x86下介绍的。
nonpointer
: 表示是否对isa指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa中包含了类信息,对象的引用计数等
has_assoc
:关联对象标志位,0没有,1存在
has_cxx_dtor
:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
shiftcls
: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。x86_64 下 44 位
magic
:用于调试器判断当前对象是真的对象还是没有初始化的空间
weakly_referenced
:志对象是否被指向或者曾经指向一个 ARC 的弱变量,
没有弱引用的对象可以更快释放
deallocating
:标志对象是否正在释放内存
has_sidetable_rc
:当对象引用技术大于 10 时,则需要借用该变量存储进位
extra_rc
:当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc。
lldb指令验证
x/4gx
person : 打印出地址
p/x
LHPerson.class : 打印类的内存地址
通过源码 搜索 object_getClass
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA();
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
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最终是(Class)(isa.bits & ISA_MASK)。
p/x
:0x00007ffffffffff8ULL & 0x001d80010000233d
最终发现
继续p/x 做位移运算
我的天 isa 指针中 真的包含 类的信息