iVarを「最新の」Objective-Cのどこに置くか?


82

RayWenderlichによる本「iOS6byTutorials」には、より「現代的な」Objective-Cコードの記述に関する非常に優れた章があります。あるセクションでは、本はiVarをクラスのヘッダーから実装ファイルに移動する方法を説明しています。すべてのiVarはプライベートである必要があるため、これは正しいことのようです。

しかし、これまでのところ、私はそうするための3つの方法を見つけました。誰もがそれを異なってやっています。

1.)中括弧のブロック内の@implementantionの下にiVarsを配置します(これは本で行われている方法です)。

2.)中括弧のブロックなしでiVarsを@implementantionの下に置きます

3.)iVarsを@implementantion(クラス拡張)の上のプライベートインターフェイス内に配置します

これらのソリューションはすべて正常に機能しているようで、これまでのところ、アプリケーションの動作に違いは見られません。それを行う「正しい」方法はないと思いますが、いくつかのチュートリアルを作成する必要があり、コードに対して1つの方法のみを選択したいと思います。

どちらに行けばいいですか?

編集:ここではiVarについてのみ話します。プロパティではありません。オブジェクトがそれ自体にのみ必要とし、外部に公開されるべきではない追加の変数のみ。

コードサンプル

1)

#import "Person.h"

@implementation Person
{
    int age;
    NSString *name;
}

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

2)

#import "Person.h"

@implementation Person

int age;
NSString *name;


- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

3)

#import "Person.h"

@interface Person()
{
    int age;
    NSString *name;
}
@end

@implementation Person

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

4
「正しい」についてのコメントはありませんが、別のオプション(私が向かっているようです)は、iVarを完全にダンプし、すべてをプロパティにします(主に.mファイルのクラス拡張子内)。一貫性があると、状態の実装の詳細について考える必要がなくなります。
フィリップミルズ

1
この質問を更新して、3つのオプションのそれぞれを示す実際のコード例を表示すると、この質問は他の人に本当に役立ちます。私は個人的にのみ、オプション1を使用するオプション3を見てきましたが、私はオプション2に精通していないよ
rmaddy

1
マディありがとう。サンプルコードをいくつか追加しました。
TalkingCode 2012年

4
申し訳ありませんが、更新に気づきました。オプション2は無効です。それはivarを作成しません。これにより、ファイルのグローバル変数が作成されます。そのクラスのすべてのインスタンスは、その1セットの変数を共有します。インスタンス変数ではなく、クラス変数にすることができます。
rmaddy 2012年

回答:


162

インスタンス変数を@implementationブロックまたはクラス拡張に配置する機能は、iOSのすべてのバージョンおよび64ビットMac OSXプログラムで使用される「最新のObjective-Cランタイム」の機能です。

32ビットのMacOS Xアプリを作成する場合は、インスタンス変数を@interface宣言に含める必要があります。ただし、32ビットバージョンのアプリをサポートする必要はない可能性があります。OS Xは、5年以上前にリリースされたバージョン10.5(Leopard)以降、64ビットアプリをサポートしています。

したがって、最新のランタイムを使用するアプリのみを作成していると仮定しましょう。ivarをどこに置くべきですか?

オプション0 :(@interfaceしないでください)

まず、インスタンス変数を宣言に入れたくない理由を見みましょう@interface

  1. インスタンス変数を@interface公開に入れると、実装の詳細がクラスのユーザーに公開されます。これにより、それらのユーザー(独自のクラスを使用している場合でも!)は、実装の詳細に依存するべきではないことになります。(これは、ivarを宣言するかどうかとは関係ありません@private。)

  2. インスタンス変数をに@interface入れると、コンパイルに時間がかかり.mます。これは、ivar宣言を追加、変更、または削除するたびに、インターフェイスをインポートするすべてのファイルを再コンパイルする必要があるためです。

したがって、インスタンス変数をに入れたくありません@interface。どこに置けばいいの?

オプション2:@implementation中括弧なし(しないでください)

次に、オプション2「中括弧のブロックなしでiVarを@implementantionの下に置く」について説明しましょう。これはインスタンス変数を宣言しませ!あなたはこれについて話している:

