【问题标题】:Why would you use an ivar?为什么要使用 ivar?
【发布时间】:2012-02-23 14:19:21
【问题描述】:

我通常看到这个问题是用另一种方式问的,比如Must every ivar be a property?(我喜欢 bbum 对这个 Q 的回答)。

我几乎只在我的代码中使用属性。然而,每隔一段时间,我就会与一个在 iOS 上开发了很长时间并且是传统游戏程序员的承包商一起工作。他编写的代码几乎不声明任何属性,并且依赖于 ivars。我认为他这样做是因为 1.) 他已经习惯了,因为在 Objective C 2.0(07 年 10 月)和 2.) 之前并不总是存在属性,因为不通过 getter/setter 的性能增益最小。

虽然他编写的代码不会泄漏,但我仍然希望他使用属性而不是 ivars。我们讨论过它,他或多或少认为没有理由使用属性,因为我们没有使用 KVO,而且他在处理内存问题方面经验丰富。

我的问题更多......你为什么要使用 ivar 时期 - 有没有经验。使用 ivar 是否真的有那么大的性能差异是合理的?

另外,作为澄清点,我根据需要覆盖 setter 和 getter,并在 getter/setter 中使用与该属性相关的 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;

要让它完全“私有”,只需在类延续中声明它。

【问题讨论】:

  • 为有趣的问题投票 - 说得好,也是我想听听有关 ivars 的案例,因为听起来我被教导要按照 Sam 的方式去做。
  • 请注意,自动引用计数 (ARC) 将相同的内存管理优势应用于 ivars 作为属性,因此在 ARC 代码中,差异实际上与封装有关。
  • 您的问题,尤其是编辑 1 部分实际上比选择的答案提供更多信息。
  • 至 Edit1:我认为可以读取和写入每个属性,即使 .h 中只有一个只读声明,使用键值编码,例如:[object setValue:[NSNumber numberWithInt :20] forKey:@"propertyname"];
  • @Sam 到您的编辑 1:如果您使用私有属性并在 .m 文件中使用类扩展名/延续,则子类不可见。您需要再次编写代码或使用另一个带有类扩展名的 .h。使用@protected/default 更容易。

标签: objective-c ios memory-management key-value-observing ivar


【解决方案1】:

封装

如果 ivar 是私有的,程序的其他部分就不能那么容易地得到它。有了声明的属性,聪明的人就可以很容易地通过访问器访问和改变。

性能

是的,这在某些情况下会有所不同。有些程序有限制,他们不能在程序的某些部分使用任何 objc 消息传递(想想实时)。在其他情况下,您可能希望直接访问它以提高速度。在其他情况下,这是因为 objc 消息传递充当优化防火墙。最后,它可以减少您的引用计数操作并最大限度地减少内存使用峰值(如果操作正确)。

非平凡类型

示例:如果您有 C++ 类型,则直接访问有时只是更好的方法。该类型可能不可复制,或者复制起来可能并不简单。

多线程

您的许多 ivars 是相互依赖的。您必须确保多线程上下文中的数据完整性。因此,您可能倾向于直接访问关键部分中的多个成员。如果您坚持使用访问器来获取相互依赖的数据,那么您的锁通常必须是可重入的,并且您通常最终会进行更多的获取(有时明显更多)。

程序正确性

由于子类可以覆盖任何方法,您最终可能会发现写入接口与适当地管理您的状态之间存在语义差异。程序正确性的直接访问在部分构造的状态中尤其常见——在您的初始化程序和dealloc 中,最好使用直接访问。您还可以在访问器、便利构造函数、copymutableCopy 和归档/序列化实现的实现中发现这很常见。

随着人们从所有东西都有一个公共的读写访问器的思维方式转变为一种很好地隐藏其实现细节/数据的思维方式,这种情况也更加频繁。有时您需要正确地绕过子类覆盖可能引入的副作用,以便做正确的事情。

二进制大小

当您考虑程序的执行时,默认情况下声明所有内容为 readwrite 通常会导致许多您永远不需要的访问器方法。所以它会给你的程序和加载时间增加一些脂肪。

最大限度地降低复杂性

在某些情况下,完全没有必要为一个简单的变量添加+类型+维护所有额外的脚手架,例如用一种方法写入并用另一种方法读取的私有 bool。


这并不是说使用属性或访问器不好 - 每个都有重要的好处和限制。与许多 OO 语言和设计方法一样,您还应该支持在 ObjC 中具有适当可见性的访问器。有时你需要偏离。出于这个原因,我认为通常最好限制对声明 ivar 的实现的直接访问(例如声明它@private)。


重新编辑 1:

我们大多数人都记住了如何动态调用隐藏的访问器(只要我们知道名称……)。同时,我们大多数人没有记住如何正确访问不可见的 ivars(除了 KVC)。类延续有帮助,但它确实引入了漏洞。

这个解决方法很明显:

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

现在只用 ivar 试试,不用 KVC。

