借助AI辅助。
1. 核心结论
在 iOS 开发中,我们几乎总是使用 nonatomic,极少使用 atomic。
使用 atomic 存在两个主要问题:
- 性能损耗:
atomic会在 setter/getter 方法中加锁,频繁访问时会严重拖慢性能。 - 虚假的线程安全:
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作为一种兜底的防护手段。