なぜivarを使うのですか?


153

私は通常、この質問が別の方法で尋ねられるのを見ます。たとえば、すべてのivarはプロパティである必要がありますか?(そして私はこのQに対するbbumの答えが好きです)。

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

彼はリークしないコードを書いていますが、私は彼にivarよりもプロパティを使用することを好みます。私達はそれについて話しました、そして彼は多かれ少なかれKVOを使っていなかったのでプロパティを使う理由がないと見ています、そして彼はメモリ問題の世話をした経験があります。

私の質問はもっと...なぜあなたはこれまでにivar期間を使いたいと思うでしょう-経験の有無にかかわらず。ivarの使用が正当化されるパフォーマンスの大きな違いは本当にありますか?

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


編集1

良い反応をありがとうございました。正しくないように思われる問題に対処したいのは、ivarを使用するとカプセル化が発生し、プロパティを使用しない場合です。クラスの継続でプロパティを定義するだけです。これにより、外部からプロパティが非表示になります。プロパティをインターフェースでreadonlyと宣言し、実装でreadwriteとして再定義することもできます:

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

そしてクラスの継続に持っています:

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

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


2
興味深い質問に賛成票を投じてください-よく言えば、サムのやり方を教えられたように聞こえるので、ivarのケースについても聞きたいです。
ダモ2012年

2
自動参照カウント(ARC)は、プロパティと同じメモリ管理の利点をivarに適用することに注意してください。そのため、ARCコードでは、カプセル化が違います。
ベンザド2012年

1
あなたの質問、特に編集1の部分は、実際には選択した回答よりもはるかに有益です。
user523234

1
Edit1の場合:Key-Value-Codingを使用して、.hでreadonlyの宣言が1つしかない場合でも、すべてのプロパティの読み取りと書き込みが可能だと思います。例:[object setValue:[NSNumber numberWithInt:20] forKey:@ "propertyname "];
Binarian 2013

1
@Sam to your Edit 1:プライベートプロパティを使用し、.mファイルでクラスの拡張子/継続を使用する場合、サブクラスには表示されません。もう一度コードを書くか、クラス拡張子を持つ別の.hを使用する必要があります。@ protected / defaultの方が簡単です。
Binarian 2014年

回答:


100

カプセル化

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

パフォーマンス

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

重要なタイプ

例:C ++型の場合、直接アクセスの方が優れている場合があります。タイプはコピーできない場合や、コピーするのが簡単ではない場合があります。

マルチスレッド

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

プログラムの正確さ

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

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

バイナリサイズ

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

複雑さを最小化

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


プロパティやアクセサの使用が悪いと言っているわけではありません。それぞれに重要な利点と制限があります。多くのオブジェクト指向言語と設計へのアプローチと同様に、ObjCで適切な可視性を持つアクセサーを優先する必要もあります。逸脱する必要がある場合があります。そのため、私はivarを宣言する実装への直接アクセスを制限することがしばしば最善であると思います(たとえば、それを宣言します@private)。


再編集1:

私たちのほとんどは、非表示のアクセサを動的に呼び出す方法を覚えています(名前を知っている限り…)。一方、私たちのほとんどは、(KVCを超えて)表示されないivarに適切にアクセスする方法を覚えていません。クラスの継続はに役立ちますが、脆弱性をもたらします。

この回避策は明白です。

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

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


@サムありがとう、そして良い質問!複雑さに関して:それは確かに両方の方向に行きます。再カプセル化-更新済み
ジャスト

@bbum RE:不思議な間違った解決策であることに私は同意しますが、多くの経験豊富なobjc開発者がそれが起こらないと信じているとは思えません。私は他の人のプログラムでそれを見てきました、そしてApp Storeは私的なApple APIの使用を禁止するまで行ってきました。
2012

1
object-> fooでプライベートivarにアクセスできませんか?覚えるのはそれほど難しいことではありません。
Nick Lockwood

1
私は、C->構文を使用して、オブジェクトからのポインター参照を使用してそれにアクセスできることを意味しました。Objective-Cクラスは、基本的には内部では単なる構造体であり、構造体へのポインターが指定されている場合、メンバーにアクセスするためのC構文は->であり、これは目的Cクラスのivarでも機能します。
Nick Lockwood、2012

1
@NickLockwood ivarがの場合、@privateコンパイラーはクラスおよびインスタンスメソッドの外部へのメンバーアクセスを禁止する必要があります-表示されていませんか?
2012

76

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

ゲッター/設定を介してivarにアクセスするには、Objective-Cメソッド呼び出しが必要です。これは、「通常の」C関数呼び出しよりもはるかに遅く(少なくとも3〜4倍)、通常のC関数呼び出しでさえ、すでに複数倍遅い構造体メンバーにアクセスします。あなたの財産の属性に応じて、コンパイラによって生成されたセッター/ゲッターの実装が機能他のCの関数呼び出しを伴うことobjc_getProperty/ objc_setProperty、これらがありますようにretain/ copy/autorelease必要に応じてオブジェクトを、さらに、必要に応じて、原子の性質のためにspinlocking行います。これは簡単に非常に高価になり、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:方法は次のように定義されています。男性より前の女性の注文、名前のアルファベット順、古い前の若い注文、注文の低から高のバランス。

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

