在iPhone开发/Objective-C中初始化实例变量

7
作为一名相对较新的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方法来设置和保留生成的对象。如果方便方法(如果正确实现)已经释放了返回的对象,因此我们不必担心重复保留它。

这就是我目前的情况。如果有人发现我的推理有任何缺陷,或者想到我忘记包含的任何其他情况,请告诉我。谢谢。

4个回答

3

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

自动调用自身的保留函数

instanceArray 没有保留自身 - 它只是对为您的实例预留的原始内存进行的分配。

你正确理解了许多人忽视的关键部分,就是应该避免在部分构建/析构状态下使用访问器。原因不仅仅是引用计数,还包括这些状态下的适当程序流程。

1b) 对我来说,将 NSArray 属性声明为 retain 是极其罕见的 - 你应该使用 copy你的初始化器应该与属性的语义一致,因此在大多数情况下,你会将其更改为 instanceArray = [parameterArray copy];

1c) 看起来不错,但你还应该考虑我在1a和1b中提到的要点。

2) 嗯,这真的取决于情况。延迟初始化并不总是最好的选择。有些情况下,在初始化程序中初始化 ivars 会更好,有些情况下则在视图加载时。请记住,你的 vc 可能会被卸载,并且当加载时销毁你创建的对象是相当典型的。因此,没有硬性规定 - 如果某些东西需要时间来创建或必须在重新加载 vc 时持久存在,则在初始化程序中处理可能更合理。这些示例看起来不错,当延迟初始化更可取时。


我刚刚摆脱了数组,因为那不是重点,而是加入了一些类“InstanceVariableClass”的变量“instanceVariable”。同时,我用“When you call alloc, the newly created object will be automatically retained”替换了你所说的那行代码。这样更有意义吗? - Ser Pounce
@CoDEFRo 好的。更正确的说法是:当你从初始化器中返回一个对象时,你有责任在使用完成后释放它。我们创建并存储到这个实例变量的实例将在 setter 方法被调用或 dealloc 方法中被发送相应的释放消息。实例的 alloc 应该与其初始化器配对使用。 - justin
值得一提的是,你回答中的1a点是一个备受争议的话题。关于是否应该在init/dealloc中使用属性访问方法存在很多分歧。我的个人观点与你所推荐的完全相反——变量应该始终通过它们的属性进行访问,即使在init/dealloc内部也是如此,并且对象应该足够了解自身以便以安全的方式这样做。 - Abhi Beckert
@Abhi 我知道有些人有这种偏好,但是偏好不应该超过程序的正确性、可维护性、良好的设计或者保持在定义行为的范围内。也许当它给你带来足够的麻烦时(就像我几年前一样),你会改变主意的。 - justin

2

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

然而,许多有经验的obj-c程序员更喜欢永远不直接访问实例变量,除非在它们的set/get方法内部(如果您使用@property@synthesize声明它们,则这些方法可能不存在)除非必要时避免某些性能瓶颈。

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

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

但是如果对代码进行性能分析后,发现set/get方法和自动释放池占用了过多的CPU时间或RAM,我有时会选择像你所做的那样编写我的代码。


0
首先,Objective-C没有类变量,只有实例变量。
其次,你想得太多了。内存管理的规则相对简单,并且与setter/getter方法或对象创建无关。在-init*方法中使用setter可能会触发副作用,如果setter被覆盖,则会出现问题。但是,如果你在-init*-dealloc期间使用setter/getter副作用出现问题,那么你可能存在更严重的架构问题。
  1. 如果你使用 +new、+alloc、-retain 或 -copy [NARC] 创建了一个对象,你需要在某个地方释放它,否则它会一直存在(很可能会泄漏)。

  2. 如果 setter 想要保留一个对象,它将会 -retain 或 -copy 它(适当的方式),而且这是它自己的事情来平衡 -retain。对于 setter 外部的代码,你不应该关心

  3. autorelease 只是一个延迟 release 的线程本地的机制。通常情况下,你不需要担心通过各种便捷的对象实例创建方法创建的 autorelease 对象,但在某些情况下,autorelease 压力可能会成为真正的性能问题,此时使用显式的 +alloc / set / -release 是有用的。

所有这些都在 Objective-C 内存管理指南 中详细解释。


这样想:

  • 当您直接对iVar进行赋值时,您没有离开调用范围,因此赋值可以消耗在调用范围中维护的+1保留计数(可能)。

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


你可以拥有静态变量,它们类似于类变量。只需考虑单例模式即可。 - Vincent Bernier
谢谢。是的,我理解所有这些原则,但我正在研究一个特定的子集,即在初始化变量时有一些特殊情况,通常你不会看到,例如在初始化器中直接访问变量还是使用属性,或者如何在视图控制器中进行初始化。 - Ser Pounce
VinceBurn:静态变量可在声明的整个范围内访问,并且可以跨越多个类(如果在单个编译单元中存在多个@implementations)。 - bbum
@CoDEFRo 我的观点是,你所谓的“特殊情况”只是因为你在合并本来是正交的概念。如果你遵循 NARC 原则,其他任何事情都不重要。属性分配时发生 retain 应该与你的调用作用域无关。 - bbum

0

情景1a)
这是无用的代码。NSArray是不可变的,一旦创建就不能更改。所以,应该这样做:instanceArray = nil;或者更好的方法是self.instanceArray = nil;
如果instanceArray是NSMutableArray,那么在那里分配它就有意义了,但是由于它不是,所以这是浪费。

1b) 如果您的属性设置为(retain),请使用它代替self.instanceArray = parameterArray

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

情景2a)
与1a)相同的答案

与1c)相同的答案

尽可能使用您的属性。因此,如果您有必须保持同步的变量,以这种方式处理会更容易。
并确保理解NSArray和NSMutableArray之间的区别。(或任何其他具有可变和不可变版本的类)


关于UIViewController和非UIViewController之间的区别。 (好吧,在那时它们可以,但此时它们是nil)
在init方法中无法访问IBOutlet。所以必须稍后初始化。
因此,通常应该在init中处理数据方面,在代码中进行视图自定义应在viewDidLoad中进行,最后一刻逻辑和/或刷新应放在viewWillAppear中。 请记住,每次视图即将出现时都会调用viewWillAppear,包括从UIViewController层次结构中返回时。
这些都是指南,就像所有指南一样,有时您需要稍微弯曲一下规则。

好的,我已经纠正了NSMutableArray的问题,尽管那与我的示例无关。是的,我知道你应该使用属性,但有些人说在初始化程序中不应该使用它。 - Ser Pounce
我不明白为什么,唯一我能想到的例外情况是在实现NSCoding协议的initWithCoder方法中,即使这也可能存在争议。访问器方法存在的目的是让你有一个独特的访问点来访问你的变量。它可以帮助你编写更可持续的代码。 - Vincent Bernier

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