web-dev-qa-db-ja.com

なぜivarを使用するのですか?

通常、この質問は すべてのivarがプロパティである必要がありますか? (およびこのQに対するbbumの回答が好きです)など、他の方法で尋ねられます。

私はコードでプロパティをほぼ排他的に使用します。ただし、iOSで長い間開発を続けており、従来のゲームプログラマーである請負業者と一緒に仕事をしていることがよくあります。彼は、プロパティをほとんど宣言せず、ivarに依存するコードを記述します。ゲッター/セッターを通過しないことによる最小限のパフォーマンスゲインのために、Objective C 2.0(07年10月)および2.)までプロパティが常に存在していなかったため、彼はこれに慣れていると思います。

彼はリークしないコードを書いていますが、私は彼がまだivarsよりもプロパティを使用することを好みます。私たちはそれについて話しましたが、彼はKVOを使用しておらず、メモリの問題を処理した経験があるため、プロパティを使用する理由は多かれ少なかれないと思います。

私の質問はもっと...なぜあなたはivarピリオドを使用したいのですか? ivarを使用することで正当化されるパフォーマンスの違いが本当に大きいのでしょうか?

また、明確化のポイントとして、必要に応じてセッターとゲッターをオーバーライドし、ゲッター/セッター内のそのプロパティと相関するivarを使用します。ただし、getter/setterまたはinitの外側では、常にself.myProperty構文。


編集1

すべての良い反応に感謝します。私が間違っていると思われるものに対処したいのは、ivarではカプセル化され、プロパティではそうではないということです。クラス継続でプロパティを定義するだけです。これにより、プロパティが部外者から隠されます。また、インターフェイスでプロパティを読み取り専用として宣言し、次のように実装でreadwriteとして再定義することもできます。

// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;

そして、クラスの継続で:

// readwrite within this file
@property (nonatomic, copy) NSString * name;

完全に「プライベート」にするには、クラス継続で宣言するだけです。

149
Sam

カプセル化

Ivarがプライベートの場合、プログラムの他の部分は簡単にアクセスできません。宣言されたプロパティを使用すると、賢い人はアクセサーを介して非常に簡単にアクセスして変更できます。

性能

はい、これはいくつかのケースで違いを生むことができます。一部のプログラムには、プログラムの特定の部分でobjcメッセージングを使用できないという制約があります(リアルタイムを考えてください)。他の場合には、速度のために直接アクセスしたいかもしれません。それ以外の場合は、objcメッセージングが最適化ファイアウォールとして機能するためです。最後に、参照カウント操作を減らし、ピークメモリ使用量を最小限に抑えることができます(正しく行われた場合)。

自明でないタイプ

例:C++型を使用している場合、直接アクセスが優れたアプローチである場合があります。タイプはコピー可能でないか、コピーが簡単ではない場合があります。

マルチスレッド

あなたのivarの多くは共依存的です。マルチスレッドコンテキストでデータの整合性を確保する必要があります。したがって、クリティカルセクションの複数のメンバーに直接アクセスすることをお勧めします。共依存データのアクセサーに固執する場合、ロックは通常リエントラントである必要があり、多くの場合、多くの場合(場合によってはさらに多く)取得することになります。

プログラムの正確さ

サブクラスは任意のメソッドをオーバーライドできるため、最終的には、インターフェースへの書き込みと状態の適切な管理との間に意味的な違いがあることがわかります。プログラムの正確さのための直接アクセスは、部分的に構築された状態で特に一般的です。初期化子とdeallocでは、直接アクセスを使用するのが最善です。また、アクセサの実装、コンビニエンスコンストラクタ、copymutableCopy、およびアーカイブ/シリアル化の実装でも共通することがあります。

また、すべてがパブリックなreadwriteアクセサマインドセットを持っているものから、実装の詳細/データをうまく隠しているものへと移動するため、より頻繁に発生します。正しいことをするために、サブクラスのオーバーライドがもたらす副作用を正しく回避する必要がある場合があります。

バイナリサイズ

デフォルトでreadwriteをすべて宣言すると、通常、プログラムの実行をしばらく考慮すると、必要のない多くのアクセサメソッドが作成されます。したがって、プログラムとロード時間にもいくらかの脂肪が追加されます。

複雑さを最小限に抑える

