初心者が理解するObjective-Cのnonatomic属性5選

初心者でもわかるObjective-Cのnonatomicの使い方とサンプルコードObjctive-C
この記事は約23分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Objective-CはApple Inc.によって開発されたプログラミング言語であり、iOSやmacOSのアプリケーション開発に広く使われています。

この言語はSmalltalkのメッセージ指向の概念とC言語の静的型付けの強さを組み合わせたもので、柔軟性とパワフルさを兼ね備えています。

初心者がObjective-Cを学ぶ際には、その特有の文法とプログラミングパラダイムを理解することが重要です。

本文では、Objective-Cの特徴や基本構造、プログラムを書き始める前の準備について詳しく説明し、nonatomic属性を含むプロパティに焦点を当てて解説していきます。

●Objective-Cとは

Objective-Cは、厳密な型検査とソフトウェアの再利用を容易にするクラスベースのオブジェクト指向機能を提供します。

また、Objective-CはC言語のスーパーセットであるため、C言語のコードをそのままObjective-Cのプログラム内で使用することができます。

この柔軟性が、特に既存のCライブラリやコードとの互換性を持たせる上で大きな利点となります。

さらに、Objective-Cではメッセージパッシングの概念を取り入れており、これによりオブジェクト間のコミュニケーションが行われます。

○Objective-Cでプログラミングを始める前に

Objective-Cでのプログラミングを開始するにあたって、Xcodeという統合開発環境(IDE)のインストールが必要です。

XcodeはAppleが無料で提供しており、Objective-Cのコードの記述、コンパイル、デバッグを行うための一連のツールを含んでいます。

また、CocoaまたはCocoa Touchフレームワークとの統合もXcodeを通じて行われ、これによりGUI要素の追加やイベント処理などの機能を実装することができます。

Objective-Cの基本を理解した後は、クラス、メソッド、プロパティなどのオブジェクト指向の概念を学ぶことになります。

また、メモリ管理に関しても、手動での管理から自動参照カウント(ARC)による管理へと移行しているため、ARCの基本的な理解も求められます。

●プロパティと属性について

プロパティと属性はObjective-Cにおける基本的な概念であり、オブジェクト指向プログラミングの強力な特性を提供します。

プロパティとは、クラスに属する変数のことを指します。

これにより、データのカプセル化が可能になり、外部からのアクセスを制御できるようになります。

一方、属性はプロパティの振る舞いを定義するための追加情報を提供し、コンパイラに対する指示として機能します。

これにより、メモリ管理の戦略や、読み書きの方法、その他のプロパティの振る舞いを詳細に制御できます。

○プロパティとは何か

プロパティとは、Objective-Cで定義されたクラスのインスタンス変数を外部から安全にアクセスするための手段です。

例えば、あるオブジェクトの名前や年齢などの値を格納する場合に、それぞれに対応するプロパティを持たせることができます。

プロパティは、直接的な変数アクセスを避け、メソッドを介したアクセスを提供することで、オブジェクトの状態の整合性を保つために役立ちます。

プロパティの宣言は次のようになります。

