154

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

私は自分のコードでほぼ独占的にプロパティを使用しています。しかし、私は、長い間 iOS での開発を行ってきた伝統的なゲーム プログラマーである請負業者と一緒に仕事をすることがよくあります。彼は、プロパティをほとんど宣言せず、ivar に依存するコードを作成します。彼がこれを行っているのは、1.) Objective C 2.0 (2007 年 10 月) までプロパティが常に存在するとは限らなかったため、彼はそれに慣れていたからです。

彼はリークしないコードを書いていますが、ivar よりもプロパティを使用する方が好きです。私たちはそれについて話しましたが、彼は多かれ少なかれプロパティを使用する理由を理解していません。私たちは KVO を使用しておらず、彼はメモリの問題に対処した経験があるからです。

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

また、明確にするために、必要に応じてセッターとゲッターをオーバーライドし、ゲッター/セッター内でそのプロパティと相関する ivar を使用します。ただし、ゲッター/セッターまたは 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;

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

4

7 に答える 7

100

カプセル化

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

パフォーマンス

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

自明でない型

例: C++ 型を使用している場合、直接アクセスの方が適切な方法である場合があります。その型はコピーできないか、コピーするのが簡単ではないかもしれません。

マルチスレッド

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

プログラムの正しさ

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

また、すべてがパブリックな読み書きアクセサーの考え方から、実装の詳細/データをうまく隠す考え方に移行するにつれて、より頻繁になります。正しいことを行うために、サブクラスのオーバーライドがもたらす可能性のある副作用を正しく回避する必要がある場合があります。

バイナリ サイズ

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

複雑さを最小限に抑える

場合によっては、あるメソッドで記述され、別のメソッドで読み取られるプライベート bool などの単純な変数のすべての余分な足場を追加 + 型 + 維持することがまったく不要です。


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


再編集1:

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

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

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

ここで、KVC を使用せずに、ivar のみで試してください。

于 2012-01-31T20:55:43.233 に答える
76

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

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

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

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

テストのセットアップは非常に簡単です。1 Mio のランダムなアカウントを作成し、それらを配列に追加して、その配列を並べ替えます。それでおしまい。もちろん、AccountAオブジェクト用とオブジェクト用の 2 つの配列が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 時間はごくわずかしかないため、最適化するのは時間の無駄のようです。そして、それは本当です、

また、CPU 時間の観点から考えていなくても、CPU 時間を浪費することはまったく問題ないと考えているため、結局のところ「無料です」と考えている場合、電力消費に起因するサーバーのホスティング コストはどうでしょうか? モバイル機器のバッテリー駆動時間は?同じモバイル アプリを 2 回 (独自のモバイル Web ブラウザーなど) 作成する場合、1 回目はすべてのクラスが getter によってのみ独自のプロパティにアクセスし、もう 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
于 2012-07-10T20:48:55.290 に答える
9

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

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

また、正しい ivar 命名により、KVO はプロパティなしで可能です (IIRC、私は KVO の専門家ではありません)。

于 2012-01-31T21:00:08.173 に答える
9

セマンティクス

  • ivar@propertyが表現できないことを表現できるもの:nonatomicおよびcopy.
  • ivar が表現できて表現@propertyできないもの:
    • @protected: サブクラスでは公開、外部では非公開。
    • @package: 64 ビットのフレームワークでは公開、外部では非公開。@public32ビットと同じ。Apple の64 ビット Class and Instance Variable Access Controlを参照してください。
    • 予選。たとえば、強いオブジェクト参照の配列: 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-cache に保持されます。これにより、起動時間が長くなり、アプリの静的メモリ使用量が増加し、コンテキストの切り替えがより困難になります。メソッド キャッシュはオブジェクトの動的クラスに固有であるため、この問題は KVO を使用するほど増加します。

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

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

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

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

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

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


ジョン。

于 2013-05-21T07:38:41.680 に答える
6

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

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

パフォーマンス「時期尚早の最適化」は悪いこと (TM) ですが、できるからといってパフォーマンスの悪いコードを書くことも、少なくとも同じくらい悪いことです。メソッド呼び出しがロードまたはストアよりも高価であることに反対するのは難しく、計算集約型のコードではコストがすぐに加算されます。

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

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

于 2012-01-31T21:19:55.637 に答える
5

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

于 2012-01-31T21:02:00.917 に答える
5

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

于 2012-01-31T21:16:59.697 に答える