【讨论】:

  • @Sam 谢谢,好问题!复杂性:它肯定是双向的。重新封装 - 更新
  • @bbum RE:似是而非的例子 虽然我同意你的观点,这是错误的解决方案,但我无法想象有许多经验丰富的 objc 开发人员相信它只是没有发生;我在其他人的程序中看到过,App Store 甚至禁止使用私有 Apple API。
  • 你不能用 object->foo 访问私有 ivar 吗?不难记住。
  • 我的意思是您可以使用 C -> 语法使用对象的指针引用来访问它。 Objective-C 类基本上只是底层结构,并且给定一个指向结构的指针,访问成员的 C 语法是 ->,它也适用于目标 C 类中的 ivars。
  • @NickLockwood 如果 ivar 是 @private,编译器应该禁止类和实例方法之外的成员访问——这不是你看到的吗?
【解决方案2】:

对我来说,这通常是性能。访问对象的 ivar 与使用指向包含此类结构的内存的指针访问 C 中的结构成员一样快。事实上,Objective-C 对象基本上是位于动态分配内存中的 C 结构。这通常与您的代码一样快,甚至手动优化的汇编代码也不会比这更快。

通过 getter/setting 访问 ivar 涉及到 Objective-C 方法调用,它比“普通”C 函数调用慢得多(至少 3-4 倍),甚至普通 C 函数调用也已经是多个比访问结构成员慢几倍。根据您的属性的属性,编译器生成的 setter/getter 实现可能涉及对函数 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。如果您查看它们的实现,您会注意到它们几乎完全相同,但有一个例外:compare: 方法。 AccountA 对象通过方法(getter)访问它们自己的属性,而AccountB 对象通过 ivar 访问它们自己的属性。这真的是唯一的区别!它们都访问另一个对象的属性以通过 getter 进行比较(通过 ivar 访问它并不安全!如果另一个对象是子类并覆盖了 getter 怎么办?)。另请注意,以 ivars 访问您自己的属性不会破坏封装(ivars 仍然不公开)。

测试设置非常简单:创建 1 个 Mio 随机帐户,将它们添加到数组中并对该数组进行排序。而已。当然,有两个数组,一个用于AccountA 对象,一个用于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 时间是完全可以接受的,毕竟“它是免费的”,那么由功耗引起的服务器托管成本呢?移动设备的电池运行时间如何?如果您要编写相同的移动应用程序两次(例如,自己的移动网络浏览器),一次是所有类仅通过 getter 访问自己的属性的版本,一次是所有类仅通过 ivars 访问它们的版本,那么不断使用第一个肯定会耗尽电池比使用第二个电池快得多,即使它们在功能上是等效的,并且对于用户来说,第二个电池甚至可能会感觉更快。

现在这是您的 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

【讨论】:

  • 非常翔实和脚踏实地的解释。为代码示例点赞
  • 我在您的帖子中看到的一个关键限定词是“......来自关键代码路径”。关键是使用使代码更容易读/写的东西,然后优化你发现的关键路径。这将在需要的地方增加复杂性。
  • @ViktorLexington 在我的代码中,我设置了一个unsigned int,无论您是否使用ARC,它都不会被保留/释放。保留/释放本身很昂贵,因此差异会更小,因为保留管理添加了始终存在的静态开销,直接使用 setter/getter 或 ivar;但是,如果您直接访问 ivar,您仍然可以节省一个额外的方法调用的开销。在大多数情况下没什么大不了的,除非你每秒这样做几千次。 Apple 表示默认使用 getter/setter,除非您使用 init/dealloc 方法或发现瓶颈。
  • @Fogmeister 添加了一个代码示例,展示了在一个非常简单的现实世界示例中,这可以多么容易地产生巨大的差异。而且这个例子与一台超级计算机做数万亿次计算无关,它更多的是对一个非常简单的数据表进行排序(在数百万个应用程序中很常见)。
  • @malhal 标记为copy 的属性将不会在您每次访问时复制其值。 copy 属性的 getter 类似于 strong/retain 属性的 getter。它的代码基本上是return [[self-&gt;value retain] autorelease];。只有 setter 复制值,它大致看起来像这样 [self-&gt;value autorelease]; self-&gt;value = [newValue copy];,而 strong/retain setter 看起来像这样:[self-&gt;value autorelease]; self-&gt;value = [newValue retain];
【解决方案3】:

最重要的原因是信息隐藏的OOP概念:如果您通过属性公开所有内容,从而允许外部对象窥视另一个对象的内部,那么您将利用这些内部,从而使更改实现复杂化。

“最小的性能”增益可以很快总结,然后成为一个问题。我从经验中知道;我正在开发一个真正将 iDevice 发挥到极致的应用程序,因此我们需要避免不必要的方法调用(当然只有在合理可能的情况下)。为了帮助实现这一目标,我们还避免使用点语法,因为它让人很难一眼看出方法调用的数量:例如,表达式self.image.size.width 触发了多少方法调用?相比之下,您可以通过[[self image] size].width 立即告知。

