「atomic」と「nonatomic」属性の違い(iOS、Objective-C、プロパティ)
iOS、Objective-Cにおけるプロパティの属性として、**「atomic」と「nonatomic」**があります。これらの属性は、プロパティへのアクセスをどのように同期するかを指定します。
「atomic」属性
- デフォルトの属性です。
- スレッドセーフを確保します。
- プロパティへのアクセスが同時に発生した場合、同期処理が行われ、データの整合性を保ちます。
- パフォーマンスオーバーヘッドが生じる可能性があります。
- プロパティへのアクセスが同時に発生した場合、データの整合性が損なわれる可能性があります。
- パフォーマンスが向上します。
どちらの属性を使用するかは、アプリケーションの要件とパフォーマンスのトレードオフを考慮して決定します。
- スレッドセーフが必要な場合は、「atomic」を使用します。
- パフォーマンスが重要で、スレッドセーフが不要な場合は、「nonatomic」を使用します。
例:
@interface MyClass : NSObject
@property (nonatomic, strong) NSString *name; // nonatomic属性
@property (atomic, strong) NSNumber *age; // atomic属性
@end
この例では、「name」プロパティは「nonatomic」属性で宣言され、「age」プロパティは「atomic」属性で宣言されています。
注意:
- 「nonatomic」属性を使用する場合、適切な同期処理を実装してデータの整合性を確保する必要があります。
- 「atomic」属性を使用しても、複雑なアクセスパターンや競合状態が発生する場合は、追加の同期処理が必要になることがあります。
例1:スレッドセーフが必要な場合(「atomic」属性)
#import <Foundation/Foundation.h>
@interface MyObject : NSObject
@property (atomic, strong) NSString *name;
@end
@implementation MyObject
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyObject *object = [[MyObject alloc] init];
// スレッド1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
object.name = @"Alice";
});
// スレッド2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
object.name = @"Bob";
});
// スレッド1とスレッド2が完了した後
NSLog(@"name: %@", object.name); // 出力: name: Alice または name: Bob (どちらか一方)
}
return 0;
}
この例では、「name」プロパティが「atomic」属性で宣言されているため、スレッドセーフが保証されます。スレッド1とスレッド2が同時にプロパティにアクセスしても、同期処理が行われ、最終的な値は「Alice」または「Bob」のどちらか一方になります。
#import <Foundation/Foundation.h>
@interface MyObject : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation MyObject
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyObject *object = [[MyObject alloc] init];
// スレッド1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
object.name = @"Alice";
});
// スレッド2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
object.name = @"Bob";
});
// スレッド1とスレッド2が完了した後
NSLog(@"name: %@", object.name); // 出力: name: Alice または name: Bob (どちらか一方、または不正な値)
}
return 0;
}
「atomic」と「nonatomic」属性の代替方法
「atomic」と「nonatomic」属性は、プロパティへのアクセスの同期方法を制御します。これらの属性の代わりに、以下のような方法を使用してプロパティへのアクセスを管理することができます。
手動同期
- NSLockやNSRecursiveLockなどのロックオブジェクトを使用して、プロパティへのアクセスを同期します。
- パフォーマンスオーバーヘッドが生じますが、より柔軟な同期制御が可能です。
@interface MyObject : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation MyObject
{
NSLock *_lock;
}
- (instancetype)init {
self = [super init];
if (self) {
_lock = [[NSLock alloc] init];
}
return self;
}
- (void)setName:(NSString *)name {
[_lock lock];
_name = name;
[_lock unlock];
}
- (NSString *)name {
[_lock lock];
NSString *name = _name;
[_lock unlock];
return name;
}
GCD(Grand Central Dispatch)
- dispatch_barrier_asyncなどの関数を使い、プロパティへのアクセスを同期します。
- GCDの仕組みを利用して、スレッド間でのプロパティへのアクセスを管理します。
@interface MyObject : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation MyObject
{
dispatch_queue_t _queue;
}
- (instancetype)init {
self = [super init];
if (self) {
_queue = dispatch_queue_create("com.example.myobject", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)setName:(NSString *)name {
dispatch_barrier_async(_queue, ^{
_name = name;
});
}
- (NSString *)name {
__block NSString *result = nil;
dispatch_sync(_queue, ^{
result = _name;
});
return result;
}
NSOperationQueue
- NSOperationの依存関係を設定し、プロパティへのアクセスを同期します。
- オペレーションキューを利用して、プロパティへのアクセスの順序を制御します。
@interface MyObject : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation MyObject
{
NSOperationQueue *_queue;
}
- (instancetype)init {
self = [super init];
if (self) {
_queue = [[NSOperationQueue alloc] init];
}
return self;
}
- (void)setName:(NSString *)name {
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
_name = name;
}];
[_queue addOperation:operation];
}
- (NSString *)name {
__block NSString *result = nil;
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
result = _name;
}];
[operation addDependency:[_queue operations].lastObject];
[_queue addOperation:operation];
[_queue waitUntilAllOperationsAreFinished];
return result;
}
ios objective-c properties