【问题标题】:Why assign nil to IBOutlets in viewDidUnload?为什么在 viewDidUnload 中将 nil 分配给 IBOutlets?
【发布时间】:2011-09-06 10:43:11
【问题描述】:

我有一个UIViewController,其中有一个IBOutlet 对应一个UILabel,标签在XIB 中连线。

#import <Foundation/Foundation.h>

@interface MyViewController : UIViewController {
    IBOutlet UILabel *aLabel; 
}
@end

根据iOS Programming: The Big Nerd Ranch Guide (2nd Edition)第7章

当 [MyViewController] 重新加载其视图时,会从 XIB 文件中创建一个新的 UILabel 实例。

因此建议在viewDidUnload释放标签。

- (void)viewDidUnload {
   [super viewDidUnload];
   [aLabel release];
   aLabel = nil;
}

作为一名 C# 程序员,我一直认为将 nil/null 分配给事物是毫无意义的。虽然我可以看到它在 Objective-C 中更有意义,但它仍然使我对代码美学的感觉略有*。我删除了它,一切正常。

但是,当我尝试对 MKMapView 执行类似操作时,在尝试加载 NIB 时出现应用程序错误 EXC_BAD_ACCESS

#import <Foundation/Foundation.h>

@interface MyViewController : UIViewController {
    IBOutlet MKMapView *mapView;
}
@end

- (void)viewDidUnload {
    [super viewDidUnload];
    [mapView release];
    mapView = nil; // Without this line an exception is thrown.
}

为什么mapView未设置为nil时会报错,而aLabel未设置为nil时不会报错?

* 我意识到我需要调整我对新语言的代码审美意识,但这需要时间。


事实证明,我对 aLabel 没有被引用是完全错误的。不知道是什么让我认为不是。

但是,这仍然留下了为什么在加载 NIB 时引用它们的问题。

当设置字段或属性时,会向旧值发送释放消息(合成的属性设置方法发送释放消息,或者setValue:forKey:如果是字段则发送消息)。因为旧值已经被释放,所以结果为EXC_BAD_ACCESS

【问题讨论】:

  • 已根据给出的错误更新。
  • 从 iOS 6.0 开始,viewDidUnload 已折旧。在内存不足的情况下不再清除视图,因此永远不会调用此方法。

标签: iphone objective-c ios cocoa-touch uikit


【解决方案1】:

这是因为内存管理,特别是缺少垃圾回收。

在 C# 中(如您所知),不再在范围内的对象将被删除。在objective-c中,这不会发生。您必须依靠保留/释放来告诉对象何时完成。

您的 mapView 错误所展示的 Objective-c 引用计数方法存在一个缺点。对一个对象调用release 可能会导致它被释放。但是,您指向该对象的指针仍将指向同一个位置 - 您的对象将不再存在。

例如

// We create an object.
MyObject *object = [[MyObject alloc] init];

// At this point, `object` points to the memory location of a MyObject instance
// (the one we created earlier). We can output that if we want :
NSLog(@"0x%08X", (int)myObject);

// You should see a number appear in the console - that's the memory address that
// myObject points to.
// It should look something like 0x8f3e4f04

// What happens if we release myObject?
[myObject release];

// Now, myObject no longer exists - it's been deallocated and it's memory has been
// marked as free

// The myObject pointer doesn't know that it's gone - see :
NSLog(@"0x%08X", (int)myObject);

// This outputs the same number as before. However, if we call a method on myObject
// it will crash :
NSLog(@"%@", myObject);

在objective-c 中,如果您尝试在nil 上调用消息,则不会发生任何事情。因此,如果每次您完成一个对象并对其调用 release 时,您还应该将其设置为 nil - 这意味着如果您再次尝试使用该指针,它不会崩溃!

【讨论】:

  • 根据我对上一个答案的回复,我的问题是什么试图访问mapView 而没有尝试访问aLabel
  • 但是,如果某些东西试图访问一个已经被释放/释放的对象,IMO 仍然是糟糕的设计。正确的设计(这并不意味着将释放的对象设置为零)应确保不会发生这种情况。
  • @Rudy Velthuis 这是防御性的 :) 您不知道您正在使用的苹果框架/库中是否存在错误 - 他们可能会尝试自己调用该对象,因此将其设置为 nil 将为您辩护针对其他错误的代码。 (并不是说我不同意这是糟糕的设计!)
  • @ICR 你怎么知道没有任何东西可以访问你的标签——仅仅因为内存已被释放并不意味着它正在被其他任何东西使用——也许标签正在以一种不是导致崩溃;)
  • @deanWombourne:对不起,那不是“防御性编程”,而是“偏执编程”。
【解决方案2】:

viewDidUnload 通常在设备收到内存警告时调用。

在视图堆栈中,可能存在设备可以通过释放未使用的对象来释放内存的情况。想象一下,您有一个带有许多视图控制器的导航堆栈。堆栈中较低的界面视图控制器不可访问,但仍在使用内存。因此,消除任何无法访问的界面元素通常是一个好主意。这些将在需要时使用 viewDidLoad 重新加载。

通常在ViewDidUnload 中,您应该释放从 Nib 文件创建或在您的 ViewDidLoad 方法中分配的任何视图对象

您的mapView 正在抛出异常,因为您的视图控制器正在尝试访问MapView,但mapView 已被释放。当您将 outlet 设置为 nil 时,发送给它的任何消息都会被忽略。

【讨论】:

  • 当我切换到控制器的视图时,在调用我的控制器的任何方法之前抛出异常。所以我的问题是什么试图访问mapView 而没有尝试访问aLabel
猜你喜欢
  • 2011-03-12
  • 2010-12-13
  • 2012-11-16
  • 1970-01-01
  • 2012-09-09
  • 1970-01-01
  • 1970-01-01
  • 2016-02-10
  • 2022-10-05
相关资源
最近更新 更多