@interface MyClass : NSObject
@property (strong, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;
@end

上記のコードは、MyClassという名前のクラスにnameageという二つのプロパティを宣言しています。

strongassignはメモリ管理に関する属性で、それぞれ参照型と値型の変数に使用されます。

○属性の役割とは

属性はプロパティの動作を定義し、コードの動作をコントロールするために重要です。

属性を使うことで、プロパティの読み書き方法、スレッドセーフな操作、およびメモリ管理戦略を指定できます。

例えば、nonatomicatomic属性は、プロパティがスレッドセーフであるかどうかを制御します。

また、strongweakcopyassignといった属性は、オブジェクトの参照カウントやコピー動作を管理します。

これらの属性を適切に使うことにより、プログラマは意図した通りのコードの動作を保証することができ、バグの発生を減少させたり、パフォーマンスを向上させたりすることが可能です。

例えば、プロパティが非スレッドセーフなアクセスのみを必要とする場合にはnonatomic属性を用いることで、パフォーマンスの向上を図ることができます。

一方で、スレッド間で共有される可能性のあるプロパティにはatomic属性を用いて、スレッドセーフなアクセスを確保することが一般的です。

●nonatomic属性の詳細

Objective-Cにおいて、プロパティの属性を定義する際に、複数のスレッドからそのプロパティが同時に読み書きされる可能性がある場合、アクセスの同期を取る必要が出てきます。

しかし、すべてのプロパティアクセスにおいて同期が必要とは限らず、オーバーヘッドを避けたい場面もあります。

そのようなコンテキストでnonatomic属性が有用になります。nonatomicは、プロパティの同期をとらないことを表し、その結果、より高速なプロパティアクセスを提供しますが、スレッドセーフではないため注意が必要です。

○nonatomicとは何か

nonatomic属性を持つプロパティは、生成されるアクセッサ(セッターとゲッター)がスレッドセーフではないことを意味します。

つまり、複数のスレッドから同時にアクセスされた場合、値が予期せず変更される可能性があり、それがプログラムのバグにつながるリスクを持っています。

nonatomic属性を使用する最も一般的なシナリオは、UI操作といったメインスレッド上でのみアクセスされるプロパティに限定されます。

○nonatomicの使い方

Objective-Cでnonatomic属性を使用するには、@property宣言の一部としてnonatomicを指定するだけです。

例えば、NSString型のプロパティを宣言するときには次のように書きます。

@interface MyClass : NSObject
@property (nonatomic, strong) NSString *myString;
@end

このコードでは、MyClassというクラスにmyStringという名前のNSString型のプロパティをnonatomicで宣言しています。

ここでstrongは、プロパティが強参照を保持することを意味し、オブジェクトのライフサイクル管理に関連しています。

○nonatomicを使用する理由

nonatomicを使用する主な理由はパフォーマンスの向上です。

スレッドセーフなアクセッサメソッドを生成するには追加のオーバーヘッドが発生しますが、nonatomicを指定すると、そのオーバーヘッドを回避できます。

このため、プロパティに対するアクセスがメインスレッドに限られるか、スレッドの安全性を保証する他の手段が講じられている場合に選択されます。

しかしながら、マルチスレッド環境では使用にあたって十分な注意を要します。

値の読み書きが予期せず重なると、データの不整合やクラッシュの原因になり得るため、実装の際には適切なスレッド管理が求められます。

●nonatomic属性を持つプロパティの実装

Objective-Cでは、プロパティの振る舞いを定義するために属性を指定することが一般的です。

その中でもnonatomic属性は、複数のスレッドが同時にプロパティにアクセスする状況下で、アトミックなアクセス(つまりスレッドセーフなアクセス)を保証しないという性質を持っています。

これはパフォーマンスを向上させるために使われることが多いですが、スレッドセーフでないことには注意が必要です。

○サンプルコード1:基本的なnonatomicプロパティの宣言

Objective-Cでnonatomicプロパティを宣言する際には、次のような形式を取ります。

@interface MyClass : NSObject
@property (nonatomic, strong) NSString *myProperty;
@end

このコードではMyClassというクラス内にmyPropertyという名前の文字列型プロパティを宣言しています。

ここでnonatomicという属性が指定されており、これによって生成されるアクセッサメソッドはスレッドセーフでないことを意味します。

またstrongはプロパティがオブジェクトに強い参照を持つことを示しており、オブジェクトがメモリ内に保持され続けることを保証します。

このコードの実行結果としては、myPropertyプロパティに対してアトミックな操作を行わず、他のスレッドによる同時アクセスがあった場合に競合する可能性があるということです。

そのため、マルチスレッドプログラミングの環境下では注意深く使用する必要があります。

○サンプルコード2:カスタムセッターとゲッターの実装

プロパティにカスタムセッターやゲッターを実装することで、より高度な振る舞いを定義することができます。

下記のサンプルでは、nonatomicプロパティのカスタムセッターとゲッターを定義しています。

@interface MyClass ()
@property (nonatomic, strong) NSString *internalProperty;
@end

@implementation MyClass

@synthesize myProperty = _internalProperty;

- (NSString *)myProperty {
    return _internalProperty;
}

- (void)setMyProperty:(NSString *)aString {
    _internalProperty = [aString copy];
}

@end

このコードでは、外部からアクセスされるmyPropertyと内部で使用されるinternalPropertysynthesizeディレクティブを使用して結びつけています。

セッターメソッドでは引数に渡された文字列をコピーしてプロパティに設定し、ゲッターメソッドでは単純にプロパティの値を返すようになっています。

このコードの実行により、MyClassのインスタンスに対してmyPropertyを介してアクセスする際に、カスタムのロジックが実行されるようになります。

例えばセッターメソッドでは、引数で渡された文字列がプロパティに設定される前にコピーされるため、元の文字列が後から変更されてもプロパティの値には影響を与えません。

これは、不変オブジェクトをプロパティとして扱いたい場合に有用です。

●nonatomic属性の応用例

Objective-Cにおいて、プロパティはクラスのインタフェース部分で定義され、外部からアクセス可能なインスタンス変数です。

属性は、これらのプロパティの振る舞いを定義するキーワードで、nonatomicはその一つです。

nonatomic属性は、マルチスレッド環境でのプロパティの安全なアクセスを保証しない代わりに、パフォーマンスを向上させることができます。

この属性の適切な応用は、特に読み取り頻度が高く、書き込み頻度が低いプロパティに有効です。

○サンプルコード3:マルチスレッド環境での利用

マルチスレッドプログラミングでは、データの一貫性を保つために通常はスレッドセーフなコードを書く必要があります。

しかし、いくつかのシナリオではパフォーマンスが重要であり、スレッドセーフである必要がない場合もあります。

例えば、あるプロパティが複数のスレッドから同時に読み書きされる可能性が低い場合、nonatomic属性を使うことでパフォーマンスを最適化することが可能です。

下記のサンプルコードは、マルチスレッド環境下でnonatomic属性を使用する方法を表しています。

@interface MyClass : NSObject
@property (nonatomic, strong) NSString *name;
@end

@implementation MyClass

// マルチスレッドでのプロパティ使用のデモンストレーション
- (void)useInMultithreadedScenario {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        self.name = @"Thread A";
        NSLog(@"Name set to %@ in %@", self.name, [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"Name accessed in %@ as %@", [NSThread currentThread], self.name);
    });
}