場合によっては、あるメソッドで書き込まれ、別のメソッドで読み取られるプライベートboolなどの単純な変数の追加の足場をすべて追加+タイプ+維持する必要はまったくありません。


プロパティやアクセサの使用が悪いというわけではありません-それぞれに重要な利点と制限があります。多くのOO言語と設計のアプローチと同様に、ObjCで適切な可視性を持つアクセサーを優先する必要があります。逸脱する必要がある場合があります。そのため、直接ivarを宣言する実装にアクセスします(例:@private)。


再編集1:

私たちのほとんどは、隠されたアクセサーを動的に呼び出す方法を覚えています(名前を知っている限り)。一方、私たちのほとんどは、(KVCを超えて)表示されていないivarに適切にアクセスする方法を記憶していますnot。クラスの継続helpsですが、脆弱性が発生します。

この回避策は明らかです:

if ([obj respondsToSelector:(@selector(setName:)])
  [(id)obj setName:@"Al Paca"];

次に、ivarのみで、KVCなしで試してください。

98
justin

私にとっては、通常パフォーマンスです。オブジェクトのivarへのアクセスは、そのような構造体を含むメモリへのポインターを使用してCの構造体メンバーにアクセスするのと同じくらい高速です。実際、Objective-Cオブジェクトは基本的に、動的に割り当てられたメモリ内にあるC構造体です。これは通常、コードが取得できる限り高速ですが、手作業で最適化されたアセンブリコードでさえ、それより速くなることはありません。

Getter/settingを介してivarにアクセスするには、Objective-Cメソッド呼び出しが必要です。これは、「通常の」C関数呼び出しよりもはるかに遅く(少なくとも3〜4回)、通常のC関数呼び出しでさえもすでに複数倍遅くなります構造体メンバーにアクセスします。プロパティの属性に応じて、コンパイラによって生成されるセッター/ゲッターの実装には、objc_getProperty/objc_setPropertyへの別のC関数呼び出しが含まれる場合があります。これらはretain/copy/autorelease必要に応じてオブジェクトを追加し、必要に応じてアトミックプロパティのスピンロックをさらに実行します。これは簡単に非常に高価になる可能性があり、私は50%遅くなることについて話していません。

これを試してみましょう:

CFAbsoluteTime cft;
unsigned const kRuns = 1000 * 1000 * 1000;

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    testIVar = i;
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"1: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    [self setTestIVar:i];
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"2: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

出力:

1: 23.0 picoseconds/run
2: 98.4 picoseconds/run

これは4.28倍遅く、これは非原子プリミティブintで、ほとんどベストケース;です。他のほとんどの場合はさらに悪化します(アトミックNSString *プロパティを試してください!)。したがって、各ivarアクセス​​が可能な場合の4〜5倍遅いという事実に耐えることができる場合、プロパティの使用は問題ありません(少なくともパフォーマンスに関しては)が、そのようなパフォーマンスの低下が起こる状況はたくさんあります。完全に受け入れられません。

更新2015-10-20

一部の人々は、これは現実の問題ではなく、上記のコードは純粋に合成的なものであり、実際のアプリケーションでは決してそれに気付かないと主張しています。それでは、実際のサンプルを試してみましょう。

以下のコードは、Accountオブジェクトを定義しています。アカウントには、所有者の名前(NSString *)、性別(enum)、年齢(unsigned)、および残高(int64_t)を記述するプロパティがあります。アカウントオブジェクトには、initメソッドとcompare:メソッドがあります。 compare:メソッドは次のように定義されます。女性の注文は男性の前に、名前はアルファベット順に、若い注文は古い順に、バランス注文は低から高になります。

実際には、AccountAAccountBの2つのアカウントクラスが存在します。それらの実装を見ると、1つの例外を除いて、ほぼ完全に同一であることがわかります。compare:メソッドです。 AccountAオブジェクトアクセスメソッド(ゲッター)による独自のプロパティAccountBオブジェクトアクセス )自分のプロパティivarによって。それが本当に唯一の違いです!どちらもゲッターで比較する他のオブジェクトのプロパティにアクセスします(ivarでアクセスすると安全ではありません!他のオブジェクトがサブクラスで、ゲッターをオーバーライドした場合はどうなりますか?)。また、ivarとして独自のプロパティにアクセスしても、カプセル化が解除されないことに注意してください(ivarはまだ公開されていません)。

