読み込み中...

Objective-Cのsetterの初心者向け15選の使い方とサンプルコード

Objective-C setterメソッドの詳細解説 Objctive-C
この記事は約28分で読めます。

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

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

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

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

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

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

はじめに

Objective-CはAppleのiOSやmacOSアプリケーション開発に使用されるプログラム言語の1つです。

この言語はC言語の上にオブジェクト指向プログラミングの機能を追加したものであり、iOSアプリケーションの開発には欠かせない存在となっています。

Objective-Cにおいては、オブジェクトのプロパティや振る舞いを定義するために「setter」と「getter」というメソッドが頻繁に利用されます。

本記事では、特に「setter」にスポットを当て、その使い方や特徴、応用例を初心者向けに詳細に解説します。

●Objective-Cとは

Objective-Cは、Smalltalkのオブジェクト指向の概念を取り入れたC言語の拡張版として、1980年代初頭にBrad CoxとTom Loveによって開発されました。

1988年にはAppleがNeXTを買収する形で、Objective-CはAppleの主要なプログラム言語となりました。

○Objective-Cの基本概念

Objective-Cは、C言語のすべての特徴を継承しつつ、オブジェクト指向プログラミングの特徴を持ち合わせています。

ここでは、Objective-Cにおける基本的な概念のいくつかを簡単に紹介します。

  1. クラスとインスタンス:クラスは設計図のようなもので、特定のオブジェクトが持つべき属性や振る舞いを定義します。インスタンスは、そのクラスから生成される具体的なオブジェクトを指します。
  2. メソッド:メソッドは、クラスが持つ機能や振る舞いを表すもので、関数と似ていますが、クラスに属する点が異なります。
  3. プロパティ:プロパティは、オブジェクトが持つ属性やデータを表すもので、変数の一種です。このプロパティの値を取得または設定するためのメソッドが「getter」と「setter」となります。
  4. メッセージ送信:Objective-Cでは、オブジェクト間のコミュニケーションは「メッセージ送信」という形で行われます。これは、あるオブジェクトが別のオブジェクトのメソッドを呼び出すという意味合いで使用されます。
  5. メモリ管理:Objective-Cには手動でのメモリ管理が求められる場面も多く、このメモリ管理の仕組みを理解することは非常に重要です。

Objective-Cの魅力は、その豊富なライブラリやフレームワークにあります。

特にAppleが提供するCocoaやCocoa Touchフレームワークは、多くの便利なクラスや機能を提供しており、これらを活用することで、効率的なアプリケーション開発が可能となります

●setterとは

Objective-Cのプログラムの中で、プロパティの値を設定する際に使われるメソッドがsetterです。

一般的には、外部からクラスの内部のプロパティにアクセスして、その値を変更するためのインターフェースとして用意されます。

○setterの役割

setterの主な役割は、オブジェクトの内部の状態やデータを外部から安全に変更するためのものです。

Objective-Cにおけるsetterは、外部から直接プロパティの変数に触れることなく、間接的に値を設定するための手段となります。

これにより、プロパティの変更時に何らかの追加処理を行ったり、不正な値の設定を防いだりすることが可能となります。

例えば、あるクラスが「年齢」というプロパティを持っているとしましょう。

この年齢の値は0以上でなければならないという制約がある場合、setterを使用することで、この制約を守るロジックを追加することができます。

○getterとの違い

setterと対になる概念がgetterです。

setterがプロパティの値を設定するためのメソッドであるのに対し、getterはプロパティの値を取得するためのメソッドです。

これらは、オブジェクト指向プログラムにおいて、データのカプセル化を実現するための基本的なツールとして使用されます。

具体的には、setterはプロパティに新しい値を設定する際に使用され、getterはプロパティの現在の値を取得する際に使用されます。

これにより、プロパティの内部的な実装やデータ構造を隠蔽し、外部からはインターフェースとしてのみアクセスすることが可能となります。

これは、プログラムの安定性を高めるための重要な考え方の一つです。