@end

このコードでは、MyClassnonatomic属性を持つnameプロパティがあります。

useInMultithreadedScenario メソッド内で、非同期にnameプロパティにアクセスしています。

ここで、データの整合性を保証することよりもパフォーマンスの最適化を優先しています。

このプログラムを実行すると、異なるスレッドがプロパティにアクセスしても排他制御が行われず、アクセスのコストが低く抑えられることが見込まれます。

実行すると、nameプロパティはThread Aに設定されますが、実際にログに出力されるnameの値は別のスレッドでのアクセスのタイミングによって異なる可能性があります。

この例では、複数のスレッドからnameプロパティにアクセスしているため、最終的な出力結果については予測できないことを理解する必要があります。

プロパティの値は読み取り専用のロジックで安全に利用できるような設計をすることが望まれます。

○サンプルコード4:パフォーマンスの最適化

アプリケーションのパフォーマンスは多くの場面で重要視されます。

nonatomic属性を適切に使用することで、ロックを必要とするatomic属性よりも優れたパフォーマンスを実現することができます。

次のコードは、nonatomic属性を用いてプロパティのアクセス速度を改善する例を表しています。

@interface FastAccessArrayWrapper : NSObject {
    NSMutableArray *_internalArray;
}

- (void)addElement:(id)element;
- (void)removeElementAtIndex:(NSUInteger)index;
- (NSArray *)allElements;

@end

@implementation FastAccessArrayWrapper

- (instancetype)init {
    self = [super init];
    if (self) {
        _internalArray = [NSMutableArray array];
    }
    return self;
}

- (void)addElement:(id)element {
    [_internalArray addObject:element];
}

- (void)removeElementAtIndex:(NSUInteger)index {
    if (index < [_internalArray count]) {
        [_internalArray removeObjectAtIndex:index];
    }
}

- (NSArray *)allElements {
    return [NSArray arrayWithArray:_internalArray];
}

@end

このコードではFastAccessArrayWrapperクラスを定義し、内部でNSMutableArrayを使用していますが、公開メソッドを通じてのみアクセスを許可し、内部状態の変更を行っています。

ここで、内部のNSMutableArrayに直接nonatomic属性を適用してはいませんが、この方法はnonatomicの精神を反映した設計です。

公開メソッドaddElement:removeElementAtIndex:、そしてallElementsを介してNSMutableArrayにアクセスすることで、スレッドセーフでないというnonatomicの性質による影響を避けつつ、高速な操作を可能にしています。

このコードの実行により、追加、削除、全要素の取得が高速に行われ、特に並行性が要求されない場合に役立ちます。

もちろん、複数のスレッドが同時にこれらのメソッドを呼び出す場合には、適切なスレッドセーフメカニズム(例:ロック)の実装が必要です。

●スレッドセーフとの関連性

スレッドセーフとは、複数のスレッドが同時にコードの特定の部分を実行しても、プログラムの実行結果に予期せぬ変更が生じないように、プログラムが正しく動作することを保証する性質のことを指します。

