关于ios:IBOutlets在ARC下是强还是弱?

Should IBOutlets be strong or weak under ARC?

我正在使用ARC专门为iOS 5开发。IBOutlets到UIViews(及其子类)是strong还是weak呢?

下列内容:

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

将摆脱所有这些:

1
2
3
4
5
6
- (void)viewDidUnload
{
    // ...
    self.button = nil;
    // ...
}

这样做有什么问题吗?这些模板使用的是strong,正如从"接口生成器"编辑器直接连接到头时自动生成的属性一样,但为什么呢?UIViewController已经有了一个strong的引用,它保留了它的亚观。


警告,过时答案:根据WWDC 2015,此答案不是最新的,有关正确答案,请参阅上面接受的答案(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).

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


苹果公司目前推荐的最佳实践是让iboutlets变得强大,除非特别需要弱者来避免保留周期。正如Johannes上面提到的,这是在WWDC 2015的"在界面构建器中实现UI设计"会议上发表的评论,其中一位苹果工程师说:

And the last option I want to point out is the storage type, which can
either be strong or weak. In general you should make your outlet
strong, especially if you are connecting an outlet to a subview or to
a constraint that's not always going to be retained by the view
hierarchy. The only time you really need to make an outlet weak is if
you have a custom view that references something back up the view
hierarchy and in general that's not recommended.

我在twitter上向ib团队的一位工程师询问了这个问题,他确认了streng应该是默认的,开发人员文档正在更新。

https://twitter.com//u danielhall/status/6207169963263500848https://twitter.com//u danielhall/status/620717252216623104


虽然文档建议在子视图的属性上使用weak,但由于iOS 6,使用strong(默认所有权限定符)似乎可以。这是由于UIViewController中视图不再卸载的更改造成的。

  • 在iOS6之前,如果您保持与周围控制器视图的子视图的强链接,如果视图控制器的主视图被卸载,那么只要视图控制器在周围,这些子视图就会保持在子视图上。
  • 由于iOS6,视图不再被卸载,而是加载一次,然后只要控制器在那里就一直保留。所以,强大的性能并不重要。它们也不会创建强引用循环,因为它们指向强引用图。

也就是说,我在使用

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

1
@property (nonatomic) IBOutlet UIButton *button;

在iOS 6及更高版本中:

  • 使用weak可以清楚地表明控制器不希望拥有按钮。

  • 但是在没有视图卸载的iOS6中,省略weak并不会造成伤害,而且更短。有些人可能会指出,这也更快,但我还没有遇到一个应用程序,因为weakIBOutlets太慢。

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

底线:由于iOS6,只要不使用视图卸载,我们就不会再犯错误。聚会时间。;)


我看不出有什么问题。在ARC之前,我一直制作iBooklets assign,因为它们已经被超视图保留了。如你所指出的,如果你把它们制作成weak,你不应该在viewdidunload中把它们零掉。

一个警告:您可以在ARC项目中支持iOS 4.x,但是如果这样做了,就不能使用weak,因此您必须使它们成为assign,在这种情况下,您仍然希望在viewDidUnload中消除引用,以避免出现悬空指针。下面是一个我经历过的悬空指针错误的例子:

