在ARC下,IBOutlets应该是弱引用还是强引用?

573

我正在使用ARC专门为iOS 5开发。将IBOutlet指向UIView(及其子类)应该使用strong还是weak

以下是问题的代码:

@property (nonatomic, weak) IBOutlet UIButton *button;
会摆脱所有这些:
- (void)viewDidUnload
{
    // ...
    self.button = nil;
    // ...
}

这样做会有什么问题吗?模板使用strong,当直接从“Interface Builder”编辑器连接到标题时自动生成的属性也是如此,但为什么要这样做?UIViewController已经有一个对其viewstrong引用,可以保留其子视图。


11
请注意,IBOutletCollection()不能声明为weak,否则它将返回nil - ohho
Xcode 8.2.1 在通过界面构建器创建 IBOutlets 时使用 weak。然而,这里许多答案建议使用 strong。 - neoneye
1
@neoneye 我刚刚尝试了使用 Xcode 8.3.2 将 storyboard 拖到 Swift 文件中,它会默认为“strong”。 - CupawnTae
11个回答

455

警告,过时的答案:此答案未更新至2015年WWDC,正确的答案请参考上方的被接受的答案(Daniel Hall)。此答案仅供记录。


这段内容摘自开发者文库

From a practical perspective, in iOS and OS X outlets should be defined as declared properties. Outlets should generally be weak, except for those from File’s Owner to top-level objects in a nib file (or, in iOS, a storyboard scene) which should be strong. Outlets that you create will therefore typically be weak by default, because:

  • Outlets that you create to, for example, subviews of a view controller’s view or a window controller’s window, are arbitrary references between objects that do not imply ownership.

  • The strong outlets are frequently specified by framework classes (for example, UIViewController’s view outlet, or NSWindowController’s window outlet).

    @property (weak) IBOutlet MyView *viewContainerSubview;
    @property (strong) IBOutlet MyOtherClass *topLevelObject;
    

10
你是如何让“开发者文库”链接跳转到苹果文档页面的特定部分的?每当我链接到苹果文档时,它总是链接到页面顶部(即使感兴趣的内容在页面中间)。谢谢。 - bearMountain
69
我从左侧导航窗格复制了链接。 :D - Alexsander Akers
28
这句话的意思是“除了从文件所有者到nib文件(或在iOS中,故事板场景)的顶级对象之外”,即不包括从文件所有者到nib文件或故事板场景的顶级对象。 - Van Du Tran
17
@VanDuTran - 它指的是在 NIB 文件中位于根级别的对象,即如果您在其中实例化了另一个视图,该视图不是主视图的直接子视图,则需要具有强引用。 - mattjgalloway
6
顶层意味着当您查看笔尖时,对象显示在左侧列表中。几乎所有的笔尖中都有一个UIView-这可能是唯一的顶层对象。如果您添加其他项目,并且它们显示在列表中,则它们是“顶层对象”。 - David H
显示剩余12条评论

288
目前苹果推荐的最佳实践是将IBOutlets设为strong,除非需要使用weak来避免保留循环。正如Johannes在上面提到的,在2015年WWDC的“在Interface Builder中实现UI设计”会议上,一位苹果工程师说:

我要指出的最后一个选项是存储类型,它可以是strongweak。通常情况下,如果您将outlet连接到子视图或约束,则应使其强大,因为这些不一定会被视图层次结构保留。您真正需要使outlet weak的唯一时间是如果您有一个自定义视图,该视图引用了视图层次结构中的某些内容,并且通常不建议这样做。

我在Twitter上向IB团队的一名工程师询问了这个问题,他确认strong应该是默认值,并且正在更新开发者文档。 https://twitter.com/_danielhall/status/620716996326350848 https://twitter.com/_danielhall/status/620717252216623104

