2012年3月30日金曜日

Objective-C のアクセス指定

Objective-C のアクセス指定がこんがらがってきたのでまとめてみます。規模は大きくならない、@public などは使わないなど切り捨てはしていますが、大体の基準としては使えると思います。といっても内容の正確性は話半分で、ということで。

参考:Apple公式日本語ドキュメント
参考:詳解 Objective-C 2.0 第3版 (to Amazon)

  • インスタンス変数については「後ろに」アンダースコアをつけるスタイルで書いています。前に入れない理由は「将来的にも余計なトラブルが避けられる」から。
  • #import など一部省略しています。
  • ARC ありき。

// File = "MyClass.h"
@interface MyClass : NSObject
{
    // デフォルトは @protected になる
    NSMutableString *name_;
    
 @private   // このクラスのみアクセスできる
    NSMutableString *namePrivate_;
    
 @protected // このオブジェクトとサブクラスからアクセスできる
    NSMutableString *nameProtected_;
    
 @public    // どこからでもアクセス可
    NSMutableString *namePublic_;
}

-(void)sayValue; // 外部からアクセス可能なメソッド
+(void)sayMaster;// クラスメソッド : MyClass alloc みたいなの。

@end

ヘッダーファイルで宣言されたメソッドは外部に公開されます。中身は .m ファイルで実装します。

ここで宣言せずに、.m ファイルに内容だけ書いた場合は外部に公開されません。ただし公開されないだけで存在するのは変わらないので呼び出しは出来てしまいます(コンパイラ警告は出る)。

C言語の範囲になりますが、static 変数とグローバル変数について復習。

// File = どこでもOK

// グローバル変数
int gloval_value = 555;       // すべてのファイルで静的に利用される。

static int staticValue = 444; // これが宣言されたファイルだけで静的に利用される。
             // (ファイルが別なら)サブクラスでは利用できない。---> クラス変数   

void printStaticValue(void)   // C言語の関数
{
    static int inDoor = 12;   // このメソッド内だけで静的に利用される
    printf("inDoor :%d", inDoor);
}

// この関数が宣言されたファイルだけで利用できる。
void static changeStaticValue(void)
{
    staticValue = staticValue * (-1);
}

「静的に利用される」ってよくわからないですね。つまりは「内容がずっと保持される」という意味です。

詳解Objective-C 2.0 曰く、クラス変数については「原則的には外部には見せないものだと考えてクラスの設計を行うべき」だそうです。もし外部からアクセスしたいならクラスメソッドのアクセサ(後述)を介して行います。

インスタンス変数へのアクセスは -> を使用して行います。

// File = "main.m"
int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        MyClass *myinstance = [[MyClass alloc] initWithName:@"AAA"];

        myinstance->namePublic_ = [NSMutableString stringWithString:@"abc"];
        NSLog(@"my public :%@", myinstance->namePublic_);
    }
    return 0;
}

また、例えば @private 指定のインスタンス変数について、クラス内で同じクラスのインスタンスに対して aInstance->value と参照することができます。

また、インスタンス変数は実装部に書くこともできます。

// File = "MyClass.m"
@implementation MyClass
{
    int valueAtImpl_;
}
@end

違いは「実装部宣言だとサブクラスからも見えなくなる」ということ(ヘッダーファイルに情報が無いから)。

オブジェクト指向の立場からは @public をつけてインスタンス変数をさらけ出すことは一般におすすめされません。そこでアクセサを使用します。Setter と Getter のこと。

// File = "MyClass.h"
@interface MyClass : NSObject
{
    int value_;// .m ファイルに書いても OK
}
-(id)initWithNumber:(int)num;
-(void)setValue:(int) aValue;// Setter 宣言
-(int)value;                 // Getter 宣言
@end
// File = "MyClass.m"
@implementation MyClass

-(id)initWithNumber:(int)num
{
    self = [super init];
    if (self != nil) {
        value_ = num;
    }
    return self;
}

-(void)setValue:(int) aValue // Setter の実装
{
    value_ = aValue;
}

-(int)value // Getter の実装
{
    return value_;
}
@end

これで以下のようにアクセスできるようになる。

// File = "main.m"
int main(int argc, const char * argv[])
{
    @autoreleasepool
    {   
        MyClass *myinstance = [[MyClass alloc] initWithNumber:12];
        
        myinstance.value = 25;
        NSLog(@"value is :%d", myinstance.value);
    }
    return 0;
}

適切なアクセサが定義されていればドット演算子で self, super のプロパティにアクセスすることもできるようになります。

// クラス内
val = self.value;
self.value = val;
val = super.value;
super.value = val;

しかしいちいち単純な Setter と Getter を書くのは面倒です(もちろん Setter で値の正当性をフィルタリングする場合などは実装は自分で書く必要があります)。とりあえずインターフェイス側を簡単にするため、宣言プロパティという仕組みを使います。

// File = "MyClass.h"
@interface MyClass : NSObject

-(id)initWithNumber:(int)num;
@property int value;

@end

「value というプロパティがある」ということを宣言する感じですかね。実装部の Setter と Getter の名前は先程作成したような名前(value & setValue:)をつけて、それがネーミングのルールにしたがっていなければいけません(ルールは省略)。

インターフェイス側の記述が簡単になりました。実装側のファイル (.m) はそのままでもOKですが、やはり1つ1つ書いていくのは面倒です。そこで、 @sythesize というキーワードを使います。

// File = "MyClass.m"
@implementation MyClass
{
    int value_;
}

@synthesize value = value_;

...
@end