簡単に言うと、setterは「設定」、getterは「取得」という役割を担っています。

Objective-Cでは、これらのメソッドは@propertyディレクティブを使用して簡単に自動生成することができるため、煩雑な実装を避けることができます。

●setterの使い方

Objective-Cでは、オブジェクト指向プログラミングの中核をなすプロパティの管理があります。

その際に頻繁に利用されるのが「setter」というメソッドです。

ここでは、setterメソッドの基本的な使い方を、初心者の方にも理解しやすく解説いたします。

○サンプルコード1:基本的なsetterの書き方

Objective-Cにおけるsetterメソッドの基本的な書き方を、サンプルコードで紹介します。

@interface MyClass : NSObject {
    NSString *_name;
}

- (void)setName:(NSString *)name;
- (NSString *)name;

@end

@implementation MyClass

- (void)setName:(NSString *)name {
    _name = name;
}

- (NSString *)name {
    return _name;
}

@end

このコードでは、MyClassというクラスに_nameというインスタンス変数を持ち、その値を設定するsetterメソッドsetName:と取得するgetterメソッドnameを実装しています。

setterメソッドの命名規則として、プロパティ名を大文字の先頭にして「set」を前につける形となっています。

この例では、nameプロパティに対してsetName:というsetterが用意されています。

このクラスを使うと、次のような操作が行えます。

MyClass *obj = [[MyClass alloc] init];
[obj setName:@"John"];
NSString *currentName = [obj name];

この時、setName:メソッドを使って名前を”John”に設定し、その後、nameメソッドを使って現在の名前を取得しています。

○サンプルコード2:カスタムsetterの実装

setterは単に値を設定するだけでなく、設定する際の処理をカスタマイズすることも可能です。

ここでは、名前を設定する際に前後の空白を自動的に削除するカスタムsetterを実装した例を紹介します。

@interface MyClassWithTrim : NSObject {
    NSString *_nameWithTrim;
}

- (void)setNameWithTrim:(NSString *)nameWithTrim;
- (NSString *)nameWithTrim;

@end

@implementation MyClassWithTrim

- (void)setNameWithTrim:(NSString *)nameWithTrim {
    _nameWithTrim = [nameWithTrim stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}

- (NSString *)nameWithTrim {
    return _nameWithTrim;
}

@end

このコードでは、setNameWithTrim:というsetterメソッドを利用して文字列を設定する際、前後の空白を自動的に削除する処理を追加しています。

具体的には、stringByTrimmingCharactersInSet:メソッドを利用して空白文字を取り除いた値をインスタンス変数に代入しています。

このクラスを使用すると、次のような操作が可能です。

MyClassWithTrim *objTrim = [[MyClassWithTrim alloc] init];
[objTrim setNameWithTrim:@"  Alice  "];
NSString *currentNameTrim = [objTrim nameWithTrim];

ここで、setNameWithTrim:メソッドを使って名前を” Alice “(前後に空白が2つずつ)と設定しますが、取得するときには前後の空白が取り除かれて”Alice”となって返ってきます。

○サンプルコード3:setterを使った初期化

Objective-Cでのプロパティの初期化は、通常のインスタンス変数として宣言する方法と、setterを利用してプロパティを初期化する方法が存在します。

ここでは、setterを利用して初期化を行う方法に焦点を当てて解説します。

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, strong) NSString *name;

- (instancetype)initWithName:(NSString *)aName;

@end

@implementation Person

- (instancetype)initWithName:(NSString *)aName {
    self = [super init];
    if (self) {
        self.name = aName;
    }
    return self;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] initWithName:@"Taro"];
        NSLog(@"%@", person.name);
    }
    return 0;
}

このコードでは、Personというクラスを定義し、その中にnameというプロパティを持たせています。

そして、initWithName:というカスタムイニシャライザを使って、オブジェクト生成時にnameプロパティを初期化しています。

main関数内でPersonオブジェクトを生成し、名前を指定して初期化しています。

