ios 为什么要使用 ivar?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9086736/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why would you use an ivar?
提问by Sam
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).
我通常会以另一种方式看到这个问题,例如必须每个 ivar 都是一个属性吗?(我喜欢 bbum 对这个问题的回答)。
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.
我几乎只在我的代码中使用属性。然而,我经常与一位长期在 iOS 上进行开发并且是传统游戏程序员的承包商合作。他编写的代码几乎不声明任何属性,并且依赖于 ivars。我认为他这样做是因为 1.) 他已经习惯了,因为直到 Objective C 2.0(07 年 10 月)和 2.) 之前,属性并不总是存在,因为不通过 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.
虽然他编写的代码不会泄漏,但我仍然希望他使用属性而不是 ivars。我们讨论过它,他或多或少认为没有理由使用属性,因为我们没有使用 KVO,而且他在处理内存问题方面经验丰富。
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?
我的问题更多......你为什么要使用 ivar 期间 - 无论是否有经验。真的有那么大的性能差异,使用 ivar 是合理的吗?
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.
同样作为澄清的一点,我根据需要覆盖 setter 和 getter,并使用与 getter/setter 内部的该属性相关的 ivar。但是,在 getter / setter 或 init 之外,我总是使用self.myProperty
语法。
Edit 1
编辑 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:
我很欣赏所有的好评。我想解决的一个似乎不正确的问题是,使用 ivar 可以获得封装,而使用您没有的属性。只需在类延续中定义属性。这将向外界隐藏该财产。您还可以在接口中声明属性 readonly 并在实现中将其重新定义为 readwrite ,例如:
// 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.
要让它完全“私有”,只需在类延续中声明它。
采纳答案by justin
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.
如果 ivar 是私有的,程序的其他部分就不能轻易地得到它。使用声明的属性,聪明的人可以通过访问器很容易地访问和改变。
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).
是的,这在某些情况下会有所作为。有些程序有一些限制,它们不能在程序的某些部分使用任何 objc 消息传递(想想实时)。在其他情况下,您可能希望直接访问它以提高速度。在其他情况下,这是因为 objc 消息传递充当优化防火墙。最后,它可以减少您的引用计数操作并最大限度地减少峰值内存使用(如果正确完成)。
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.
示例:如果您有 C++ 类型,有时直接访问只是更好的方法。类型可能不可复制,或者复制可能不是微不足道的。
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).
你的许多 ivar 是相互依赖的。您必须确保多线程上下文中的数据完整性。因此,您可能更喜欢直接访问临界区中的多个成员。如果您坚持使用访问器来获取相互依赖的数据,那么您的锁通常必须是可重入的,并且您通常最终会进行更多的获取(有时会更多)。
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.
由于子类可以覆盖任何方法,您最终可能会发现写入接口与适当管理状态之间存在语义差异。程序正确性的直接访问在部分构造的状态中特别常见——在您的初始化程序和 中dealloc
,最好使用直接访问。您可能还会在访问器、便利构造函数copy
、mutableCopy
、 和归档/序列化实现的实现中发现这种情况很常见。
It's also more frequent as one moves from the everything has a public readwrite accessormindset 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.
当您考虑一下程序的执行时,默认情况下声明所有内容为 readwrite 通常会导致许多您永远不需要的访问器方法。所以它会给你的程序和加载时间增加一些脂肪。
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.
在某些情况下,完全没有必要为一个简单的变量添加+类型+维护所有额外的脚手架,例如在一种方法中写入并在另一种方法中读取的私有 bool。
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
).
这并不是说使用属性或访问器不好——每个都有重要的好处和限制。与许多面向对象的语言和设计方法一样,您还应该支持在 ObjC 中具有适当可见性的访问器。有时您需要偏离。出于这个原因,我认为通常最好限制对声明 ivar 的实现的直接访问(例如声明它@private
)。
re Edit 1:
重新编辑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 notmemorized how to properly access ivars which aren't visible (beyond KVC). The class continuation helps, but it does introduce vulnerabilities.
我们大多数人都记住了如何动态调用隐藏的访问器(只要我们知道名称......)。同时,我们中的大多数人还没有记住如何正确访问不可见(超出 KVC)的 ivars。类延续有帮助,但它确实引入了漏洞。
This workaround's obvious:
这个解决方法很明显:
if ([obj respondsToSelector:(@selector(setName:)])
[(id)obj setName:@"Al Paca"];
Now try it with an ivar only, and without KVC.
现在只用 ivar 试试,不用 KVC。
回答by Mecki
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.
对我来说,它通常是性能。访问对象的 ivar 与使用指向包含此类结构的内存的指针访问 C 中的结构成员一样快。事实上,Objective-C 对象基本上是位于动态分配内存中的 C 结构体。这通常与您的代码可以达到的速度一样快,即使是手动优化的汇编代码也不能比这更快。
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 to retain
/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.
通过 getter/setting 访问 ivar 涉及一个 Objective-C 方法调用,它比“普通”C 函数调用慢得多(至少 3-4 倍),即使是普通 C 函数调用也比普通 C 函数调用慢很多倍访问结构成员。根据你的财产的属性,由编译器生成的setter /吸气实施可能需要另一个C函数调用函数objc_getProperty
/ objc_setProperty
,因为这些将不得不retain
/ copy
/autorelease
对象根据需要,进一步进行spinlocking用于原子性质必要。这很容易变得非常昂贵,而且我不是在谈论慢 50%。
Let's try this:
让我们试试这个:
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);
Output:
输出:
1: 23.0 picoseconds/run
2: 98.4 picoseconds/run
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.
这是慢 4.28 倍,这是一个非原子原始 int,几乎是最好的情况;大多数其他情况甚至更糟(尝试原子NSString *
属性!)。因此,如果您可以接受每个 ivar 访问比实际速度慢 4-5 倍的事实,那么使用属性就可以了(至少在性能方面),但是,在很多情况下,这种性能下降是完全不能接受。
Update 2015-10-20
更新 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 an init
method and a compare:
method. The compare:
method is defined as: Female orders before male, names order alphabetically, young orders before old, balance orders low to high.
下面的代码定义了Account
对象。帐户具有描述其所有者的姓名 ( NSString *
)、性别 ( enum
) 和年龄 ( unsigned
) 以及余额 ( int64_t
) 的属性。帐户对象具有init
方法和compare:
方法。该compare:
方法被定义为:女订单前男性,名字按照字母顺序排序,先老,平衡订单从低到高的年轻订单。
Actually there exists two account classes, AccountA
and AccountB
. If you look their implementation, you'll notice that they are almost entirely identical, with one exception: The compare:
method. AccountA
objects access their own propertiesby method (getter), while AccountB
objects access their own propertiesby 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).
实际上存在两个帐户类,AccountA
和AccountB
。如果您查看它们的实现,您会注意到它们几乎完全相同,只有一个例外:compare:
方法。AccountA
对象访问自己的属性通过方法(吸气剂),而AccountB
对象访问自己的属性由伊娃。这真的是唯一的区别!它们都访问另一个对象的属性以通过 getter 进行比较(通过 ivar 访问它是不安全的!如果另一个对象是子类并覆盖了 getter 怎么办?)。另请注意,以 ivars 访问您自己的属性不会破坏封装(ivars 仍然不是公开的)。
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 for AccountB
objects and both arrays are filled with identical accounts (same data source). We time how long it takes to sort the arrays.
测试设置非常简单:创建 1 个 Mio 随机帐户,将它们添加到一个数组中并对该数组进行排序。就是这样。当然,有两个数组,一个用于AccountA
对象,一个用于AccountB
对象,并且两个数组都填充了相同的帐户(相同的数据源)。我们计算对数组进行排序所需的时间。
Here's the output of several runs I did yesterday:
这是我昨天执行的几次运行的输出:
runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076
As you can see, sorting the array of AccountB
objects is always significant fasterthan sorting the array of AccountA
objects.
如您所见,对AccountB
对象数组进行排序总是比对AccountA
对象数组进行排序要快得多。
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).
任何声称最多 1.32 秒的运行时差异没有区别的人最好永远不要进行 UI 编程。例如,如果我想更改大表的排序顺序,像这样的时间差异确实会对用户产生巨大的影响(可接受的 UI 和缓慢的 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.
同样在这种情况下,示例代码是此处执行的唯一实际工作,但您的代码是否只是复杂发条的一小部分?如果每一个齿轮都像这样减慢整个过程,那最终对整个发条的速度意味着什么?特别是如果一个工作步骤依赖于另一个工作步骤的输出,这意味着所有的低效率都会加起来。大多数低效率本身并不是问题,而是它们的总和成为整个过程的问题。这样的问题不是分析器可以轻松显示的,因为分析器旨在寻找关键热点,但这些低效率本身都不是热点。CPU时间只是平均分布在它们之间,而它们每个都只有那么一小部分,优化它似乎完全浪费时间。这是真的,
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.
而且即使你不考虑CPU时间,因为你认为浪费CPU时间是完全可以接受的,毕竟“它是免费的”,那么功耗造成的服务器托管成本呢?移动设备的电池运行时间如何?如果您要编写两次相同的移动应用程序(例如,自己的移动 Web 浏览器),一次是所有类仅通过 getter 访问自己的属性的版本,一次是所有类仅通过 ivars 访问它们的版本,那么经常使用第一个肯定会耗尽电池比使用第二个要快得多,即使它们的功能相同,而且对于用户来说,第二个甚至可能感觉更快一些。
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):
现在这是您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
回答by Jano
Semantics
语义
- What
@property
can express that ivars can't:nonatomic
andcopy
. - What ivars can express that
@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.- Qualifiers. For example, arrays of strong object references:
id __strong *_objs
.
- 什么
@property
可以表达 ivars 不能表达的:nonatomic
和copy
。 - ivars 能表达的不能表达
@property
:@protected
: 子类是公共的,外部是私有的。@package
:在 64 位框架上公开,外部私有。与@public
32 位相同。请参阅 Apple 的64 位类和实例变量访问控制。- 预选赛。例如,强对象引用数组:
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 emailfrom lists.apple.com.
小故事: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
Properties affect performance in a lot of ways:
As already discussed, sending a message to do a load/store is slower than just doing the load/store inline.
Sending a message to do a load/store is also quite a bit more codethat needs to be kept in i-cache: even if the getter/setter added zero extra instructions beyond just the load/store, there'd be a solid half-dozen extra instructions in the caller to set up the message send and handle the result.
Sending a message forces an entry for that selector to be kept in the method cache, and that memory generally sticks around in d-cache. This increases launch time, increases the static memory usage of your app, and makes context switches more painful. Since the method cache is specific to the dynamic class for an object, this problem increases the more you use KVO on it.
Sending a message forces all values in the function to be spilled to the stack(or kept in callee-save registers, which just means spilling at a different time).
Sending a message can have arbitrary side-effectsand therefore
- forces the compiler to reset all of its assumptions about non-local memory
- cannot be hoisted, sunk, re-ordered, coalesced, or eliminated.
In ARC, the result of a message send will always get retained, either by the callee or the caller, even for +0 returns: even if the method doesn't retain/autorelease its result, the caller doesn't know that and has to try to take action to prevent the result from getting autoreleased. This can never be eliminated because message sends are not statically analyzable.
In ARC, because a setter method generally takes its argument at +0, there is no way to "transfer" a retain of that object (which, as discussed above, ARC usually has) into the ivar, so the value generally has to get retain/released twice.
None of this means that they're always bad, of course — there are a lot of good reasons to use properties. Just keep in mind that, like many other language features, they're not free.
John.
Subject: Re: when do you use properties vs. ivars? From: John McCall <email@hidden> Date: Sun, 17 Mar 2013 15:10:46 -0700
属性在很多方面影响性能:
正如已经讨论过的,发送消息来执行加载/存储比仅执行加载/存储 inline 慢。
发送消息以执行加载/存储也是需要保留在 i-cache 中的更多代码:即使 getter/setter 添加零额外指令,而不仅仅是加载/存储,也会有一半- 调用者中的十几个额外指令来设置消息发送和处理结果。
发送消息会强制该选择器的条目保留在方法 cache 中,并且该内存通常保留在 d-cache 中。这会增加启动时间,增加应用的静态内存使用量,并使上下文切换更加痛苦。由于方法缓存特定于对象的动态类,因此您在其上使用 KVO 越多,这个问题就会增加。
发送消息会强制函数中的所有值溢出到堆栈中(或保存在被调用者保存寄存器中,这意味着在不同的时间溢出)。
发送消息可能会产生任意的副作用,因此
- 强制编译器重置所有关于非本地内存的假设
- 不能被提升、沉没、重新排序、合并或消除。
在 ARC 中,消息发送的结果将始终被保留,无论是被调用者还是调用者,即使是 +0 返回:即使该方法不保留/自动释放其结果,调用者也不知道并且有尝试采取措施防止结果自动发布。这永远无法消除,因为消息发送是不可静态分析的。
在 ARC 中,因为 setter 方法通常将其参数取为 +0,所以无法将该对象的保留(如上所述,ARC 通常具有)“转移”到 ivar,因此该值通常必须得到保留/释放两次。
当然,这并不意味着它们总是不好的——使用属性有很多很好的理由。请记住,与许多其他语言功能一样,它们不是免费的。
约翰。
回答by DarkDust
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.
最重要的原因是信息隐藏的 OOP 概念:如果您通过属性公开所有内容,从而允许外部对象查看另一个对象的内部,那么您将使用这些内部对象,从而使更改实现变得复杂。
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
.
“最小性能”的增益可以快速总结,然后成为一个问题。我从经验中知道;我正在开发一个真正将 iDevices 发挥到极致的应用程序,因此我们需要避免不必要的方法调用(当然只有在合理可能的情况下)。为了帮助实现这个目标,我们还避免使用点语法,因为它很难第一眼看到方法调用的数量:例如,表达式self.image.size.width
触发了多少方法调用?相比之下,您可以立即使用[[self image] size].width
.
Also, with correct ivar naming, KVO is possible without properties (IIRC, I'm not an KVO expert).
此外,通过正确的 ivar 命名,KVO 可以没有属性(IIRC,我不是 KVO 专家)。
回答by CRD
Properties vs. instance variables is a trade-off, in the end the choice comes down to the application.
属性与实例变量是一种权衡,最终选择取决于应用程序。
Encapsulation/Information HidingThis 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 implementationcome as close as you'll get.
封装/信息隐藏从设计的角度来看,这是一件好事 (TM),窄接口和最少的链接使软件易于维护和理解。在 Obj-C 中隐藏任何东西都非常困难,但是在实现中声明的实例变量尽可能接近。
PerformanceWhile "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.
性能虽然“过早优化”是一件坏事 (TM),但仅仅因为您可以编写性能不佳的代码至少也同样糟糕。很难反对方法调用比加载或存储更昂贵,而且在计算密集型代码中,成本很快就会增加。
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.
在具有属性的静态语言(例如 C#)中,对 setter/getter 的调用通常可以被编译器优化掉。然而,Obj-C 是动态的,删除此类调用要困难得多。
AbstractionAn 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 againstinstance variables.
抽象传统上,反对 Obj-C 中实例变量的一个论点是内存管理。由于 MRC 实例变量需要在整个代码中传播对保留/释放/自动释放的调用,属性(合成与否)将 MRC 代码保存在一个地方 - 抽象原则是一件好事(TM)。然而,对于 GC 或 ARC,这个论点消失了,所以内存管理的抽象不再是反对实例变量的论点。
回答by Justin
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.
属性将您的变量暴露给其他类。如果您只需要一个仅与您正在创建的类相关的变量,请使用实例变量。这是一个小例子:用于解析 RSS 等的 XML 类通过一堆委托方法等循环。有一个 NSMutableString 实例来存储每个不同的解析传递的结果是很实用的。外部类没有理由需要访问或操作该字符串。因此,您只需在标题中或私下声明它并在整个类中访问它。为其设置属性可能仅有助于确保没有内存问题,使用 self.mutableString 调用 getter/setter。
回答by dreamlax
Backwards compatibilitywas 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.
向后兼容性对我来说是一个因素。我无法使用任何 Objective-C 2.0 功能,因为我正在开发必须在 Mac OS X 10.3 上运行的软件和打印机驱动程序作为要求的一部分。我知道你的问题似乎是针对 iOS 的,但我想我仍然会分享我不使用属性的原因。