小码哥iOS学习笔记第二十四天: Tagged Pointer

2,000 阅读2分钟

一、iOS程序的内存布局

二、Tagged Pointer

  • 从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumberNSDateNSString等小对象的存储
  • 在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
  • 使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了: Tag + Data,也就是将数据直接存储在了指针中
  • 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
NSNumber *num1 = [NSNumber numberWithInt:1];
NSNumber *num2 = @(2);
NSNumber *num3 = @3;
NSNumber *num4 = @(1233221132133211233);

NSLog(@"%p", num1);
NSLog(@"%p", num2);
NSLog(@"%p", num3);
NSLog(@"%p", num4);

  • objc_msgSend能识别Tagged Pointer,比如NSNumberintValue方法,直接从指针提取数据,节省了以前的调用开销
NSNumber *num1 = [NSNumber numberWithInt:1];
[num1 intValue];
  • 如何判断一个指针是否为Tagged Pointer
    • iOS平台,最高有效位是1(第64bit)
    • Mac平台,最低有效位是1

  • 判断一个指针是否是Tagged Pointer的源码使用的是_objc_isTaggedPointer函数
static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) {
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

三、面试题

1、下面这段代码执行后, 会发生什么

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 1000; i++) {
        dispatch_async(queue, ^{
            self.name = [NSString stringWithFormat:@"abcdefghijklmn"];
        });
    }
}

@end
  • 运行程序, 可以看到崩溃在了objc_release

  • 这主要是因为在-setName:方法中, 实际的实现如下
- (void)setName:(NSString *)name
{
    if (_name != name) {
        [_name release];
        _name = [name copy];
    }
}
  • 因为使用多线程赋值, 所以会有多个线程同时调用[_name release], 所以才发触发上面的崩溃
  • 解决的方式就是加锁, 可以使用atomic, 或者其他的锁
@property (atomic, copy) NSString *name;

2、下面的代码为什么可以正常运行, 不会崩溃

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, copy) NSString *name;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i < 1000; i++) {
        dispatch_async(queue, ^{
            self.name = [NSString stringWithFormat:@"abc"];
        });
    }
}
@end
  • 运行程序, 上面的代码确实不会发生崩溃
  • 这是因为[NSString stringWithFormat:@"abc"]是一个Tagged Pointer, 在调用-setName:方法时, 底层使用的是objc_msgSend(self, @selector(setName:)
  • 此时就会在底层调用_objc_isTaggedPointer函数判断是否是Tagged Pointer, 如果是, 就会直接将地址赋值给_name, 没有releasecopy的操作