この例では、initWithName:内でsetterを使ってnameプロパティに値を設定しています。

これにより、他のメソッド内で行われる可能性のある値の検証や副作用の処理を一元化できます。

このコードを実行すると、コンソールに「Taro」と表示されます。

○サンプルコード4:setterでの値の検証

setterを使用する大きな利点の一つは、値の検証を行い、無効な値や意図しない値が設定されるのを防ぐことができる点です。

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, strong) NSString *name;

- (void)setName:(NSString *)aName;

@end

@implementation Person

- (void)setName:(NSString *)aName {
    if ([aName length] > 10) {
        NSLog(@"Error: Name is too long!");
    } else {
        _name = aName;
    }
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person new];
        person.name = @"TaroYamada";
        NSLog(@"%@", person.name);
        person.name = @"TaroYamadaTaro";
    }
    return 0;
}

このコードでは、setName:メソッド内で名前の長さを検証しています。

名前が10文字を超える場合はエラーメッセージを表示し、設定を拒否します。

このコードを実行すると、コンソールに「TaroYamada」と表示された後、「Error: Name is too long!」というメッセージが表示されます。

これにより、データの整合性を保ちつつ、無効な値の設定を防ぐことができます。

●setterの応用例

Objective-Cのsetterは、単純なプロパティの設定だけでなく、さまざまな応用例が存在します。

ここではsetterを利用して、より高度なプログラミングを行う方法を2つのサンプルコードを用いて紹介します。

○サンプルコード5:setterを使ったプロパティの変更通知

setterはプロパティの値が変更されたときに、特定のアクションをトリガーすることができます。

この特性を利用して、プロパティの変更を通知する機構を実装することができます。

#import <Foundation/Foundation.h>

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

@implementation SampleClass

- (void)setName:(NSString *)name {
    if(![_name isEqualToString:name]) {
        _name = name;
        [self nameDidChangedTo:name];
    }
}

- (void)nameDidChangedTo:(NSString *)newName {
    NSLog(@"名前が %@ に変更されました。", newName);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        SampleClass *sample = [[SampleClass alloc] init];
        sample.name = @"太郎";
        sample.name = @"次郎";
    }
    return 0;
}

このコードではSampleClassというクラスにnameというプロパティを持っています。

setterの中で、新しい名前と現在の名前が異なる場合にのみ、nameDidChangedTo:メソッドを呼び出すことで、名前の変更を通知しています。

このサンプルを実行すると、次のような結果が得られます。

名前が太郎に変更されました。
名前が次郎に変更されました。

setterを用いて、プロパティの変更時の処理を追加することで、プロパティの変更を検知しやすくなります。

○サンプルコード6:setterを使ったキャッシュ機構の実装

キャッシュは、処理の結果を一時的に保存し、再利用することで処理速度を向上させる技術です。

setterを利用することで、簡易的なキャッシュ機構を実装することができます。

#import <Foundation/Foundation.h>

@interface CacheClass : NSObject
@property (nonatomic, strong) NSString *data;
@property (nonatomic, strong) NSString *cachedProcessedData;
@end

@implementation CacheClass

- (void)setData:(NSString *)data {
    if(![_data isEqualToString:data]) {
        _data = data;
        _cachedProcessedData = nil;  // データが変更されたらキャッシュをクリア
    }
}

- (NSString *)processedData {
    if (!_cachedProcessedData) {
        _cachedProcessedData = [NSString stringWithFormat:@"Processed: %@", _data];
    }
    return _cachedProcessedData;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CacheClass *cache = [[CacheClass alloc] init];
        cache.data = @"Example";
        NSLog(@"%@", cache.processedData);
        NSLog(@"%@", cache.processedData);
        cache.data = @"New Example";
        NSLog(@"%@", cache.processedData);
    }
    return 0;
}

このコードでは、CacheClassというクラスがdataプロパティと、それを加工したprocessedDataプロパティを持っています。

