你为什么要用伊娃?

我通常会以另一种方式看待这个问题,比如每个伊娃都必须是一个财产? (我喜欢bbum对这个问题的回答)。

我几乎完全在我的代码中使用属性。 然而,我经常和一个在iOS上开发很长时间的承包商合作,他是一个传统的游戏程序员。 他写的代码几乎没有声明任何属性,并依赖于ivars。 我认为他是这样做的,因为1)他已经习惯了,因为属性并不总是存在,直到目标C 2.0(2007年10月)和2)为最小的性能增益不经过一个getter / setter。

虽然他写的代码不会泄漏,但是我仍然希望他使用超过ivars的属性。 我们谈到了这一点,他或多或less看不到使用属性的理由,因为我们没有使用KVO,而且他在处理记忆问题方面经验丰富。

我的问题是更多…为什么你会想要使用一个伊娃期 – 有经验与否。 真的有这么大的performance差异,使用伊娃是有道理的吗?

同样作为澄清的一点,我根据需要重写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; 

完全“私人”只能在课堂上继续申报。

封装

如果伊娃是私人的,程序的其他部分就不能轻易得到。 有了一个声明的属性,聪明的人可以通过访问者很容易地访问和变异。

性能

是的,这可以在一些情况下有所作为。 有些程序在程序的某些部分不能使用任何objc消息传递(想想实时)时会受到一些限制。 在其他情况下,您可能需要直接访问以获得速度。 在其他情况下,这是因为objc消息传递作为优化防火墙。 最后,它可以减less引用计数操作,并将峰值内存使用量降至最低(如果正确的话)。

非平凡types

例如:如果你有一个C ++types,直接访问有时候是更好的方法。 该types可能不可复制,也可能不是微不足道的复制。

multithreading

你的许多ivars是相互依赖的。 您必须确保您的数据在multithreading环境下的完整性。 因此,您可能喜欢直接访问关键部分中的多个成员。 如果您坚持使用访问者来获取相关数据,那么您的锁通常必须是可重入的,而且您通常最终会进行更多的获取(时间显着更多)。

程序的正确性

由于子类可以覆盖任何方法,因此您最终可能会看到写入界面与正确pipe理状态之间存在语义差异。 程序正确性的直接访问在部分构build的状态中尤其常见 – 在初始化程序和dealloc ,最好使用直接访问。 您也可以在访问器,便捷构造器, copymutableCopy和归档/序列化实现的实现中发现这一点。

一切公共读写访问者的思维转移到隐藏其实现细节/数据的东西时,它也更加频繁。 有时候,你需要正确地绕过一个副作用,为了做正确的事情,可能会引入一个子类。

二进制大小

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

最大限度地减less复杂性

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


这完全不是说使用属性或访问器是坏的 – 每个都有重要的好处和限制。 像许多OO语言和devise方法一样,您也应该在ObjC中使用具有适当可见性的访问器。 有时你需要偏离。 出于这个原因,我认为通常最好限制直接访问声明伊娃的实现(例如声明@ @private )。


重新编辑1:

我们大多数人已经记住了如何dynamic地调用一个隐藏的访问器(只要我们知道这个名字…)。 同时,我们大多数人还没有记住如何正确访问不可见的ivars(超越KVC)。 类继续有帮助 ,但确实引入了漏洞。

这个解决方法很明显:

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

现在只用一个ivar来尝试,而不用KVC。

对我来说通常是performance。 访问对象的ivar与使用指向包含这样的结构的内存的指针访问C中的结构成员一样快。 实际上,Objective-C对象基本上是位于dynamic分配内存中的C结构。 这通常和你的代码一样快,甚至连手工优化的汇编代码都可以比这更快。

通过getter /设置来访问ivar涉及一个Objective-C方法调用,它比“正常”的C函数调用慢得多(至less3-4次),甚至一个正常的C函数调用也会比访问一个结构成员。 根据属性的属性,编译器生成的setter / getter实现可能会涉及到对函数objc_getProperty / objc_setProperty另一个C函数调用,因为它们必须根据需要retain / copy / autorelease对象,并进一步对primefaces执行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倍慢,这是一个非primefaces基元int,几乎是最好的情况 ; 大多数其他情况更糟(尝试primefacesNSString *属性!)。 所以,如果你能忍受每一个伊娃的访问速度比它慢4-5倍,那么使用属性是可以的(至less在性能方面),然而,在很多情况下,这样的性能下降是完全不能接受。

更新2015-10-20

有些人认为,这不是真正的世界问题,上面的代码是纯粹的综合,你永远不会注意到在一个真正的应用程序。 那么,让我们试试一个真实的世界样本。

下面的代码定义了Account对象。 一个帐户具有描述其所有者的名称( NSString * ),性别( enum )和年龄( unsigned )以及余额( int64_t )的属性。 一个账户对象有一个init方法和一个compare:方法。 compare:方法定义为:男性之前的女性订单,按字母sorting,年轻的订单,平衡订单从低到高。

实际上存在两个账户类别AccountAAccountB 。 如果你看他们的实现,你会发现他们几乎完全相同,只有一个例外: compare:方法。 AccountA对象通过方法(getter)访问自己的属性 ,而AccountB对象通过ivar访问自己的属性 。 这是唯一的区别! 他们都通过getter访问另一个对象的属性来比较(如果另一个对象是一个子类,并且已经重写了getter?)。 还要注意,访问你自己的属性作为ivars 不会破坏封装 (ivars仍然不公开)。

testing设置非常简单:创build1个Mio随机帐户,将它们添加到数组中并对该数组进行sorting。 而已。 当然,有两个数组,一个用于AccountA对象,一个用于AccountB对象,两个数组都填充相同的帐户(相同的数据源)。 我们花费多长时间对数组进行sorting。

下面是我昨天做过的几次运行的输出:

 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对象数组进行sorting始终比sortingAccountA对象数组的速度快得多

谁声称高达1.32秒的运行时差异没有什么区别,最好不要做UI编程。 例如,如果我想更改大型表的sorting顺序,像这样的时间差异会对用户产生巨大的影响(可接受UI和缓慢UI之间的区别)。

同样在这种情况下,示例代码是在这里执行的唯一真正的工作,但是多久你的代码只是一个复杂的发条的小齿轮? 而且如果每个齿轮都这样减速,整个发条的速度到底是什么意思呢? 特别是如果一个工作步骤取决于另一个的输出,这意味着所有的低效率将总结。 大多数效率低下的问题本身并不是问题,这是他们纯粹的问题。 而这样的问题不是一个简介,因为一个分析器是关于寻找关键的热点,但这些低效率都不是热点。 CPU时间只是平均分布在其中,但它们中的每一个都只有这样一小部分,这似乎是浪费时间来优化它。 事实上,仅仅优化其中的一个将毫无帮助,优化所有这些都可以大有帮助。

即使你不考虑CPU时间,因为你认为浪费CPU时间是完全可以接受的,毕竟“免费”,那么服务器托pipe成本又是怎么样的呢? 移动设备的电池运行时间是什么? 如果你要两次写相同的移动应用程序(例如一个自己的移动networking浏览器),一旦所有类只有获取者访问属性的版本,以及所有类只能通过Ivars访问它们的版本,使用第一个类的应用程序一定会耗尽电池比使用第二个电池快得多,即使它们function相当,对于用户来说,第二个电池甚至可能感觉到更快一点。

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

最重要的原因是信息隐藏的OOP概念:如果你通过属性暴露所有的东西,从而允许外部对象偷看另一个对象的内部,那么你将使用这些内部的,从而使改变实现变得复杂。

