8ステップで理解するObjective-Cのreadonly属性

初心者が理解しやすいObjective-Cのreadonly属性のイラスト解説Objctive-C
この記事は約25分で読めます。

 

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

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

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

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

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

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

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

はじめに

Objective-Cは、AppleのOS XやiOSのアプリケーション開発に広く使用されるプログラミング言語です。

その中でもプロパティは、オブジェクトの値を読み書きするための重要な要素であり、Objective-Cのreadonly属性はプロパティをより柔軟に扱うことを可能にします。

この記事では、プログラミングの初心者も理解しやすいように、readonly属性の基本から、使い方、カスタマイズ方法、ベストプラクティス、注意点まで、8ステップに分けて詳細に解説していきます。

Objective-Cの基本を学び、さらに深く理解を深めたい方にとって、このガイドは必読の内容です。

○Objective-Cのreadonly属性とは?

readonly属性は、プロパティの値が外部から変更できないように制限するために用いられます。

Objective-Cにおいてプロパティは通常、自動生成されるセッターとゲッターによって管理され、外部のオブジェクトからアクセスされます。

しかし、readonlyを指定することで、自動生成されるセッターが生成されず、プロパティの値はそのオブジェクト内でのみ設定でき、外部からは読み取り専用となります。

この属性は、変更されるべきでない値を持つインスタンス変数を扱う場合に特に有用です。

たとえば、オブジェクトの一意の識別子や設定後に変更を許容しない設定値などがこれに該当します。

readonly属性を持つプロパティは、インスタンス化時に初期値が与えられた後は、そのオブジェクトのライフサイクル全体を通じて変更不可の一定値を保持することになります。

これにより、データの整合性が保たれ、不用意な値の変更によるバグを防ぐことができます。

また、読み取り専用の属性を使うことで、クラスの内部実装は隠蔽され、外部インターフェースを通じてのみデータにアクセスできるため、オブジェクト指向の原則により良く適合します。

●readonly属性の基本

Objective-Cにおけるreadonly属性は、プロパティが外部から直接変更されることを防ぐために使用されます。

これは、オブジェクト指向プログラミングにおけるカプセル化の原則に沿った実装を提供し、オブジェクトの内部状態を厳密に管理することを可能にします。

readonly属性が付与されたプロパティは、クラスの外部から見ると読み取り専用となりますが、クラスの内部では読み書きが可能です。

この属性を使用する最も一般的なシナリオは、公開インターフェイスを介して他のクラスがアクセスすることができる値を提供する場合ですが、これらの値をクラス自身のメソッドを介してのみ変更可能にしたいときです。

例えば、ユーザーの年齢を表すプロパティは、外部のオブジェクトによって直接変更されることなく、ある特定のロジックを通じてのみ更新を許可したい場合にreadonly属性が有用です。

readonly属性は、@propertyディレクティブを使用して宣言されます。

ここでは、Objective-Cでreadonlyプロパティを定義する基本的な構文を紹介します。

○readonly属性の定義と利点

Objective-Cでのreadonlyプロパティの宣言は次のようになります。

@interface MyClass : NSObject
@property (readonly) int readOnlyProperty;
@end

このコードでは、MyClassというクラスにreadOnlyPropertyという整数型のプロパティを追加しています。

readonly修飾子によって、このプロパティは読み取り専用として外部に公開されます。

つまり、外部のクラスからはこのプロパティの値を取得することはできますが、設定することはできません。

このような宣言による利点は、オブジェクトの状態の不変性を保証することです。

不変性を持つオブジェクトは、一度作成されるとその状態が変わらないことが保証されており、プログラムの安全性と予測可能性を高めることができます。

また、マルチスレッド環境において、読み取り専用プロパティは競合状態(race condition)やデータの不整合のリスクを減少させるために重要です。

上記のサンプルコードでは、プロパティはデフォルトでatomic(原子的)であるため、マルチスレッド環境においてアトミックな読み取りが保証されます。

ただし、atomic属性によるスレッドセーフティはパフォーマンスのコストを伴うことがあるため、シングルスレッドの環境や高いパフォーマンスが要求される場合はnonatomic属性を検討することが推奨されます。