dataのsetterで、データが変更されたときにキャッシュをクリアしています。

そして、processedDataを取得する際にキャッシュが存在しなければデータを加工し、キャッシュとして保存します。

このサンプルを実行すると、次のような結果が得られます。

Processed: Example
Processed: Example
Processed: New Example

setterを利用してキャッシュ機構を実装することで、不要な処理を省き、処理の効率を向上させることができます。

○サンプルコード7:setterを使ったメモリ管理

Objective-Cでは、メモリ管理は非常に重要なテーマです。

特にARC(Automatic Reference Counting)を使用しない場合、開発者が手動でメモリを管理する必要があります。

setterを使用する際も、このメモリ管理を適切に行うことが求められます。

下記のコードは、setterを使用してメモリ管理を行う基本的な方法を表しています。

@interface MyClass : NSObject {
    NSString *_name;
}

@property (nonatomic, retain) NSString *name;

@end

@implementation MyClass

- (void)setName:(NSString *)newName {
    if (_name != newName) {
        [_name release];
        _name = [newName retain];
    }
}

- (void)dealloc {
    [_name release];
    [super dealloc];
}

@end

このコードでは、nameプロパティのsetterメソッドをオーバーライドしています。

この例では、新しい値と古い値が異なる場合にのみ、古い値を解放し、新しい値を保持しています。

これにより、メモリリークや二重解放などの問題を防ぐことができます。

○サンプルコード8:setterを利用した動的なプロパティ更新

setterを使用することで、プロパティの更新時に動的な操作を追加することが可能です。

下記のコードは、setterを利用してプロパティの更新時にログを出力する例を表しています。

@interface MyClass : NSObject {
    NSInteger _age;
}

@property (nonatomic, assign) NSInteger age;

@end

@implementation MyClass

- (void)setAge:(NSInteger)newAge {
    _age = newAge;
    NSLog(@"Age has been set to %ld", (long)newAge);
}

@end

このコードでは、ageプロパティのsetterメソッドをオーバーライドしています。

この例では、ageプロパティが更新されるたびに、新しい値がログに出力されるようにしています。

○サンプルコード9:複数のプロパティを同時に更新するsetter

setterを使うことで、一つのメソッド内で複数のプロパティを同時に更新することもできます。

下記のコードは、firstNamelastNameの2つのプロパティを持つクラスで、一つのsetterメソッドで両方のプロパティを更新する例を表しています。

@interface Person : NSObject {
    NSString *_firstName;
    NSString *_lastName;
}

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;

@end

@implementation Person

- (void)setFirstName:(NSString *)first andLastName:(NSString *)last {
    _firstName = first;
    _lastName = last;
}

@end

このコードのポイントは、setFirstName:andLastName:メソッドによって、2つのプロパティを一度に設定できることです。

このようにsetterを活用することで、柔軟なコード設計が可能になります。

●注意点と対処法

Objective-Cのsetterを使用する際、避けたい問題やリスクがあります。

これらの問題を事前に理解し、それを解消するための対処法を身につけることで、安全かつ効果的にsetterを使用することができます。

○setter内での無限ループのリスク

setter内でプロパティの値を更新する際、間違って同じsetterを再度呼び出してしまうと、無限ループに陥る可能性があります。

これはアプリケーションのクラッシュを引き起こす危険性があるため、注意が必要です。

このコードでは、setName:メソッド内でself.nameのsetterを再度呼び出している例を表しています。

この例では、setName:が呼び出されると、無限ループが発生します。

- (void)setName:(NSString *)name {
    if (![self.name isEqualToString:name]) {
        self.name = name; // これは無限ループを引き起こす
    }
}

対処法として、インスタンス変数を直接参照することで無限ループを回避することができます。

- (void)setName:(NSString *)name {
    if (![_name isEqualToString:name]) {
        _name = name;
    }
}

この修正されたコードでは、_nameというインスタンス変数を直接操作しているため、無限ループは発生しません。

○メモリリークの問題と対策

