ObjectiveCでの変数の場所の宣言/定義?


113

iOSアプリと目標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に関して:プライベートインターフェイスでメソッドを宣言したいのはなぜですか?私のプライベートクラスメソッドは、インターフェイスでの宣言なしで問題なくコンパイルできるようです。それは主に読みやすさのためですか?

どうもありがとうございました!

回答:


154

あなたの混乱を理解できます。特に、最近の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を宣言しない場合は、コンパイラーによってivarが追加され、を使用しなくてもアクセサーメソッドが生成されます@synthesize

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

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

// MyClass.m
@implementation MyClass
@end

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

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

@property (nonatomic, readonly) myReadOnlyVar;

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

変数をブロック@interfaceまたは@implementationブロックの外で完全に宣言する場合と同様に、それらはプレーンなC変数であり、まったく同じように機能します。


2
すばらしい答えです!また注:stackoverflow.com/questions/9859719/...
nycynik

44

まず、@ 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


ただ疑問に思っていますが、なぜ明示的に合成しないのですか?私がコードを理解しやすいのは、特に一部のプロパティに合成アクセサがあり、一部にはカスタム実装がある場合、合成に慣​​れているためです。エクスプリシット合成に欠点はありますか?
Metabble 2012

ドキュメントとして使用する場合の問題は、実際には何もドキュメント化されないことです。合成を使用しているにもかかわらず、1つまたは両方のアクセサをオーバーライドした可能性があります。合成ラインから本当に役立つものを区別する方法はありません。文書がないことよりも悪い唯一のことは、文書を誤解させることです。はなれる。
Rob Napier

3
なぜ#6は珍しいのですか?これはプライベート変数を取得する最も簡単な方法ではありませんか?
pfrank 2013

プライベートプロパティを取得する最も簡単で最良の方法は、#5です。
Rob Napier

1
@RobNapierまだ@合成を使用する必要がある場合があります(たとえば、プロパティが読み取り専用の場合、アクセサがオーバーライドされます)
Andy

6

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

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

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

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

3&7:パブリックメソッドとプロパティの宣言、次に実装。

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


1
あなたが何かを台無しにしたとは思わないでください:)いくつかのコメント-#1&#4 esp#4を使用すると、静的ストレージ変数が表示されることがよくあります。#1では、externストレージが指定され、次に#4で割り当てられた実際のストレージが表示されることがよくあります。#2)通常、サブクラスが何らかの理由でそれを必要とする場合のみ。#5宣言するプライベートメソッドを転送する必要がなくなりました。
Carl Veazey、2012

ええ、私は自分で前方宣言をチェックしました。以前は、あるプライベートメソッドがその後に定義された別のプライベートメソッドを、前方宣言なしで呼び出すと警告を発していましたよね?警告を出さなかったとき、私はちょっと驚いた。
Metabble 2012

ええ、それはコンパイラの新しい部分です。彼らは最近、本当に多くの進歩を遂げました。
Carl Veazey、2012

6

これは、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変更しません。

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

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

ファイル: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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.