在ARC中,我应该在init方法中使用self.property吗?

51

一个快速的问题。

如果我在.h文件中有一个同名的属性和实例变量:

属性声明:

(Reminder*)reminder;
@property(nonatomic,strong)(Reminder*)reminder;

如果我正在使用ARC,那么在.init方法中,我应该使用ivar还是property?

- (id)initWithReminder:(Reminder*)reminder_ {
    self = [super init];
    if (self) {
        reminder = reminder_;
    }
    return self;
}

或者我应该使用属性来获得自动引用计数的好处,像这样:

- (id)initWithReminder:(Reminder*)reminder_ {
    self = [super init];
    if (self) {
        self.reminder = reminder_;
    }
    return self;
}

我不确定在对象初始化的哪个阶段可以用点表示法访问属性。


4
如果你使用合成器(我猜你是这样做的),并且你使用现代编译器(我猜你会因为iOS5),你就不需要声明实例变量了,Objective-C会自动为你完成。(这不是你问题的答案,只是一则旁注)。 - choise
这是一个很好的知识点,到目前为止我总是为属性声明ivars。是的,我使用默认名称对该属性进行@synthesize操作。 - Alex Stone
正要告诉你和 @choise 一样的事情。我相信无论你是否使用属性,你仍然可以获得 ARC 的好处。 - Undeadlegion
3
不需要使用@synthesize - nielsbot
3个回答

69

在部分构造的状态下,无论ARC如何,都要使用直接访问:

- (id)initWithReminder:(Reminder*)reminder_ {
    self = [super init];
    if (self) {
        reminder = reminder_;
        // OR
        reminder = [reminder_ retain];
    }
    return self;
}
这是因为self.whatever会触发其他副作用,例如键值观察(KVO)通知,或者您的类实现(明确地)或子类覆盖了setWhatever: -- 这可能会将您的部分初始化实例暴露给其他API(包括它自己),这些API正确地假设它们正在处理完全构造的对象。
您可以手动验证一个类能够在部分初始化状态下运行,但这需要大量维护,当其他人想要子类化您的类时(坦白地说)这是不切实际或不可能的。这需要大量的时间和维护,并且如果您尝试将此方法用作约定,则没有实质性的好处。
因此,保证正确性的统一方式是在部分构建状态下使用直接访问,并避免使用访问器。
注意:我使用“部分构建”是因为初始化只是其中一半;-dealloc也有类似的注意事项。
有关为什么应该在部分构建状态下(ARC || MRC)使用直接访问的更多详细信息,请参阅此处:Initializing a property, dot notation

1
当涉及到子类时,我有点困惑。如果我们在init方法中不应使用self.propertyName,则如何在子类的init方法中访问父类的属性?在子类中使用_propertyName会导致未声明的变量,唯一的解决方法是使用self.propertyName。例如:http://stackoverflow.com/questions/16622776/how-to-access-super-classs-variables-in-init-method - Zhang
2
@张 我认为在子类的init中调用超类的属性是安全的。当你到达子类的init的"body"时,超类的init应该已经完成。这意味着,超类本身已经完全初始化(如果编写正确的话)。因此,即使在子类中覆盖该属性,访问其(=超类)属性也应该是安全的。问题出现在超类的init中访问该属性时。在这种情况下,如果你在子类中重写setter方法,可能会得到部分初始化的对象。 - manicaesar
@manicaesar 这并不是很安全,特别是当类层次结构和类的复杂性增加时:https://dev59.com/_2025IYBdhLWcg3wkGx7#5932733 -- 确实,一个可以证明安全的设计是可行的,但是谁想要验证每当实现被修改时它仍然是安全的呢?这很复杂/繁琐/容易出错,并且并没有给你带来太多好处。 - justin
1
@justin 好的,当我子类化UITableViewCell并想在初始化时将一些自定义UIView作为其contentView的子视图添加时,我应该怎么做,而不是使用:[self.contentView addSubview:someSubview];? - manicaesar
1
@justin 如果我在代码中构建我的UITableViewCell子类(你不能说它是被禁止的),那么awakeFromNib不是一个选项。在这种情况下使用延迟初始化是我认为解决问题的简单方法。我只在逻辑上使用延迟初始化(例如,当非延迟初始化是瓶颈时,这在这里不太可能是情况)-而不是为了遵循其他(相当无关的)规则...但好吧,我想我们只是有两种不同的方法和优先级来应用我们的代码 :) - manicaesar
显示剩余3条评论

5

不,你不应该这样做!

你可以在这里找到为什么的描述。
同时苹果也建议不要这样做。在 这里 阅读。


0
我不确定在对象初始化的哪个阶段可以使用点符号访问属性。
由于点符号仍然是Objective-C方法(实际上是ObjC方法下的C方法),因此,只要该方法准备好处理内存中的底层类型并处于任何状态,使用点符号或调用该方法就是完全安全的。避免使用未初始化(可能)的垃圾内存段的正常规则仍然适用。这是在init中使用ivar的最强动机。
但是,如果您的方法(getter | setter)能够正确地使用内存段-无论它们在读取之前是否首先写入-那么请务必在init方法中使用getter。懒惰的getter利用了指针将初始化为“nil”的假设来决定执行初始化的时间。如果您不能假设内存的初始内容,则初始化ivar可能是最安全的方法。
如果该方法能够在此场景中正确运行,为什么有从不在init中使用setter或getter的规则?

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