テストのセットアップは本当に簡単です。1つのMioランダムアカウントを作成し、それらを配列に追加して、その配列を並べ替えます。それでおしまい。もちろん、2つの配列があり、1つはAccountAオブジェクト用で、もう1つはAccountBオブジェクト用です。両方の配列には、同じアカウント(同じデータソース)が格納されます。配列の並べ替えにかかる時間を計ります。

昨日行ったいくつかの実行の出力は次のとおりです。

runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076

ご覧のとおり、AccountBオブジェクトの配列の並べ替えは、AccountAオブジェクトの配列の並べ替えよりも常に大幅に高速です。

最大1.32秒のランタイムの違いが違いをもたらさないと主張する人は、UIプログラミングを行うべきではありません。たとえば、大きなテーブルの並べ替え順序を変更する場合、このような時間差はユーザーに大きな違いをもたらします(許容可能なUIと遅いUIの違い)。

また、この場合、サンプルコードはここで実行される唯一の実際の作業ですが、コードはどれくらいの頻度で複雑な時計仕掛けの小さな歯車ですか?そして、すべてのギアがこのようにプロセス全体を遅くする場合、それは最終的に時計仕掛け全体の速度にとって何を意味しますか?特に、ある作業ステップが別の作業ステップの出力に依存している場合、すべての非効率性が合計されます。ほとんどの非効率性はそれ自体では問題ではありません。プロセス全体にとって問題になるのは彼らの総計です。プロファイラーは重要なホットスポットを見つけることを目的としているため、このような問題はプロファイラーが簡単に示すものではありませんが、これらの非効率性はそれ自体がホットスポットではありません。 CPU時間はそれらの間で平均的に分散していますが、それぞれのCPU時間はごくわずかであり、最適化するのは時間の無駄です。確かに、そのうちの1つだけを最適化してもまったく役に立ちません。すべてを最適化しても劇的に役立ちます。

CPU時間を考慮しなくても、CPU時間の浪費は「無料」であるため、まったく許容できると考えているため、電力消費によるサーバーホスティングコストについてはどうでしょうか。モバイルデバイスのバッテリーランタイムはどうですか?同じモバイルアプリを2回作成する場合(たとえば、独自のモバイルWebブラウザー)、1回はすべてのクラスがゲッターによってのみ独自のプロパティにアクセスし、1回はすべてのクラスがivarによってのみそれらにアクセスします。バッテリーは機能的に同等であり、ユーザーにとっては2番目のバッテリーはおそらく少し速ささえ感じますが、2番目のバッテリーを使用するよりもはるかに高速です。

main.mファイルのコードは次のとおりです(コードはARCが有効になっていることに依存しており、コンパイル時に最適化を使用して完全な効果を確認してください)。

#import <Foundation/Foundation.h>

typedef NS_ENUM(int, Gender) {
    GenderMale,
    GenderFemale
};


@interface AccountA : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountA *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


@interface AccountB : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountB *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


static
NSMutableArray * allAcocuntsA;

static
NSMutableArray * allAccountsB;

static
int64_t getRandom ( const uint64_t min, const uint64_t max ) {
    assert(min <= max);
    uint64_t rnd = arc4random(); // arc4random() returns a 32 bit value only
    rnd = (rnd << 32) | arc4random();
    rnd = rnd % ((max + 1) - min); // Trim it to range
    return (rnd + min); // Lift it up to min value
}

