【问题标题】:Should I refer to self.property in the init method with ARC?我应该在 ARC 的 init 方法中引用 self.property 吗?
【发布时间】:2011-12-24 18:22:47
【问题描述】:

一个简单的问题。

如果我有一个属性和一个用相同名称声明的 ivar:

在 .h 文件中:

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

在 .m 文件中,如果我使用 ARC,我应该使用 ivar 还是 init 方法中的属性?

- (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;
}

我不确定在对象初始化的哪个时间点可以使用点符号访问属性。

【问题讨论】:

  • 如果你使用 synthesize(我猜你会这样做)并且你使用现代编译器(我猜你这样做是因为 ios5)你不需要声明 ivar,objective-c 会自动为你。 (这不是您问题的答案,只是一个旁注)。
  • 这是一件好事,到目前为止,我一直为属性声明 ivars。是的,我使用默认名称 @synthesize 属性。
  • 正要告诉你和@choise一样的事情。而且我相信无论您是否使用该资产,您仍然可以从 ARC 中受益。
  • 你甚至不需要@synthesize

标签: objective-c ios5 initialization automatic-ref-counting reference-counting


【解决方案1】:

在部分构造的状态下使用直接访问,不管 ARC:

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

这是因为self.whatever 会触发其他副作用,例如键值观察 (KVO) 通知,或者您的类实现(显式)或子类覆盖 setWhatever: - 这可能会暴露您的部分初始化实例到其他 API(包括它自己的),它们正确地假设它们正在处理一个完全构造的对象。

可以手动验证一个类是否能够在部分初始化的状态下运行,但这需要大量维护并且(坦率地说)当其他人想要继承您的类时是不切实际或不可能的。它需要大量的时间和维护,这样做并没有实质性的好处,尤其是如果您尝试使用这种方法作为惯例。

所以保证正确性的统一方式是在部分构造状态下使用直接访问,避免使用访问器。

注意:我使用的是“部分构造”,因为初始化只是图片的一半; -dealloc 有类似的警告。

有关为何应在部分构造状态 (ARC || MRC) 中使用直接访问的更多详细信息可在此处找到:Initializing a property, dot notation

【讨论】:

  • 当谈到子类时,我对此有点困惑。如果我们不应该在 init 方法中使用 self.propertyName,那么如何在子类的 init 方法中访问父类的属性?在子类中使用 _propertyName 表示未声明的变量,唯一的解决方案是使用 self.propertyName。示例:stackoverflow.com/questions/16622776/…
  • @Zhang 通常,您应该避免在初始化程序中访问基类的属性。该方法要求您的派生类了解其超类的实现,以及所有可以覆盖访问器实现的派生类。在 objc 中,您实际上可以最终调用(未初始化的)派生类的重写访问器方法。因此,您可以通过以下方式避免这种头痛/风险:遵循上述建议,提供更合适的初始化程序,在完全构造的实例上使用访问器,使用不太复杂的类型(组合),方便的构造函数等。
  • @Zhang 我认为在子类的 init 中调用超类的属性是安全的。当你到达子类 init 的“主体”时,超类的 init 应该已经完成​​了。这意味着,超类本身(或者至少应该,如果编写正确的话)已经完全初始化。因此访问它的(=超类')属性应该是安全的——即使你覆盖了子类中的属性。问题是当您在超类的 init 中访问该属性时。在这种情况下,如果你在子类中重写 setter,你可能会得到部分初始化的对象。
  • @justin 好的,所以当我继承 UITableViewCell 并想在初始化时添加一些自定义 UIView 作为其 contentView 的子视图时,我应该做什么而不是:[self.contentView addSubview:someSubview] ; ?
  • @justin 如果我在代码中构造我的 UITableViewCell 子类(你不能说它是被禁止的),那么 awakeFromNib 不是一个选项。在我看来,在这种情况下使用延迟初始化是解决问题的简单方法。我仅在符合逻辑的情况下使用惰性初始化(例如,当非惰性初始化是瓶颈时,这在此处并非如此)-不是为了遵守其他一些(完全不相关的)规则...但是好的,我认为我们只有两种不同的方法和优先级应用于我们的代码:)
【解决方案2】:

不,你不应该!

你可以找到描述为什么here
苹果也建议不要这样做。 Read here

【讨论】:

    【解决方案3】:

    我不确定在对象初始化的哪个时间点可以使用点符号访问属性。

    由于点表示法仍然是一种 Objective-C 方法(实际上是 ObjC 方法下的 C 方法),因此点表示法或调用该方法是完全安全的,因为该方法已准备好处理底层类型无论他们碰巧处于什么状态,都在记忆中。 关于避免使用未初始化(可能)车库内存段的正常规则仍然适用。这是在 init 中使用 ivar 的最大动机。

    但是,如果您的方法 (getter|setter) 能够正确使用内存段——不管它是否在被读取之前先被写入——那么一定要在 init 方法中使用你的 getter。 Lazy getter 利用它将初始化的指针以“nil”开始的假设来决定执行初始化。如果你不能假设你记忆的初始内容,那么初始化 ivar 可能是最安全的过程。

    如果方法能够在这种情况下正确运行,为什么在 init 中永远不使用 setter 或 getter 的规则?

    【讨论】:

      猜你喜欢
      • 2014-02-28
      • 2011-10-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-26
      • 1970-01-01
      • 2011-03-30
      • 1970-01-01
      相关资源
      最近更新 更多