●readonly属性の使い方

Objective-Cにおけるreadonly属性は、プロパティの一種で、クラス外からの値の変更を制限し、読み取り専用として扱うために使用されます。

プロパティを定義する際にこの属性を用いることで、値を外部から保護し、予期しない変更を防ぎます。

この機能はクラスのカプセル化を強化し、外部からのアクセスを制御することでオブジェクト指向プログラミングの原則に沿った堅牢なコードを書くことを可能にします。

○サンプルコード1:基本的なreadonlyプロパティ

下記のサンプルコードは、Objective-Cで基本的なreadonlyプロパティを宣言し、実装する方法を表しています。

プロパティは.hファイル(ヘッダファイル)に公開され、.mファイル(実装ファイル)にて読み取り専用として実装されています。

// MyClass.h
@interface MyClass : NSObject
@property (readonly) NSString *myProperty;
@end

// MyClass.m
@implementation MyClass
- (instancetype)init {
    self = [super init];
    if (self) {
        _myProperty = @"Initial Value";
    }
    return self;
}
@end

このコードでは、MyClassというクラスにmyPropertyというNSString型のreadonlyプロパティを定義しています。

この例では、initメソッド内で_myPropertyの初期値を設定しており、これが唯一の設定時点となります。

外部からmyPropertyの値を取得することはできますが、変更することはできません。

このコードの実行により、MyClassのインスタンスを作成した際、myPropertyは読み取り専用となり、"Initial Value"という値が割り当てられます。

実際にMyClassのインスタンスを生成し、myPropertyにアクセスするコードを紹介します。

MyClass *myInstance = [[MyClass alloc] init];
NSLog(@"%@", myInstance.myProperty); // "Initial Value"を出力します。

上記のコードを実行するとコンソールには"Initial Value"が表示され、これはmyPropertyの値が正しく読み取られていることを意味します。

この挙動によって、readonlyプロパティが外部から保護されていることが確認できます。

○サンプルコード2:カスタムゲッターを持つreadonlyプロパティ

プロパティのカスタムゲッターを使用することで、readonlyプロパティの挙動をさらに洗練されたものにすることができます。

下記のサンプルコードでは、カスタムゲッターを使用して、プロパティの値を計算または変換して返す方法を表しています。

// MyClass.h
@interface MyClass : NSObject
@property (readonly) NSString *computedProperty;
@end

// MyClass.m
@implementation MyClass {
    NSString *_internalData;
}

- (NSString *)computedProperty {
    // _internalDataがnilでなければその値を大文字にして返し、
    // nilであれば"Default"を返すカスタムゲッターの実装例
    return _internalData ? [_internalData uppercaseString] : @"Default";
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _internalData = @"some data";
    }
    return self;
}
@end

この例では、computedPropertyの値が要求されるたびに、_internalDataの値を大文字に変換して返すか、または_internalDatanilの場合はデフォルトの文字列"Default"を返しています。

このようにして、プロパティの値はその時点の内部状態に応じて動的に決定されます。

カスタムゲッターを持つreadonlyプロパティを実際に使用すると、次のようになります。

MyClass *myInstance = [[MyClass alloc] init];
NSLog(@"%@", myInstance.computedProperty); // "SOME DATA"が出力されます。

このコードを実行すると、computedPropertyから返される値は"SOME DATA"となります。

これは、内部で保持されている_internalDataが大文字に変換された結果です。

この振る舞いを通じて、readonlyプロパティが動的な読み取りをサポートしていることが表されます。

●readonly属性のカスタマイズ

Objective-Cでreadonly属性を持つプロパティは、その名の通り読み取り専用のプロパティです。

これは、クラス外部からは値を読むことはできても、変更はできないことを意味します。

しかし、クラスの内部実装においては、読み取り専用とマークされているプロパティに対しても、カスタムロジックを用いて値を変更することが可能です。

readonly属性のカスタマイズは、多くの場合、クラスの外部に公開するインターフェースを保護する一方で、クラス内部ではフレキシブルな値の管理を可能にします。

