【问题标题】:Initializing instance variables in iPhone Development / Objective-C在 iPhone 开发/Objective-C 中初始化实例变量
【发布时间】:2011-11-10 06:13:29
【问题描述】:

作为 iPhone / Objective-C 开发的新手,我想问这个问题以确保我将在不同的场景中正确初始化实例变量。所以在下面,我将介绍一些场景,如果有人发现有什么不正确的地方,请告诉我。 (注意:对于我的示例,我将使用“instanceVariable”作为我们要初始化的实例变量,它是“InstanceVariableClass”类的对象。)

场景 1:在非 UIViewController 类中初始化

a) 新分配

- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {
        instanceVariable = [[InstanceVariableClass alloc] init];
    }
    return self;
}

在初始化器中,直接访问变量(即不通过它的属性)并分配它是可以的。当您调用 alloc 时,新创建的对象将被自动保留,稍后当您将它与您的 getter 和 setter 方法一起使用时,它将完美地工作。您不想使用属性分配变量,即self.instanceVariable = [[InstanceVariableClass alloc] init];,否则您将保留它两次(一次在您的 setter 方法中,一次在 alloc 中)。

b) 参数

- (id)initWithFrame:(CGRect)frame object(InstanceVariableClass*) theInstanceVariable {

    self = [super initWithFrame:frame];
    if (self) {
        instanceVariable = [theInstanceVariable retain];
    }
    return self;
}

再一次,可以在初始化程序中直接访问您的实例变量。由于您没有分配变量,只是想拥有一个传递给您的副本,因此您需要让它显式地保留自己。如果您使用了 setter 方法,它会为您保留它,但要避免访问初始化程序中的属性。

c) 便捷方法

- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {
        instanceVariable = [[InstanceVariableClass returnInitializedObject] retain];
    }
    return self;
}

当使用便捷方法返回一个新对象时,您也需要显式保留,原因与参数相同。便利方法(如果实施得当)将自动释放它生成的新对象,因此我们不必担心双重保留它。

场景 2:在 UIViewController 类中初始化

a) 新分配

- (void) viewDidLoad // or - (void) loadView if you implemented your view programmatically
{
    [super viewDidLoad];

    InstanceVariableClass *tempInstanceVariable = [[InstanceVariableClass alloc] init];
    [self setInstanceVariable: tempInstanceVariable];
    [tempInstanceVariable release];
}

在 UIViewController 中,您希望在 viewDidLoad 方法中初始化实例变量以采用延迟加载的做法,或者仅在您需要它们的确切时刻加载变量。在初始化器之外,直接访问变量是不好的做法,所以我们现在将使用我们的综合 setter 方法来设置变量。您不想使用setter 方法分配变量,即[self setInstanceVariable] = [[InstanceVariableClass alloc] init];,否则您将保留它两次(一次在您的setter 方法中,一次在alloc 中)。所以最好的做法是创建一个新的临时变量,初始化临时变量,将你的实例变量设置为临时变量,然后释放临时变量。 synthesize setter 方法将为您保留变量。

b) 便捷方法

- (void) viewDidLoad // or - (void) loadView if you implemented your view programmatically
{
    [super viewDidLoad];

    [self setInstanceVariable: [InstanceVariableClass instanceVariableClassWithInt:1]];
}

在初始化方法之外初始化实例变量,我们可以简单地使用我们的 setter 方法来设置和保留生成的对象。便利方法(如果实施得当)将自动释放它返回的对象,因此我们不必担心双重保留它。

这就是我目前所拥有的。如果有人能在我的推理中发现任何缺陷,或者想到我忘记包含的任何其他场景,请告诉我。谢谢。

