在Objective-C中,实例变量的分配和初始化应该采用什么正确的方式?

3
我在Jeff LaMarche的博客blog上看到了一些示例代码,其中包含以下内容:
- (void)applicationDidFinishLaunching:(UIApplication*)application { CGRect rect = [[UIScreen mainScreen] bounds];
window = [[UIWindow alloc] initWithFrame:rect];
GLViewController *theController = [[GLViewController alloc] init]; self.controller = theController; [theController release];
// ... }
在.h文件中,我们可以看到"window"和"controller"被声明为实例变量:
@interface OpenGLTestAppDelegate : NSObject { UIWindow *window; GLViewController *controller; } @property (nonatomic, retain) IBOutlet UIWindow *window; @property (nonatomic, retain) IBOutlet GLViewController *controller; @end
我的问题是:为什么"window"和"controller"以不同的方式赋值?
我认为我知道每种赋值方式的原理(跟踪保留计数),但是为什么它们以不同的方式赋值?具体而言,为什么没有像window那样通过单行代码进行控制器的赋值,而是要通过setter方法进行赋值?
请注意,本文中的html标记应保留。
一般情况下,何时使用单行方法,何时使用多行方法?
谢谢。
3个回答

3

他是否为controller实例变量创建了自定义setter方法?

如果是这样的话,当通过setter方法更改controller变量时将会调用某些代码。仅仅使用以下方式设置controller变量是不够的:

controller = [[GLViewController alloc] init];

不会调用setter方法;但是,将新分配的对象赋值给局部变量,然后使用以下代码进行设置:
self.controller = theController;

因为它是一种简写方式,所以会调用setter方法:

[self setController:theController];

在setter方法中额外的代码将被执行。这通常是你期望区分两种方法的地方。

编辑:

显然,在查看代码后,他没有实现自定义的setter方法,但是他使用的方法仍然是在实现自定义setter方法时最常用的方法。

我猜测额外代码背后的原因是他计划在分配变量后释放它,如果分配给一个局部变量,他可以使用局部变量调用setter方法,然后在局部变量上调用release。这比使用...总体上更易读。

[[self controller] release]

然而,这种方式有点奇怪,因为setter方法的合成实现将保留实例变量,但是一旦它设置为实例变量,就会释放它。由于release调用抵消了retain调用,使用一行代码的方法来设置变量更加合理。


如果你查看从博客文章链接的代码,你会发现他确实这样做了。即便如此,第一行和第三行都是不必要的样板文件。添加一个autorelease可以使它更简单,并且完全相同。 - Quinn Taylor
同意。我只是在解释上面两种实现之间的区别。这取决于个人口味 - 有些人喜欢嵌套方法调用,而其他人则更喜欢编写额外的代码以提高可读性。 - Alex Rozanski
哦,天啊,我一定是瞎了。我找不到自定义的setter方法。我的问题是关于应用程序委托类的,而不是视图类,后者也有一个“控制器”实例变量和一个自定义的setter方法。你能告诉我应用程序委托的“控制器”实例变量的自定义setter方法在哪里吗?我在应用程序委托的实现中只看到了applicationDidFinishLaunching和dealloc两个方法。 - sam

2
额外的代码似乎只是因为他特别想使用该属性(设置方法)。在他的实现(GLView.m)中,-setController 方法还基于控制器是否响应(实现)-setupView: 方法来设置一个布尔 ivar。
即便如此,似乎一行代码的解决方案也同样有效:
self.controller = [[[GLViewController alloc] init] autorelease];

同一行作为显式消息发送(不使用点语法)同样有效。
[self setController:[[[GLViewController alloc] init] autorelease]];

任何一种方法都会使新控制器具有正确的保留计数,并且仍然按预期使用setter属性。
(注意:有关代码请参见此博客文章的结尾。)

编辑:

抱歉如果有任何混淆。代码在___PROJECTNAMEASIDENTIFIER___AppDelegate.mGLView.m中都有一个"GLViewController *controller"的实例变量和属性,我看的是后者。(在前者中,setter确实被综合了,并且它将保留控制器。在77-81行,您可以看到我提到的代码,他实际上没有保留控制器 - 只有AppDelegate保留它。)

在应用程序委托代码中,综合的setter将保留GLViewController,因此我的一行替换建议仍然有效。关于可读性,人们可以争论双方,但对于那些很好地理解保留释放习惯的人来说,我建议使用一行版本更可读。它简洁地传达了意图,甚至提供了一个隐含的提示,即setter将保留控制器。额外的本地变量只是不必要的花哨。


嗨,Quinn,谢谢你的回答。你能指出应用程序委托的“controller”实例变量的自定义setter吗?我看到视图类也有一个“controller”实例变量,确实有一个自定义setter,但在我的问题代码中,“self”指的是应用程序委托对象,而不是视图对象。或者我完全搞混了? - sam
抱歉,我的修改后的答案应该能帮助你找到我所说的内容。谢谢! - Quinn Taylor
嗨,Quinn。对于这段代码,只需要这样做不是更简单、更清晰吗:controller = [[GLViewController alloc] init];这样就不用担心setter是否执行了其他步骤,也不需要填充autorelease池。我明白为什么所有不同的选项在保留计数方面都能正确工作,但我不明白为什么原始代码使用了两种不同的方法来依次分配ivars以实现相同的目的。 - sam

2
正如Quinn所指出的那样,可以使用autorelease方法将对控制器ivars的赋值写成一行。使用更详细的版本来避免自动释放池,并改为手动释放是因为苹果建议在iPhone上尽量减少使用自动释放池。因此,在调用setter之后必须将新分配的对象的引用存储在局部变量中以进行释放。
考虑何时使用直接赋值给实例变量(如window ivar)以及何时使用setter方法(如controller ivar),这在很大程度上是一个风格问题,但最好保持一致。
有两种ivar设置样式:
1.始终直接赋值给ivar。仅为必须执行某些附加工作的ivar切换到setter方法。 2.始终为所有ivar使用setter方法。
个人认为,使用第二种样式会导致更一致和可维护的代码。如果有一天您发现您的setter必须执行更多工作,则应仅更改setter,而使用第一种样式时,您还应更改所有直接赋值到setter调用的出现次数。
在另一个线程中找到了一个有关此问题的好讨论:instance variable/ method argument naming in Objective C

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