@implementation Person

int age;
NSString *name;

...

そのコードは2つのグローバル変数を定義します。インスタンス変数は宣言しません。

グローバル変数が必要な場合は、.mファイル内であっても、ファイル内でグローバル変数を定義する@implementationことは問題ありません。たとえば、すべてのインスタンスでキャッシュなどの状態を共有する必要があるためです。ただし、このオプションはivarを宣言しないため、このオプションを使用してivarを宣言することはできません。(また、実装にプライベートなグローバル変数は通常static、グローバル名前空間を汚染してリンク時エラーのリスクを回避するために宣言する必要があります。)

それはあなたのオプション1と3を残します。

オプション1:@implementation中括弧付き(Do It)

通常、オプション1を使用@implementationします。次のように、中かっこでメインブロックに配置します。

@implementation Person {
    int age;
    NSString *name;
}

これらをここに配置するのは、それらの存在を非公開に保ち、前述の問題を防止し、通常、クラス拡張に配置する理由がないためです。

では、いつオプション3を使用して、それらをクラス拡張に配置するのでしょうか。

オプション3:クラス拡張内(必要な場合にのみ実行)

それらをクラスと同じファイルのクラス拡張子に入れる理由はほとんどありません@implementation@implementationその場合は、それらを入れた方がよいでしょう。

ただし、ソースコードを複数のファイルに分割するのに十分な大きさのクラスを作成する場合があります。カテゴリを使用してそれを行うことができます。たとえば、UICollectionView(かなり大きなクラスを)実装している場合、再利用可能なビュー(セルと補足ビュー)のキューを管理するコードを別のソースファイルに配置することを決定する場合があります。これらのメッセージをカテゴリに分類することで、これを行うことができます。

// UICollectionView.h

@interface UICollectionView : UIScrollView

- (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
@property (nonatomic, retain) UICollectionView *collectionViewLayout;
// etc.

@end

@interface UICollectionView (ReusableViews)

- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;

- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;

- (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;
- (id)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;

@end

これで、でメインUICollectionViewメソッドをUICollectionView.m実装でき、UICollectionView+ReusableViews.mで再利用可能なビューを管理するメソッドを実装できるようになりました。これにより、ソースコードがもう少し管理しやすくなります。

ただし、再利用可能なビュー管理コードには、いくつかのインスタンス変数が必要です。これらの変数はのメインクラス@implementationに公開する必要があるUICollectionView.mため、コンパイラはそれらを.oファイルに出力します。また、これらのインスタンス変数をのコードに公開してUICollectionView+ReusableViews.m、これらのメソッドがivarを使用できるようにする必要もあります。

ここでクラス拡張が必要です。reusable-view-managementivarをプライベートヘッダーファイルのクラス拡張子に入れることができます。

// UICollectionView_ReusableViewsSupport.h

@interface UICollectionView () {
    NSMutableDictionary *registeredCellSources;
    NSMutableDictionary *spareCellsByIdentifier;

    NSMutableDictionary *registeredSupplementaryViewSources;
    NSMutableDictionary *spareSupplementaryViewsByIdentifier;
}

- (void)initReusableViewSupport;

@end

このヘッダーファイルをライブラリのユーザーに出荷することはありません。これらのivarを表示する必要があるすべてのものがそれらを表示できるようUICollectionView.mUICollectionView+ReusableViews.m、インポートするだけです。また、mainメソッドが再利用可能なビュー管理コードを初期化するために呼び出すメソッドを投入しました。私たちは、からそのメソッドを呼び出しますで、私たちはそれを実装します。init-[UICollectionView initWithFrame:collectionViewLayout:]UICollectionView.mUICollectionView+ReusableViews.m


4
オプション3:1)独自の実装内でもivarのプロパティの使用を強制する場合(@property int age)、2)読み取​​り専用のパブリックプロパティをオーバーライドする場合(@property(readonly)int age)にも使用できます。ヘッダーで、コードが実装の読み取り/書き込みとしてプロパティにアクセスできるようにします(@property(readwrite)intage)。これを行う他の方法がない限り、@ rob mayoff?
リーン2013