Objective-Cにおいて、setterを使用する際のもう一つの大きな問題は、メモリリークです。

特にretainプロパティのsetterをカスタマイズする場合、オブジェクトの所有権の管理に注意する必要があります。

下記のコードは、retainプロパティのsetterをカスタマイズした例を表しています。

この例では、前のオブジェクトをリリースせずに新しいオブジェクトを保持してしまうため、メモリリークが発生します。

- (void)setObject:(MyObject *)object {
    _object = object;
    [_object retain];
}

この問題を解決するためには、前のオブジェクトを適切にリリースしてから新しいオブジェクトを保持するようにする必要があります。

- (void)setObject:(MyObject *)object {
    [_object release];
    _object = [object retain];
}

このコードでは、以前の_objectをリリースした後で、新しいオブジェクトをretainしています。

これにより、メモリリークを回避することができます。

○マルチスレッド環境でのsetterの取り扱い

マルチスレッド環境でのsetterの使用は、データの整合性や同時アクセスに関する問題が発生する可能性があります。

特に、同じオブジェクトのプロパティを複数のスレッドから同時に変更しようとすると、予期しない動作やデータの破損が発生する恐れがあります。

この問題を回避するためには、setter内での操作を排他制御することが考えられます。

Objective-Cでは、@synchronizedディレクティブを使用して、排他制御を実現することができます。

下記のコードは、setter内で@synchronizedディレクティブを使用して排他制御を行っている例を表しています。

この例では、同時にこのsetterが呼び出された場合でも、一度に一つのスレッドだけが処理を行うことが保証されます。

- (void)setData:(NSData *)data {
    @synchronized(self) {
        if (_data != data) {
            [_data release];
            _data = [data retain];
        }
    }
}

このコードの@synchronized(self)ブロック内では、selfオブジェクトをロックして、他のスレッドからの同時アクセスを防いでいます。

このようにして、マルチスレッド環境でのデータの整合性を保つことができます。

●カスタマイズ方法

Objective-Cのsetterメソッドは非常に便利で、多くの機能やカスタマイズが可能です。

ここでは、setterのカスタマイズ方法として、特定の動作のカスタマイズや例外処理、さらにはコールバック機能の追加について説明していきます。

○サンプルコード10:setterの動作をカスタマイズ

Objective-Cでは、setterの動作をカスタマイズすることで、特定の条件下でのプロパティの更新を制御することができます。

このコードでは、Personクラスのageプロパティのsetterをカスタマイズして、年齢が0未満にならないように制御する例を表しています。

@interface Person : NSObject {
    NSInteger _age;
}

- (void)setAge:(NSInteger)age;

@end

@implementation Person

- (void)setAge:(NSInteger)age {
    if (age < 0) {
        NSLog(@"年齢は0未満には設定できません。");
        return;
    }
    _age = age;
}

@end

この例では、setAge:メソッド内で、年齢が0未満かどうかをチェックしています。

0未満の場合、エラーメッセージを表示して、プロパティの更新を中止しています。

このコードを使用して、年齢を-1に設定しようとすると、コンソールに「年齢は0未満には設定できません。」と表示され、ageプロパティの値は変更されません。

○サンプルコード11:setterでの例外処理の追加

setterメソッド内でのエラーチェックだけでなく、例外を発生させることも可能です。

この例では、不正な値が設定された場合に例外をスローするsetterの実装方法を紹介します。

@interface Person : NSObject {
    NSString *_name;
}

- (void)setName:(NSString *)name;

@end

@implementation Person

- (void)setName:(NSString *)name {
    if ([name length] > 50) {
        @throw [NSException exceptionWithName:@"InvalidNameLengthException" reason:@"名前の長さが長すぎます。" userInfo:nil];
    }
    _name = name;
}

@end

このコードでは、setName:メソッド内で、名前の長さが50文字を超える場合、InvalidNameLengthExceptionという名前の例外をスローしています。

