Objective-C 101 (保留 vs 赋值) NSString

54

一个101问题

假设我正在制作汽车数据库,每个汽车对象的定义如下:

#import <UIKit/UIKit.h>

@interface Car:NSObject{
    NSString *name;
}

@property(nonatomic, retain) NSString *name;
为什么是@property(nonatomic, retain) NSString *name;而不是@property(nonatomic, assign) NSString *name;
我知道assign不会像retain一样增加引用计数。但既然nametodo对象的成员,它的作用域只限于todo本身,那为什么要使用retain呢?
此外,没有其他外部函数会修改它。

29
为什么?请说明原因! - bobobobo
15
简单来说,NSMutableString是NSString的一种。如果有人传递给你一个被保留的可变字符串,他们随后可以更改它。由于您的属性是NSString类型,您可能没有预料到这种行为。总的来说,不可变类通过调用自己的retain方法来实现-copy,因此除非您真正需要该内存,否则通常不会浪费任何内存。 - ipmcc
@ipmcc,对于readonly属性(@property (nonatomic, retain/assign/copy, readonly) NSString *myString;),最好是使用retaincopy还是assign - chown
1
对于只读属性,对外部调用者而言,保留/复制/分配没有区别,但仍应准确反映您在内部处理类的基础值的方式。如果您正在使用ARC和@synthesize创建iVars,则这一点非常重要,因为编译器将使用您的属性声明作为指示,告诉ARC在自动处理保留/释放时如何处理该值。 - ipmcc
@ipmcc:这并不完全正确。我必须再次验证一下,但据我记得,如果只读对象属性被赋值,getter 只会返回 iVar 的值,如果它是 retain 或 copy,getter 返回的是 [[... retain] autorelease] 的 iVar 值,如果你在代码中以某种方式替换了 iVar 的值,这可能是一个重要的区别。 - Mecki
@RDC,为什么你要将本应只标记为“objective-c”的问题添加“ios”标签? - Sulthan
8个回答

67
在Objective-C中,"对象的作用域"并不存在。作用域规则与对象的生命周期无关,保留计数才是关键。通常情况下,您需要声明对实例变量的所有权。请参阅Objective-C内存管理规则。使用"retain"属性时,属性setter会声明对新值的所有权并放弃对旧值的所有权。使用"assign"属性时,周围的代码必须执行此操作,这在责任和关注点分离方面是一样混乱的。您使用"assign"属性的原因是在无法保留该值的情况下(例如,BOOL或NSRect等非对象类型)或保留它会导致不必要的副作用的情况下。顺便说一句,在NSString的情况下,正确的属性类型通常是"copy"。这样,如果有人传递一个NSMutableString(这是有效的——它是NSString的一种),它就不会在您的控制下更改。

11
对于所有有可变版本的类,你应该使用“copy”而不是“retain”。例如:NSAArray、NSSet、NSDictionary、NSData、NSCharacterSet、NSIndexSet和NSString。 - PeyloW
9
一般来说,所有带有对象引用的属性都应该使用copy或retain,唯一的例外是代理(delegates),应该使用assign以避免循环引用。 - PeyloW
1
@Matt:Efreedom只是拥有SO问题的副本,我建议链接到原始SO问题 - Hans Olsson
2
@PeyloW:不仅仅是委托;任何非拥有关系的东西都应该使用assign而不是copyretain。沿着对象图向下的路径应该是拥有的;向上的路径(如果包含在内)应该是非拥有的。否则,您将创建一个循环引用,而没有在圆圈中设置委托。 - Peter Hosey
@PeyloW:为什么要使用assign?为什么不用weak?https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/EncapsulatingData/EncapsulatingData.html建议使用弱引用来避免循环引用。 - gmuhammad
显示剩余3条评论

19

不要忘记通过这种方式访问它

self.name = something;

因为

name = something;

不会关心生成的setter/getter方法,而是直接分配值。


谢谢,这非常重要!遵循置顶答案的建议仍然无法保留该值。非常重要的是要执行 self.name = ... - ck_

12

如果没有使用retain,则无法保证您通过name设置的NSString*对象的生存时间超过赋值语句本身。通过为合成setter方法使用retain属性,您允许它告诉内存管理系统至少还有一个对象有兴趣保留该NSString*对象。


好的...嗯,我想我有点明白你的意思了。 请详细告诉我您的意思。 - qstar
因为对于retain方法而言它是存活的,那么为什么对于赋值语句不是呢? - qstar
8
使用retain可以确保对象的存活,而使用assign则不能保证。使用retain意味着您拥有该对象,只要您拥有它,它就不会被销毁。而使用assign则没有这样的保证。 - Dave DeLong
2
@qstar:NSInteger不是一个对象。 - Chuck
你说的“没有保证”是什么意思?是指内存可能会随机消失,导致错误吗?还是指如果有人在“你正在设置名称的NSString*”上执行了[release]或[dealloc],那么你就会遇到问题?另外这是一个单独的问题,[dealloc]会完全销毁NSString,无论引用计数是多少,对吗? - bobobobo
显示剩余8条评论

9

self. 在以下代码中:

self.name = something;

这很重要!如果没有它,你将直接访问变量并绕过setter。

旧的方式(如果我错了,请纠正我)应该是:

[self setName:something];

无论如何,当我寻找适当的@propertiesNSStrings上时,这种记号是我真正需要的(听起来有点熟悉)。谢谢Axel。


这是一个非常好的信息@jem。第一次看,我认为它比Java中的“this”要多。 - swdev

9

对于那些正在寻找的人,苹果公司关于属性特征的文档在这里


4
那个链接已经失效了。 - iDev

8

阅读了许多文章、SO帖子并制作了演示应用程序来检查变量属性属性后,我决定将所有属性信息汇总在一起。

  1. atomic //默认值
  2. nonatomic
  3. strong=retain //默认值
  4. weak=unsafe_unretained
  5. retain
  6. assign //默认值
  7. unsafe_unretained
  8. copy
  9. readonly
  10. readwrite //默认值

因此,下面是详细的文章链接,您可以在其中找到上述所有属性,这肯定会对您有所帮助。非常感谢在此提供最佳答案的所有人!

iOS中的变量属性属性或修饰符

  1. retain = strong
    • 它被保留,旧值被释放并分配
    • retain指定新值应该在分配时发送-retain,并且旧值发送-release
    • retain与strong相同。
    • 苹果表示,如果您编写保留,则会自动转换/像强一样工作。
    • 像“alloc”这样的方法包括一个隐式的“retain”

例子:

@property (nonatomic, retain) NSString *name;

@synthesize name;
  1. assign
    • assign是默认的,仅执行变量分配操作。
    • assign是一个属性属性,告诉编译器如何合成属性的设置器实现。
    • 我会使用assign来处理C原始类型的属性,而将弱引用Objective-C对象使用weak关键字声明。

示例:

@property (nonatomic, assign) NSString *address;

@synthesize address;

3

谷歌的Objective-C风格指南对此有很好的覆盖:

接受NSString的setter应该始终复制其接受的字符串。不要仅仅保留字符串。这样可以避免调用者在您不知情的情况下更改它。不要假设因为您接受了一个NSString,它实际上不是NSMutableString。


2

如果您的类得到这个字符串对象,然后它从其下方消失了,那么这会很不幸,您知道吗?就像您的类第二次提到该对象时,它已经被另一个对象释放了。

这就是为什么您想要使用 retain 设置语义。


好的。那么,你为什么会选择不使用 retain 呢? - Madbreaks
2
如果您正在创建一个.delegate属性,那么delegate可能会retain您。如果您再次retain它,就会创建一个依赖循环,导致两个对象都无法被释放。当然,在ARC下,这个问题已经过时了(我们现在使用“weak”和“strong”来暗示类似的东西)。 - Dan Ray
@DanRay 只有在 Object A 的 dealloc 中释放了 Object B(保留了 Object A 的委托)时,两个对象才会被释放。 - OzBoz

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