OC基础 - Tagged Pointer <objc4-723>

▶ 什么是 Tagged Pointer

从 bit64 开始 iOS 就引入了 Tagged Pointer 技术,用来优化 NSNumber、NSDate、NSString等小对象的存储。在没有 Tagged Pointer 之前这些小对象同样要使用动态分配内存、维护引用计数等

NSNumber *number = [NSNumber numberWithInt:100]; 
// number 指向 [NSNumber numberWithInt:100] 对象的内存地址

在使用了 Tagged Pointer 后,number指针 就存储了数据 Tag + Data格式,就是将数据直接存放在了指针变量里面,不再指向对象内存地址

我们知道 64位 系统中一个指针占 8 字节,一个对象占用 16 字节,那么采用传统的方式使用 NSNumber 就至少需要 24字节 的存储空间!注:因为内存对齐的缘故,指向对象的指针最低有效位一定是 0

使用 Tagged Pointer 技术,对于小对象来讲一个指针就可以搞定数据的存取;但是如果数据足够大且指针不能满足对该数据的存储,就会采用原来的动态分配进内存的方式进行存取

复制代码
#import <Foundation/Foundation.h>
// 是否是 Tagged Pointer
BOOL isTaggedPointer(id pointer){

    // mac 环境下 Tagged Pointer 最后一位一定是 1
    return (long)(void *)pointer & 1;
}

int main(int argc, const char * argv[]) {


    //-------------- NSNumber ----------------
    NSNumber *num1 = @7;
    NSNumber *num2 = @8;
    NSNumber *num3 = @9;
    // 数据足够大时,动态分配内存
    NSNumber *num4 = @(0xFFFFFFFFFFFFFFFF);

    // 验证 Tagged Pointer 内存地址最后一位一定是 1
    NSLog(@"%p",num1); // 0x80af 33fd 400d b5df
    NSLog(@"%p",num2); // 0x80af 33fd 400d badf
    NSLog(@"%p",num3); // 0x80af 33fd 400d bbdf
    // f 对应二进制 1111,最低位是 1

    // num4 的内存地址最低位一定是 0
    NSLog(@"%p",num4); // 0x1007242a0

    // 验证
    NSLog(@"%d   %d   %d   %d",isTaggedPointer(num1),isTaggedPointer(num2),isTaggedPointer(num3),isTaggedPointer(num4));
    // 1   1   1   0


    int num01 = [num1 intValue];
    NSLog(@"%d",num01); // 7
    // 其实质是 objc_msgSend(num1,@selector(intValue))
    // 虽然 num1 采用的是 Tagged Pointer,但是 objc_msgSend 可以识别 Tagged Pointer
    // objc_msgSend 内部作了判断,如果是 Tagged Pointer 则直接从指针中抽取出对应的数据


    //------------------------------ NSString
    NSString *testA = [NSString stringWithFormat:@"123abc"];;
    NSString *testB = [NSString stringWithFormat:@"aaabbbcccddddeee"];

    NSLog(@"testA = %p  testB = %p",testA,testB);
    // testA = 0xc4f6e511aeedd237  testB = 0x100517ba0

    NSLog(@"testA = %@  testB = %@",[testA class],[testB class]);
    // testA = NSTaggedPointerString  testB = __NSCFString

    return 0;

}
复制代码

▶ 如何判定 Tagged Pointer

我们可以从 release方法 中找到 isTaggedPointer

A. release方法

  

B. 在这里我们就可以看到 isTaggedPointer

它最终实现如下

 

_OBJC_TAG_MASK 是个什么鬼 ?

如果 iOS平台 _OBJC_TAG_MASK = 1UL << 63;如果 Mac平台 _OBJC_TAG_MASK = 1UL

在 iOS平台 下最高有效位是  1 就是 Tagged Pointer;在 Mac平台 下最低有效位是 1 就是 Tagged Pointer

▶ 开发中遇到的问题

我们分别对 Person 中的 _yourName 是动态分配内存和 tagger pointer  的两种情况进行测试

复制代码
#import <Foundation/Foundation.h>
// Person.h
@interface Person : NSObject
@property(nonatomic,strong)NSString *yourName;
-(void)testTaggedPointer;
@end

// Person.m
@implementation Person
-(void)testTaggedPointer{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 1000; i++) {
        dispatch_async(queue, ^{
            
            // ---------- 测试一:程序crash-------------
            // 原因 Thread 5: EXC_BAD_ACCESS
            self.yourName = [NSString stringWithFormat:@"aaabbbcccddddeee"];
            
            /* 修正方式
             
             方式一:yourName属性 使用 atomic特性 修饰
             方式二:线程锁
             @synchronized(self){
             self.yourName = [NSString stringWithFormat:@"aaabbbcccddddeee"];
             }
             */
            
            // ---------- 测试二:正常运行 ------------
            self.yourName = [NSString stringWithFormat:@"abc"];
        });
    }
}
@end

// main函数
int main(int argc, const char * argv[]) {
    Person *per =[Person new];
    [per testTaggedPointer];
    return 0;
}
复制代码

测试一崩溃原因是异步队会列抢占同一块资源 _yourName。因为此时的实例变量是动态分配的,我们知道点语法内部赋值时会有旧值 [_yourName release] ,异步执行很可能会造成同一资源过度 release,造成程序crash

测试二是 tagger pointer,就是一个完完全全的指针,是不涉及对象内存的动态分配问题

 

posted on   低头捡石頭  阅读(35)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2017-05-17 iOS篇 - 历代iPhone尺寸
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示