iOS 底层 - runtime之isa详解-->取值、设值、位
本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !
要想学习Runtime,首先要了解它底层的一些数据结构,比如isa指针
在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址;
从arm64架构开始,对isa进行了优化,变成了一个联合体(union
)结构,还使用位域来存储更多的信息;至此isa需要进行一次位运算(&ISA_MASK
)才能计算出真实地址;
源码 objc4-779.1 --> objc-private.h
位域宏定义(真机环境arm64)
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; 拿二进制的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)
isa详解-位域@2x.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; // ISA_BITFIELD 在 isa.h文件中
};
#endif
};
struct objc_object {
private:
isa_t isa; //arm64是 Class isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
// changeIsa() should be used to change the isa of existing objects.
// If this is a new object, use initIsa() for performance.
Class changeIsa(Class newCls);
bool hasNonpointerIsa();
bool isTaggedPointer();
bool isBasicTaggedPointer();
bool isExtTaggedPointer();
bool isClass();
// object may have associated objects?
bool hasAssociatedObjects();
void setHasAssociatedObjects();
// object may be weakly referenced?
bool isWeaklyReferenced();
void setWeaklyReferenced_nolock();
// object may have -.cxx_destruct implementation?
bool hasCxxDtor();
// Optimized calls to retain/release methods
id retain();
void release();
id autorelease();
// Implementations of retain/release methods
id rootRetain();
bool rootRelease();
id rootAutorelease();
bool rootTryRetain();
bool rootReleaseShouldDealloc();
uintptr_t rootRetainCount();
// Implementation of dealloc methods
bool rootIsDeallocating();
void clearDeallocating();
void rootDealloc();
private:
void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);
// Slow paths for inline control
id rootAutorelease2();
uintptr_t overrelease_error();
#if SUPPORT_NONPOINTER_ISA
// Unified retain count manipulation for nonpointer isa
id rootRetain(bool tryRetain, bool handleOverflow);
bool rootRelease(bool performDealloc, bool handleUnderflow);
id rootRetain_overflow(bool tryRetain);
uintptr_t rootRelease_underflow(bool performDealloc);
void clearDeallocating_slow();
// Side table retain count overflow for nonpointer isa
void sidetable_lock();
void sidetable_unlock();
void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
bool sidetable_addExtraRC_nolock(size_t delta_rc);
size_t sidetable_subExtraRC_nolock(size_t delta_rc);
size_t sidetable_getExtraRC_nolock();
#endif
// Side-table-only retain count
bool sidetable_isDeallocating();
void sidetable_clearDeallocating();
bool sidetable_isWeaklyReferenced();
void sidetable_setWeaklyReferenced_nolock();
id sidetable_retain();
id sidetable_retain_slow(SideTable& table);
uintptr_t sidetable_release(bool performDealloc = true);
uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);
bool sidetable_tryRetain();
uintptr_t sidetable_retainCount();
#if DEBUG
bool sidetable_present();
#endif
};
位运算之 &
&的作用:可以用来取出特定的位
0011
&0010
--------
0010
取出倒数第二位的值 1 ;方法:找到对应掩码按位与(&) ;
位运算之 |
0010 1000
| 0000 0010 //掩码
--------
0010 1010
取出倒数第二位的值 1 ;方法:找到对应掩码按位或(|) ;
位运算之 ~
按位取反
~0000 0010
位运算之掩码
掩码:一般用来按位与(&)运算的,通常用某某某Mask来命名
//#define XYHTallMask 1
//#define XYHRichMask 2
//#define XYHHandsomeMask 4
//#define XYHTallMask 0b00000001
//#define XYHRichMask 0b00000010
//#define XYHHandsomeMask 0b00000100
// <<
表示位移
define XYHTallMask (1<<0)
define XYHRichMask (1<<1)
define XYHHandsomeMask (1<<2)
三组宏一样,不过最后一组表达更简洁
代码示例:
@interface XYHPerson : NSObject
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
@end
第一种写法
#define XYHTallMask (1<<0)
#define XYHRichMask (1<<1)
#define XYHHandsomeMask (1<<2)
@interface XYHPerson()
{
char _tallRichHansome;
}
@end
@implementation XYHPerson
// 0010 1010
//&1111 1101
//----------
// 0010 1000
- (instancetype)init
{
if (self = [super init]) {
_tallRichHansome = 0b00000100;
}
return self;
}
- (void)setTall:(BOOL)tall
{
if (tall) {
_tallRichHansome |= XYHTallMask;
} else {
_tallRichHansome &= ~XYHTallMask;
}
}
- (BOOL)isTall
{
return !!(_tallRichHansome & XYHTallMask);
}
- (void)setRich:(BOOL)rich
{
if (rich) {
_tallRichHansome |= XYHRichMask;
} else {
_tallRichHansome &= ~XYHRichMask;
}
}
- (BOOL)isRich
{
return !!(_tallRichHansome & XYHRichMask);
}
- (void)setHandsome:(BOOL)handsome
{
if (handsome) {
_tallRichHansome |= XYHHandsomeMask;
} else {
_tallRichHansome &= ~XYHHandsomeMask;
}
}
- (BOOL)isHandsome
{
// ‘!!’ 双取反
return !!(_tallRichHansome & XYHHandsomeMask);
}
@end
第二种写法
@interface XYHPerson()
{
// 位域
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
} _tallRichHandsome;
}
@end
@implementation XYHPerson
- (void)setTall:(BOOL)tall
{
_tallRichHandsome.tall = tall;
}
- (BOOL)isTall
{
return !!_tallRichHandsome.tall;
}
- (void)setRich:(BOOL)rich
{
_tallRichHandsome.rich = rich;
}
- (BOOL)isRich
{
return !!_tallRichHandsome.rich;
}
- (void)setHandsome:(BOOL)handsome
{
_tallRichHandsome.handsome = handsome;
}
- (BOOL)isHandsome
{
return !!_tallRichHandsome.handsome;
}
@end
第三种写法 (苹果官方使用,如isa)
#define XYHTallMask (1<<0)
#define XYHRichMask (1<<1)
#define XYHHandsomeMask (1<<2)
#define XYHThinMask (1<<3)
@interface XYHPerson()
{
union {
char bits;
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
char thin : 1;
};
/**
//作用是为了增加代码可读性,没有本质作用;值是存储在bits属性中的
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
char thin : 1;
};
*/
} _tallRichHandsome;
}
@end
@implementation XYHPerson
- (void)setTall:(BOOL)tall
{
if (tall) {
_tallRichHandsome.bits |= XYHTallMask;
} else {
_tallRichHandsome.bits &= ~XYHTallMask;
}
}
- (BOOL)isTall
{
return !!(_tallRichHandsome.bits & XYHTallMask);
}
- (void)setRich:(BOOL)rich
{
if (rich) {
_tallRichHandsome.bits |= XYHRichMask;
} else {
_tallRichHandsome.bits &= ~XYHRichMask;
}
}
- (BOOL)isRich
{
return !!(_tallRichHandsome.bits & XYHRichMask);
}
- (void)setHandsome:(BOOL)handsome
{
if (handsome) {
_tallRichHandsome.bits |= XYHHandsomeMask;
} else {
_tallRichHandsome.bits &= ~XYHHandsomeMask;
}
}
- (BOOL)isHandsome
{
return !!(_tallRichHandsome.bits & XYHHandsomeMask);
}
- (void)setThin:(BOOL)thin
{
if (thin) {
_tallRichHandsome.bits |= XYHThinMask;
} else {
_tallRichHandsome.bits &= ~XYHThinMask;
}
}
- (BOOL)isThin
{
return !!(_tallRichHandsome.bits & XYHThinMask);
}
@end
小常识: 0xff 的有符号数就是-1,无符号数就是255
union : 共用体,内部所有成员共用同一块内存;存储时要求不同的成员用不同的位存储,否则可能会覆盖其他成员的值,如0x0000 0000 从最后一位开始占多少位是需要定好的。
为什么要
&ISA_MASK
才能获取到地址值 ?
uintptr_t shiftcls 占33位,这里就是用来存放地址值的;ISA_MASK就是shiftcls的掩码,与(&)上这个掩码才能够拿到真正的地址。
注意:类对象、元类对象的内存地址值(二进制)最后3位一定是0;
证明:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%p", [ViewController class]);
NSLog(@"%p", object_getClass([ViewController class]));
}
打印结果:
[16416:7767940] 0x10a2acd88
[16416:7767940] 0x10a2acdb0
一个16进制位代表4个二进制位
看到输出的16进制地址末位要么是8 要么是 0,8的二进制是0b1000,0的二进制就是0b0000
位运算的常用方法:
//typedef enum {
// XYHOptionsOne = 1, // 0b0001
// XYHOptionsTwo = 2, // 0b0010
// XYHOptionsThree = 4, // 0b0100
// XYHOptionsFour = 8 // 0b1000
//} XYHOptions;
typedef enum {
// XYHOptionsNone = 0, // 0b0000
XYHOptionsOne = 1<<0, // 0b0001
XYHOptionsTwo = 1<<1, // 0b0010
XYHOptionsThree = 1<<2, // 0b0100
XYHOptionsFour = 1<<3 // 0b1000
} XYHOptions;
@interface ViewController ()
@end
@implementation ViewController
/*
0b0001
0b0010
0b1000
------
0b1011
&0b0100
-------
0b0000
*/
- (void)setOptions:(XYHOptions)options
{
if (options & XYHOptionsOne) {
NSLog(@"包含了XYHOptionsOne");
}
if (options & XYHOptionsTwo) {
NSLog(@"包含了XYHOptionsTwo");
}
if (options & XYHOptionsThree) {
NSLog(@"包含了XYHOptionsThree");
}
if (options & XYHOptionsFour) {
NSLog(@"包含了XYHOptionsFour");
}
}
使用
- (void)viewDidLoad {
[super viewDidLoad];
[self setOptions: XYHOptionsOne | XYHOptionsFour];
}
总结:
isa作用: 用来查找类对象、元类对象(实例对象的isa指向类对象,类对象的isa指向元类对象);
在arm64之前ISA就是普通的指针,里面存储着类对象或者元类对象的地址,arm64之后isa采用共用体的数据结构定义,在64位里面存储了很多东西包括
nonpointer
是否优化过
has_assoc
是否设置过关联对象
weakly_referenced
是否被弱引用指向过
has_cxx_dtor
是否有析构函数, 是否调用过C++的函数 销毁成员变量
shiftcls
类对象内存地址或者元类对象内存地址信息等
其中shiftcls
占用了33位,用来专门存储地址值
extra_rc
rc是Reference Counting
的简写,代表引用计数
; 里面存储的值是对象的引用计数减一
has_sidetable_rc
引用计数是否过大无法存储在ISA中,如果是,那么引用计数会存储在一个叫SideTable
的类的refcnts
属性中
疑问 :
extra_rc
中存储的值为什么是引用计数减一,直接存引用计数不香吗 ?
不香
- 一个对象被初始化出来引用计数默认是1,引用计数的值是存储在对象本身的ISA中的;减去这个1是不是直接就得到了持有该对象的数量 ?直接把减一后的值存进去更合理
- 减一后数值就会相对变小,而且引用计数是在不断变化的,有限内存下存储越小的值,就意味着越节省空间
- 当extra_rc为0时就表示,这个对象将要被释放了。0这个值可能会在其他地方被当做标记来用(个人猜测);
注意这里是否优化过、设置过表达的意思是:只要有过就算,而不是现在有没有;只要有过就会有值。