テストのセットアップは非常に簡単です。1Mioランダムアカウントを作成し、それらを配列に追加して、その配列を並べ替えます。それでおしまい。もちろん、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の違い)。

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

また、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

3
非常に有益で実用的な説明。コードサンプルへの
賛成

1
あなたの投稿で私が見る重要な修飾子の1つは、「クリティカルコードパスから」です。重要なのは、コードを読み書きしやすくするものを使用して、クリティカルパスであることがわかったものを最適化することです。これにより、必要に応じて複雑さが増します。
Sandy Chapman、

1
@ViktorLexington私のコードではunsigned int、ARCを使用するかどうかに関係なく、保持/解放されないを設定していました。保持/解放自体は高価であり、setter / getterまたはivarを直接使用して、保持管理が常に存在する静的オーバーヘッドを追加するため、差は小さくなります。ただし、ivarに直接アクセスする場合は、1つの追加のメソッド呼び出しのオーバーヘッドを節約できます。1秒間に数千回実行している場合を除き、ほとんどの場合、大した問題ではありません。Appleは、init / deallocメソッドを使用していないか、ボトルネックを発見していない限り、デフォルトでゲッター/セッターを使用すると述べています。
メッキー

1
@Fogmeister非常に単純な実世界の例でこれがどれほど簡単に大きな違いを生むことができるかを示すコードサンプルを追加しました。そして、この例は、何兆もの計算を実行するスーパーコンピューターとは何の関係もありません。それは、本当に単純なデータテーブル(何百万ものアプリでかなり一般的なケース)をソートすることに関するものです。
Mecki、

2
マークされ@malhal Aプロパティはcopyなりませ、あなたがそれにアクセスするたびに、その値のコピーを作成します。ゲッターcopyプロパティは、ゲッターのようなものですstrong/ retainプロパティ。コードは基本的にreturn [[self->value retain] autorelease];です。唯一のセッターをコピーした値と、それはおおよそ次のようになります。[self->value autorelease]; self->value = [newValue copy];一方、strong/ retainこのようなセッターのルックス:[self->value autorelease]; self->value = [newValue retain];
Mecki

9

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

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

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


3
+1「最小限のパフォーマンス」に関する良い応答は、すべてのメソッド呼び出しを明示的に追加して表示したいと考えています。プロパティでドット構文を使用すると、カスタムゲッター/セッターで行われる多くの作業が確実にマスクされます(特に、そのゲッターが呼び出されるたびに何かのコピーを返す場合)。
Sam

1
KVOは、セッターを使用しないと機能しません。ivarを直接変更しても、値が変更されたというオブザーバーは呼び出されません!
Binarian 2013

2
KVCはivarにアクセスできます。KVOはivarへの変更を検出できません(代わりに、呼び出されるアクセサーに依存しています)。
Nikolai Ruhe 2013年

9

意味論

  • 何を@property:アイバーズができないことを表現できるnonatomiccopy
  • ivarが表現@propertyできないこと:
    • @protected:サブクラスではパブリック、外部ではプライベート。
    • @package:64ビットのフレームワークではパブリック、外部ではプライベート。@public32ビットと同じです。Appleの64ビットクラスとインスタンス変数のアクセス制御をご覧ください。
    • 修飾子。たとえば、強いオブジェクト参照の配列:id __strong *_objs

パフォーマンス

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

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

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

  5. メッセージを送信すると、任意の副作用が生じる可能性があるため、

    • コンパイラに、非ローカルメモリに関するすべての想定をリセットするように強制します
    • 巻き上げたり、沈めたり、並べ替えたり、結合したり、除去したりすることはできません。

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

  7. ARCでは、セッターメソッドは通常+0で引数を取るため、そのオブジェクト(前述のように、ARCは通常持っている)の保持をivarに「転送」する方法はないため、値は通常、保持/解放2回

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


ジョン。


6

プロパティとインスタンス変数はトレードオフであり、結局のところ、選択はアプリケーションにかかっています。

カプセル化/情報の隠蔽これは、設計の観点から見たグッドシング(TM)であり、インターフェイスが狭く、最小限のリンクでソフトウェアが保守可能で理解しやすくなっています。Obj-Cで何かを隠すのはかなり難しいですが、実装で宣言されたインスタンス変数は、できるだけ近づきます。

パフォーマンス「時期尚早の最適化」は悪いこと(TM)ですが、少なくとも同じくらいに悪いことができるという理由だけで、パフォーマンスの悪いコードを書くことはできます。ロードやストアよりもメソッド呼び出しの方が高額であることに反対するのは難しく、計算集約型のコードではすぐにコストが増大します。

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

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


5

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


5

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

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