Objective-C原始类型的属性

10
在Objective-C中,对于原始类型,指定一个属性为nonatomic是否有意义?
我想知道这两个属性之间的区别:
@property (nonatomic) BOOL myBool;
@property BOOL myBool;
3个回答

26

技术上讲,它们是不同的,但实际上除非您编写自己的访问器,否则它们并没有区别。

让我解释一下。对于对象指针属性,比如@property NSObject *foo,如果您使用synthesize访问器,生成的代码中会有明显而重要的差异。这在苹果文档中有描述,其中指出如果该属性是原子性的(如果您未指定nonatomic,则默认为原子性),则合成的访问器将锁定该对象。

因此,对于对象属性,简单地说:nonatomic更快,但不是线程安全的;atomic(您无法指定,但它是默认值)是线程安全的,但可能较慢。

(注意:如果您习惯了Java,可以将使用nonatomic视为类似于不指定synchronized,而不指定nonatomic则类似于指定synchronized。换句话说,atomic = synchronized)

但是BOOL是一个基本类型——实际上是C中的有符号字符,因此访问应该是原子性的,无需像Jano的答案中提到的那样锁定对象。因此,在合成访问器时,有两种可能性: 1:编译器很聪明,看到属性是基本类型并避免锁定它, 2:编译器始终为原子属性锁定对象。
就我所看到的,这在任何地方都没有得到记录,因此我尝试使用XCode中的Generate->Assembly选项进行比较。答案并不完全确凿,但足以说明我几乎可以确定答案是#1,编译器是聪明的。我这么说,是因为为原子对象属性生成的汇编代码与非原子对象属性生成的汇编代码有很大不同(更多的代码用于锁定对象)。另一方面,对于BOOL属性,只有一行不同的代码——一个单独的“mov”,看起来不可能有什么影响。但我仍然有些疑虑。有趣的是,原子版本的BOOL具有一些额外的注释行用于调试——因此编译器显然在不同地处理它。
尽管如此,相似性是如此之大,以至于我会说它们在实际用途中是相同的。
但它们在技术上仍然不同,并且如果您无法看到实现,它们在您阅读的某个其他库中可能会有实质性的不同(您没有自己编写代码),这就是原因:原子属性具有合同。合同规定:“如果您在多个线程上访问我的值,则我承诺每个设置或获取操作将在任何其他操作开始之前完成”。
但是,您说,BOOL仍然是天然的原子性,所以这个合同不是隐含的吗?
不是的。BOOL 变量天然是原子性的,但我们谈论的是一个属性。一个属性可能不会被合成,甚至可能没有单个变量来支持它。这实际上是相当普遍的。考虑:
@property (getter=isEmptyThingo) BOOL emptyThingo;

...
- (BOOL)isEmptyThingo
{
    Thingo *thingo = [self deriveTheThingo];
    if ([thingo length] == 0) {
        return YES;
    }
    return NO;
}

谁知道在deriveTheThingo中发生了什么!? 好吧,这有点牵强,但重点是isEmptyThingo - 我们的getter看起来不太原子化,对吗?如果一个线程正在派生thingo,而另一个线程调用查找它是否为空会发生什么。

长话短说:该属性不是原子的。因此,我们应该声明它为原子的。

因此,我的原始答案合格:如果您自己编写此属性并使用@synthesize,则它们可能相同,但通常不应将它们视为相同。

作为经验法则,如果您不需要多线程支持-通常在像UIViewControllers这样的UI代码中不需要-则只需声明所有内容为非原子性。


4
非常好的解释。很奇怪为什么没有更多人点赞这个。 - Accatyyc
原子性并不意味着线程安全,在您的答案中应进行更正。 - Bradley Thomas

9

在一个x位的架构中(比如:32位、64位等),任何小于或等于x位的值都将被原子地读取或写入。这是任何合理硬件实现的属性。

默认的原子属性意味着属性值总是整体设置或获取,无视其他线程正在做什么。这只对超过架构位数的属性才是一个问题。对于任何其他类型,非原子性都会被编译器完全忽略。

例如:

@property struct { int d; } flag;
@property (atomic) struct { float x; float y; } point;
@property (atomic,copy) NSString *s;
  • struct { int d; }已经是原子型的,因此访问器不需要互斥。

  • struct { float x, float y} 如果不是原子的,则可能处于不一致状态。例如:两个线程分别设置{1,2}{3,4},这两个写入操作可能会重叠,导致结构体最终的值为每个集合中的一个值:{1,4}

  • 指针存储在单个内存位置中,但需要多条语句进行内存管理。

原子属性有助于线程安全,避免竞态条件造成的不一致值或内存管理问题。但仅凭这一点并不能保证线程安全,因为它并未解决死锁、饥饿、可见性等其他问题。


2

是的。 nonatomic 不是一个内存管理关键字,它与线程安全有关。此外,属性默认情况下是原子性的(没有显式声明为 nonatomic),因此你列出的这两个声明之间存在差异。


有所不同,但我认为问题在于当您的属性是“基本”类型时,是否值得支付线程安全逻辑的代价。 我猜这意味着诸如BOOL,int等类型,在某些语言中保证不会遭受部分写入。 - Martin
1
@Martin,它们在C中被称为原始类型。无论如何,将您的属性设置为原子性本身并不能保证线程安全,因此您可以将所有属性设置为非原子性。 - user529758

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接