Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Contact ADC

< 前ページ次ページ >

プロパティの宣言と実装

プロパティには、宣言と実装という2つの部分があります。

このセクションの内容:

プロパティの宣言
プロパティの実装ディレクティブ
プロパティ宣言属性
プロパティの再宣言
パフォーマンスとスレッド
マークアップと破棄
Core Foundation


プロパティの宣言

プロパティの宣言はキーワード@propertyから始まります。@propertyは、クラスの@interfaceにあるメソッド宣言リスト内の任意の場所に置くことができます。@propertyは、プロトコルやカテゴリの宣言の中にも置くことができます。

@property(attributes) type name;

@propertyはプロパティを宣言します。オプションの括弧内の属性セットは、格納方法のセマンティクスやプロパティのその他の振る舞いについて、追加情報を指定します。可能な値については、プロパティ宣言属性を参照してください。Objective-Cの他の型と同様に、各プロパティには型指定と名前があります。

プロパティの実装ディレクティブ

@implementationブロックで@synthesizeディレクティブと@dynamicディレクティブを使って、特定のコンパイラの動作が行われるように指定できます。このディレクティブは、@property宣言ではどちらも必須ではありません。

重要: デフォルト値は@dynamicです。したがって、特定のプロパティに@synthesize@dynamicを指定しない場合は、そのプロパティのgetterメソッドとsetterメソッド(readonlyプロパティの場合はgetterのみ)の実装を用意する必要があります。

@synthesize

@implementationブロック内でsetterメソッドとgetterメソッドを指定しない場合に、そのプロパティのsetterメソッドとgetterメソッドを合成する必要があることをコンパイラに伝えるには@synthesizeキーワードを使います。

リスト 4-2  @synthesizeの使用

@interface MyClass : NSObject
{
    NSString *value;
}
@property(copy, readwrite) NSString *value;
@end
 
// ガーベジコレクションの使用を想定
@implementation MyClass
@synthesize value;
@end

次に示すように、property=ivarの形式を使って、プロパティに対して特定のインスタンス変数を使用する必要があることを指示できます。

@synthesize firstName, lastName, age = yearsOld;

これは、firstNamelastNameageに対するアクセサメソッドを合成して、プロパティageをインスタンス変数yearsOldによって表すことを指定しています。合成されたメソッドの残りの側面は、オプションの属性によって決まります(プロパティ宣言属性を参照)。

次のようにランタイムに依存する動作に関していくつか相違があります(ランタイムの相違を参照)。

@dynamic

プロパティによって暗黙に示されるAPIコントラクトを満たすために、メソッド実装を直接提供したり、コードの動的なロードや動的なメソッド解決など、ほかのメカニズムを使って実行時に用意することをコンパイルに伝えるには、@dynamicキーワードを使います。リスト 4-3の例は、直接的なメソッド実装の使用を示しています。これはリスト 4-2で取り上げた例と同じです。

リスト 4-3  @dynamicと直接的なメソッド実装の使用

@interface MyClass : NSObject
{
    NSString *value;
}
@property(copy, readwrite) NSString *value;
@end
 
// ガーベジコレクションの使用を想定
@implementation MyClass
@dynamic value;
 
- (NSString *)value {
    return value;
}
- (void)setValue:(NSString *)newValue {
    if (newValue != value) {
        value = [newValue copy];
    }
}
@end

プロパティ宣言属性

必要に応じて、@property(attributes)の形式を使ってプロパティを属性で装飾することができます。コンパイラはこれらの属性を使ってメソッドとインスタンス変数の合成を管理します(インスタンス変数の合成は32ビットの「フラジャイルなインスタンス変数」のランタイムではサポートされません)。ガーベジコレクションを使っている場合は、プロパティの宣言の中でストレージ修飾子の__weak__strongを使用できますが、これらは属性のリストの正式な部分ではありません。

メソッドと同様に、プロパティの有効範囲はそれを囲んでいるインターフェイス宣言内です。コンマ区切りの変数名リストを使うプロパティ宣言の場合、プロパティ属性は名前付きプロパティのすべてに適用されます。

getter=getterName, setter=setterName

getter=setter=はそれぞれ、プロパティのgetおよびsetアクセサの名前を指定します。デフォルトの名前は、キー値コーディングの表記規約に従います(『Key-Value Coding Programming Guide』を参照)。

getterはプロパティの型と一致する型を返し、引数は取りません。setterメソッドはプロパティの型と一致する型の引数を1つ取り、voidを返します。

