3. iOS开发中使用atomic,有什么问题?

5 阅读2分钟

借助AI辅助。

1. 核心结论

在 iOS 开发中,我们几乎总是使用 nonatomic,极少使用 atomic

使用 atomic 存在两个主要问题:

  1. 性能损耗atomic 会在 setter/getter 方法中加锁,频繁访问时会严重拖慢性能。
  2. 虚假的线程安全atomic 只能保证属性的读写操作(Accessors)是原子的,但不能保证对象的操作逻辑是线程安全的。

2. 深度解析:为什么说它是“虚假”的线程安全?

atomic 保证的是:当一个线程在写数据(Setter)时,另一个线程无法同时去写,也无法同时去读(Getter)。它保证了你读到的数据要么是“修改前”的,要么是“修改后”的,不会读到“写了一半”的脏数据。

但是!它不管后续的操作。

举个例子: 假设你有一个 atomic 的数组属性 self.dataArray

@property (atomic, strong) NSMutableArray *dataArray;

场景: 线程 A 在读取数组的第 0 个元素,线程 B 同时在清空数组。

// 线程 A
id obj = [self.dataArray objectAtIndex:0]; 

// 线程 B
[self.dataArray removeAllObjects];

结果: 依然会崩溃(Crash)。

原因:

  • [self.dataArray] 这个读取操作是原子的(安全的),你确实拿到了数组对象。
  • 但是在你拿到数组后,紧接着调用 objectAtIndex:0 时,线程 B 可能刚好把数组清空了。
  • atomic 锁不住 objectAtIndex:removeAllObjects 这些方法调用。它只管 self.dataArray = ... (setter) 和 ... = self.dataArray (getter)。

结论: 要想真正实现线程安全,你需要使用更高层级的锁(如 @synchronized, NSLock, dispatch_semaphore 或串行队列)来包裹住整段逻辑代码,而不仅仅是依赖属性的 atomic


3. 性能问题(底层实现)

atomic 的底层实现大致如下(伪代码):

- (void)setName:(NSString *)name {
    // 自动加锁
    [self.internalLock lock];
    _name = name;
    [self.internalLock unlock];
}

- (NSString *)name {
    // 自动加锁
    [self.internalLock lock];
    NSString *result = [[_name retain] autorelease];
    [self.internalLock unlock];
    return result;
}

每次访问属性都要经历 lock -> unlock 的过程。在 UI 渲染或高频计算等对性能敏感的场景下,这种开销是不可接受的。相比之下,nonatomic 直接访问内存,速度快得多。


4. 什么时候真正需要用 atomic?

虽然很少,但也不是完全没有。

  • 如果你开发的不是 App,而是一个第三方 SDK底层库
  • 并且你确定该属性仅仅是保存一个简单的值(比如一个整数配置项,或者一个指针),不涉及复杂的集合操作或逻辑依赖。
  • 此时为了防止外部调用者在多线程环境下读到脏数据,可以使用 atomic 作为一种兜底的防护手段。