例えば、外部のオブジェクトがプロパティの値を直接変更することを防ぎつつ、内部メソッドで特定の条件下でのみ値を更新するような場合です。

カスタマイズは主に次の手順に従って実施されます。

  1. ヘッダーファイルでreadonly属性を使用してプロパティを宣言する
  2. 実装ファイルにおいて、クラスの拡張(匿名カテゴリ)を使用して、同じプロパティをreadwrite属性を持つよう再宣言し、カスタムゲッターやセッターを実装する

この方法により、プロパティが外部から直接変更されるのを防ぎつつ、クラスの内部で定義されたメソッドからは値を自由に変更できるようになります。

○サンプルコード3:readonlyプロパティのカスタムゲッターのカスタマイズ

Objective-Cにおけるreadonlyプロパティのカスタムゲッターをカスタマイズするためには、次のようにコードを書くことができます。

@interface MyClass : NSObject

@property (nonatomic, readonly) NSString *customString;

@end

// MyClassの実装ファイル
@implementation MyClass {
    NSString *_customStringBackingInstanceVariable;
}

// customStringのカスタムゲッターを定義する
- (NSString *)customString {
    // ここにカスタムロジックを追加
    // _customStringBackingInstanceVariableがnilであれば何らかの初期値を設定するなどの処理が可能
    if (!_customStringBackingInstanceVariable) {
        _customStringBackingInstanceVariable = @"Initial Value";
    }
    return _customStringBackingInstanceVariable;
}

// 他のメソッドから_customStringBackingInstanceVariableを変更する例
- (void)updateCustomString {
    if ([someCondition]) {
        // 条件に応じて_customStringBackingInstanceVariableの値を更新する
        _customStringBackingInstanceVariable = @"Updated Value";
    }
}

@end

このコードでは、MyClassクラスにcustomStringというreadonlyプロパティを定義しています。

このプロパティの背後には、_customStringBackingInstanceVariableというインスタンス変数が存在し、カスタムゲッターではこの変数の値を返します。

また、updateCustomStringメソッド内では、特定の条件下でこのインスタンス変数の値を「Updated Value」に更新しています。

こうすることで、クラスの外部からはcustomStringの値を読むことしかできず、変更はクラスの内部で制御された方法でのみ行われるようになります。

このコードの実行結果は、外部からcustomStringプロパティへのアクセスに対しては常に現在設定されている値(初期値または更新値)が返され、内部からのみcustomStringの値の変更が可能になります。

これにより、外部からの不要な変更を防ぎつつも、クラスの設計者が意図する柔軟な値の更新を実現できるのです。

●readonly属性の応用例

Objective-Cでのプログラミングにおいて、readonly属性はプロパティが読み出し専用であることを指定するために使用されます。

これにより、そのプロパティに対して外部からの変更を防ぎ、より管理しやすいコードを書くことができます。

読み取り専用プロパティは主に、外部からの変更を許可せず、内部メカニズムでのみ値が設定されるインスタンス変数を持つクラスで利用されます。

この属性は、インカプセレーションを強化し、オブジェクト指向設計の原則に沿った堅牢なプログラムを作成するのに役立ちます。

また、データの整合性を保つためにも重要です。

○サンプルコード4:readonlyプロパティを使ったデータモデル

Objective-Cにおけるデータモデルでは、外部からは読み取りのみ可能ながら、クラス内部でのみデータの更新ができるようにする場合にreadonly属性が非常に有効です。

例えば、ユーザー情報を扱うモデルがあるとします。

ユーザーIDやユーザー名は一度設定すると変更されない情報として扱われる場合、これらの情報はreadonlyとして定義されるべきです。

@interface UserModel : NSObject
@property (nonatomic, readonly) NSString *userID;
@property (nonatomic, readonly) NSString *username;
- (instancetype)initWithUserID:(NSString *)userID username:(NSString *)username;
@end