38
这是真的吗?还是得到300多个赞的答案才是正确的?我注意到,当你从StoryBoard向.h文件Ctrl-drag时,默认情况下InterfaceBuilder使用弱引用(weak)。 - Arunabh Das
4
得票数超过400的那个答案是正确的,但已经过时了。自从iOS 6以来,viewDidUnload不再被调用,所以使用弱引用并没有什么好处。 - kjam
8
@kjam,这里有一些好处。首先,最重要的是您不应该对自己没有创建的东西持有强引用。其次,性能提升微不足道。不要仅仅因为某个人(即使是一个地位很高的人)说这会快10微秒而违反编程的最佳实践。编写清晰的意图代码,不要试图充当优化编译器。只有在特定情况下经过测量表明性能成为问题时才编写针对性能的代码。 - Cameron Lowell Palmer
5
让我不同意你的观点。在Objective-C中,“持有你没有创建的某个东西的强引用”经常发生。这就是为什么有一个引用计数,而不是单一所有者。您有支持此建议的参考资料吗?请列出使用弱引用的其他好处。 - kjam
6
这是答案中提到的WWDC视频链接 https://developer.apple.com/videos/play/wwdc2015/407/?time=1946 - petrsyn
显示剩余7条评论

52

尽管文档建议在子视图的属性上使用weak,但自iOS 6以来,使用strong(默认的所有权标识符)似乎也可以。这是由于UIViewController中的更改,使得视图不再被卸载。

  • 在iOS 6之前,如果您对控制器视图的子视图保留了强链接,那么如果视图控制器的主视图被卸载,则这些子视图将一直保留,只要视图控制器存在。
  • 自iOS 6以来,视图不再被卸载,而是加载一次,然后只要它们的控制器存在,就会一直存在。因此,强属性不会有影响。它们也不会创建强引用循环,因为它们指向下面的强引用图。

话虽如此,我在使用时感到犹豫。

@property (nonatomic, weak) IBOutlet UIButton *button;

@property (nonatomic) IBOutlet UIButton *button;

在iOS 6及以后:

  • 使用 weak 明确表示控制器不想拥有该按钮。

  • 但是,在没有视图卸载的情况下,在 iOS 6 中省略 weak 并不会有任何问题,而且更短。有些人可能会指出这也更快,但我还没有遇到过因为 weak IBOutlet 而导致应用程序太慢的情况。

  • 不使用 weak 可能会被看作是一个错误。

底线是:自从 iOS 6 以来,在不使用视图卸载的情况下,我们再也不会做错了。是时候开趴了。;)


没错,但你可能仍然想自己卸载视图。在这种情况下,您必须手动将所有输出设置为 nil - hypercrypt
在这种情况下,“strong”也是有意义的。如果您使用视图卸载,那么“strong”只会带来麻烦 - 但现在谁还会这样做呢? :) - Tammo Freese
你对这种情况有什么看法?SO:Objective C:ViewController中的GestureRecognizer -> 保留周期 - FreeNickname
@FreeNickname 请看我的回答。这不是问题,因为手势识别器不会对目标/委托保持强引用。 :) - Tammo Freese
2
@Rocotilos 第一代iPhone的RAM非常有限。如果我没记错的话,只有128MB,留给活动应用程序的空间大约只有10MB。拥有小内存占用是至关重要的,因此需要视图卸载。随着我们现在拥有越来越多的RAM,这种情况已经发生了改变,并且苹果在iOS 6中优化了UIView,以便在内存警告时可以释放大量内存而不必卸载视图。 - Tammo Freese
显示剩余3条评论

34
我认为这样做没有任何问题。在ARC之前,我总是使用assign来定义我的IBOutlets,因为它们已经被它们的superviews保留了。如果你将它们定义为weak,就不需要像你指出的那样在viewDidUnload中将它们设为nil。
但要注意一点:在ARC项目中可以支持iOS 4.x,但是如果这样做,你不能使用weak,而必须使用assign,在这种情况下,你仍然需要在viewDidUnload中将引用设置为nil,以避免悬空指针。以下是我曾经遇到过的悬空指针错误示例:
一个UIViewController拥有一个UITextField用于输入邮编。它使用CLLocationManager来反向地解析用户的位置并设置邮编。以下是委托回调函数:
-(void)locationManager:(CLLocationManager *)manager
   didUpdateToLocation:(CLLocation *)newLocation
          fromLocation:(CLLocation *)oldLocation {
    Class geocoderClass = NSClassFromString(@"CLGeocoder");
    if (geocoderClass && IsEmpty(self.zip.text)) {
        id geocoder = [[geocoderClass alloc] init];
        [geocoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks, NSError *error) {
            if (self.zip && IsEmpty(self.zip.text)) {
                self.zip.text = [[placemarks objectAtIndex:0] postalCode];
            }
        }];    
    }
    [self.locationManager stopUpdatingLocation];
}

