謎のinsertArrangedSubviewでクラッシュする問題

筆者が開発しているiOSのアプリで、App Store Connect経由でクラッシュログが繰り返し送られてくるものの、再現方法も解決方法もずっと謎であった不具合が解決したのでメモとして残しておきます。

## 問題の内容

送られてくるクラッシュログは以下のようなものでした。

```
Last Exception Backtrace:
0   CoreFoundation                	0x18d2d75b4 __exceptionPreprocess + 220 (NSException.m:199)
1   libobjc.A.dylib               	0x1a12c742c objc_exception_throw + 60 (objc-exception.mm:565)
2   CoreFoundation                	0x18d1d3aac +[NSException raise:format:] + 112 (NSException.m:155)
3   UIKitCore                     	0x19001b6d4 -[UIStackView insertArrangedSubview:atIndex:] + 184 (UIStackView.m:103)
4   UIKitCore                     	0x18f1723e4 -[_UIButtonBar _layoutBar] + 2576 (UIButtonBar.m:555)
5   UIKitCore                     	0x18f175b38 __42-[_UIButtonBarStackView updateConstraints]_block_invoke + 52 (UIButtonBar.m:1293)
6   UIKitCore                     	0x190112468 +[UIView(Animation) performWithoutAnimation:] + 104 (UIView.m:13927)
7   UIKitCore                     	0x18f175ad8 -[_UIButtonBarStackView updateConstraints] + 120 (UIButtonBar.m:1292)
8   UIKitCore                     	0x19003458c -[UIView(AdditionalLayoutSupport) _sendUpdateConstraintsIfNecessaryForSecondPass:] + 484 (NSLayoutConstraint_UIKitAdditions.m:0)
9   UIKitCore                     	0x190034a44 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 948 (NSLayoutConstraint_UIKitAdditions.m:4409)
10  UIKitCore                     	0x190034924 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 660 (NSLayoutConstraint_UIKitAdditions.m:4390)
11  UIKitCore                     	0x190034924 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 660 (NSLayoutConstraint_UIKitAdditions.m:4390)
12  CoreAutoLayout                	0x1a154c060 -[NSISEngine withBehaviors:performModifications:] + 88 (NSISEngine.m:1917)
13  UIKitCore                     	0x1900351f4 __100-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:]_block_invoke + 116 (NSLayoutConstraint_UIKitAdditions.m:4455)
14  UIKitCore                     	0x190033b30 -[UIView(AdditionalLayoutSupport) _withUnsatisfiableConstraintsLoggingSuspendedIfEngineDelegateExists:] + 128 (NSLayoutConstraint_UIKitAdditions.m:4154)
15  UIKitCore                     	0x190034db4 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:] + 188 (NSLayoutConstraint_UIKitAdditions.m:4454)
16  UIKitCore                     	0x190035cac -[UIView(AdditionalLayoutSupport) _updateConstraintsAtEngineLevelIfNeededWithViewForVariableChangeNotifications:] + 436 (NSLayoutConstraint_UIKitAdditions.m:4707)
17  UIKitCore                     	0x19010c028 -[UIView _updateConstraintsAsNecessaryAndApplyLayoutFromEngine] + 404 (UIView.m:12304)
18  UIKitCore                     	0x18f2659f8 -[UIToolbar layoutSubviews] + 56 (UIToolbar.m:712)
19  UIKitCore                     	0x19011f7dc -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2500 (UIView.m:17460)
20  QuartzCore                    	0x190627464 -[CALayer layoutSublayers] + 296 (CALayer.mm:10129)
21  QuartzCore                    	0x190627920 CA::Layer::layout_if_needed(CA::Transaction*) + 524 (CALayer.mm:9996)
22  QuartzCore                    	0x19063bd48 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 144 (CALayer.mm:2478)
23  QuartzCore                    	0x1905833e4 CA::Context::commit_transaction(CA::Transaction*, double, double*) + 416 (CAContextInternal.mm:2380)
24  QuartzCore                    	0x1905ae6dc CA::Transaction::commit() + 732 (CATransactionInternal.mm:449)
25  QuartzCore                    	0x1905afa3c CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 96 (CATransactionInternal.mm:925)
26  CoreFoundation                	0x18d253454 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 36 (CFRunLoop.c:1799)
27  CoreFoundation                	0x18d24d868 __CFRunLoopDoObservers + 576 (CFRunLoop.c:1912)
28  CoreFoundation                	0x18d24de18 __CFRunLoopRun + 1056 (CFRunLoop.c:2953)
29  CoreFoundation                	0x18d24d4cc CFRunLoopRunSpecific + 600 (CFRunLoop.c:3242)
30  GraphicsServices              	0x1a3c31820 GSEventRunModal + 164 (GSEvent.c:2259)
31  UIKitCore                     	0x18fbf2a28 -[UIApplication _run] + 1072 (UIApplication.m:3270)
32  UIKitCore                     	0x18fbf8104 UIApplicationMain + 168 (UIApplication.m:4739)
33  KifuBox                       	0x102e6c2b0 main + 68 (WaitingViewController.swift:12)
34  libdyld.dylib                 	0x18cf14e60 start + 4
```

厄介な点の1つとして、自分が直接書いたソースコードはスタック中にはなく、何をトリガに起きているのかわからないことがありました。
最終的には`UIStackView`の`insertArrangedSubview`で落ちており、スタックの途中には`_UIButtonBarStackView`などが出ているので、多分`UIBarButtonItem`に関連する何かだとは思うのですが、どこで問題が起きているのか全くわかりませんでした。

発生の傾向を見ると、iOS13.4以降でしか起きていないようで、それがiOS13.4以降でしか発生しない現象なのか、たまたま時期的な関係でそう見えているのかはよくわかりませんでした。

## 原因と解決方法

結論としては、Apple Developers Forumにあった以下の記事が当たりでした。
https://developer.apple.com/forums/thread/130038

上記記事では`UIAlertController`だそうですが、筆者のアプリでは画面下部の`UIToolbar`に`UIBarButtonItem`を追加する際に、同じオブジェクトが含まれていると、iOS13.4以降ではクラッシュするという問題でした。(iOS13.3以前では同じコードでも問題は発生しない)

```swift:MyViewController.swift
self.setToolbarItems([filterButton, fixSpace1, dummyIcon, flexSpace1, filterStateButton,
     flexSpace2, dummyIcon, fixSpace2, dummyIcon], animated: true)
```

上記のソースコードが問題で、ツールバーにボタンを並べる際のレイアウト調整のために、見えないダミーのボタン(`dummyIcon`)を3つ追加していたのですが、その3つが同じオブジェクトであったためにクラッシュしていました。

以下のように3つ別のオブジェクトにすることで解決しました(オブジェクトの生成コード等は省略)

```swift:MyViewController.swift
self.setToolbarItems([filterButton, fixSpace1, dummyIcon1, flexSpace1, filterStateButton,
     flexSpace2, dummyIcon2, fixSpace2, dummyIcon3], animated: true)
```

手がかりが`insertArrangedSubview`くらいしかなくて難航したので、少しでもネット上で情報が見つかるように記事を書いてみました。