1. ホーム
  2. objective-c

[解決済み] モダンな "Objective-C "でiVarsをどこに置くか?

2023-07-15 16:10:54

質問

Ray Wenderlich による書籍 "iOS6 by Tutorials" には、より現代的な Objective-C コードを書くための非常に素晴らしい章があります。この本のあるセクションで、iVarsをクラスのヘッダから実装ファイルに移動する方法が説明されています。 すべてのiVarsはprivateであるべきなので、これは正しいことのように思われます。

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

1.) iVarsを@implementantionの下に中括弧で囲む(本ではこうなっている)。

2.) 中括弧のない@implementantionの下にiVarsを入れる。

3.) iVarsを@implementantionの上のprivate Interfaceの中に入れる(クラスの拡張)。

これらの解決策はすべてうまくいくようで、今のところ、私のアプリケーションの動作に違いは見られません。 私は、それを行うための正しい方法はないと思いますが、いくつかのチュートリアルを書く必要があり、私のコードのための唯一の方法を選択したいと思います。

どのようにすればよいのでしょうか。

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

コードサンプル

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

どのように解決するのですか?

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

32 ビットの Mac OS X アプリケーションを書きたい場合は、インスタンス変数を @interface 宣言で指定する必要があります。 しかし、32 ビット版のアプリをサポートする必要はない可能性が高いです。 OS X は、5 年以上前にリリースされたバージョン 10.5 (Leopard) から 64 ビット アプリケーションをサポートしています。

では、最新のランタイムを使用するアプリだけを書くと仮定しましょう。 どこにアイバーを置くべきでしょうか。

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

まず、なぜ をしないのか にインスタンス変数を置きたいのか、その理由を説明します。 @interface 宣言の中にインスタンス変数を入れたくありません。

  1. インスタンス変数を @interface に置くと、そのクラスのユーザに実装の詳細が公開されます。 これは、そのようなユーザ(あなた自身のクラスを使っているときでさえも!)を、彼らがすべきでない実装の詳細に依存させるかもしれません。 (このことは、私たちがアイバー @private .)

  2. インスタンス変数を @interface に置くと、コンパイルに時間がかかります。なぜなら、ivar 宣言を追加、変更、削除するたびに、すべての .m ファイルを再コンパイルする必要があるからです。

ですから、インスタンス変数を @interface . どこに置けばいいのでしょうか?

オプション2: ファイル中の @implementation を中括弧なしで書く (Don't Do It)

次に、選択肢2「iVarsを@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 に実装することができ、ソースコードがもう少し管理しやすくなります。

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

ここで、クラス拡張が必要になります。 プライベートヘッダーファイルのクラス拡張にreusable-view-managementのivarを置くことができます。

// UICollectionView_ReusableViewsSupport.h

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

    NSMutableDictionary *registeredSupplementaryViewSources;
    NSMutableDictionary *spareSupplementaryViewsByIdentifier;
}

- (void)initReusableViewSupport;

@end

このヘッダーファイルは、私たちのライブラリのユーザには配布しません。 ただ、このヘッダファイルを UICollectionView.m で、そして UICollectionView+ReusableViews.m になるように、すべての が必要とする が見ることができるようにします。 また、メインとなる init メソッドに投げました。このメソッドは、再利用可能なビュー管理コードを初期化するために呼び出されます。 このメソッドは -[UICollectionView initWithFrame:collectionViewLayout:] の中で UICollectionView.m で実装し、それを UICollectionView+ReusableViews.m .