跳过正文
  1. Posts/

Tagged Pointer小记

··3 分钟

记录时间:2018.7.4

本文使用的测试环境是arm64架构真机

为了探究Tagged Pointer本质,可以查看runtime源码,主要看文件objc-internal.h

宏定义
#

可以看到以下宏定义,只有在64位系统才支持Tagged Pointer

#if __LP64__
#define OBJC_HAVE_TAGGED_POINTERS 1
#endif

64-bit的mac,tag存储在LSB(Least Significant Bit 最低位)。其它情况比如64位的真机和模拟器,tag存储在MSB(Most Significant Bit 最高位)。

#if TARGET_OS_OSX && __x86_64__
    // 64-bit Mac - tag bit is LSB
#   define OBJC_MSB_TAGGED_POINTERS 0
#else
    // Everything else - tag bit is MSB
#   define OBJC_MSB_TAGGED_POINTERS 1
#endif

#if OBJC_MSB_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)
#   define _OBJC_TAG_INDEX_SHIFT 60
#   define _OBJC_TAG_SLOT_SHIFT 60
#   define _OBJC_TAG_PAYLOAD_LSHIFT 4
#   define _OBJC_TAG_PAYLOAD_RSHIFT 4
#   define _OBJC_TAG_EXT_MASK (0xfUL<<60)
#   define _OBJC_TAG_EXT_INDEX_SHIFT 52
#   define _OBJC_TAG_EXT_SLOT_SHIFT 52
#   define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 12
#   define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#else
#   define _OBJC_TAG_MASK 1UL
#   define _OBJC_TAG_INDEX_SHIFT 1
#   define _OBJC_TAG_SLOT_SHIFT 0
#   define _OBJC_TAG_PAYLOAD_LSHIFT 0
#   define _OBJC_TAG_PAYLOAD_RSHIFT 4
#   define _OBJC_TAG_EXT_MASK 0xfUL
#   define _OBJC_TAG_EXT_INDEX_SHIFT 4
#   define _OBJC_TAG_EXT_SLOT_SHIFT 4
#   define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 0
#   define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#endif

接下来是一个枚举定义,定义了默认的使用Tagged Pointer的类。例如NSString、NSNumber、NSIndexPath、NSDate(OBJC_TAG_NSAtom、OBJC_TAG_1、OBJC_TAG_NSManagedObjectID不知道是啥意思,还请知道的同学告诉我)。

enum objc_tag_index_t : uint16_t
enum
{
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6, 
    OBJC_TAG_RESERVED_7        = 7, 

    OBJC_TAG_First60BitPayload = 0, 
    OBJC_TAG_Last60BitPayload  = 6, 
    OBJC_TAG_First52BitPayload = 8, 
    OBJC_TAG_Last52BitPayload  = 263, 

    OBJC_TAG_RESERVED_264      = 264
};

方法定义
#

判断是不是Tagged Pointer

static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr) 
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

生成一个Tagged Pointer,最高的4位是tagged,剩下的是数据

static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
    if (tag <= OBJC_TAG_Last60BitPayload) {
        return (void *)
            (_OBJC_TAG_MASK | 
             ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | 
             ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
    } else {
        return (void *)
            (_OBJC_TAG_EXT_MASK |
             ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
             ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
    }
}

Tagged Pointer中取出值

static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr) 
{
    // assert(_objc_isTaggedPointer(ptr));
    uintptr_t basicTag = ((uintptr_t)ptr >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    if (basicTag == _OBJC_TAG_INDEX_MASK) {
        return ((uintptr_t)ptr << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
    } else {
        return ((uintptr_t)ptr << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;
    }
}

NSNumber应用举例
#

可以使用下面代码来验证NSNumber如何使用Tagged Pointer

    NSNumber *charNumber = [NSNumber numberWithChar:'1'];
    NSNumber *shortNumber = [NSNumber numberWithShort:1];
    NSNumber *intNumber = [NSNumber numberWithInt:1];
    NSNumber *floatNumber = [NSNumber numberWithFloat:1.0];
    NSNumber *longNumber = [NSNumber numberWithLong:1];
    NSNumber *doubleNumber = [NSNumber numberWithDouble:1.0];
    
    // 输出变量的指针地址:
    // charNumber 0xb000000000000310
    // shortNumber 0xb000000000000011
    // intNumber 0xb000000000000012
    // floatNumber 0xb000000000000014
    // longNumber 0xb000000000000013
    // doubleNumber 0xb000000000000015

不难发现规律,都是以b(1011)开头

  • 最高位是1,说明这个指针是一个Tagged Pointer
  • 第61-63位是11(十进制是3),也就是OBJC_TAG_NSNumber(查上面的枚举)
  • 第1-4位是NSNumber的类型:比如,char是0、short是1、int是2、float是4
  • 剩下的56位就是真正的值了

NSString应用举例
#

    NSString *str1 = [NSString stringWithFormat:@"a"];
    NSString *str2 = [NSString stringWithFormat:@"ab"];
    
    // 输出变量的指针地址:
    // str1: 0xa000000000000611
    // str2: 0xa000000000062612

与NSNumber类似

  • 最高位是1,说明这个指针是一个Tagged Pointer
  • 第61-63位是11(十进制是2),也就是OBJC_TAG_NSString
  • 第1-4位是字符串长度
  • 剩下的56位就是真正的值了

更多细节推荐这篇文章采用Tagged Pointer的字符串


参考文章

相关文章

URLWithString return nil

··1 分钟
记录时间:2018.7.3 问题描述 # 在使用URLWithString生成NSURL时,如果出现中文,会导致返回的NSURL为nil。代码如下:

iOS消息转发小记

··2 分钟
消息转发流程图 如果类接收到无法处理的消息,会触发消息转发机制,一共有三个步骤,接受者在每一步中均有机会处理消息。步骤越往后,处理消息的代价就越大,所以最好再第一步就处理完。

探寻Objective-C引用计数本质

··5 分钟
本文涉及到的CPU架构为arm64,其它架构大同小异。 源码来自苹果开源-runtime。 Objective-C中采用引用计数机制来管理内存,在MRC时代,需要我们手动retain和release,在苹果引入ARC后大部分时间我们不用再关心引用计数问题。但是为了深入Objective-C本质,引用计数究竟是怎么实现的还是值得我们去探寻的。

iOS用原生代码读写Webview的Local Storage

··5 分钟
背景 # 公司项目使用的Cordova混合开发的,有一个模块以前用H5实现的,新版本用原生来实现,于是需要迁移数据。H5使用的Local Storage存的数据,原生要拿到数据有两种方案: