为什么要使用 ivar?
我通常看到这个问题以另一种方式提出,例如 必须每个 ivar 都是一个属性?(我喜欢 bbum 对这个问题的回答)。
我几乎只在代码中使用属性。然而,我经常与一位长期从事 iOS 开发并且是传统游戏程序员的承包商合作。他编写的代码几乎没有声明任何属性,并且依赖于 ivars。我认为他这样做是因为 1.) 他已经习惯了,因为直到 Objective C 2.0(2007 年 10 月)为止属性并不总是存在,2.) 为了不通过 getter/setter 获得最小的性能增益。
虽然他编写的代码不会泄漏,但我仍然更喜欢他使用属性而不是 ivars。我们讨论过这个问题,他或多或少认为没有理由使用属性,因为我们没有使用 KVO,而且他在处理内存问题方面经验丰富。
我的问题是更多...为什么你想要使用 ivar period - 有经验的或没有经验的。使用 ivar 真的有那么大的性能差异吗?
另外,作为澄清一点,我根据需要重写 setter 和 getter,并使用与 getter/setter 内部的该属性相关的 ivar 。但是,在 getter / setter 或 init 之外,我总是使用 self.myProperty 语法。
编辑 1
我感谢所有好的回应。我想解决的一个似乎不正确的问题是,使用 ivar,你会得到封装,但属性却没有。只需在类延续中定义属性即可。这将使财产不被外人看到。您还可以在接口中声明该属性为只读,并在实现中将其重新定义为读写,例如:
// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;
并在类延续中拥有:
// readwrite within this file
@property (nonatomic, copy) NSString * name;
要使其完全“私有”,只需在类延续中声明它。
I usually see this question asked the other way, such as Must every ivar be a property? (and I like bbum's answer to this Q).
I use properties almost exclusively in my code. Every so often, however, I work with a contractor who has been developing on iOS for a long time and is a traditional game programmer. He writes code that declares almost no properties whatsoever and leans on ivars. I assume he does this because 1.) he's used to it since properties didn't always exist until Objective C 2.0 (Oct '07) and 2.) for the minimal performance gain of not going through a getter / setter.
While he writes code that doesn't leak, I'd still prefer him to use properties over ivars. We talked about it and he more or less sees not reason to use properties since we weren't using KVO and he's experienced with taking care of the memory issues.
My question is more... Why would you ever want to use an ivar period - experienced or not. Is there really that great of a performance difference that using an ivar would be justified?
Also as a point of clarification, I override setters and getters as needed and use the ivar that correlates with that property inside of the getter / setter. However, outside of a getter / setter or init, I always use the self.myProperty
syntax.
Edit 1
I appreciate all of the good responses. One that I'd like to address that seems incorrect is that with an ivar you get encapsulation where with a property you don't. Just define the property in a class continuation. This will hide the property from outsiders. You can also declare the property readonly in the interface and redefine it as readwrite in the implementation like:
// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;
and have in the class continuation:
// readwrite within this file
@property (nonatomic, copy) NSString * name;
To have it completely "private" only declare it in the class continuation.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
封装
如果 ivar 是私有的,则程序的其他部分就无法轻松获取它。有了声明的属性,聪明的人就可以通过访问器很容易地访问和改变。
性能
是的,这在某些情况下会产生影响。有些程序有限制,不能在程序的某些部分使用任何 objc 消息传递(想想实时)。在其他情况下,您可能希望直接访问它以提高速度。在其他情况下,这是因为 objc 消息传递充当优化防火墙。最后,它可以减少引用计数操作并最大限度地减少峰值内存使用(如果做得正确)。
重要类型
示例:如果您有 C++ 类型,有时直接访问是更好的方法。该类型可能不可复制,或者复制可能并不简单。
多线程
您的许多 ivar 都是相互依赖的。您必须确保多线程上下文中的数据完整性。因此,您可能倾向于直接访问关键部分中的多个成员。如果您坚持使用相互依赖数据的访问器,那么您的锁通常必须是可重入的,并且您通常最终会进行更多的获取(有时会明显更多)。
程序正确性
由于子类可以重写任何方法,因此您最终可能会发现写入接口与适当管理状态之间存在语义差异。直接访问程序正确性在部分构造的状态中尤其常见 - 在初始化程序和dealloc中,最好使用直接访问。您还可能会发现这种情况在访问器、便捷构造函数、复制、mutableCopy 和归档/序列化实现的实现中很常见。
当人们从“一切都有公共读写访问器”的心态转变为隐藏其实现细节/数据的心态时,这种情况也更加频繁。有时,您需要正确地解决子类重写可能引入的副作用,以便做正确的事情。
二进制大小
当您考虑一下程序的执行时,默认情况下将所有内容声明为可读可写通常会导致许多您永远不需要的访问器方法。所以它会增加你的程序和加载时间。
最小化复杂性
在某些情况下,完全没有必要为简单变量(例如在一种方法中编写并在另一种方法中读取的私有 bool)添加+类型+维护所有额外的脚手架。
这并不是说使用属性或访问器不好 - 每个属性或访问器都有重要的优点和限制。与许多 OO 语言和设计方法一样,您还应该支持 ObjC 中具有适当可见性的访问器。有时你需要偏离。出于这个原因,我认为通常最好限制对声明 ivar 的实现的直接访问(例如声明它
@private
)。重新编辑 1:
我们大多数人都记住了如何动态调用隐藏的访问器(只要我们知道名称......)。与此同时,我们大多数人没有记住如何正确访问不可见的ivars(超出KVC)。类延续有帮助,但它确实引入了漏洞。
这个解决方法很明显:
现在尝试仅使用 ivar,而不使用 KVC。
Encapsulation
If the ivar is private, the other parts of the program can't get at it as easily. With a declared property, the clever people can access and mutate quite easily via the accessors.
Performance
Yes, this can make a difference in some cases. Some programs have constraints where they can not use any objc messaging in certain parts of the program (think realtime). In other cases, you may want to access it directly for speed. In other cases, it's because objc messaging acts as an optimization firewall. Finally, it can reduce your reference count operations and minimize peak memory usage (if done correctly).
Nontrivial Types
Example: If you have a C++ type, direct access is just the better approach sometimes. The type may not be copyable, or it may not be trivial to copy.
Multithreading
Many of your ivars are codependent. You must ensure your data integrity in multithreaded context. Thus, you may favor direct access to multiple members in critical sections. If you stick with accessors for codependent data, your locks must typically be reentrant and you will often end up making many more acquisitions (significantly more at times).
Program Correctness
Since the subclasses can override any method, you may eventually see there is a semantic difference between writing to the interface versus managing your state appropriately. Direct access for program correctness is especially common in partially constructed states -- in your initializers and in
dealloc
, it's best to use direct access. You may also find this common in the implementations of an accessor, a convenience constructor,copy
,mutableCopy
, and archiving/serialization implementations.It's also more frequent as one moves from the everything has a public readwrite accessor mindset to one which hides its implementation details/data well. Sometimes you need to correctly step around side effects a subclass' override may introduce in order to do the right thing.
Binary Size
Declaring everything readwrite by default usually results in many accessor methods you never need, when you consider your program's execution for a moment. So it will add some fat to your program and load times as well.
Minimizes Complexity
In some cases, it's just completely unnecessary to add+type+maintain all that extra scaffolding for a simple variable such as a private bool that is written in one method and read in another.
That's not at all to say using properties or accessors is bad - each has important benefits and restrictions. Like many OO languages and approaches to design, you should also favor accessors with appropriate visibility in ObjC. There will be times you need to deviate. For that reason, I think it's often best to restrict direct accesses to the implementation which declares the ivar (e.g. declare it
@private
).re Edit 1:
Most of us have memorized how to call a hidden accessor dynamically (as long as we know the name…). Meanwhile, most of us have not memorized how to properly access ivars which aren't visible (beyond KVC). The class continuation helps, but it does introduce vulnerabilities.
This workaround's obvious:
Now try it with an ivar only, and without KVC.
对我来说,通常是表现。访问对象的 ivar 与使用指向包含此类结构的内存的指针访问 C 中的结构成员一样快。事实上,Objective-C 对象基本上是位于动态分配内存中的 C 结构体。这通常是您的代码所能达到的最快速度,即使是手工优化的汇编代码也不能比这更快。
通过 getter/setting 访问 ivar 涉及 Objective-C 方法调用,这比“普通”C 函数调用慢得多(至少 3-4 倍),甚至普通 C 函数调用也已经比“普通”C 函数调用慢几倍。访问结构体成员。根据属性的属性,编译器生成的 setter/getter 实现可能涉及对函数
objc_getProperty
/objc_setProperty
的另一个 C 函数调用,因为这些函数必须 <根据需要 code>retain/copy
/autorelease
对象,并在必要时进一步对原子属性执行自旋锁定。这很容易变得非常昂贵,而且我并不是说慢 50%。让我们试试这个:
输出:
速度慢了 4.28 倍,并且这是一个非原子原始 int,几乎是最佳情况;大多数其他情况甚至更糟(尝试原子
NSString *
属性!)。因此,如果您可以接受每个 ivar 访问速度比实际速度慢 4-5 倍的事实,那么使用属性就很好(至少在性能方面),但是,在很多情况下,这种性能下降是很严重的。完全不能接受。更新2015-10-20
有些人认为,这不是一个现实世界的问题,上面的代码纯粹是合成的,你永远不会在真正的应用程序中注意到这一点。好吧,让我们尝试一下现实世界的示例。
下面的代码定义了
Account
对象。帐户具有描述其所有者的姓名 (NSString *
)、性别 (enum
) 和年龄 (unsigned
) 的属性,以及余额 (int64_t
)。帐户对象具有init
方法和compare:
方法。compare:
方法定义为:女性顺序在男性之前,姓名按字母顺序排列,年轻人在老年人之前,平衡顺序从低到高。实际上存在两个帐户类:
AccountA
和AccountB
。如果您查看它们的实现,您会发现它们几乎完全相同,但有一个例外:compare:
方法。AccountA
对象通过方法(getter)访问自己的属性,而AccountB
对象通过 ivar 访问自己的属性。这确实是唯一的区别!它们都通过 getter 访问另一个对象的属性来进行比较(通过 ivar 访问它是不安全的!如果另一个对象是子类并且覆盖了 getter 怎么办?)。另请注意,访问您自己的属性作为 ivars 不会破坏封装(ivars 仍然不公开)。测试设置非常简单:创建 1 个 Mio 随机帐户,将它们添加到一个数组中并对该数组进行排序。就是这样。当然,有两个数组,一个用于
AccountA
对象,一个用于AccountB
对象,并且两个数组都填充了相同的帐户(相同的数据源)。我们计算对数组进行排序所需的时间。以下是我昨天执行的几次运行的输出:
如您所见,对
AccountB
对象数组进行排序总是比对AccountA
数组进行排序要快得多代码>对象。谁声称运行时间差异最多 1.32 秒没有什么区别,最好永远不要进行 UI 编程。例如,如果我想更改一个大表的排序顺序,这样的时间差异确实会给用户带来巨大的差异(可接受的 UI 和缓慢的 UI 之间的差异)。
同样在这种情况下,示例代码是此处执行的唯一实际工作,但是您的代码有多少次只是复杂发条装置的一个小齿轮?如果每个齿轮都像这样减慢整个过程,那么最终对整个发条装置的速度意味着什么?特别是如果一个工作步骤依赖于另一工作步骤的输出,这意味着所有低效率都会累积起来。大多数效率低下本身并不是问题,而是它们的总和才成为整个流程的问题。分析器不会轻易显示这样的问题,因为分析器的目的是查找关键热点,但这些低效率本身都不是热点。 CPU 时间只是平均分布在它们之间,但它们每个都只占很小的一部分,优化它似乎完全是浪费时间。确实,仅优化其中一个绝对没有任何帮助,优化所有这些则可以带来巨大帮助。
即使你不考虑CPU时间,因为你认为浪费CPU时间是完全可以接受的,毕竟“它是免费的”,那么功耗带来的服务器托管成本又如何呢?移动设备的电池运行时间怎么样?如果您将同一个移动应用程序编写两次(例如自己的移动网络浏览器),一次是所有类仅通过 getter 访问自己的属性的版本,一次是所有类仅通过 ivars 访问它们的版本,则不断使用第一个肯定会耗尽资源电池比使用第二块电池快得多,尽管它们的功能相同,而且对于用户来说,第二块电池甚至可能感觉更快一点。
现在,这是
main.m
文件的代码(该代码依赖于启用 ARC,并且一定要在编译时使用优化才能看到完整的效果):For me it is usually performance. Accessing an ivar of an object is as fast as accessing a struct member in C using a pointer to memory containing such a struct. In fact, Objective-C objects are basically C structs located in dynamically allocated memory. This is usually as fast as your code can get, not even hand optimized assembly code can be any faster than that.
Accessing an ivar through a getter/setting involves an Objective-C method call, which is much slower (at least 3-4 times) than a "normal" C function call and even a normal C function call would already be multiple times slower than accessing a struct member. Depending on the attributes of your property, the setter/getter implementation generated by the compiler may involve another C function call to the functions
objc_getProperty
/objc_setProperty
, as these will have toretain
/copy
/autorelease
the objects as needed and further perform spinlocking for atomic properties where necessary. This can easily get very expensive and I'm not talking about being 50% slower.Let's try this:
Output:
This is 4.28 times slower and this was a non-atomic primitive int, pretty much the best case; most other cases are even worse (try an atomic
NSString *
property!). So if you can live with the fact that each ivar access is 4-5 times slower than it could be, using properties is fine (at least when it comes to performance), however, there are plenty of situations where such a performance drop is completely unacceptable.Update 2015-10-20
Some people argue, that this is not a real world problem, the code above is purely synthetic and you will never notice that in a real application. Okay then, let's try a real world sample.
The code following below defines
Account
objects. An account has properties that describe name (NSString *
), gender (enum
), and age (unsigned
) of its owner, as well as a balance (int64_t
). An account object has aninit
method and acompare:
method. Thecompare:
method is defined as: Female orders before male, names order alphabetically, young orders before old, balance orders low to high.Actually there exists two account classes,
AccountA
andAccountB
. If you look their implementation, you'll notice that they are almost entirely identical, with one exception: Thecompare:
method.AccountA
objects access their own properties by method (getter), whileAccountB
objects access their own properties by ivar. That's really the only difference! They both access the properties of the other object to compare to by getter (accessing it by ivar wouldn't be safe! What if the other object is a subclass and has overridden the getter?). Also note that accessing your own properties as ivars does not break encapsulation (the ivars are still not public).The test setup is really simple: Create 1 Mio random accounts, add them to an array and sort that array. That's it. Of course, there are two arrays, one for
AccountA
objects and one forAccountB
objects and both arrays are filled with identical accounts (same data source). We time how long it takes to sort the arrays.Here's the output of several runs I did yesterday:
As you can see, sorting the array of
AccountB
objects is always significant faster than sorting the array ofAccountA
objects.Whoever claims that runtime differences of up to 1.32 seconds make no difference should better never do UI programming. If I want to change the sorting order of a large table, for example, time differences like these do make a huge difference to the user (the difference between an acceptable and a sluggish UI).
Also in this case the sample code is the only real work performed here, but how often is your code just a small gear of a complicated clockwork? And if every gear slows down the whole process like this, what does that mean for the speed of the whole clockwork in the end? Especially if one work step depends on the output of another one, which means all the inefficiencies will sum up. Most inefficiencies are not a problem on their own, it's their sheer sum that becomes a problem to the whole process. And such a problem is nothing a profiler will easily show because a profiler is about finding critical hot spots, but none of these inefficiencies are hot spots on their own. The CPU time is just averagely spread among them, yet each of them only has such a tiny fraction of it, it seems a total waste of time to optimize it. And it's true, optimizing just one of them would help absolutely nothing, optimizing all of them can help dramatically.
And even if you don't think in terms of CPU time, because you believe wasting CPU time is totally acceptable, after all "it's for free", then what about server hosting costs caused by power consumption? What about battery runtime of mobile devices? If you would write the same mobile app twice (e.g. an own mobile web browser), once a version where all classes access their own properties only by getters and once where all classes access them only by ivars, using the first one constantly will definitely drain the battery much faster than using the second one, even though they are functional equivalent and to the user the second one would even probably even feel a bit swifter.
Now here's the code for your
main.m
file (the code relies on ARC being enabled and be sure to use optimization when compiling to see the full effect):语义
@property
可以表达 ivars 不能表达的内容:nonatomic
和copy
。@property
不能表达的内容:@protected
:在子类上公开,在外部私有。@package
:在 64 位框架上公开,在外部私有。与 32 位上的@public
相同。请参阅 Apple 的 64 位类和实例变量访问控制。id __strong *_objs
。性能
简短的故事:ivars 更快,但对于大多数用途来说并不重要。
nonatomic
属性不使用锁,但直接 ivar 速度更快,因为它跳过了访问器调用。有关详细信息,请阅读以下列表中的电子邮件。苹果网站。Semantics
@property
can express that ivars can't:nonatomic
andcopy
.@property
can't:@protected
: public on subclasses, private outside.@package
: public on frameworks on 64 bits, private outside. Same as@public
on 32 bits. See Apple's 64-bit Class and Instance Variable Access Control.id __strong *_objs
.Performance
Short story: ivars are faster, but it doesn't matter for most uses.
nonatomic
properties don't use locks, but direct ivar is faster because it skips the accessors call. For details read the following email from lists.apple.com.最重要的原因是信息隐藏的 OOP 概念:如果您通过属性公开所有内容,从而允许外部对象窥视另一个对象的内部,那么您将利用这些内部,从而使更改对象变得复杂。执行。
“最小性能”增益很快就会累积起来,然后成为一个问题。我从经验中知道;我正在开发一个真正将 iDevices 发挥到极限的应用程序,因此我们需要避免不必要的方法调用(当然只有在合理可能的情况下)。为了帮助实现这一目标,我们还避免使用点语法,因为它让人很难第一眼看到方法调用的数量:例如,表达式
self.image.size.width 执行了多少个方法调用触发?相比之下,您可以通过
[[self image] size].width
立即判断。另外,通过正确的 ivar 命名,KVO 无需属性也是可能的(IIRC,我不是 KVO 专家)。
The most important reason is the OOP concept of information hiding: If you expose everything via properties and thus make allow external objects to peek at another object's internals then you will make use of these internal and thus complicate changing the implementation.
The "minimal performance" gain can quickly sum up and then become a problem. I know from experience; I work on an app that really takes the iDevices to their limits and we thus need to avoid unnecessary method calls (of course only where reasonably possible). To aid with this goal, we're also avoiding the dot syntax since it makes it hard to see the number of method calls on first sight: for example, how many method calls does the expression
self.image.size.width
trigger? By contrast, you can immediately tell with[[self image] size].width
.Also, with correct ivar naming, KVO is possible without properties (IIRC, I'm not an KVO expert).
属性与实例变量是一种权衡,最终的选择取决于应用程序。
封装/信息隐藏从设计角度来看,这是一件好事(TM),狭窄的接口和最少的链接使得软件易于维护和理解。在 Obj-C 中隐藏任何东西都非常困难,但是在实现中声明的实例变量已经非常接近了。
性能 虽然“过早优化”是一件坏事 (TM),但仅仅因为可以而编写性能不佳的代码至少同样糟糕。很难否认方法调用比加载或存储更昂贵,并且在计算密集型代码中,成本很快就会增加。
在具有属性的静态语言(例如 C#)中,对 setter/getter 的调用通常可以由编译器优化。然而 Obj-C 是动态的,删除此类调用要困难得多。
抽象 反对 Obj-C 中实例变量的争论传统上是内存管理。对于 MRC 实例变量,需要在整个代码中传播对保留/释放/自动释放的调用,属性(无论是否合成)将 MRC 代码保留在一个位置 - 抽象原则是一件好事 (TM)。然而,对于 GC 或 ARC,这个论点消失了,因此内存管理的抽象不再是实例变量的论点。
Properties vs. instance variables is a trade-off, in the end the choice comes down to the application.
Encapsulation/Information Hiding This is a Good Thing (TM) from a design perspective, narrow interfaces and minimal linkage is what makes software maintainable and understandable. It is pretty hard in Obj-C to hide anything, but instance variables declared in the implementation come as close as you'll get.
Performance While "premature optimisation" is a Bad Thing (TM), writing badly performing code just because you can is at least as bad. Its hard to argue against a method call being more expensive than a load or store, and in computational intensive code the cost soon adds up.
In a static language with properties, such as C#, calls to setters/getters can often be optimised away by the compiler. However Obj-C is dynamic and removing such calls is much harder.
Abstraction An argument against instance variables in Obj-C has traditionally been memory management. With MRC instance variables require calls to retain/release/autorelease to be spread throughout the code, properties (synthesized or not) keep the MRC code in one place - the principle of abstraction which is a Good Thing (TM). However with GC or ARC this argument goes away, so abstraction for memory management is no longer an argument against instance variables.
属性将您的变量暴露给其他类。如果您只需要一个仅与您正在创建的类相关的变量,请使用实例变量。下面是一个小例子:用于解析 RSS 等的 XML 类通过一堆委托方法等进行循环。使用 NSMutableString 的实例来存储每个不同的解析过程的结果是很实用的。外部类没有理由需要访问或操作该字符串。因此,您只需在标头或私有中声明它,然后在整个类中访问它。为其设置属性可能仅对确保不存在内存问题有用,使用 self.mutableString 调用 getter/setter。
Properties expose your variables to other classes. If you just need a variable that is only relative to the class you're creating, use an instance variable. Here's a small example: the XML classes for parsing RSS and the like cycle through a bunch of delegate methods and such. It's practical to have an instance of NSMutableString to store the result of each different pass of the parse. There's no reason why an outside class would need to ever access or manipulate that string. So, you just declare it in the header or privately and access it throughout the class. Setting a property for it might only be useful to make sure there are no memory issues, using self.mutableString to invoke the getter/setters.
向后兼容性对我来说是一个因素。我无法使用任何 Objective-C 2.0 功能,因为我正在开发必须在 Mac OS X 10.3 上运行的软件和打印机驱动程序,这是一项要求的一部分。我知道你的问题似乎是针对 iOS 的,但我想我仍然会分享我不使用属性的原因。
Backwards compatibility was a factor for me. I couldn't use any Objective-C 2.0 features because I was developing software and printer drivers that had to work on Mac OS X 10.3 as part of a requirement. I know your question seemed targeted around iOS, but I thought I'd still share my reasons for not using properties.