web-dev-qa-db-ja.com

Objective Cの変数の場所の宣言/定義?

IOSアプリとObjective Cで作業を開始して以来、変数を宣言および定義できるさまざまな場所に本当に困惑していました。一方には従来のCアプローチがあり、もう一方にはOOを追加する新しいObjectiveCディレクティブがあります。これらの場所を変数に使用して、現在の理解を修正したい場合のベストプラクティスと状況を理解するのに役立ちますか?

サンプルクラス(.hおよび.m)は次のとおりです。

#import <Foundation/Foundation.h>

// 1) What do I declare here?

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

// 3) class-specific method / property declarations

@end

そして

#import "SampleClass.h"

// 4) what goes here?

@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

@implementation SampleClass
{
    // 6) define ivars
}

// 7) define methods and synthesize properties from both public and private
//    interfaces

@end
  • 1と4の私の理解は、これらはクラスの概念をまったく理解していないCスタイルのファイルベースの宣言と定義であり、Cでの使用方法を正確に使用する必要があるということです。前に静的変数ベースのシングルトンを実装するために使用されました。私が見逃している他の便利な用途はありますか?
  • IOSでの作業からの私の見解は、@ synthesizeディレクティブの外でivarが完全に段階的に廃止されたため、ほとんど無視できることです。そうですか?
  • 5に関して:なぜプライベートインターフェイスでメソッドを宣言したいのですか?私のプライベートクラスメソッドは、インターフェイスで宣言せずにコンパイルできるようです。主に読みやすさのためですか?

たくさんの人、ありがとう!

109

あなたの混乱を理解できます。特にXcodeと新しいLLVMコンパイラの最近の更新により、ivarとプロパティの宣言方法が変更されたためです。

「現代の」Objective-C(「古い」Obj-C 2.0)では、多くの選択肢がありませんでした。インスタンス変数は、中括弧{ }の間のヘッダーで宣言されていました。

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

実装でのみこれらの変数にアクセスできましたが、他のクラスからはアクセスできませんでした。そのためには、次のようなアクセサメソッドを宣言する必要がありました。

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}

- (int)myVar;
- (void)setMyVar:(int)newVar;

@end


// MyClass.m
@implementation MyClass

- (int)myVar {
   return myVar;
}

- (void)setMyVar:(int)newVar {
   if (newVar != myVar) {
      myVar = newVar;
   }
}

@end

このようにして、通常の角括弧構文を使用してメッセージを送信する(メソッドを呼び出す)ことにより、他のクラスからもこのインスタンス変数を取得および設定できました。

// OtherClass.m
int v = [myClass myVar];  // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];

すべてのアクセサメソッドを手動で宣言して実装するのは非常に面倒なので、@property@synthesizeが導入されて、アクセサメソッドが自動的に生成されました。

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@synthesize myVar;
@end

結果は、はるかに明確で短いコードです。アクセサメソッドは自動的に実装され、以前と同様にブラケット構文を使用できます。ただし、さらに、ドット構文を使用してプロパティにアクセスすることもできます。

// OtherClass.m
int v = myClass.myVar;   // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;

Xcode 4.4以降では、インスタンス変数を自分で宣言する必要がなくなり、@synthesizeもスキップできます。 ivarを宣言しない場合、コンパイラはそれを追加し、@synthesizeを使用せずにアクセサメソッドも生成します。

自動生成されたivarのデフォルト名は、アンダースコアで始まる名前またはプロパティです。 @synthesize myVar = iVarName;を使用して、生成されたivarの名前を変更できます

// MyClass.h
@interface MyClass : NSObject 
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@end

これは上記のコードとまったく同じように機能します。互換性の理由から、ヘッダーでivarを宣言できます。しかし、それを行う(プロパティを宣言しない)唯一の理由はプライベート変数を作成することであるため、実装ファイルでも同様に行うことができ、これが好ましい方法です。