このコードを使用して、50文字以上の名前を設定しようとすると、アプリケーションはInvalidNameLengthException例外を発生させ、適切なエラーハンドリングが必要となります。

○サンプルコード12:setterでのコールバック機能の追加

setterの動作をカスタマイズすることで、特定の条件や状況での追加的な動作、例えばコールバックの実行なども可能となります。

ここでは、setterを使ってコールバックを実装する方法の一例を紹介します。

@interface Person : NSObject {
    NSInteger _age;
}

@property (nonatomic, copy) void (^ageChangedCallback)(NSInteger);

- (void)setAge:(NSInteger)age;

@end

@implementation Person

- (void)setAge:(NSInteger)age {
    _age = age;
    if (self.ageChangedCallback) {
        self.ageChangedCallback(age);
    }
}

@end

この例では、PersonクラスにageChangedCallbackというブロックプロパティを追加しています。

このブロックは、ageプロパティが変更されるたびに呼び出されます。

例えば、次のようにコールバックを設定し、ageプロパティを変更すると、コンソールに「年齢がXXに変わりました。」と表示されます。

Person *person = [[Person alloc] init];
person.ageChangedCallback = ^(NSInteger age) {
    NSLog(@"年齢が%ldに変わりました。", age);
};
person.age = 25;  // 「年齢が25に変わりました。」と表示される

●setterと他のプログラム言語

Objective-Cのsetterは、プロパティの値を設定するためのメソッドです。

しかし、他のプログラム言語でも類似の機能や考え方が存在します。

ここでは、Objective-Cのsetterと他のプログラム言語との比較を行い、それぞれの特徴や違いを理解することを目指します。

○Pythonとの比較

Pythonもオブジェクト指向言語の一つであり、属性の取得や設定に関するメソッドが存在します。

Pythonでは、@propertyデコレータを用いてgetterを定義し、同じ名前のメソッドに@<属性名>.setterデコレータを用いることでsetterを定義することができます。

このコードでは、Pythonにおけるgetterとsetterの実装方法を表しています。

この例では、Personクラスにnameという属性を持ち、この属性の取得や設定を行うメソッドを定義しています。

class Person:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if isinstance(value, str) and len(value) > 0:
            self._name = value
        else:
            print("無効な名前です")

p = Person("Alice")
print(p.name)  # Alice
p.name = "Bob"
print(p.name)  # Bob
p.name = 123  # 無効な名前です

このサンプルコードの実行結果は、”Alice”、”Bob”、”無効な名前です”の順に出力されます。

○JavaScriptとの比較

JavaScriptは、Webブラウザ上で動作するプログラム言語の一つです。

JavaScriptには、オブジェクトのプロパティの取得や設定を行うためのgetterとsetterという機能が存在します。

このコードでは、JavaScriptにおけるgetterとsetterの実装方法を表しています。

この例では、Personオブジェクトにnameというプロパティを持ち、このプロパティの取得や設定を行うメソッドを定義しています。

class Person {
    constructor(name) {
        this._name = name;
    }

    get name() {
        return this._name;
    }

    set name(value) {
        if (typeof value === "string" && value.length > 0) {
            this._name = value;
        } else {
            console.log("無効な名前です");
        }
    }
}

const p = new Person("Alice");
console.log(p.name);  // Alice
p.name = "Bob";
console.log(p.name);  // Bob
p.name = 123;  // 無効な名前です

このサンプルコードの実行結果は、”Alice”、”Bob”、”無効な名前です”の順に出力されます。

まとめ

Objective-Cのsetterは、プロパティの値を管理するための重要な機能です。

今回の記事では、その基本的な使い方や他のプログラム言語との比較を通じて、Objective-Cのsetterの特性を深く理解するための情報を紹介しました。

PythonやJavaScriptとの比較を行いながら、各言語の独自の特徴や取り扱い方の違いを学ぶことができると思います。

Objective-Cの開発を行う際には、setterの正しい使い方を心がけることで、より堅牢で効率的なコードの実装が可能になります。