“最低性能”的收益可以快速总结,然后成为一个问题。 我从经验中知道; 我工作的应用程序,真正把设备的极限,我们因此需要避免不必要的方法调用(当然只有在合理的可能)。 为了达到这个目的,我们也避免了点语法,因为它很难看到方法调用的数目:例如,expression式self.image.size.width触发了多less个方法调用? 相比之下,您可以立即用[[self image] size].width

另外,用正确的伊娃命名,KVO可能没有属性(IIRC,我不是一个KVO专家)。

语义

  • 什么@property可以expression的ivars不能:nonatomic和copy
  • 什么ivars可以expression@property不能:
    • @protected :公共子类,私人外部。
    • @package :在64位的框架上公开的,私有的。 与@public在32位上相同。 请参阅Apple的64位类和实例variables访问控制 。
    • 预选赛。 例如,强对象引用的数组: id __strong *_objs

性能

简短的故事:ivars更快,但对大多数用途无关紧要。 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中保存更多的代码 :即使getter / setter在加载/存储之外添加了零个额外的指令,实际上也会有一半 – 在调用者中添加额外的指令来设置消息发送和处理结果。

  3. 发送消息强制该select器的条目保存在方法高速caching中 ,并且该存储器通常驻留在d-cache中。 这会增加启动时间,增加应用程序的静态内存使用量,并使上下文切换更加痛苦。 由于方法caching特定于对象的dynamic类,因此此问题会增加您使用KVO的次数。

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

  5. 发送消息可以有任意的副作用 ,因此

    • 强制编译器重置所有关于非本地内存的假设
    • 不能被吊起来,沉没,重新排列,合并或者消除。
  6. 在ARC中, 消息发送的结果总是被被调用者或调用者保留 ,即使是+0返回:即使该方法不保留/ autorelease结果,调用者也不知道并且试图采取行动,以防止结果自动释放。 这不能被消除,因为消息发送不是静态可分析的。

  7. 在ARC中,因为setter方法通常以+0为参数,所以没有办法将该对象的保留(如上所述,ARC通常具有)转移到ivar中,所以通常必须得到该值保留/释放两次

这些都不意味着它们总是不好的 – 当然有很多好的理由来使用属性。 请记住,像许多其他语言function一样,它们不是免费的。

约翰。

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

属性与实例variables是一种折衷,最终select归结于应用程序。

封装/信息隐藏从devise的angular度来看,这是一件好事(TM),狭窄的接口和最less的连接使得软件可以被维护和理解。 Obj-C很难隐藏任何东西,但是在实现中声明的实例variables就像你会得到的一样。

性能虽然“不成熟的优化”是一个坏事(TM),只是因为你可以至less一样写糟糕的代码。 它很难反对一种方法调用比一个加载或存储更昂贵,而在计算密集型代码中,成本很快就会增加。

在具有诸如C#等属性的静态语言中,通常可以通过编译器对设置者/获取者的调用进行优化。 然而Obj-C是dynamic的,消除这种调用要困难得多。

抽象在Obj-C中针对实例variables的一个论据传统上是内存pipe理。 MRC实例variables需要调用来保留/释放/自动释放以遍布整个代码,属性(合成或不合成)将MRC代码保存在一个地方 – 抽象原理是Good Thing(TM)。 然而,使用GC或ARC这个参数会消失,所以内存pipe理的抽象不再是实例variables的一个参数。

属性将你的variables暴露给其他类。 如果您只需要一个仅与您创build的类相关的variables,请使用实例variables。 下面是一个小例子:用于parsingRSS等的XML类通过一些委托方法等循环。 有一个NSMutableString的实例来存储每个不同的parsing过程的结果是很实用的。 没有理由为什么外部类需要访问或操纵这个string。 所以,你只需要在头文件中声明它,或者在整个课程中访问它。 为它设置属性可能只是有用的,以确保没有内存问题,使用self.mutableString来调用getter / setters。

    Interesting Posts