uiviewController具有用于邮政编码的uitextfield。它使用cllocationmanager反向地理编码用户的位置并设置邮政编码。以下是委托回调:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-(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上抛出一个错误的访问异常。


由于性能原因,IBOutlet应该很强。参见故事板参考、强大的iBooklet、iOS 9中的场景停靠

As explained in this paragraph, the outlets to subviews of the view
controller’s view can be weak, because these subviews are already
owned by the top-level object of the nib file. However, when an Outlet
is defined as a weak pointer and the pointer is set, ARC calls the
runtime function:

id objc_storeWeak(id *object, id value);

This adds the pointer
(object) to a table using the object value as a key. This table is
referred to as the weak table. ARC uses this table to store all the
weak pointers of your application. Now, when the object value is
deallocated, ARC will iterate over the weak table and set the weak
reference to nil. Alternatively, ARC can call:

void objc_destroyWeak(id * object)

Then, the object is
unregistered and objc_destroyWeak calls again:

objc_storeWeak(id *object, nil)

This book-keeping associated
with a weak reference can take 2–3 times longer over the release of a
strong reference. So, a weak reference introduces an overhead for the
runtime that you can avoid by simply defining outlets as strong.

从xcode 7开始,建议使用strong

如果您在Interface Builder中观看WWDC 2015第407部分实现用户界面设计,它会给出建议(抄本来自http://asciiwwdc.com/2015/sessions/407)

And the last option I want to point out is the storage type, which can either be strong or weak.

In general you should make your outlet strong, especially if you are connecting an outlet to a sub view or to a constraint that's not always going to be retained by the view hierarchy.

The only time you really need to make an outlet weak is if you have a custom view that references something back up the view hierarchy and in general that's not recommended.

So I'm going to choose strong and I will click connect which will generate my outlet.


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

在Mac开发中,iboutlet通常是一个弱引用:如果您有nsviewcontroller的子类,则只保留顶级视图,当您取消分配控制器时,其所有子视图和出口都将自动释放。

uiviewcontroller使用键值编码来设置使用强引用的出口。因此,当您取消分配uiviewcontroller时,顶视图将自动取消分配,但您还必须在取消分配方法中取消分配其所有出口。

在这篇来自Big Nerd Ranch的文章中,他们讨论了这个主题,并解释了为什么在iboutlet中使用强引用不是一个好的选择(即使苹果在本例中推荐)。


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

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

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

这个苹果支付的例子使用了弱: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/listments/avfoundationpipplayer_playerview controller_swift.html//apple_ref/doc/uid/tp40016166-avfoundationpipplayer_playerview controller_swift-dontlinkeelementid_4

和列表器示例一样:https://developer.apple.com/library/ios/samplecode/lister/lister-listcell-swift.html//apple-ref/doc/uid/tp40014701-lister-listcell-swift-dontlinkeelementid_57

核心位置示例也一样:https://developer.apple.com/library/ios/samplecode/potloc/listments/potloc_potlocviewcontroller_swift.html//apple_-ref/doc/uid/tp40016176-potloc_potlocviewcontroller_swift-dontlinkeelementid_6

和视图控制器预览示例一样:https://developer.apple.com/library/ios/samplecode/viewcontrollerpreviews/listments/projects_previewusingdelegate_previewusingdelegate_detailview controller_swift.html//apple_ref/doc/uid/tp40016546-项目_previewusingdelegate_previewusingdelegate_detailview controller_swift-dontlinkeelementid_5

与HomeKit示例一样:https://developer.apple.com/library/ios/samplecode/homekitcatalog/listments/hmcatalog_homes_action_set_actionsetviewcontroller_swift.html//apple_ref/doc/uid/tp40015048-hmcatalog_homes_action_set_actionsetviewcontroller_swift-dontlinkeelementid_23

所有这些都是iOS 9的完全更新版本,并且都使用弱插座。从中我们了解到,问题并不像有些人想象的那么简单。B.苹果反复改变主意,C.你可以用任何让你高兴的东西:)

特别感谢PaulHudson(www.hackingwithsift.com的作者),他给了我澄清,并提供了这个答案的参考资料。

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

当心。


从WWDC 2015开始,有一个关于在Interface Builder中实现UI设计的会议。在32分钟左右,他说你总是想让你的江户变得强大。


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


看起来这些年来有些变化,现在苹果公司建议在一般情况下使用STRONG。WWDC会话上的证据在会话407中—在接口生成器中实现UI设计,从32:30开始。我从他所说的话中得出的结论是(几乎,如果不准确的话,引用他的话):

  • 出口连接通常应该很牢固,特别是如果我们连接的子视图或约束并不总是由视图层次结构

  • 创建自定义视图时,可能需要弱的出口连接,这些视图在视图层次结构中有一些备份的引用。一般不建议

在其他方面,只要我们的一些自定义视图没有创建一个保留周期,并且其中一些视图在视图层次结构中处于上一级,那么它现在应该一直很强大。

编辑:

有些人可能会问这个问题。将它与强引用保持在一起不会创建一个保留循环,因为根视图控制器和拥有的视图保持对它的引用?或者为什么会发生这种变化?我认为这个问题的答案在本文的前面,他们描述了如何从XIB创建笔尖。有一个单独的NIB为VC和视图创建。我认为这可能是他们改变建议的原因。不过,从苹果那里得到更深入的解释还是不错的。


我认为最重要的信息是:XIB中的元素自动位于视图的子视图中。子视图不是数组。NSARRAY拥有它的元素。ETC对他们有很强的指示力。所以在大多数情况下,您不想创建另一个强指针(iboutlet)

有了ARC,你不需要在viewDidUnload中做任何事情。