@implementation UserModel
- (instancetype)initWithUserID:(NSString *)aUserID username:(NSString *)aUsername {
    self = [super init];
    if (self) {
        _userID = aUserID;   // イニシャライザ内でのみ設定可能
        _username = aUsername; // イニシャライザ内でのみ設定可能
    }
    return self;
}
@end

このコードではUserModelクラスがあり、userIDusernameの二つのプロパティを持っています。

これらのプロパティはreadonlyとして宣言されており、これによりオブジェクトが一度生成された後は、これらのプロパティを外部から変更することはできません。

しかし、クラスのイニシャライザ内では、プライベート変数_userID_usernameに直接アクセスし、値を設定しています。

これにより、オブジェクトの作成時にのみこれらの値を設定でき、その後は読み取り専用となります。

このクラスを使用するとき、外部のコードは次のようにUserModelオブジェクトのuserIDusernameを読み出すことはできますが、設定することはできません。

UserModel *user = [[UserModel alloc] initWithUserID:@"123" username:@"Alice"];
NSString *userID = user.userID;  // 読み出し可能
NSString *userName = user.username;  // 読み出し可能
// user.userID = @"456";  // コンパイルエラー: 読み出し専用プロパティへの書き込みはできない

○サンプルコード5:readonlyプロパティを活用したUIコンポーネント

UIコンポーネントを設計する際にも、readonly属性を利用することで、外部からは見た目に関するプロパティのみを提供し、内部の状態や挙動に関わる部分は隠蔽することができます。

たとえば、カスタムビューが内部的には複数の状態を持っているが、外部からはその状態の読み取りのみを許可したい場合です。

@interface CustomButton : UIButton
@property (nonatomic, readonly) BOOL isAnimating;
@end

@implementation CustomButton
- (void)startAnimation {
    _isAnimating = YES;
    // アニメーションの開始コード
}
- (void)stopAnimation {
    _isAnimating = NO;
    // アニメーションの停止コード
}
@end

この例ではCustomButtonというUIButtonのサブクラスを作成し、アニメーションの状態を表すisAnimatingプロパティを追加しています。

このプロパティはreadonlyであるため、ボタンのアニメーションが実行中かどうかを外部から確認することはできますが、直接この状態を変更することはできません。

アニメーションの開始と停止は、クラスに定義されたメソッドを通じてのみ制御できます。

上記のコードを利用すると、UIの利用者はボタンがアニメーション状態にあるかを確認することはできますが、isAnimatingフラグを直接設定することによる意図しない挙動を防ぐことができます。

●readonly属性を使ったベストプラクティス

Objective-Cでのプログラミングにおいて、readonly属性はデータの不変性を保証する上で非常に重要です。

readonly属性を持つプロパティは、そのオブジェクトが初期化された後、その値が変更されないことを外部に公表するのに有効です。

これは特に、複数のクラスやモジュール間でデータを共有する場合に、誤ってデータを変更するリスクを減らすために役立ちます。

readonly属性を効果的に使うことで、プログラムはより安全で、デバッグが容易で、他の開発者にとっても理解しやすいコードになります。

この属性を使う際のベストプラクティスとしては、次のような手法が挙げられます。

最初に、readonlyプロパティは公開インターフェースで宣言し、クラスの実装ファイル内でreadwriteのプロパティを拡張することで内部的に変更可能にするという手法です。

これにより、クラスの外部からは値を変更できない一方で、クラスの内部のメソッドでは値の更新が可能になります。

また、readonlyプロパティの値が設定されるタイミングは、主にクラスの初期化時です。

しかし、場合によっては、他の方法で後から値を設定する必要があるかもしれません。

このような場合には、プライベートセットメソッドをクラスの内部に作成するか、あるいはカテゴリやクラス拡張を使用してreadonlyプロパティをオーバーライドすることが可能です。

readonly属性の正しい使用法を理解し、適切な場面でのみ値を変更することは、プログラムの予測可能性を高めるために重要です。

それでは、Objective-Cでreadonly属性をどのように最適に使用するか、実際のサンプルコードを通して見ていきましょう。

○サンプルコード6:効率的なreadonlyプロパティの利用法