我发现如果我在正确的时间解雇了这个视图,并且没有在viewDidUnload中将self.zip设为nil,那么委托回调可能会在self.zip.text上引发一个坏访问异常。


4
据我了解,weak属性在viewDidUnload中不需要置为nil。但是,为什么苹果创建outlet的模板包括[self setMySubview:nil]呢? - Yang Meyer
3
在现实世界中,使用 strong/retained 修饰 IBOutlet 是否会引起问题?或者这只是一个冗余的 retain,意味着代码风格不佳但不会影响您的代码? - Enzo Tran
1
有没有所谓的冗余保留?如果有额外的保留,那么它将导致计数不正确,因此不会被尽快释放,因为其保留计数中有额外的保留。 - karlbecker_com

27
为了性能原因,IBOutlet 应该是strong类型。详情见iOS 9中Storyboard Reference、Strong IBOutlet和Scene Dock
解释如下:控制器视图的子视图的出口指向可以是弱引用,因为这些子视图已经被nib文件的顶级对象所拥有。然而,当一个 Outlet 被定义成弱指针并且指针被设置时,ARC会调用运行时函数:
id objc_storeWeak(id *object, id value);
这会将指针(object)使用对象值作为键添加到一个表中。这个表称为弱表。ARC使用此表存储应用程序的所有弱指针。现在,当对象值被释放时,ARC将在弱表上迭代,并将弱引用设置为 nil。或者,ARC可以调用:
void objc_destroyWeak(id * object)
然后,对象将被注销,并再次调用objc_storeWeak:
objc_storeWeak(id *object, nil)
与强引用释放相比,与弱引用相关的这种记录需要2-3倍的时间。因此,你可以通过简单地将 Outlet 定义为 strong 来避免弱引用引入运行时的开销。
从Xcode 7开始,建议使用strong。如果您观看WWDC 2015会议407 在Interface Builder中实现UI设计,它建议(来自http://asciiwwdc.com/2015/sessions/407]的转录)
引用:

我想指出的最后一个选项是存储类型,可以是强或弱。

通常情况下,应该将outlet设置为强,特别是当您将outlet连接到子视图或不总是由视图层次结构保留的约束时。

唯一需要使outlet弱的时间是如果您有一个自定义视图引用了视图层次结构中的某些内容,在一般情况下不建议这样做。

所以我要选择强,然后点击连接,这将生成我的outlet。


1
伟大的答案解释了实际原因 -为什么- - micnguyen
这很好,但我看到从在Storyboard中实现的手势识别器中出现了泄漏。 - thibaut noah
1
我无法理解这行代码。"唯一需要将插座声明为弱引用的情况是当您有一个自定义视图引用了视图层次结构中的某些内容,但通常不建议这样做。" 有任何例子吗? - user1872384
我计算了弱引用和强引用的deinit时间,结果完全相同。 - touti
但在Swift中,情况更加如此。弱引用更快。 - geekay
@thesummersign 我在 Swift 中测量 weak 和 strong 的时间时没有看到任何区别。 - visc

20

iOS开发中,与Mac开发相比,NIB加载有一点不同。

在Mac开发中,IBOutlet通常是一个弱引用:如果你有NSViewController的子类,只有顶层视图会被保留,当你dealloc掉controller时,所有其子视图和outlet都会自动释放。

UiViewController使用键值编码(Key Value Coding)来设置强引用的outlet。因此,当你dealloc你的UIViewController时,顶部视图会自动释放,但你必须在dealloc方法中释放所有它的outlet。

在这篇Big Nerd Ranch的文章中,他们涵盖了这个话题,并解释了为什么在IBOutlet中使用强引用不是一个好选择(即使在这种情况下,苹果也建议这样做)。


17
它解释的是2009年的情况。有了ARC,这种情况发生了显着变化。 - Dafydd Williams
1
:( Big Nerd Ranch 的链接已经失效了...但我真的需要阅读它。有人知道这篇文章的更多细节,这样我就可以找到它吗? - Motti Shneor
@MottiShneor 别担心,这没什么大不了的,因为链接是关于ARC之前的时代,并且现在已经不相关了。 - Sergey Grischyov

18

我想在这里指出一件事情,那就是,尽管苹果工程师在他们自己的WWDC 2015视频中所说的:

https://developer.apple.com/videos/play/wwdc2015/407/

苹果公司在这个问题上一直改变主意,这告诉我们没有一个单一的正确答案。为了表明即使是苹果工程师对此问题也有分歧,请看一下苹果最近的示例代码,你会发现有些人使用弱引用,而有些人则不使用。

这个Apple Pay示例使用了弱引用: https://developer.apple.com/library/ios/samplecode/Emporium/Listings/Emporium_ProductTableViewController_swift.html#//apple_ref/doc/uid/TP40016175-Emporium_ProductTableViewController_swift-DontLinkElementID_8

以下是翻译内容:

就像这个画中画的例子一样: https://developer.apple.com/library/ios/samplecode/AVFoundationPiPPlayer/Listings/AVFoundationPiPPlayer_PlayerViewController_swift.html#//apple_ref/doc/uid/TP40016166-AVFoundationPiPPlayer_PlayerViewController_swift-DontLinkElementID_4

还有这个Lister的例子: https://developer.apple.com/library/ios/samplecode/Lister/Listings/Lister_ListCell_swift.html#//apple_ref/doc/uid/TP40014701-Lister_ListCell_swift-DontLinkElementID_57

就像Core Location示例一样: https://developer.apple.com/library/ios/samplecode/PotLoc/Listings/Potloc_PotlocViewController_swift.html#//apple_ref/doc/uid/TP40016176-Potloc_PotlocViewController_swift-DontLinkElementID_6

就像视图控制器预览示例一样: https://developer.apple.com/library/ios/samplecode/ViewControllerPreviews/Listings/Projects_PreviewUsingDelegate_PreviewUsingDelegate_DetailViewController_swift.html#//apple_ref/doc/uid/TP40016546-Projects_PreviewUsingDelegate_PreviewUsingDelegate_DetailViewController_swift-DontLinkElementID_5

如同HomeKit示例一样: https://developer.apple.com/library/ios/samplecode/HomeKitCatalog/Listings/HMCatalog_Homes_Action_Sets_ActionSetViewController_swift.html#//apple_ref/doc/uid/TP40015048-HMCatalog_Homes_Action_Sets_ActionSetViewController_swift-DontLinkElementID_23 所有这些都已经完全更新为iOS 9,并且全部使用弱引用。从中我们可以得出以下结论:A. 这个问题并不像有些人想象的那么简单。B. Apple已经多次改变了他们的想法。C. 你可以使用任何让你感到满意的方式 :)
特别感谢Paul Hudson(www.hackingwithsift.com的作者)给我提供了解释和参考资料。
希望这能更好地澄清这个问题!
保重。

我已经检查了一段时间这个问题,没有找到任何确切的答案。由于上面的链接建议两者都可以,一般情况下使用Xcode自动建议的内容即可。 - subin272

10

有趣。我猜这是在视图卸载时被移除的改变? - hypercrypt

6
请注意,IBOutletCollection 应该是 @property (strong, nonatomic)

3
为什么不直接复制,因为它是一个 NSArray? - hypercrypt

5
看起来多年来已经发生了一些变化,现在苹果建议总体上使用 strong。他们在 WWDC 会议上的证据在“407 - 在 Interface Builder 中实现 UI 设计”的 32:30 开始。他所说的我的笔记几乎都是引用他的话:
- outlet 连接通常应该是强的,特别是当我们连接到某个子视图或约束时,该子视图或约束并未始终由视图层次结构保留。 - 当创建具有对视图层次结构中某个后备内容的引用的自定义视图时可能需要使用弱 outlet 连接,但通常不建议这样做。
换句话说,只要我们的一些自定义视图不会与视图层次结构中的一些视图创建循环引用,就应该始终使用强引用。
编辑:
一些人可能会问:保持它具有强引用是否会创建循环引用,因为根视图控制器和拥有视图保留对它的引用?或者为什么会发生这种变化? 我认为答案在这次讲话中早就有了,当他们描述如何从 xib 创建 nib 时,VC 和 view 分别创建了一个单独的 nib。我认为这可能是他们改变建议的原因。不过,从苹果那里获得更深入的解释会更好。

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