特に、共有リソースにアクセスするコードの部分で重要となります。

Objective-Cを使用する開発者にとって、スレッドセーフのコーディングは不可欠なスキルです。

これはプログラムが複数の作業を同時に処理するマルチスレッド環境下で特に重要となり、意図しないデータの競合や不整合を避けるために必要です。

○スレッドセーフプログラミングとは

スレッドセーフプログラミングとは、複数のスレッドがデータやリソースを共有する際に、一つのスレッドの操作が他のスレッドに悪影響を与えないようにするプログラミング手法です。

Objective-Cでは、この概念はプロパティのアクセス方法と密接に関係しており、特にnonatomic属性を用いたプロパティ宣言がその典型例です。

nonatomic属性は、アトミックな操作を行わず、そのため通常よりも高速ですが、複数のスレッドから同時にアクセスされた場合に値の不整合が生じる可能性があります。

○nonatomicとスレッドセーフの関係

nonatomicプロパティは、Objective-Cでスレッドセーフではないことが明示的に指定されたプロパティです。

nonatomic属性を持つプロパティのアクセスはアトミック性(原子性)を保証しません。

アトミックプロパティは、一度に一つのスレッドのみが値にアクセスできるように内部でロック機構を使用しますが、nonatomicプロパティはこのような保護機構がないため、パフォーマンスは向上しますが、スレッドの安全性は犠牲になります。

スレッドセーフな環境を保持するためには、開発者は適切な同期メカニズム(例えば、セマフォやミューテックスなど)を実装する責任があります。

これにより、一度に一つのスレッドだけが特定のコードセクションやデータにアクセスすることが保証され、データの整合性が維持されます。

しかし、Objective-Cのnonatomicプロパティは、このようなスレッドセーフなコーディングパターンを強制しないため、開発者がスレッドセーフティを自ら考慮する必要があります。

●nonatomic属性の注意点と対処法

Objective-Cにおいて、プロパティの属性としてnonatomicを指定する際にはいくつかの重要な注意点があります。

nonatomic属性を使用する最大の目的はパフォーマンスの向上ですが、それにはスレッドセーフでないというトレードオフが伴います。

具体的には、複数のスレッドが同時に同じオブジェクトのnonatomicプロパティにアクセスした場合、予期せぬ結果を招く可能性があるのです。

この問題を避けるためには、アクセスされるプロパティが常に正しい状態を保持していることを確認する必要があります。

例えば、同時に複数のスレッドからそのプロパティが読み書きされる状況が予想される場合、スレッドセーフなコードを実装するか、排他制御の仕組みを利用することが求められます。

○注意点

nonatomic属性を使用する際には、次の3つの主要な注意点を意識する必要があります。

  1. スレッドセーフでない:nonatomicはスレッドセーフを保証しません。これは、同時に複数のスレッドがプロパティにアクセスした際に競合が起きる可能性があることを意味します。
  2. アクセス速度の向上:nonatomicを使用すると、スレッドの排他制御を行わないため、アクセス速度は向上しますが、それによりデータの整合性が損なわれるリスクが高まります。
  3. マルチスレッド環境での使用:マルチスレッド環境でnonatomicプロパティを使用する場合、明示的な同期処理を実装することが必須となります。

これらの注意点を理解し、適切に対応することが非常に重要です。

○対処法

スレッドセーフでない問題に対処するためには、次の2つの対処法を検討することが有効です。

□排他制御の実装

@synchronizedブロックを使用して排他制御を行い、一度に一つのスレッドのみがプロパティにアクセスできるようにします。

これにより、データの競合を防ぐことができます。

例えば、次のようにしてプロパティへのアクセスを同期させることができます。

@interface MyClass : NSObject
@property (nonatomic, strong) id myProperty;
@end

@implementation MyClass

- (id)myProperty {
    @synchronized(self) {
        return _myProperty;
    }
}

- (void)setMyProperty:(id)newProperty {
    @synchronized(self) {
        _myProperty = newProperty;
    }
}

@end

このコードでは、myPropertyへのアクセスを排他制御するために@synchronizedブロックが使われています。

この例ではselfがロックオブジェクトとして用いられており、同じインスタンスに対するmyPropertyのアクセスがスレッドセーフになります。

□GCD (Grand Central Dispatch)の利用

iOSでの並行プログラミングにおいては、GCDを用いることでスレッドセーフなコードをより簡単に実装できます。

例えば、シリアルキューを使ってプロパティへのアクセスをシリアライズすることができます。

