【问题标题】:Should IBOutlets be strong or weak under ARC?在 ARC 下,IBOutlets 应该强还是弱?
【发布时间】:2014-08-06 02:04:32
【问题描述】:

我正在使用 ARC 专门为 iOS 5 开发。 IBOutlets 到 UIViews(和子类)应该是 strong 还是 weak

以下内容:

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

将摆脱所有这些:

- (void)viewDidUnload
{
    // ...
    self.button = nil;
    // ...
}

这样做有什么问题吗?模板使用strong,就像从“Interface Builder”编辑器直接连接到标题时创建的自动生成的属性一样,但为什么呢? UIViewController 已经有一个 strong 引用对其 view 保留其子视图。

【问题讨论】:

  • 注意,IBOutletCollection()不能是weak,否则返回为nil
  • Xcode 8.2.1 在通过接口生成器创建 IBOutlets 时使用弱。但是这里关于 SO 的许多答案都建议使用 strong。
  • @neoneye 我刚刚尝试使用 xcode 8.3.2 从情节提要拖动到 swift 文件,默认为 strong

标签: ios objective-c cocoa-touch interface-builder automatic-ref-counting


【解决方案1】:

警告,过时的答案:根据 WWDC 2015,此答案不是最新的,正确答案请参阅上面的 accepted answer(Daniel Hall)。此答案将留作记录。


总结自developer library

从实际的角度来看,在 iOS 和 OS X 中,出口应该被定义为声明的属性。出口通常应该是弱的,除了从文件所有者到 nib 文件中的顶级对象(或者,在 iOS 中,故事板场景)应该是强的。因此,您创建的 Outlets 通常默认情况下很弱,因为:

  • 您创建的 Outlets,例如视图控制器视图的子视图或窗口控制器的窗口,是对象之间的任意引用,并不暗示所有权。

  • 强大的出口通常由框架类指定(例如,UIViewController 的视图出口,或 NSWindowController 的窗口出口)。

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

【讨论】:

  • 您是如何获得“开发者库”链接以跳转到苹果文档页面的特定部分的?每当我链接到苹果文档时,它总是链接到页面顶部(即使感兴趣的内容位于页面的中间)。谢谢。
  • 我从左侧导航窗格中复制了链接。 :D
  • “除了那些从文件所有者到 nib 文件(或者,在 iOS 中,故事板场景)中的顶级对象之外”是什么意思?
  • @VanDuTran - 它表示 NIB 中位于根级别的对象,也就是说,您在其中实例化了另一个视图,它不是主视图的直接子视图,那么它需要有一个强参考。
  • 顶级意味着当您查看笔尖时,对象出现在左侧列表中。几乎所有的笔尖都有一个 UIView - 这可能是唯一的顶级对象。如果您添加其他项目,并且它们显示在列表中,则它们是“顶级对象”
【解决方案2】:

Apple 当前推荐的最佳实践是让 IBOutletsstrong 除非特别需要 weak 以避免保留周期。正如 Johannes 上面提到的,这在 WWDC 2015 的“在 Interface Builder 中实现 UI 设计”会议中对此进行了评论,其中一位 Apple 工程师说:

我要指出的最后一个选项是存储类型,它可以 要么强要么弱。一般来说,你应该让你的出口 强,尤其是当您将插座连接到子视图或 视图并不总是保留的约束 等级制度。唯一真正需要使出口变弱的情况是 你有一个自定义视图,它引用了一些备份视图的东西 层次结构,通常不建议这样做。

我在 Twitter 上向 IB 团队的一位工程师询问了这个问题,他确认 strong 应该是默认设置,并且正在更新开发人员文档。

https://twitter.com/_danielhall/status/620716996326350848 https://twitter.com/_danielhall/status/620717252216623104

【讨论】:

  • 这真的是真的还是有 300+ 票的答案是正确的?我注意到,当您从情节提要 Ctrl 拖动到 .h 时,默认情况下 InterfaceBuilder 使用弱
  • 400+ 票是正确的,但是已经过时了。由于 iOS 6 viewDidUnload 没有被调用,所以弱出口没有任何好处。
  • @kjam 有好处。首先,你不应该强烈引用你没有创建的东西。其次,性能增益可以忽略不计。不要仅仅因为某些人,即使是一个有地位的人,说这快了 10 微秒,就违反了编程中的最佳实践。代码意图明确,不要试图玩优化编译器。仅当在特定情况下测量性能存在问题时才为性能编写代码。
  • 让我不同意你的观点。在 Objective-C 中一直存在“对不是你创建的东西的强引用”。这就是为什么有一个参考counting,而不是一个单一的所有者。您是否有任何支持此建议的参考资料?您能否列出弱网点的其他好处?
  • 这里是答案developer.apple.com/videos/play/wwdc2015/407/?time=1946中提到的WWDC视频