static
void createAccounts ( const NSUInteger ammount ) {
    NSArray *const maleNames = @[
        @"Noah", @"Liam", @"Mason", @"Jacob", @"William",
        @"Ethan", @"Michael", @"Alexander", @"James", @"Daniel"
    ];
    NSArray *const femaleNames = @[
        @"Emma", @"Olivia", @"Sophia", @"Isabella", @"Ava",
        @"Mia", @"Emily", @"Abigail", @"Madison", @"Charlotte"
    ];
    const NSUInteger nameCount = maleNames.count;
    assert(maleNames.count == femaleNames.count); // Better be safe than sorry

    allAcocuntsA = [NSMutableArray arrayWithCapacity:ammount];
    allAccountsB = [NSMutableArray arrayWithCapacity:ammount];

    for (uint64_t i = 0; i < ammount; i++) {
        const Gender g = (getRandom(0, 1) == 0 ? GenderMale : GenderFemale);
        const unsigned age = (unsigned)getRandom(18, 120);
        const int64_t balance = (int64_t)getRandom(0, 200000000) - 100000000;

        NSArray *const nameArray = (g == GenderMale ? maleNames : femaleNames);
        const NSUInteger nameIndex = (NSUInteger)getRandom(0, nameCount - 1);
        NSString *const name = nameArray[nameIndex];

        AccountA *const accountA = [[AccountA alloc]
            initWithName:name age:age gender:g balance:balance
        ];
        AccountB *const accountB = [[AccountB alloc]
            initWithName:name age:age gender:g balance:balance
        ];

        [allAcocuntsA addObject:accountA];
        [allAccountsB addObject:accountB];
    }
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        @autoreleasepool {
            NSUInteger ammount = 1000000; // 1 Million;
            if (argc > 1) {
                unsigned long long temp = 0;
                if (1 == sscanf(argv[1], "%llu", &temp)) {
                    // NSUIntegerMax may just be UINT32_MAX!
                    ammount = (NSUInteger)MIN(temp, NSUIntegerMax);
                }
            }
            createAccounts(ammount);
        }

        // Sort A and take time
        const CFAbsoluteTime startTime1 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAcocuntsA sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime1 = CFAbsoluteTimeGetCurrent() - startTime1;

        // Sort B and take time
        const CFAbsoluteTime startTime2 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAccountsB sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime2 = CFAbsoluteTimeGetCurrent() - startTime2;

        NSLog(@"runTime 1: %f", runTime1);
        NSLog(@"runTime 2: %f", runTime2);
    }
    return 0;
}



