为什么在实际的 Objective-C 开发中, 几乎所有的属性都声明为 nonatomic ? 声明为 atomic 的属性我是真的没见过
在实际的 Objective-C 开发中,大多数属性通常声明为 nonatomic
,主要原因包括性能考虑和常见的设计模式。具体原因如下:
-
性能问题:
atomic
属性的实现会在生成的 getter 和 setter 方法中加入锁机制,以确保操作的原子性。这会导致一定的性能开销,尤其是在高频率访问的场景中,性能损失更为明显。nonatomic
属性的读写操作不需要加锁,因此在性能上比atomic
更高效,特别是在需要高效数据访问的应用中,如 UI 操作或实时处理。
-
并发访问的复杂性:
- 即使将属性声明为
atomic
,也只能保证单次读或写操作的原子性。大多数情况下,线程安全问题不仅仅是单次读写操作,而是涉及多个操作的组合。这种情况下,仅依赖atomic
是不够的。 - 开发者通常需要更高级的并发控制机制来确保线程安全,如使用锁、队列(GCD)、NSOperationQueue 等。这些机制提供了更细粒度的控制,能够处理更复杂的并发访问场景。
- 即使将属性声明为
-
惯用设计模式:
- 许多 Objective-C 代码库和框架默认使用
nonatomic
属性,因为它们假定开发者会自行处理必要的线程安全控制。 - 使用
nonatomic
属性可以使代码更加简洁,并且开发者可以根据需要灵活地选择适当的并发控制策略。
- 许多 Objective-C 代码库和框架默认使用
-
UI 相关的属性:
- 在 iOS 开发中,大部分 UI 操作必须在主线程上执行,因此通常不需要
atomic
属性来保证线程安全。UI 相关的属性声明为nonatomic
更为常见和合理。
- 在 iOS 开发中,大部分 UI 操作必须在主线程上执行,因此通常不需要
示例
以下是一个常见的属性声明为 nonatomic
的例子:
@interface MyClass : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger count;
@end
线程安全的实现
为了实现更复杂的线程安全控制,开发者通常会这样做:
- 使用锁:
@interface MyClass : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger count;
@property (nonatomic, strong) NSLock *lock;
@end@implementation MyClass
- (void)setName:(NSString *)name {[self.lock lock];_name = name;[self.lock unlock];
}- (NSString *)name {[self.lock lock];NSString *name = _name;[self.lock unlock];return name;
}
@end
- 使用 GCD:
@interface MyClass : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger count;
@property (nonatomic, strong) dispatch_queue_t syncQueue;
@end@implementation MyClass
- (instancetype)init {self = [super init];if (self) {_syncQueue = dispatch_queue_create("com.example.MyClassSyncQueue", DISPATCH_QUEUE_SERIAL);}return self;
}- (void)setName:(NSString *)name {dispatch_sync(self.syncQueue, ^{_name = name;});
}- (NSString *)name {__block NSString *name;dispatch_sync(self.syncQueue, ^{name = _name;});return name;
}
@end
通过这些高级的并发控制机制,开发者可以确保属性的线程安全,而不需要依赖 atomic
,同时还能避免其带来的性能损耗。