下記のコードは、シリアルディスパッチキューを使って、プロパティへのスレッドセーフなアクセスを提供する方法を表しています。

@interface MyClass : NSObject
@property (nonatomic, strong) id myProperty;
@property (nonatomic, strong) dispatch_queue_t propertyQueue;
@end

@implementation MyClass

- (instancetype)init {
    if ((self = [super init])) {
        _propertyQueue = dispatch_queue_create("com.example.mypropertyqueue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}

- (id)myProperty {
    __block id localProperty;
    dispatch_sync(self.propertyQueue, ^{
        localProperty = _myProperty;
    });
    return localProperty;
}

- (void)setMyProperty:(id)newProperty {
    dispatch_sync(self.propertyQueue, ^{
        _myProperty = newProperty;
    });
}

@end

この例ではpropertyQueueディスパッチキューを使用して、myPropertyの読み書きをスレッドセーフに行っています。

dispatch_sync関数は、指定されたブロックがキューに追加され、完了するまで現在のスレッドの実行をブロックします。

●カスタマイズ方法

Objective-Cにおけるプロパティのカスタマイズ方法は、ソフトウェア開発の現場でよく要求される技術です。

カスタマイズとは、デフォルトの動作を変更して、特定のニーズに合わせたプロパティの振る舞いを定義することを指します。

プロパティは通常、ドット記法を用いてアクセスされる値を格納しますが、その設定や読み出し方法を開発者が制御したい場合があります。

たとえば、値が変更されたときに特定のロジックを実行したい、またはプロパティが特定の値の範囲内に収まっていることを保証したいなどの要件が該当します。

Objective-Cでプロパティをカスタマイズする際には、@synthesize@dynamic ディレクティブを理解しておくことが重要です。

これらはコンパイラに対して、プロパティのゲッター(読み出しメソッド)とセッター(設定メソッド)をどのように扱うかを指示します。

○サンプルコード5:nonatomicプロパティのカスタマイズ例

ここでは、Objective-Cでnonatomicプロパティのカスタマイズの一例を見ていきます。

nonatomic属性は、マルチスレッド環境ではスレッドセーフでないことを意味しているため、その動作をカスタムセッターを通じて調整することができます。

下記のサンプルコードは、カスタムセッターを用いて、プロパティの値が変更されるたびにログを出力するシンプルな例です。

#import <Foundation/Foundation.h>

@interface MyClass : NSObject
@property (nonatomic, strong) NSString *customString;
@end

@implementation MyClass
@synthesize customString = _customString;

- (void)setCustomString:(NSString *)aString {
    if (_customString != aString) {
        NSLog(@"値が更新されます: %@", aString);
        _customString = [aString copy];
    }
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyClass *myInstance = [[MyClass alloc] init];
        myInstance.customString = @"初期値";
        myInstance.customString = @"更新値";
    }
    return 0;
}

このコードではMyClassクラスにcustomStringというNSString型のプロパティを宣言しています。

そしてsetCustomString:メソッド内で、新しい値を設定する前に、現在の値と新しい値が異なるかを確認しています。

異なる場合はログを出力し、プロパティに新しい値を設定します。

実行すると、myInstancecustomStringプロパティの値として@"初期値"@"更新値"が設定されるたびに、コンソールにその変更がログとして出力されることを確認できます。

ログは次のようになります。

値が更新されます: 初期値
値が更新されます: 更新値

このカスタマイズ方法は、値の変更を監視するなどのシンプルな用途から、より複雑なビジネスロジックの挿入まで、幅広い応用が可能です。

特に、マルチスレッドプログラミングやパフォーマンスが重要なアプリケーションにおいて、カスタマイズされたプロパティの振る舞いを適切に制御することは、アプリケーションの安定性と信頼性を確保する上で重要な役割を果たします。

まとめ

Objective-Cにおけるnonatomic属性は、多くのiOSアプリケーションの開発で重要な役割を担っています。

この記事では、nonatomic属性に焦点を当て、その概要と使い方、さらには注意点までを初心者にも分かりやすく解説しました。

nonatomic属性は、プロパティのスレッドセーフでないアクセス方法を指定するものであり、Objective-Cのプログラミングにおいて頻繁に使用されます。

この記事を通じて、初心者の方々がObjective-Cのnonatomic属性の重要性と、その具体的な使用法を理解する手助けになればと考えています。

さらに深い理解と実践的なスキルを身に付けるためには、この知識を基にした実際のコード作成に挑戦してみてください。