iOS - 如何最好地管理IBOutlets的内存?

10

我一直在审查苹果文档和示例代码,试图确定IBOutlets的最佳内存管理方式。说实话,我有点困惑。

CurrentAddress示例代码将IBOutlets声明为属性:

@interface MapViewController : UIViewController <MKMapViewDelegate, MKReverseGeocoderDelegate>

{
    MKMapView *mapView;
    UIBarButtonItem *getAddressButton;
}
@property (nonatomic, retain) IBOutlet MKMapView *mapView;
@property (nonatomic, retain) IBOutlet UIBarButtonItem *getAddressButton;

太好了。这些将在dealloc中释放:

- (void)dealloc
{
    [mapView release];
    [getAddressButton release];
    [super dealloc];
}

现在,这些属性不应该设置为assign吗?因为当设置为retain时,IBOutlet的保留计数将增加两次:一次是在nib加载时,另一次是在设置属性时。并且在dealloc中将这些属性设置为nil而不是释放是否更好呢?

5个回答

7

苹果文档指出我们应该在iOS中保留属性。
所保留的插座应在deallocviewDidUnload中释放并置为nil

在Mac上,除非被父视图保留,否则不会自动保留每个插座在加载nib时。这在iOS中并非如此。这就是为什么理论上只保留视图层次结构中的插座而不是视图。

Jeff LaMarche有一篇非常有用的文章涉及到了这个话题:Outlets, Cocoa vs. Cocoa Touch


@Jilouc:为什么你不在dealloc方法中释放完IBOutlet后将其设置为null呢?把这两个调用分开成两个方法不会让你面临潜在的问题吗? - FreeAsInBeer
因为你可能不是发布它的人(它可能在 @synthesize 内部发布),而 viewDidUnload 发生的时间比释放内存更早的 dealloc 更早。 - slf
我没有足够准确(回答已编辑)。我在dealloc中也将我的插座设置为nil。通常,我在viewDidUnload中使用self.myOutlet = nil(这样就可以释放+nil),并在dealloc中使用[myOutlet release],myOutlet = nil;。但那只是一种习惯。 - Jilouc
谢谢Jilouc,这真的很有帮助。但是为什么你不在dealloc中使用self.myOutlet = nil呢? - Vega
这也是有效的。 这只是个人习惯(我通常使用宏来执行“释放然后将其设置为nil”)。 - Jilouc
1
这不仅是个人习惯。你可以编写具有副作用的setter,例如,如果设置了姓名,你可以计算姓名的缩写。你不应该在dealloc中调用这些setter,因为副作用可能会导致不可预测的行为。假设缩写变量早已被释放,当你将self.name设置为nil时,这可能会导致异常。你不应该在init和dealloc中使用self.foo =。通常情况下,什么也不会发生。 - Matthias Bauch

2

当 nib 载入器完成所有加载并连接所有 IBOutlets 后,它会自动释放它所加载的所有对象。如果你的 IBOutlet 属性声明为 assign,那么它指向的对象在下次自动释放池被清空时将被删除。

您可以在 dealloc 中将属性设置为 nil 而不是直接释放它们,结果是一样的。需要注意的是,如果您提供了自己的 setter 实现,您需要记住,您的对象的一些其他成员可能已经被释放。


1

这在MacOSX和iOS上是不同的。在iOS中,视图加载并建立nib连接后,保留计数将为2。

每个元素都会被视图和控制器各自保留一次。视图中的其他元素仅由视图保留。

当控制器释放这两个元素时,它们的保留计数降至1。之后调用[super dealloc]。UIViewController在其dealloc中有一个[view release],因此视图被释放(除非在其他地方保留或先前释放)。当视图被释放时,它会释放其子视图,元素最终完全释放。

[object release]之所以优先于dealloc,是因为键值编码(或您自己的代码)可能会导致在写入[self setObject:nil]时运行其他代码。这可能会导致其他对象与正在释放自身的控制器交互。出于同样的原因,不应在init方法中使用setter。

只进行释放的第二个原因是,通过保留该值而不将其设置为nil,我们将在dealloc期间稍后检测到代码错误访问我们对象上的变量。这可以帮助捕获可能不容易跟踪的错误。


0
我假设您已经使用@synthesize这些属性。如果没有这样做,您需要手动释放自己。您的假设非常正确,如果在设置属性时继续保留,将会导致内存泄漏。
让我们思考一下... 在我们拥有高级@synthesize声明之前,属性是什么样子的?
id _propertyName; // the ivar

- (id) propertyName {
  return _propertyName;
}

- (void) setPropertyName:(id)v {
  if (_propertyName) {
    [_propertyName release]; // release the previously retained property
  }
  _propertyName = [v retain]; // retain this one so it doesn't fly away on us
}

现在,你不需要输入这些东西了,因为 @synthesize 很酷,会自动生成它们,如果你没有将某些东西指定为 nonatomic,它还会生成 @synchronized 块,这也非常棒。
如果你指定了 assign 而不是 retain,你会得到类似于这样的东西。
id _propertyName; // the ivar

- (id) propertyName {
  return _propertyName;
}

- (void) setPropertyName:(id)v {
  _propertyName = v;
}

当事物不是对象时,这就是你唯一能做的事情,因为它们只是值(有时也称为值类型,而对象则是引用类型)。由于值类型无法保留,另一种类型的块将毫无意义。尝试使用BOOL创建保留属性并观察LLVM或GCC告诉你要做什么吧;)


0
这些属性应该设置为assign吧?因为当设置为retain时,IBOutlet的引用计数将增加两次:一次是在加载nib文件时,另一次是在设置属性时。
嗯,你发布的代码本身就是正确的。
当你使用:
@property (nonatomic, retain) IBOutlet MKMapView *mapView;

你只是在告诉xCode创建一个setter方法,每次调用时都会创建并保留你的MKMapView对象。

yourMapViewController.mapView = someMapView; // from out

// or

self.mapView = someMapView; // from in

之后 mapView 的引用计数会增加 1,你的 MapViewController 代码需要这样做,因为现在你可以指向 mapView 并管理它...

不用担心 IB nib 文件...

当你使用 nib 加载 UIViewController,比如你的类 MapViewController : UIViewController,IB nib 对象将在你释放 MapViewController 时释放...只需关注你所保留的对象...


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