【解决方案3】:

虽然文档建议在子视图的属性上使用 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 并没有什么坏处,而且更短。有人可能会指出这也更快,但是我还没有遇到过因为weakIBOutlets而导致太慢的应用程序。

  • 不使用weak 可能会被视为错误。

底线:从 iOS 6 开始,只要我们不使用视图卸载,我们就不会再犯这个错误了。是时候聚会了。 ;)

【讨论】:

  • 确实如此,但您可能仍想自己卸载视图。在这种情况下,您必须手动将所有网点设置为 nil
  • PS:weak 在 ARM64 中要便宜很多:D
  • 没错,如果你实现视图卸载,weak 属性或__weak 实例变量是要走的路。我只是想指出这里出错的可能性较小。至于 weak 在 arm64 上更便宜,我什至没有看到 armv7 上 weak IBOutlets 的实际性能问题。 :)
  • 在这种情况下,strong 也有意义。 strong 仅在您使用视图卸载时才有害——但这些天谁会这样做? :)
  • @Rocotilos 第一款 iPhone 的 RAM 非常有限。如果我没记错的话,128 MB,剩下大约 10 MB 用于活动应用程序。拥有较小的内存占用至关重要,因此存在视图卸载。随着我们现在拥有越来越多的 RAM,并且 Apple 在 iOS 6 中优化了 UIViews,这种情况发生了变化,因此在出现内存警告时,可以在不卸载视图的情况下释放大量内存。
【解决方案4】:

我认为这没有任何问题。 Pre-ARC,我一直把我的 IBOutletsassign,因为他们已经被他们的 superviews 保留了。正如您所指出的,如果您将它们设为weak,则不必在 viewDidUnload 中将它们归零。

一个警告:您可以在 ARC 项目中支持 iOS 4.x,但如果这样做,则不能使用 weak,因此您必须将它们设为 assign,在这种情况下您需要仍然希望将viewDidUnload 中的引用归零以避免悬空指针。这是我遇到的一个悬空指针错误的示例:

一个 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,则委托回调可能会在 self.zip.text 上引发错误的访问异常。

【讨论】:

  • 我的理解也是weak属性不需要在viewDidUnload中置零。但是,为什么 Apple 用于创建网点的模板包含[self setMySubview:nil]
  • 在现实世界中是否有任何情况下使用 strong/retained 为您的 IBOutlet 可能会导致问题?或者它只是一个多余的保留,这意味着糟糕的编码风格但不会影响你的代码?
  • 有多余的retain这种东西吗?如果有一个额外的保留,这将导致它无法正确计算,因此不会尽快释放,因为它的保留计数上有一个额外的保留。
【解决方案5】:

IBOutlet 应该很强大,出于性能原因。见Storyboard Reference, Strong IBOutlet, Scene Dock in iOS 9

正如本段所述,视图子视图的出口 控制器的视图可能很弱,因为这些子视图已经 由 nib 文件的顶级对象拥有。然而,当一个奥特莱斯 被定义为弱指针并且指针被设置,ARC 调用 运行时函数:

id objc_storeWeak(id *object, id value);

这增加了指针 (对象)使用对象值作为键的表。这张表是 简称弱表。 ARC 使用此表存储所有 应用程序的弱指针。现在,当对象值为 释放后,ARC 将遍历弱表并设置弱表 参考零。或者,ARC 可以调用:

void objc_destroyWeak(id * object)

那么,对象是 未注册和 objc_destroyWeak 再次调用:

objc_storeWeak(id *object, nil)

此簿记关联 使用弱引用可能需要 2-3 倍的时间比发布 强参考。因此,弱引用会为 只需将 outlet 定义为 strong 即可避免运行时。

从 Xcode 7 开始,它建议 strong