【问题讨论】:

    标签: iphone objective-c ios cocoa-touch design-patterns


    【解决方案1】:

    1a) 完美,除了这一点:

    自动调用retain

    instanceArray 不会保留自己 - 它只是分配给为您的实例保留的原始内存。

    您做对的一个关键部分是很多人忽略的,那就是您应该avoid using the accessors in partially constructed/destructed states。原因不仅在于引用计数,还在于这些状态下的正确程序流程。

    1b) 将NSArray 属性声明为retain 是极其罕见的(对我而言)——您应该改用copyYour initializer should agree with the semantics of your property,所以在大多数情况下,您可以将其更改为 instanceArray = [parameterArray copy];

    1c) 看起来不错,但您也应该考虑我在 1a 和 1b 中提出的观点。

    2) 好吧,这真的取决于。延迟初始化并不总是最好的。在某些情况下,最好在初始化程序中初始化您的 ivars,而在某些情况下,在加载视图时会更好。请记住,您的 vc 可能已被卸载,并且在加载时破坏您创建的内容是很常见的。因此,实际上并没有硬性规定 - 如果某些东西需要时间来创建或者必须在重新加载您的 vc 时持续存在,那么在初始化程序中处理它可能更合乎逻辑。当延迟初始化更可取时,这些示例看起来不错。

    【讨论】:

    • 我刚刚摆脱了数组,因为这不是重点,而是放入了类“InstanceVariableClass”的一些变量“instanceVariable”。另外,将您所说的那一行替换为“当您调用 alloc 时,将自动保留新创建的对象”。这更有意义吗?
    • @CoDEFRo 好的。更正确的说法是(类似于以下内容):“当你从它的初始化程序返回一个对象时,你有责任在完成它时释放它。我们创建并存储到这个 ivar 的实例将被发送当调用 setter 或 dealloc" 时,它的匹配版本。实例的alloc 应该与其初始化器配对。
    • 值得一提的是,您的回答中的第 1a 点是一个备受争议的话题。关于是否应该在 init/dealloc 中使用属性访问器方法存在很多分歧。我的个人观点与您的建议完全相反 - 变量应始终通过其属性访问,即使在 init/dealloc 内部也是如此,并且对象应该对其自身有足够的了解,以便以安全的方式这样做。
    • @Abhi 我知道有些人有这种偏好,但偏好不应超过程序的正确性、可维护性、良好的设计或保持在定义的行为范围内。也许在你受够了困扰之后,你会改变主意(就像我多年前所做的那样)。
    【解决方案2】:

    您提供的所有示例都完全有效。

    然而,许多有经验的 obj-c 程序员更喜欢从不直接访问实例变量,除非在他们的 set/get 方法内部(如果你用 @property 和 @ 声明它们甚至可能不存在) 987654322@) 除非有必要避免一些性能瓶颈。

    所以,我的构造函数通常看起来像这样:

    - (id)initWithFrame:(CGRect)frame {
      self = [super initWithFrame:frame];
      if (self) {
        self.instanceArray = [NSArray array];
      }
      return self;
    }
    

    但如果分析代码显示 set/get 方法和自动释放池占用过多 CPU 时间或 RAM,我有时会选择完全按照您的方式编写代码。

    【讨论】:

      【解决方案3】:

      Senario 1 a)
      这是无用的代码。 NSArray 是不可变的,因此一旦创建就无法更改。所以改为这样做instanceArray = nil; 或事件更好地做self.instanceArray = nil;
      如果 instanceArray 是 NSMutableArray ,将它分配到那里是有意义的,但既然不是,那就浪费了。

      1b) 如果您的属性设置为(保留)使用它 insted self.instanceArray = parameterArray

      1c) 这不是一种方便的方法。方便的方法通常是返回自动释放对象的类方法。
      还有你在那里显示的代码,我确定它没有编译。

      Senario 2a)
      答案与 1a) 相同

      与 1c) 的答案相同

      尽可能多地使用您的财产。因此,如果您有足够多的变量保持同步,那么这样做会更容易。
      并确保了解 NSArray 和 NSMutableArray 之间的区别。 (或任何其他具有可变和不可变版本的类)


      关于 UIViewController 和非 UIViewController 的区别。
      (好吧,他们可以,但他们当时是nil

      无法在 init 方法中访问 IBOutlet。所以他们必须稍后初始化。

      所以一般来说数据端应该在 init 中完成,代码中的视图自定义应该进入 viewDidLoad,lastMinute 逻辑和/或刷新应该进入 viewWillAppear。
      请记住,每次视图即将出现时都会调用 viewWillAppear,包括从 UIViewController 层次结构的较低层返回时。

      这是指导线,与所有指导线一样,您有时需要将线稍微弯曲。

      【讨论】:

      • 好的,我更正了 NSMutableArray 的问题,尽管这确实超出了我的示例的重点。是的,我知道你应该使用属性,但有些人说你不应该在初始化程序中使用它。
      • 我不明白为什么,我能想到的唯一例外是在实现 NSCoding 协议时的 initWithCoder 方法中,但它仍然值得商榷。访问器方法在那里,因此您可以对变量拥有唯一的访问点。它可以帮助您编写更可持续的代码。
      【解决方案4】:

      首先,Objective-C 没有类变量;只是实例变量。

      其次,你想太多了。内存管理的规则相对简单,并且与 setter/getter 方法和/或对象创建正交。在-init* 方法中使用setter 是一个问题,因为如果setter 被覆盖,您可能会触发副作用。但是,如果您在 -init*-dealloc 期间出现了 setter/getter 副作用,那么您可能会遇到更糟糕的架构问题。

      1. 如果您 +new、+alloc、-retain 或 -copy [NARC] 一个对象,您需要在某处 -release 它,否则它会一直存在(并且很可能会泄漏)。

      2. 如果 setter 想要保留一个对象,它会 -retain 或 -copy (视情况而定),这就是平衡 -retain 的任务。在设置器的外部你不应该关心

      3. autorelease 只不过是每个线程延迟的release。虽然通常您无需担心通过各种便捷的对象实例创建方法创建的autoreleased 对象,但在某些情况下,自动释放压力可能是一个真正的性能问题,并且使用显式的 +alloc / set / -release 可以有用。

      这一切在Objective-C memory management guide中有详细的解释。


      这样想:

      • 当您对 iVar 进行直接分配时,您不会离开调用范围,因此,分配可能会消耗调用范围内(可能)维护的 +1 保留计数。

      • 当您通过方法调用(点语法或其他方式)进行分配时,在调用范围内维护的保留计数与该 setter 方法中发生的情况无关。两者都需要独立维护各自的保留计数增量。也就是说,如果 setter 想要保留对象,它将保留它。调用者独立维护它的保留计数。

      【讨论】:

      • 您可以拥有属于类变量的静态变量。想想单身人士吧。
      • 谢谢。是的,我了解所有这些原则,但我正在查看一个特定的子集,即当您初始化具有一些您通常看不到的特殊情况的变量时,即您应该直接访问初始化程序中的变量还是使用属性,或者如何在视图控制器中进行初始化。
      • VinceBurn:静态变量可以在整个声明范围内访问,并且可以跨越多个类(如果单个编译单元中存在多个@implementation)。
      • @CoDEFRo 我的观点是,您的“特殊情况”仅存在,因为您正在串通其他正交的概念。如果您遵循 NARC 规则,则其他一切都不重要。 retain 发生在属性分配上应该与您的调用范围无关。
      猜你喜欢
      • 2011-09-24
      • 1970-01-01
      • 2010-11-02
      • 1970-01-01
      • 2012-09-17
      • 2023-03-14
      • 2012-05-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多