@implementation AccountA
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (self.gender != account.gender) {
            if (self.gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![self.name isEqualToString:account.name]) {
            return [self.name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (self.age != account.age) {
            if (self.age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (self.balance != account.balance) {
            if (self.balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end


@implementation AccountB
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (_gender != account.gender) {
            if (_gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![_name isEqualToString:account.name]) {
            return [_name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (_age != account.age) {
            if (_age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (_balance != account.balance) {
            if (_balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end
73
Mecki

セマンティクス

  • @propertyは、ivarができないことを表現できます:nonatomicおよびcopy
  • Ivarsが表現できること@propertyできない:
    • @protected:サブクラスでパブリック、外部でプライベート。
    • @package:64ビットのフレームワークでパブリック、外部でプライベート。と同じ @public 32ビット。 Appleの 64ビットクラスおよびインスタンス変数アクセス制御 を参照してください。
    • 予選。たとえば、強力なオブジェクト参照の配列:id __strong *_objs

パフォーマンス

短編:ivarは高速ですが、ほとんどの用途には関係ありません。 nonatomicプロパティはロックを使用しませんが、直接ivarはアクセサー呼び出しをスキップするため高速です。詳細については、lists.Apple.comから email をお読みください。

Subject: Re: when do you use properties vs. ivars?
From: John McCall <email@hidden>
Date: Sun, 17 Mar 2013 15:10:46 -0700

プロパティはさまざまな方法でパフォーマンスに影響します。

  1. 既に説明したように、ロード/ストアを実行するためのメッセージの送信は、ロード/ストアをインラインで実行するよりも遅くなります

  2. ロード/ストアを行うためのメッセージの送信も、i-cacheに保持する必要があるもう少しのコードです:ゲッター/セッターが追加の命令を追加した場合でも、ロード/ストアでは、発信者にメッセージの送信を設定し、結果を処理するためのしっかりした半ダースの余分な命令があります。

  3. メッセージを送信すると、そのセレクターのエントリはmethod cacheに保持され、メモリは通常dキャッシュに残ります。これにより、起動時間が長くなり、アプリの静的メモリ使用量が増加し、コンテキストの切り替えがより苦痛になります。メソッドキャッシュはオブジェクトの動的クラスに固有であるため、この問題はKVOを使用するほど増加します。

  4. メッセージを送信すると、関数内のすべての値が強制的にスタックに流出します(または呼び出し先保存レジスタに保持されます。つまり、異なる時間に流出します)。

  5. メッセージの送信は、任意の副作用を引き起こす可能性があるため、

    • 非ローカルメモリに関するすべての仮定をコンパイラに強制的にリセットさせます
    • 吊り上げ、沈め、並べ替え、合体、または排除することはできません。

  6. ARCでは、メッセージ送信の結果は、+ 0が返された場合でも、呼び出し先または呼び出し元のいずれかによって常に保持されます:メソッドが保持/自動解放されない場合でもその結果、呼び出し元はそれを知らず、結果が自動解放されないようにアクションをとる必要があります。メッセージ送信は静的に分析できないため、これを排除することはできません。

  7. ARCでは、セッターメソッドは通常+0で引数を取るため、そのオブジェクト(上記で説明したように、ARCが通常持っている)の保持をivarに「転送」する方法はありません。したがって、通常、値は2回保持/解放する必要があります

もちろん、これらが常に悪いことを意味するわけではありません。プロパティを使用する理由はたくさんあります。他の多くの言語機能と同様に、これらは無料ではないことに注意してください。


ジョン。

9
Jano

最も重要な理由はOOP 情報隠蔽の概念です:プロパティを介してすべてを公開し、外部オブジェクトが別のオブジェクトの内部を覗くことができるようにすると、これらの内部の使用により、実装の変更が複雑になります。

「最小限のパフォーマンス」の向上はすぐにまとめられ、問題になる可能性があります。私は経験から知っています。私は実際にiDeviceを限界まで引き上げるアプリを開発しているので、不必要なメソッド呼び出しを避ける必要があります(もちろん合理的に可能な場合のみ)。この目標を支援するために、ドットシンタックスも回避しています。これは、メソッド呼び出しの数が一目でわかりにくくなるためです。たとえば、式self.image.size.width 引き金?対照的に、[[self image] size].width

また、正しいivarネーミングにより、プロパティなしでKVOを使用できます(IIRC、私はKVOの専門家ではありません)。

9
DarkDust

プロパティとインスタンス変数はトレードオフであり、最終的に選択はアプリケーションに委ねられます。

カプセル化/情報の隠蔽これは、設計の観点からは良いこと(TM)であり、インターフェイスが狭く、最小限のリンケージがソフトウェアの保守と理解を可能にします。 Obj-Cで何かを隠すことはかなり難しいですが、implementationで宣言されたインスタンス変数は、できるだけ近くに来ます。

パフォーマンス「時期尚早な最適化」は悪いことですが、できるからといってパフォーマンスの悪いコードを書くことは少なくとも同じくらい悪いです。メソッド呼び出しがロードやストアよりも高価であることを議論するのは難しく、計算量の多いコードではすぐにコストが増加します。

C#などのプロパティを持つ静的言語では、セッター/ゲッターへの呼び出しは多くの場合、コンパイラーによって最適化されます。ただし、Obj-Cは動的であり、そのような呼び出しを削除するのははるかに困難です。

Abstraction Obj-Cのインスタンス変数に対する引数は、伝統的にメモリ管理でした。 MRCインスタンス変数では、保持/リリース/自動リリースの呼び出しがコード全体に広がる必要があり、プロパティ(合成されているかどうかに関係なく)はMRCコードを1か所に保持します-抽象化の原則はGood Thing(TM)です。ただし、GCまたはARCではこの引数はなくなるため、メモリ管理の抽象化はインスタンス変数の引数againstではなくなりました。

6
CRD

下位互換性は私にとっての要因でした。要件の一部としてMac OS X 10.3で動作する必要があるソフトウェアとプリンタードライバーを開発していたため、Objective-C 2.0の機能を使用できませんでした。あなたの質問はiOSをターゲットにしているように見えますが、プロパティを使用しない理由をまだ共有すると思いました。

5
dreamlax

プロパティは、変数を他のクラスに公開します。作成しているクラスに相対的な変数のみが必要な場合は、インスタンス変数を使用します。以下に小さな例を示します。RSSなどを解析するためのXMLクラスは、一連のデリゲートメソッドなどを循環します。 NSMutableStringのインスタンスを使用して、解析の各異なるパスの結果を保存するのが実用的です。外部クラスがその文字列にアクセスしたり操作したりする必要がある理由はありません。したがって、ヘッダーで宣言するか、プライベートで宣言して、クラス全体にアクセスします。プロパティを設定すると、self.mutableStringを使用してゲッター/セッターを呼び出すことで、メモリの問題がないことを確認する場合にのみ役立つ場合があります。

5
Justin