此外,通过正确的 ivar 命名,可以在没有属性的情况下使用 KVO(IIRC,我不是 KVO 专家)。

【讨论】:

  • +1 关于“最低性能”增益的良好响应加起来并希望显式查看所有方法调用。将点语法与属性一起使用肯定会掩盖在自定义 getter/setter 中进行的大量工作(尤其是当 getter 每次调用时都返回一个副本时)。
  • 如果不使用 setter,KVO 对我不起作用。直接更改 ivar 不会调用观察者的值已更改!
  • KVC 可以访问 ivars。 KVO 无法检测到对 ivars 的更改(而是依赖于要调用的访问器)。
【解决方案4】:

语义

  • @property 可以表达 ivars 不能表达的内容:nonatomiccopy
  • ivars 可以表达@property 不能表达的内容:
    • @protected:子类公开,外部私有。
    • @package:在 64 位框架上公开,外部私有。与 32 位上的 @public 相同。请参阅 Apple 的 64-bit Class and Instance Variable Access Control
    • 限定符。例如,强对象引用数组:id __strong *_objs

性能

简短的故事:ivars 更快,但对于大多数用途来说并不重要。 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 中:即使 getter/setter 除了加载/存储之外,添加了零个额外指令,会有一个 调用者中有六条额外的指令来设置 消息发送并处理结果。

  3. 发送消息会强制将该选择器的条目保留在 方法缓存 中,并且该内存通常保留在 数据缓存。这增加了启动时间,增加了静态内存 使用您的应用程序,并使上下文切换更加痛苦。由于 方法缓存特定于对象的动态类,这 使用 KVO 的次数越多,问题就越大。

  4. 发送消息强制将函数中的所有值溢出到堆栈(或保存在被调用者保存寄存器中,这意味着 在不同的时间溢出)。

  5. 发送消息可能会产生任意副作用,因此

    • 强制编译器重置所有关于非本地内存的假设
    • 无法提升、沉没、重新排序、合并或消除。

  6. 在 ARC 中,消息发送的结果将始终由被调用者或调用者保留,即使是 +0 返回:即使 方法不保留/自动释放其结果,调用者不知道 并且必须尝试采取行动以防止结果得到 自动发布。这永远无法消除,因为消息发送是 不可静态分析。

  7. 在 ARC 中,由于 setter 方法通常将其参数设为 +0,因此无法“转移”该对象的保留(如 上面讨论的,ARC 通常有) 到 ivar 中,所以 的值 通常需要保留/释放两次

当然,这并不意味着它们总是坏的——有一个 使用属性的很多充分理由。请记住,就像 许多其他语言功能,它们不是免费的。


约翰。

【讨论】:

    【解决方案5】:

    属性与实例变量是一种权衡,最终选择取决于应用程序。

    封装/信息隐藏 从设计的角度来看,这是一件好事 (TM),狭窄的接口和最少的链接使软件易于维护和理解。在 Obj-C 中隐藏任何东西是相当困难的,但是在 实现 中声明的实例变量会尽可能接近。

    性能 虽然“过早的优化”是一件坏事 (TM),但仅仅因为你可以编写性能不佳的代码至少同样糟糕。很难反对方法调用比加载或存储更昂贵,而且在计算密集型代码中,成本很快就会增加。

    在具有属性的静态语言(例如 C#)中,对 setter/getter 的调用通常可以被编译器优化掉。然而,Obj-C 是动态的,移除此类调用要困难得多。

    抽象 Obj-C 中反对实例变量的一个论点传统上是内存管理。对于 MRC 实例变量,需要在整个代码中传播保留/释放/自动释放的调用,属性(无论是否合成)将 MRC 代码保留在一个地方 - 抽象原则是一件好事 (TM)。然而,对于 GC 或 ARC,这个论点消失了,因此内存管理的抽象不再是 反对 实例变量的论点。

    【讨论】:

      【解决方案6】:

      属性将您的变量暴露给其他类。如果您只需要一个仅与您正在创建的类相关的变量,请使用实例变量。这是一个小例子:用于解析 RSS 等的 XML 类通过一堆委托方法等循环。有一个 NSMutableString 的实例来存储解析的每个不同传递的结果是很实用的。外部类没有理由需要访问或操作该字符串。因此,您只需在标题中或私下声明它并在整个类中访问它。为其设置属性可能仅对确保没有内存问题有用,使用 self.mutableString 调用 getter/setter。

      【讨论】:

        【解决方案7】:

        向后兼容性对我来说是一个因素。我无法使用任何 Objective-C 2.0 功能,因为我正在开发必须在 Mac OS X 10.3 上运行的软件和打印机驱动程序,作为要求的一部分。我知道您的问题似乎是针对 iOS 的,但我想我仍然会分享我不使用属性的原因。

        【讨论】:

          猜你喜欢
          • 2013-06-15
          • 1970-01-01
          • 2014-05-12
          • 2012-04-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-05-16
          相关资源
          最近更新 更多