返回

指针纷争:一次标签指针惹的祸

IOS

一次 Tagged Pointer 惹的祸

最近,我在项目中遇到了一起线上 Crash 事故,起因是objc_setAssociatedObject 和 objc_getAssociatedObject 这两个函数。

事故的根源在于 Tagged Pointer。Tagged Pointer 是一种特殊的指针,它在低位存储了额外的信息,这些信息可以用来快速访问对象的元数据。在 Objective-C 中,Tagged Pointer 被用来存储对象的类信息和 isa 指针。

问题就出在 Tagged Pointer 上。当我们使用 objc_setAssociatedObject 和 objc_getAssociatedObject 时,会将一个 Tagged Pointer 存储在对象的 isa 指针中。如果我们不小心,可能会导致 Tagged Pointer 指向错误的位置,从而引发意外的 Crash。

事故复现

为了复现这个事故,我写了一个简单的代码:

@interface ViewController : UIViewController

@property (nonatomic, strong) NSString *name;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.name = @"张三";
    
    // 这里使用 objc_setAssociatedObject 函数将一个 Tagged Pointer 存储在对象的 isa 指针中
    objc_setAssociatedObject(self, @selector(name), @"李四", OBJC_ASSOCIATION_RETAIN);
    
    // 这里使用 objc_getAssociatedObject 函数获取存储在 Tagged Pointer 中的值
    NSString *newName = objc_getAssociatedObject(self, @selector(name));
    
    NSLog(@"name: %@, newName: %@", self.name, newName);
}

@end

当我们运行这段代码时,会在控制台看到以下输出:

name: 张三, newName: 李四

这说明我们成功地将一个 Tagged Pointer 存储在了对象的 isa 指针中,并且可以通过 objc_getAssociatedObject 函数获取存储在 Tagged Pointer 中的值。

但是,如果我们在 objc_setAssociatedObject 函数中不小心将 Tagged Pointer 指向错误的位置,就会引发 Crash。例如,如果我们将以下代码添加到上面的代码中:

// 这里将 Tagged Pointer 指向错误的位置
objc_setAssociatedObject(self, @selector(name), self, OBJC_ASSOCIATION_RETAIN);

那么当我们运行这段代码时,就会在控制台看到以下错误:

*** Terminating app due to uncaught exception 'EXC_BAD_ACCESS', reason: 'pointer being freed was not allocated'

这是因为 Tagged Pointer 指向了一个错误的位置,导致系统在释放内存时出现了问题。

解决方案

为了避免这种情况发生,我们应该谨慎使用 objc_setAssociatedObject 和 objc_getAssociatedObject 函数。在使用这些函数时,我们应该确保 Tagged Pointer 指向正确的位置。

如果我们确实需要将 Tagged Pointer 指向一个新的位置,那么我们应该先将旧的 Tagged Pointer 释放掉。例如,如果我们想将 Tagged Pointer 指向一个新的对象,那么我们可以使用以下代码:

// 先释放旧的 Tagged Pointer
objc_setAssociatedObject(self, @selector(name), nil, OBJC_ASSOCIATION_ASSIGN);

// 再将 Tagged Pointer 指向新的对象
objc_setAssociatedObject(self, @selector(name), newObject, OBJC_ASSOCIATION_RETAIN);

这样就可以避免 Tagged Pointer 指向错误的位置,从而防止 Crash 的发生。

总结

在 Objective-C 中,Tagged Pointer 是一种非常有用的工具,它可以用来快速访问对象的元数据。但是,如果我们不小心,可能会导致 Tagged Pointer 指向错误的位置,从而引发意外的 Crash。

因此,在使用 objc_setAssociatedObject 和 objc_getAssociatedObject 函数时,我们应该谨慎操作,确保 Tagged Pointer 指向正确的位置。