この1行だけで myinstance.value という表記でインスタンス変数にアクセスできるようになります。裏側では適当な Setter と Getter が自動的に作られるようです。value_ = num; の箇所は self.value = num; と書くこともできます。なお、

@synthesize value = value_;

のイコール以下を省略出来る場合がありますが、推奨されていないようなので省略。

また、Setter で値の正当性をチェックしたり、Getter で計算で求めた値を返すような場合はやはり自前で書かなければいけません。例えば設定した値の2倍の値を返す Getter を書くとすると、以下のように @dynamic というキーワードを使用します。

// File = "MyClass.h"
@interface MyClass : NSObject

@property int value;
@property (readonly) int twoTimes;
-(id)initWithNumber:(int)num;

@end
// File = "MyClass.m"
@implementation MyClass
{
    int value_;
}
@synthesize value = value_;

@dynamic twoTimes;

-(int)twoTimes {
    return 2 * value_;
}

-(id)initWithNumber:(int)num
{
    ...
}
@end

宣言部で readonly というキーワードがありますが、これは「Getter だけしかありませんよ」という意味。この readonly のようにつけることのできるオプションは以下のようなものがあります。複数つけるならカンマ区切り。

// getter/setter 指定。実際の処理を行うメソッドを指定する。
// ただし呼び出し自体は変わらず、myinstance.value。
@property (getter=gainMaster) int value;
@property (setter=setMaster:) int value;// コロンを忘れずに

// 読み書き
@property (readonly) int value;
@property (readwrite) int value;

// 値の扱い
@property (assign) int value;
@property (retain) int value;
@property (unsafe_unretained) int value;
@property (strong) int value;
@property (weak) int value;
@property (copy) int value;

// 複数スレッド上で同時に実行されるかもしれないけど OK という意味。
@property (nonatomic) int value;

ARCを利用している場合にクラスオブジェクトに「値の扱い」のオプションを指定していないとコンパイル時に警告が表示されます。

オプションの細かな内容についてはリファレンスなどで。nonatomic は「頻繁にアクセスされるが、スレッドが競合する危険がない」場合に有効です。

追記:プライベートなアクセサを書きたい場合は実装ファイルの冒頭のクラスエクステンションに記述します。これはヘッダーファイルに書かれたものとして扱われるようです。記述例はまとめのコードで。

その他、カテゴリなどのアクセスについても情報はありますが、実装部ファイルを分割したり既存クラスを拡張したりしない限り必要ないでしょう。

それから、プライベートなメソッド名についてはサブクラスでの予期しないオーバーライドの危険性があるため、固有なプレフィックスをつけることが推奨されています。具体的には組織名を接頭語に付けるなどするらしいですが、詳しくは知りません。

まとめ。

// File = "MyClass.h"
@interface MyClass : NSObject
{
    // サブクラスでも使うならここ
    int valueSharedWithSub_;
}

// 外部に公開する値は @property キーワードでアクセサ宣言
// クラスオブジェクトはオプションを忘れずに。
@property int value;
@property(weak) MyClassB *instanceB;

-(id)initWithNumber:(int)num;
-(void)sayValue; // 外部に公開するメソッド
+(void)sayMaster;// クラスメソッド

@end
// File = "MyClass.m"
@interface MyClass ()

// クラスエクステンション部
@property (nonatomic, strong) NSString *message;

@end

@implementation MyClass
{
    // このクラスにプライベートな値はここ
    int value_;

    __weak MyClassB *instanceB_;
    __strong NSString *message_;
    // @property のオプションと適合した宣言になっていること
    // (ただし違っていても警告は出ない。)
}

// クラス変数 : 外部からアクセスさせるならクラスメソッドからのアクセサで。
static int onlyThisClass = 123;

// グローバル変数 : プレフィックス推奨。
int RACMasterValue = 555;

// 自前処理でなければ @synthesize 書いてアクセサ実装
@synthesize value = value_;
@synthesize instanceB = instanceB_;
@synthesize message = message_;

-(id)initWithNumber:(int)num
{
    // 外部公開メソッドの実装
    self = [super init];
    if (self != nil) {
        value_ = num;
    }
    return self;
}

-(void)sayValue {
    ...// 外部公開メソッドの実装
}

+(void)sayMaster {
    ...// クラスメソッドの実装
}

-(void)funcInner {
    ...// プライベートなメソッド(サブクラスも見えない)
}

@end

プライベートなメソッドで実現できるので static 関数はいれてません。外部からアクセスされないという点ではプライベートなメソッドよりも安全性は上ですが、クラス内部のインスタンス変数などにはアクセスできないので、使い所はせまいでしょう。

@public は使うべきではないと書きましたが、構造体様クラスでは @public でも良いでしょう。ARC を使っていると構造体(+ 共用体)はクラスオブジェクトのメンバを持てないという制約があるからです。もちろんそのクラスにアクセサを作っても良いですが。

それから、Setter と Getter を通すことによるオーバーヘッド(実行速度の低下)については問題にならない程度だそうです。自前処理がなければインスタンス変数にロックだけ加味されて直通状態でコンパイルされるのではないでしょうか。一般的には生産性の面からは利用すべき。


以上。


考えて書いたお陰で頭の中でまとまった感じがします。Java やらそっち系統でなれていると理解するのに時間がかかります。プライベートなメソッドは見えないけど呼び出せるというあたりの「緩さ」が Objective-C らしいといえば「らしい」のでしょうか。

間違いなどありましたら、ご指摘おねがいしマス。

0 件のコメント:

コメントを投稿