readonly

プロパティが読み取り専用であることを示します。デフォルトは、読み取り/書き込みが可能です。

ドット構文を使って値を代入しようとすると、コンパイラエラーが発生します。@implementationではgetterメソッドだけが必須であり、@synthesizeを使う場合は、getterメソッドだけが合成されます。

readwrite

プロパティを読み取り/書き込み可能として扱うべきであることを示します。これはデフォルトで適用されます。

@implementationではgetterとsetterの両方のメソッドが必須であり、@synthesizeを使う場合はgetterメソッドとsetterメソッドが合成されます。

assign

setterで単純代入を使用することを指定します。これはデフォルトで適用されます。

このキーワードを使う場合、アプリケーションはガーベジコレクション(GC)を使っていないと、単純代入は適切な振る舞いではなくなるためコンパイラ警告が発生します。GCでないアプリケーションの場合は、オブジェクトへの警告を避けるために、格納方法の属性の1つを明示的に指定する必要があります。

GCを使わないアプリケーションで変数がNSCopyingプロトコルを採用していると、その状況でのassignの使用は通常は適切でないため、この属性に対して警告が発生します。

retain

代入時にオブジェクトに対してretainを呼び出す必要があることを指定します。

この属性はObjective-Cオブジェクトに対してのみ有効です (Core Foundationオブジェクトに対してはretainは指定できません。詳しくはCore Foundationを参照してください)。

copy

代入にオブジェクトのコピーを使用することを指定します。

コピーは、copyメソッドを呼び出すことによって作成されます。この属性はオブジェクト型に対してのみ有効であり、その場合はNSCopyingプロトコルを実装する必要があります。

nonatomic

合成されるアクセサが非アトミックになるように指定します。

デフォルトでは、合成されるアクセサはすべてアトミックです。これは、マルチスレッド環境でプロパティへの堅牢なアクセスを可能にすることを意図しています。つまり、getterから返される値やsetterを通じて設定される値は、ほかのスレッドが同時に実行しているかどうかに関係なく必ず完全に取得または設定されます。詳細については、パフォーマンスとスレッドを参照してください。

インターフェイス属性のgetter=setter=readonlyreadwriteは、クラス、カテゴリ、プロトコルの宣言で使用できます。assignretaincopyは相互に排他的な操作です。readonlyreadwriteは、属性リストではどちらか一方しか使用できません。setter=/getter=はどちらもオプションであり、readonly以外のすべての属性で使用できます。プロパティがreadonlyの場合にsetter=でsetterも指定すると、コンパイラ警告が発生します。

コンパイラにアクセサメソッドの作成を指定する(@synthesizeディレクティブを使う)場合、生成されるコードはキーワードによって指定された仕様に合致します。アクセサメソッドを自身で実装する場合は、そのアクセサメソッドが仕様に合致していることを確認する必要があります(たとえばcopyを指定した場合は、setterメソッドで入力値をコピーしていることを確認する必要があります)。

プロパティの再宣言

サブクラスでプロパティを再宣言できますが、(1つの例外を除き)プロパティの属性をそのサブクラス全体で繰り返す必要があります。あるカテゴリまたはプロトコルで宣言されたプロパティについても同じことがいえます。つまりあるカテゴリまたはプロトコルでプロパティが再宣言されているときには、そのプロパティの属性を全体で繰り返す必要があります。

1つの例外はreadonlyreadwriteです。readonlyとして宣言したプロパティを、カテゴリ、プロトコル、サブクラスでreadwriteとして再宣言できます。詳しくはプロパティを使ったサブクラス化を参照してください。カテゴリの再宣言の場合、@synthesize文よりも前に再宣言されたプロパティは、setterが合成されます。これにより2つの一般的な実装パターンが可能になります。すなわち不変クラスの可変サブクラス(NSStringNSArrayNSDictionaryなど)と、パブリックAPIがreadonlyであっても、クラスの内部に対してはプライベートなreadwrite実装を有するプロパティです。

パフォーマンスとスレッド

独自のメソッド実装を用意する場合、プロパティを宣言したという事実は、効率化やスレッドの安全性にはまったく影響しません。

合成プロパティを使う場合、コンパイラによって生成されるメソッドの実装は、指定する仕様によって異なります。パフォーマンスとスレッド処理に影響する宣言の属性は、retainassigncopynonatomicです。最初の3つは、下記に示すようにsetメソッドの代入部分の実装にのみ影響します(実装は正確にこのとおりでない場合もあります)。