ここでは、Objective-Cにおけるreadonlyプロパティの利用法を示す具体的なコード例を解説します。

このコードでは、readonlyプロパティをクラスの外部からアクセス不可能にしつつ、クラスの内部でのみ変更を許可する方法を表しています。

// MyClass.h ファイル
@interface MyClass : NSObject

@property (nonatomic, readonly) NSString *importantData;

@end

// MyClass.m ファイル
@interface MyClass ()

@property (nonatomic, readwrite) NSString *importantData;

@end

@implementation MyClass

- (instancetype)initWithImportantData:(NSString *)data {
    self = [super init];
    if (self) {
        _importantData = data;
    }
    return self;
}

- (void)updateImportantDataInternaly {
    _importantData = @"Updated Data";
}

@end

このコードでは、MyClass.hファイルでimportantDataプロパティをreadonlyとして公開しています。

これにより、他のクラスからimportantDataへの直接的な書き換えを防いでいます。

しかし、MyClass.mファイル内のクラスのプライベートインターフェースでは、importantDataプロパティをreadwriteとして再宣言しており、これによってクラスの内部でのみ値を変更できるようになっています。

●注意点と対処法

Objective-Cのreadonly属性を理解し利用する上でいくつかの注意点があります。

まず、readonly属性を持つプロパティは、外部からの値の変更が制限されていることを意味します。

これは、その値がインスタンス化後に変更不可能であることを保証するために使われます。

しかし、内部的には変更可能なケースがあるため、開発者はこれを誤解してはなりません。

たとえば、readonlyプロパティは、初期設定後には変更されない「イミュータブル」な設定として使用することが一般的です。

しかし、クラスの内部メソッドまたはイニシャライザ内で、値をセットすることは可能です。

この機能を適切に使用しないと、オブジェクトの状態が予期せず変更される可能性があります。

また、readonlyプロパティの値は、通常はセッターメソッドを通じては変更できませんが、非公式な手段を使って強制的に変更することは技術的に可能です。

このような操作はオブジェクトの一貫性を損なうため、通常は避けるべきです。

さらに、readonlyプロパティは多くの場合、スレッドセーフではありません。

そのため、複数のスレッドが同時にそのプロパティの値を読み出そうとすると問題が発生することがあります。

この点に注意し、必要に応じて適切なスレッド同期メカニズムを実装する必要があります。

○readonly属性を使用する際の共通の落とし穴

Objective-Cでreadonly属性を持つプロパティを利用する際によくある問題の一つに、読み取り専用プロパティが誤って変更されてしまうケースがあります。

これを防ぐためには、プロパティが本当に変更不可能であることを確認し、クラスの設計を慎重に行う必要があります。

また、クラスの外部からアクセスできるインターフェースと内部実装の間で適切な情報隠蔽を行うことが重要です。

プログラマが頻繁に遭遇するもう一つの問題は、readonlyプロパティが正しくメモリ管理されていないことです。

Objective-Cにおいてメモリ管理は特に注意を要する部分であり、retainやcopy、autoreleaseの概念を正しく理解し適用する必要があります。

不適切なメモリ管理は、メモリリークや野良ポインタを引き起こし、最終的にはアプリケーションのクラッシュにつながりかねません。

○サンプルコード7:readonlyプロパティにおけるメモリ管理

Objective-Cでのメモリ管理は、特にreadonlyプロパティを扱う際には、ARC(Automatic Reference Counting)を使用することが望ましいですが、ARCが利用できない環境や、古いコードベースで作業している場合は、手動でメモリ管理を行う必要があります。

ここでは、readonlyプロパティの宣言と、内部での安全な値の変更方法を表すサンプルコードを紹介します。

// MyClass.h
@interface MyClass : NSObject
@property (nonatomic, readonly, strong) NSString *immutableString;
@end

// MyClass.m
#import "MyClass.h"

@interface MyClass ()
@property (nonatomic, readwrite, strong) NSString *immutableString; // 内部でのreadwriteオーバーライド
@end

@implementation MyClass