如果您观看 WWDC 2015 会话 407 Implementing UI Designs in Interface Builder,它建议(来自http://asciiwwdc.com/2015/sessions/407 的成绩单)

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

一般来说,您应该使您的出口变得强大,尤其是当您将出口连接到子视图或视图层次结构并不总是保留的约束时。

唯一真正需要使出口变弱的情况是,如果您有一个自定义视图引用了视图层次结构中的某些内容,通常不建议这样做。

所以我要选择strong,然后我会点击connect,这会生成我的outlet。

【讨论】:

  • 很好的答案,解释了实际原因-为什么-
  • 这很好,但我看到了故事板中实现的手势识别器的泄漏。
  • 这行我看不懂。 “唯一真正需要使出口变弱的情况是,如果您有一个自定义视图引用了视图层次结构中的某些内容,通常不建议这样做。”有什么例子吗?
  • 我计算了weak和strong所需要的deinit时间,完全一样。
  • 但在 swift 中情况更是如此。弱引用更快。
【解决方案6】:

在 iOS 开发中,NIB 加载与 Mac 开发略有不同。

在 Mac 开发中,IBOutlet 通常是一个弱引用:如果您有 NSViewController 的子类,则只会保留顶级视图,而当您释放控制器时,它的所有子视图和出口都会自动释放。

UiViewController 使用键值编码来设置使用强引用的 outlet。因此,当您释放 UIViewController 时,顶视图将自动释放,但您还必须在 dealloc 方法中释放其所有出口。

In this post from the Big Nerd Ranch,他们涵盖了这个主题并解释了为什么在 IBOutlet 中使用强引用不是一个好的选择(即使在这种情况下 Apple 推荐它)。

【讨论】:

  • 它解释了 2009 年的情况。使用 ARC,这种情况发生了显着变化。
  • :( Big Nerd Ranch 链接已失效……但我真的需要阅读它。有人知道有关该帖子的更多详细信息,所以我可以找到它吗?
  • @MottiShneor 别担心,这没什么大不了的,因为该链接大约在 ARC 之前的时代,并且不再相关。
【解决方案7】:

我想在这里指出一件事,那就是,尽管 Apple 工程师在他们自己的 WWDC 2015 视频中已经说过:

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

Apple 在这个问题上不断改变主意,这告诉我们这个问题没有唯一的正确答案。为了表明即使是苹果工程师在这个问题上也存在分歧,看看苹果最近的 示例代码,你会看到有些人使用weak,有些人没有。

这个 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

与核心位置示例一样: 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. 苹果一再改变主意,C. 你可以用任何让你开心的东西:)

特别感谢 Paul Hudson(www.hackingwithsift.com 的作者)为我提供了澄清,并提供了此答案的参考资料。

我希望这能更好地阐明主题!

保重。

【讨论】:

  • 我一直在检查这个问题,但没有找到任何具体的答案。由于上面的链接表明两者都很好,并且通常符合 Xcode 自动建议的内容。
【解决方案8】:

从 WWDC 2015 开始,Implementing UI Designs in Interface Builder 上有一个会话。在 32 分钟左右,他说你总是想让你的 @IBOutlet strong

【讨论】:

  • 有趣。我猜这在视图卸载被移除时改变了?
【解决方案9】:

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

【讨论】:

  • 为什么不是copy,因为它是NSArray
【解决方案10】:

这些年来似乎发生了一些变化,现在 Apple 建议总体上使用 strong。他们 WWDC 会议的证据在 session 407 - Implementing UI Designs in Interface Builder 中,从 32:30 开始。我对他所说的话的注释是(几乎,如果不完全是引用他的话):

  • 一般来说,出口连接应该是强的,特别是如果我们连接的子视图或约束并不总是由 查看层次结构

  • 在创建自定义视图时可能需要弱插座连接,该视图对视图层次结构中的备份有一些引用 而且一般不推荐

在其他方面,只要我们的一些自定义视图不创建保留循环,其中一些视图在视图层次结构中向上,它现在应该总是很强大

编辑:

有些人可能会问这个问题。使用强引用保持它不会创建一个保留周期,因为根视图控制器和拥有视图保持对它的引用?或者为什么会发生这种变化? 当他们描述如何从 xib 创建 nib 时,我认为答案在本次演讲的前面。为 VC 和视图创建了一个单独的 nib。我认为这可能是他们改变建议的原因。不过,如果能从 Apple 那里得到更深入的解释,那就太好了。

【讨论】:

    【解决方案11】:

    我认为最重要的信息是: xib 中的元素会自动出现在视图的子视图中。子视图是 NSArray。 NSArray 拥有它的元素。等对他们有很强的指示。所以在大多数情况下你不想创建另一个强指针(IBOutlet)

    使用 ARC,您无需在 viewDidUnload 中执行任何操作

    【讨论】:

      猜你喜欢
      • 2016-06-25
      • 2023-03-22
      • 2012-06-30
      • 2013-05-10
      • 2012-05-06
      • 2015-06-07
      • 1970-01-01
      相关资源
      最近更新 更多