UINavigationController方法setToolbarHidden在Xcode 9中的bug:自动布局约束的无限计算导致OOM

我有一个嵌套在UITabBarControllerUINavigationController实例。 我使用导航控制器来达到一些视图控制器(标签栏仍然可见),从中我继续到第二个视图控制器(标签栏不再可见)。

在第二个视图控制器中,只要我调用: [self.navigationController setToolbarHidden:NO]应用程序冻结和内存增长,直到OOMexception崩溃。

我承认,不推荐在导航栏中embedded导航控制器,但是这个设置在iOS 11之前似乎没问题。

编辑:当停止执行,我看到很多电话:

UIView(UIConstraintBasedLayout)

UIView(AdditionalLayerSupport)

NSLayoutConstraint

这是完整的堆栈跟踪

 * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP * frame #0: 0x0000000106dd895c libobjc.A.dylib`objc_msgSend + 28 frame #1: 0x00000001067b6b9b Foundation`-[NSConcreteMapTable removeObjectForKey:] + 138 frame #2: 0x00000001069e6019 Foundation`_substituteOutAllOccurencesOfBodyVar + 1282 frame #3: 0x00000001067f3c5b Foundation`-[NSISEngine tryAddingDirectly:] + 144 frame #4: 0x00000001067f332f Foundation`-[NSISEngine tryToAddConstraintWithMarker:expression:integralizationAdjustment:mutuallyExclusiveConstraints:] + 440 frame #5: 0x00000001069f2067 Foundation`-[NSLayoutConstraint _addLoweredExpression:toEngine:integralizationAdjustment:lastLoweredConstantWasRounded:mutuallyExclusiveConstraints:] + 273 frame #6: 0x00000001067ea601 Foundation`-[NSLayoutConstraint _addToEngine:integralizationAdjustment:mutuallyExclusiveConstraints:] + 240 frame #7: 0x0000000109c9488d UIKit`__57-[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:]_block_invoke_2 + 452 frame #8: 0x00000001067f0de1 Foundation`-[NSISEngine withBehaviors:performModifications:] + 131 frame #9: 0x0000000109c946a2 UIKit`__57-[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:]_block_invoke + 604 frame #10: 0x0000000109c9441e UIKit`-[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:] + 223 frame #11: 0x00000001091ed84f UIKit`__45-[UIView(Hierarchy) _postMovedFromSuperview:]_block_invoke + 112 frame #12: 0x00000001067f0de1 Foundation`-[NSISEngine withBehaviors:performModifications:] + 131 frame #13: 0x00000001091ed778 UIKit`-[UIView(Hierarchy) _postMovedFromSuperview:] + 855 frame #14: 0x00000001091fe031 UIKit`-[UIView(Internal) _addSubview:positioned:relativeTo:] + 1927 frame #15: 0x0000000109b507e1 UIKit`-[_UILayoutArrangement insertItem:atIndex:] + 502 frame #16: 0x0000000109ca1b4d UIKit`__50-[_UIOrderedLayoutArrangement insertItem:atIndex:]_block_invoke + 50 frame #17: 0x0000000109ca18df UIKit`-[_UIOrderedLayoutArrangement _trackChangesAffectingExternalBaselineConstraints:] + 320 frame #18: 0x0000000109ca1aea UIKit`-[_UIOrderedLayoutArrangement insertItem:atIndex:] + 478 frame #19: 0x000000010982edea UIKit`-[UIStackView insertArrangedSubview:atIndex:] + 283 frame #20: 0x0000000109b29972 UIKit`-[_UIButtonBar _layoutBar] + 3639 frame #21: 0x0000000109b2bb44 UIKit`-[_UIButtonBarStackView updateConstraints] + 48 frame #22: 0x0000000109c958b6 UIKit`-[UIView(AdditionalLayoutSupport) _sendUpdateConstraintsIfNecessaryForSecondPass:] + 161 frame #23: 0x0000000109c95ed2 UIKit`-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 1296 frame #24: 0x0000000109c95d51 UIKit`-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 911 frame #25: 0x0000000109c95d51 UIKit`-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 911 frame #26: 0x0000000109c95d51 UIKit`-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 911 frame #27: 0x00000001067f0de1 Foundation`-[NSISEngine withBehaviors:performModifications:] + 131 frame #28: 0x0000000109c96703 UIKit`__100-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:]_block_invoke + 90 frame #29: 0x0000000109c94f61 UIKit`-[UIView(AdditionalLayoutSupport) _withUnsatisfiableConstraintsLoggingSuspendedIfEngineDelegateExists:] + 104 frame #30: 0x0000000109c96272 UIKit`-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:] + 160 frame #31: 0x0000000109c9738c UIKit`-[UIView(AdditionalLayoutSupport) _updateConstraintsAtEngineLevelIfNeededWithViewForVariableChangeNotifications:] + 401 frame #32: 0x00000001091ef1b6 UIKit`-[UIView(Hierarchy) layoutBelowIfNeeded] + 1517 frame #33: 0x000000010957b35e UIKit`-[_UIButtonBarButton willMoveToWindow:] + 63 frame #34: 0x00000001091ec996 UIKit`-[UIView(Hierarchy) _willMoveToWindow:] + 861 frame #35: 0x00000001091eb493 UIKit`__UIViewWillBeRemovedFromSuperview + 484 frame #36: 0x00000001091eb0ea UIKit`-[UIView(Hierarchy) removeFromSuperview] + 95 frame #37: 0x0000000109b295d3 UIKit`-[_UIButtonBar _layoutBar] + 2712 frame #38: 0x0000000109b2bb44 UIKit`-[_UIButtonBarStackView updateConstraints] + 48 frame #39: 0x0000000109c958b6 UIKit`-[UIView(AdditionalLayoutSupport) _sendUpdateConstraintsIfNecessaryForSecondPass:] + 161 frame #40: 0x0000000109c95ed2 UIKit`-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 1296 frame #41: 0x0000000109c95d51 UIKit`-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 911 frame #42: 0x0000000109c95d51 UIKit`-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 911 frame #43: 0x0000000109c95d51 UIKit`-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 911 frame #44: 0x00000001067f0de1 Foundation`-[NSISEngine withBehaviors:performModifications:] + 131 frame #45: 0x0000000109c96703 UIKit`__100-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:]_block_invoke + 90 frame #46: 0x0000000109c94f61 UIKit`-[UIView(AdditionalLayoutSupport) _withUnsatisfiableConstraintsLoggingSuspendedIfEngineDelegateExists:] + 104 frame #47: 0x0000000109c96272 UIKit`-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:] + 160 frame #48: 0x0000000109c9738c UIKit`-[UIView(AdditionalLayoutSupport) _updateConstraintsAtEngineLevelIfNeededWithViewForVariableChangeNotifications:] + 401 frame #49: 0x00000001091efa5b UIKit`-[UIView(Hierarchy) _updateConstraintsAsNecessaryAndApplyLayoutFromEngine] + 159 frame #50: 0x00000001095742d5 UIKit`-[UILayoutContainerView layoutSubviews] + 270 frame #51: 0x0000000109204551 UIKit`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1331 frame #52: 0x00000001064db4ba QuartzCore`-[CALayer layoutSublayers] + 153 frame #53: 0x00000001064df5a9 QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 401 frame #54: 0x00000001064681cd QuartzCore`CA::Context::commit_transaction(CA::Transaction*) + 365 frame #55: 0x0000000106493ae4 QuartzCore`CA::Transaction::commit() + 500 frame #56: 0x0000000109160687 UIKit`_afterCACommitHandler + 272 frame #57: 0x00000001080f8db7 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23 frame #58: 0x00000001080f8d0e CoreFoundation`__CFRunLoopDoObservers + 430 frame #59: 0x00000001080dd324 CoreFoundation`__CFRunLoopRun + 1572 frame #60: 0x00000001080dca89 CoreFoundation`CFRunLoopRunSpecific + 409 frame #61: 0x000000010dc429c6 GraphicsServices`GSEventRunModal + 62 frame #62: 0x0000000109135d30 UIKit`UIApplicationMain + 159 frame #63: 0x0000000101ff6bf9 MyAppName`main(argc=1, argv=0x00007fff5de3e0a8) at main.m:23 frame #64: 0x000000010f453d81 libdyld.dylib`start + 1 frame #65: 0x000000010f453d81 libdyld.dylib`start + 1 