- (id)initWithString:(NSString *)aString {
    self = [super init];
    if (self) {
        _immutableString = [aString copy]; // イニシャライザ内での値の設定
    }
    return self;
}

- (void)dealloc {
    [_immutableString release]; // メモリ管理のためのdealloc
    [super dealloc];
}

@end

このコードでは、ヘッダーファイル(MyClass.h)でimmutableStringプロパティをreadonlyとして公開しています。

実装ファイル(MyClass.m)では、クラスの拡張(クラスのカテゴリー部分)でreadonlyプロパティをreadwriteにオーバーライドし、値の変更を可能にしています。

initメソッドでは、immutableStringプロパティに対してcopyを使用し、引数として受け取ったaStringの値をコピーして設定しています。

これにより、immutableStringが保持する文字列データは、元のaStringオブジェクトのライフサイクルから独立し、MyClassのインスタンスが解放されるまで保持されます。

deallocメソッドでは、不要になったimmutableStringプロパティをreleaseしています。

このサンプルコードを実行すると、immutableStringは外部からの変更はできませんが、MyClassのインスタンスが割り当てられるときに一度だけセットされ、その後は読み取り専用の値として振る舞います。

また、メモリ管理の観点からも、immutableStringが適切に管理されていることがわかります。

●カスタマイズ方法

Objective-Cでのプログラミングにおいて、readonly属性は、プロパティが外部から直接変更されないように保護するために使用されます。

カスタマイズ方法を理解することは、アプリケーションのデータ整合性を保ちつつ、特定の場面での柔軟性を高めることに役立ちます。

カスタムセッターやゲッターの実装は、readonly属性のあるプロパティにカスタマイズされた動作を組み込む際に特に重要です。

ここでは、カスタマイズ可能なアプローチについて、サンプルコードを交えながら解説していきます。

○サンプルコード8:readonlyプロパティのカスタムセッターの実装

Objective-Cでは、プロパティの振る舞いをカスタマイズすることで、そのプロパティの利用方法を細かくコントロールできます。

ここでは、readonlyプロパティを持つクラスにカスタムセッターを実装する方法を紹介します。

@interface MyClass : NSObject
@property (nonatomic, strong, readonly) NSString *myProperty;
- (void)customSetter:(NSString *)newValue;
@end

@implementation MyClass {
    NSString *_myPropertyInternal;
}

- (id)init {
    self = [super init];
    if (self) {
        _myPropertyInternal = @"Initial Value";
    }
    return self;
}

- (NSString *)myProperty {
    return _myPropertyInternal;
}

- (void)customSetter:(NSString *)newValue {
    // カスタムロジックをここに記述する。
    // たとえば、新しい値が特定の条件を満たす場合のみ_updatePropertyを呼び出すなど。
    if ([newValue length] > 0) { // 新しい値が空でない場合のみ更新を許可
        _myPropertyInternal = newValue;
        // 必要ならば、プロパティが変更されたことを他のクラスに通知する。
    }
}

@end

このコードでは、MyClass というクラスに myProperty というreadonlyプロパティが定義されています。

実際の値は _myPropertyInternal という内部変数に保存され、外部から直接変更することはできません。

カスタムセッターメソッド customSetter: を通じてのみ、myProperty の値を変更することができます。

この例では、新しい値が空文字列でない場合に限り、プロパティを更新しています。

まとめ

Objective-Cのreadonly属性は、プログラマがプロパティの読み取り専用のアクセスを実現するために使用されます。

この属性を使用すると、プロパティの値はそのクラスのメソッド内か、またはイニシャライザでのみ設定でき、外部からは変更できなくなります。

readonly属性は、不変性を持たせたい時や、外部からのアクセスを厳密に制御したい場合に便利です。

たとえば、外部クラスがオブジェクトの内部状態を変更することを防ぎたいときや、一度設定されたら変更されたくない設定値などを扱う場合に有効です。

Objective-Cにおけるreadonly属性の理解は、プロパティの保護という面だけでなく、クラス設計の上でも非常に重要です。

この属性を使いこなすことで、安全かつ効率的なコードを書くための一歩を踏み出すことができるでしょう。