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,就是一个完完全全的指针,是不涉及对象内存的动态分配问题
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2017-05-17 iOS篇 - 历代iPhone尺寸