iOS 底层 - runtime之isa详解-->取值、设值、位域、联合体

本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !

要想学习Runtime,首先要了解它底层的一些数据结构,比如isa指针

在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址;
从arm64架构开始,对isa进行了优化,变成了一个联合体(union)结构,还使用位域来存储更多的信息;至此isa需要进行一次位运算(&ISA_MASK)才能计算出真实地址;

QQ20200326-104436@2x.png

源码 objc4-818.2 --> isa.h

位域宏定义(真机环境arm64)
# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
       uintptr_t nonpointer        : 1;                                       \
       uintptr_t has_assoc         : 1;                                       \
       uintptr_t weakly_referenced : 1;                                       \
       uintptr_t shiftcls_and_sig  : 52;                                      \
       uintptr_t has_sidetable_rc  : 1;                                       \
       uintptr_t extra_rc          : 8
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     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 unused            : 1;                                       \
       uintptr_t has_sidetable_rc  : 1;                                       \
       uintptr_t extra_rc          : 19
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   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 unused            : 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详解-位域@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属性中
unused 是否可用
magic 用于在调试时分辨对象是否未完成初始化

疑问 :extra_rc中存储的值为什么是引用计数减一,直接存引用计数不香吗 ?

不香

  • 一个对象被初始化出来引用计数默认是1,引用计数的值是存储在对象本身的ISA中的;减去这个1是不是直接就得到了持有该对象的数量 ?直接把减一后的值存进去更合理
  • 减一后数值就会相对变小,而且引用计数是在不断变化的,有限内存下存储越小的值,就意味着越节省空间
  • 当extra_rc为0时就表示,这个对象将要被释放了。0这个值可能会在其他地方被当做标记来用(个人猜测);

注意这里是否优化过、设置过表达的意思是:只要有过就算,而不是现在有没有;只要有过就会有值。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容