// assign
property = newValue;
 
// retain
if (property != newValue)
{
    [property release];
    property = [newValue retain];
}
 
// copy
if (property != newValue)
{
    [property release];
    property = [newValue copy];
}

nonatomic属性の影響は環境によって異なります。デフォルトでは、合成されるアクセサはすべてアトミックです。マネージドメモリ環境では、アトミックな動作を保証するにはロックを使用する必要があります。また、返されるオブジェクトは保持され自動解放されます。このようなアクセサが頻繁に呼び出されると、アトミックな動作はパフォーマンスに重大な影響をもたらすことがあります。ガベージコレクトされる環境では、ほとんどの合成メソッドはこうしたオーバーヘッドなしでアトミックになります。

アトミックな実装の目的は堅牢なアクセサを提供することであり、コードの正確性を保証することではないことを理解することが重要です。「アトミック」とは、プロパティへのアクセスがスレッドセーフであるという意味ですが、単にクラス内のすべてのプロパティをアトミックにするだけでそのクラス(一般にはオブジェクトグラフ)が「スレッドセーフ」になるということではありません。スレッドの安全性を個々のアクセサメソッドのレベルで表現することはできません。マルチスレッドの詳細については、『Multithreading Programming Topics』を参照してください。

マークアップと破棄

プロパティはCスタイルのデコレータをすべてサポートします。次の例に示すように、プロパティを破棄して__attribute__スタイルのマークアップをサポートすることができます。

@property CGFloat x
AVAILABLE_MAC_OS_X_VERSION_10_1_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_4;
@property CGFloat y __attribute__((...));

Core Foundation

プロパティの実装ディレクティブで説明したとおり、非オブジェクト型に対してretain属性を指定することはできません。このため、次の例に示すように型がCFTypeのプロパティを宣言してアクセサを合成すると、

@interface MyClass : NSObject
{
    CGImageRef myImage;
}
@property(readwrite) CGImageRef myImage;
@end
 
@implementation MyClass
@synthesize myImage;
@end

マネージドメモリ環境では生成されたsetアクセサは、インスタンス変数に単に新しい値を代入します(新しい値は保持されず、古い値は解放されません)。これは通常は適切でないため、メソッドを合成せずに自分自身でメソッドを実装する必要があります。

ガーベジコレクトされる環境では、変数がstrongとして宣言されている場合、

...
__strong CGImageRef myImage;
...
@property CGImageRef myImage;

アクセサは適切に合成されます。つまりこのイメージはCFRetainされず、setterは書き込みバリアを実行します。

ここではプロパティの使用例をいくつか示します。

リスト 4-4  クラスに対するプロパティの宣言

@protocol Link
@property id <Link> next;
@end
 
 
@interface MyClass : NSObject <Link>
{
    NSTimeInterval intervalSinceReferenceDate;
    CGFloat gratuitousFloat;
    id <Link> nextLink;
}
@property(readonly) NSTimeInterval creationTimestamp;
@property(copy) __strong NSString *name;
@property CGFloat gratuitousFloat;
@property(readonly, getter=nameAndAgeAsString) NSString *nameAndAge;
 
@end
 
 
@implementation MyClass
 
@synthesize creationTimestamp = intervalSinceReferenceDate, name;
// フラジャイルなインスタンス変数のランタイムでは、'name'を合成するとエラーになる
// フラジャイルでないインスタンス変数のランタイムでは、インスタンス変数は合成される
 
@synthesize next = nextLink;
// 格納用にインスタンス変数"nextLink"を使用
 
@dynamic gratuitousFloat;
// @implementationで-gratuitousFloatと-setGratuitousFloat:が 発生しなければ警告する
 
 
- (CGFloat)gratuitousFloat
{
    return gratuitousFloat;
}
- (void)setGratuitousFloat:(CGFloat)aValue
{
    gratuitousFloat = aValue;
}
 
 
- (NSString *)nameAndAgeAsString
{
    return [NSString stringWithFormat:@"%@ (%fs)", self.name,
               [NSDate timeIntervalSinceReferenceDate] - intervalSinceReferenceDate];
}
 
 
 - init
{
    if (self = [super init])
    {
        intervalSinceReferenceDate = [NSDate timeIntervalSinceReferenceDate];
    }
    return self;
}
 
@end


< 前ページ次ページ >


Last updated: 2007-10-31




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.
Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2007 Apple Inc.
All rights reserved. | Terms of use | Privacy Notice