@leannereadonlyプロパティをオーバーライドして非公開にreadwriteする場合は、クラス拡張を使用する必要があります。しかし、問題は、プロパティ宣言をどこに置くかではなく、ivarをどこに置くかについてでした。クラス拡張を使用すると、実装がivarにアクセスできなくなる方法がわかりません。これを行うには、メソッドの実装をカテゴリに分類する必要があります。コンパイラは、を認識する前に、すべてのivar宣言クラス拡張を確認する必要があるため@implementationです。
rob mayoff 2013

@rob mayoff、trueおよびtrue-有効なポイントを作成します。Holliは、プロパティはここでは問題にならないと指定し、「強制」はそれらの使用に関して私の側で少し強かったです。とにかく、直接アクセスするのではなく、プロパティを使用してivarにアクセスしたい場合は、それがその方法です。もちろん、「_」(_ age = someAge)を使用して直接ivarにアクセスすることもできます。多くの人が一緒に使用しているので、ivarsについて議論するときにプロパティの使用について言及する価値があると思ったので、コメントを追加しました。これがその方法です。
リーン2013

それは「オプション0:@interface(Do n't Do It)」です。
rob mayoff 2015

5

オプション2は完全に間違っています。これらはグローバル変数であり、インスタンス変数ではありません。

オプション1と3は基本的に同じです。まったく違いはありません。

選択は、インスタンス変数をヘッダーファイルに配置するか実装ファイルに配置するかです。ヘッダーファイルを使用する利点は、インスタンス変数とインターフェイス宣言を表示および編集するためのすばやく簡単なキーボードショートカット(XcodeではCommand + Control + Up)があることです。

欠点は、クラスのプライベートな詳細をパブリックヘッダーで公開することです。特に他の人が使用するコードを書いている場合は、これは望ましくない場合があります。もう1つの潜在的な問題は、Objective-C ++を使用している場合、ヘッダーファイルにC ++データ型を入れないようにすることをお勧めします。

実装インスタンス変数は特定の状況に最適なオプションですが、ほとんどのコードでは、Xcodeで作業するコーダーとしての方が便利であるという理由だけで、インスタンス変数をヘッダーに配置しています。私のアドバイスは、あなたがあなたにとってより便利だと思うことは何でもすることです。


4

主に、サブクラスに対するivarの可視性と関係があります。サブクラスは、で定義されたインスタンス変数にアクセスできなくなります@implementationブロックで。

配布する予定の再利用可能なコード(ライブラリまたはフレームワークコードなど)で、インスタンス変数を公開検査に公開したくない場合は、実装ブロック(オプション1)にivarを配置する傾向があります。


3

インスタンス変数は、実装の上のプライベートインターフェイスに配置する必要があります。オプション3。

これについて読むべきドキュメントは、Programming inObjective-Cガイドです。

ドキュメントから:

プロパティなしでインスタンス変数を定義できます

値または別のオブジェクトを追跡する必要があるときはいつでも、オブジェクトのプロパティを使用することをお勧めします。

プロパティを宣言せずに独自のインスタンス変数を定義する必要がある場合は、次のように、クラスインターフェイスまたは実装の上部にある中括弧内にそれらを追加できます。


1
私はあなたに同意します:あなたがivarを定義しようとしていたなら、プライベートクラス拡張は最高の場所です。ただし、不思議なことに、参照するドキュメントは、ivarを定義する場所を示していないだけでなく(3つの選択肢すべてを示しているだけです)、さらに進んで、ivarをまったく使用しないことを推奨しています。むしろ「プロパティを使用するのがベストプラクティスです」。それは私がこれまでに見た中で最高のアドバイスです。
ロブ

1

パブリックivarは、実際には@interfaceでプロパティとして宣言する必要があります(おそらく1で考えていることです)。最新のXcodeを実行していて、最新のランタイム(64ビットOS XまたはiOS)を使用している場合、プライベートivarは、クラス拡張ではなく@implementation(2)で宣言できます。 3で考え直します。

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