実装ファイルの@interfaceブロックは、実際には Extension であり、メソッドの宣言(不要)の転送やプロパティの(再)宣言に使用できます。たとえば、ヘッダーでreadonlyプロパティを宣言できます。

@property (nonatomic, readonly) myReadOnlyVar;

実装ファイルでreadwriteとして再宣言して、ivarへの直接アクセスだけでなく、プロパティ構文を使用して設定できるようにします。

@interfaceまたは@implementationブロックの完全に外側で変数を宣言することに関しては、はい、それらは単純なC変数であり、まったく同じように機能します。

147
DrummerB

まず、@ DrummerBの答えを読んでください。理由と一般的にすべきことの概要です。それを念頭に置いて、あなたの特定の質問に:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

ここには実際の変数定義はありません(実行していることを正確に知っているが、絶対に実行しない場合は、技術的には合法です)。他のいくつかの種類のものを定義できます。

  • typdefs
  • 列挙型
  • 外部

外部は変数宣言のように見えますが、実際には別の場所で宣言する約束です。 ObjCでは、定数を宣言するためにのみ使用し、通常は文字列定数のみを使用する必要があります。例えば:

extern NSString * const MYSomethingHappenedNotification;

次に、.mファイルで実際の定数を宣言します。

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

DrummerBが指摘したように、これはレガシーです。ここには何も入れないでください。


// 3) class-specific method / property declarations

@end

うん。


#import "SampleClass.h"

// 4) what goes here?

上記の外部定数。ファイルの静的変数もここに移動できます。これらは、他の言語のクラス変数と同等です。


@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

うん


@implementation SampleClass
{
    // 6) define ivars
}

しかし、めったにありません。ほとんどの場合、clang(Xcode)が変数を作成できるようにする必要があります。例外は通常、ObjC以外のivar(Core Foundationオブジェクト、特にObjC++クラスの場合はC++オブジェクトなど)、または奇妙なストレージセマンティクスを持つivar(何らかの理由でプロパティと一致しないivarなど)に関するものです。


// 7) define methods and synthesize properties from both public and private
//    interfaces

一般的に、@ synthesizeはもうすべきではありません。 Clang(Xcode)があなたのためにそれを行います。

過去数年間で、物事は劇的に簡単になりました。副作用は、3つの異なる時代(脆弱なABI、非脆弱なABI、非脆弱なABI +自動合成)が存在することです。したがって、古いコードを見ると、少し混乱する可能性があります。したがって、単純さから生じる混乱:D

41
Rob Napier

私もかなり新しいので、うまくいけば何も台無しにしないでください。

1および4:Cスタイルのグローバル変数:ファイル全体のスコープを持ちます。 2つの違いは、ファイル幅が広いため、最初のヘッダーはヘッダーをインポートするすべてのユーザーが使用できるのに対し、2番目はヘッダーをインポートできないことです。

2:インスタンス変数。ほとんどのインスタンス変数は、プロパティを使用してアクセサを介して合成および取得/設定されます。これは、メモリ管理がわかりやすく簡単になり、わかりやすいドット表記ができるためです。

6:実装ivarはやや新しいものです。パブリックヘッダーに必要なものだけを公開したいが、サブクラスはそれらを継承しないため、プライベートivarを配置するのに適した場所です。

3と7:パブリックメソッドとプロパティの宣言、そして実装。

5:プライベートインターフェイス。私は物事をきれいに保ち、一種のブラックボックス効果を作り出すことができるときはいつでも常にプライベートインターフェイスを使用します。彼らがそれについて知る必要がなければ、そこに置いてください。私は読みやすさのためにそれをします、他の理由があるかどうかわかりません。

6
Metabble

これは、Objective-Cで宣言されたすべての種類の変数の例です。変数名はそのアクセスを示します。

ファイル:Animal.h

@interface Animal : NSObject
{
    NSObject *iProtected;
@package
    NSObject *iPackage;
@private
    NSObject *iPrivate;
@protected
    NSObject *iProtected2; // default access. Only visible to subclasses.
@public
    NSObject *iPublic;
}