这是OP从同一个团队的开发者那里得到的答案。

经过一轮研究后,我们发现问题在我们这边:

 - (NSArray *)toolbarItems { return [self toolbarItemsWithRunningAdditionalAnimation:NO]; } 

原始开发人员在每次调用方法时都使toolbarItemsWithRunningAdditionalAnimation方法返回新对象。 当去到这个控制器时,iOS会调用toolbarItems至less3次,所以每当我们把iOS弄糊涂的时候给它一个新的对象,这样就会在无限循环中重新计算自动布局约束。 我们原来的下面的修正也在工作,但它变得过时了,因为我们开始总是返回相同的项目,如:

 - (NSArray *)toolbarItems { if (_cachedToolbarItems) { return _cachedToolbarItems; } _cachedToolbarItems = [self toolbarItemsWithRunningAdditionalAnimation:NO]; return _cachedToolbarItems' } 

我们正在做我们的应用程序(伪代码)的以下内容:

 UIToolbar *toolbar = self.navigationController.toolbar; NSArray <UIBarButtonItem *> *items = @[ flexibleSpace, share, flexibleSpace, play, flexibleSpace, stats, flexibleSpace ]; [toolbar setItems:items animated:animated]; [self.navigationController setToolbarHidden:NO animated:animated]; 

导致自动布局约束无限计算的问题项目是sharestats 。 他们都有共同点 – 他们都是使用-[UIBarButtonItem initWithImage:style:target:action:]初始值设定项创build的。 当我们开始使用另一个初始化器时:

  UIButton *customShareButton = ... // we create button ourselves. UIBarButtonItem *shareItem = [[UIBarButtonItem alloc] initWithCustomView: customShareButton]; 

问题消失了。

除了被接受的答案之外,我们发现我们的问题与我们的类中重写方法- (NSArray *)toolbarItems

每次调用方法时,都会创build一组新的toolbarItems。 看起来像从iOS 11,每次新的工具栏项返回到这个方法, UINavigationController:layoutIfNeeded再次被调用,这又调用toolbarItems ,由于我们的实现返回新的项目。 这会导致无限循环。

如果遇到这个问题,检查是否覆盖- (NSArray *)toolbarItems