@property (nonatomic,strong) NSObject *iPublic2;

@end

ファイル:Animal.m

#import "Animal.h"

// Same behaviour for categories (x) than for class extensions ().
@interface Animal(){
@public
    NSString *iNotVisible;
}
@property (nonatomic,strong) NSObject *iNotVisible2;
@end

@implementation Animal {
@public
    NSString *iNotVisible3;
}

-(id) init {
    self = [super init];
    if (self){
        iProtected  = @"iProtected";
        iPackage    = @"iPackage";
        iPrivate    = @"iPrivate";
        iProtected2 = @"iProtected2";
        iPublic     = @"iPublic";
        _iPublic2    = @"iPublic2";

        iNotVisible   = @"iNotVisible";
        _iNotVisible2 = @"iNotVisible2";
        iNotVisible3  = @"iNotVisible3";
    }
    return self;
}

@end

INotVisible変数は他のクラスからは見えないことに注意してください。これは可視性の問題であるため、@propertyまたは@publicで宣言しても変更されません。

コンストラクタ内では、副作用を避けるために、selfの代わりにアンダースコアを使用して、@propertyで宣言された変数にアクセスすることをお勧めします。

変数にアクセスしてみましょう。

ファイル:Cow.h

#import "Animal.h"
@interface Cow : Animal
@end

ファイル:Cow.m

#import "Cow.h"
#include <objc/runtime.h>

@implementation Cow

-(id)init {
    self=[super init];
    if (self){
        iProtected    = @"iProtected";
        iPackage      = @"iPackage";
        //iPrivate    = @"iPrivate"; // compiler error: variable is private
        iProtected2   = @"iProtected2";
        iPublic       = @"iPublic";
        self.iPublic2 = @"iPublic2"; // using self because the backing ivar is private

        //iNotVisible   = @"iNotVisible";  // compiler error: undeclared identifier
        //_iNotVisible2 = @"iNotVisible2"; // compiler error: undeclared identifier
        //iNotVisible3  = @"iNotVisible3"; // compiler error: undeclared identifier
    }
    return self;
}
@end

ランタイムを使用して、表示されない変数に引き続きアクセスできます。

ファイル:Cow.m(パート2)

@implementation Cow(blindAcess)

- (void) setIvar:(NSString*)name value:(id)value {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    object_setIvar(self, ivar, value);
}

- (id) getIvar:(NSString*)name {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    id thing = object_getIvar(self, ivar);
    return thing;
}

-(void) blindAccess {
    [self setIvar:@"iNotVisible"  value:@"iMadeVisible"];
    [self setIvar:@"_iNotVisible2" value:@"iMadeVisible2"];
    [self setIvar:@"iNotVisible3" value:@"iMadeVisible3"];
    NSLog(@"\n%@ \n%@ \n%@",
          [self getIvar:@"iNotVisible"],
          [self getIvar:@"_iNotVisible2"],
          [self getIvar:@"iNotVisible3"]);
}

@end

表示されない変数にアクセスしてみましょう。

ファイル:main.m

#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
    @autoreleasepool {
        Cow *cow = [Cow new];
        [cow performSelector:@selector(blindAccess)];
    }
}

これは印刷します

iMadeVisible 
iMadeVisible2 
iMadeVisible3

サブクラス専用のバッキングivar _iNotVisible2にアクセスできたことに注意してください。 Objective-Cでは、@privateとマークされているものも含め、すべての変数を読み取りまたは設定できます。例外はありません。

関連するオブジェクトやC変数は別の鳥なので、それらは含めませんでした。 C変数については、@interface X{}または@implementation X{}の外部で定義された変数は、ファイルスコープと静的ストレージを持つC変数です。

メモリ管理属性、または読み取り専用/読み取り書き込み、getter/setter属性については説明しませんでした。

5
Jano