iOS13対応したら、SKProductsRequest周りでつまずいた

これ[iOS13、Xcode11 私はこうしてつまずいた Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ios13_xcode11)　の23日目を担当するアガツマです
普段は、ビジネス版マッチングアプリ [yenta](https://yenta.talentbase.io/yenta) のiOS版を開発をしています。

前日の記事
[iOS13のメインスレッドチェック厳格化とNotificationCenterの組合せでつまづいた](https://qiita.com/y-some/items/938adfc9df6fbf3631d7)
と少し被るのですが、１つのケースとして誰かの役に立てたら幸いです！

# つまずいた経緯

iOS13対応の諸々を完了し、アプリの課金プランについて記述している画面を開こうとしたところクラッシュ。。。
xcodeでは、以下のようなエラーメッセージが出力されていました。

```
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.'
```
UIの更新は Main Thread で行うべきというのは以前からありましたが、iOS13に対応したらこのエラーが出るように。。。。

### 環境設定
iOS 13.2.2
Xcode11.2.0

# やったこと
## 原因となっている箇所を特定
[Main Thread Checker](https://developer.apple.com/documentation/code_diagnostics/main_thread_checker) を活用して、Main Threadで呼ばれるべき処理が、実行時に Main Thread以外で呼ばれていないかをチェック！


### Main Thread Checker 設定方法
Breakpoint Editor を開く
<img width="385" alt="スクリーンショット 2019-12-23 15.09.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266462/4a7776cf-0d83-9693-c7fa-1d36f7eb0ca3.png">

Runtime Issue Breakpoint を追加
<img width="226" alt="スクリーンショット 2019-12-23 15.13.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266462/98427697-b025-88f7-a5d6-bf8ec660ba67.png">

追加した際に出る設定画面で type を **Main Thread Checker** を選択
<img width="588" alt="スクリーンショット 2019-12-23 15.09.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266462/49f7645e-6b9f-3b8e-70cb-95ec88d64913.png">

これで、実行時に Main Thread以外でUI更新をしている部分で処理が止まるので、原因となっている部分を特定しに行きました。

## 原因の特定と調査

このような過程で原因を調査していったところ、、、
`SKProductsRequest`の`Delegate`メソッドの中で Main Thread 以外でUIに関する処理をしていることがわかりました。

 [SKProductsRequest](https://developer.apple.com/documentation/storekit/skproductsrequest)は、アプリ内課金アイテムの情報を取得する際に利用しているのですが、色々と調べてみると下記２つのメソッドが Main Thread 以外で実行されるようになったみたいです。
なぜ、その様になったかはまだはっきりとはわかっていません。。。

この２つのメソッドが Main Thread で実行されることを想定していた実装をしている場合は注意が必要です。

```swift:
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {}
func request(_ request: SKRequest, didFailWithError error: Error) {}
```

## 改善案
上で挙げた２つのメソッド内の処理を、`DispatchQueue.main.async`を用いて明示的にメインスレッド Main Thread で実行されるようにしました。
ここでは`productsRequest(...)`の例のみ記載します。

**修正前のコード**

```swift:
// MARK: - SKProductsRequestDelegate
extension ViewController: SKProductsRequestDelegate {
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        //...(省略)...

        // 以下の処理でクラッシュする(Main Thread 以外でUI更新している)
        collectionView.reloadData()
        button.isHidden = false
    }
}
```

**修正後のコード**

```swift:
// MARK: - SKProductsRequestDelegate
extension ViewController: SKProductsRequestDelegate {
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        DispatchQueue.main.async { // 書く
            //...(省略)...

            // Main Threadで実行される
            collectionView.reloadData()
            button.isHidden = false
        }
    }
}
```

# さいごに
iOS13に対応したことで、想定外の部分でクラッシュする画面が出てくるのはなかなかつまづくポイントだなと思います。
このようなアップデート時にテストを入念にするというのはもちろんですが、iOSの自動テストなどを組み込みアップデートにより起こってしまう問題を検知する方法もしっかり模索していかなければいけないなと思いました。

### 参考
[Are SKProductsRequestDelegate methods always called on the main thread?](https://stackoverflow.com/questions/58194976/are-skproductsrequestdelegate-methods-always-called-on-the-main-thread)
[Multithreaded rendering only crashes on iOS 13](https://forums.developer.apple.com/thread/121424)
[【iOS】iOS13からSKProductsRequestのDelegateメソッドがメインスレッドで呼ばれなくなった](https://iganin.hatenablog.com/entry/2019/10/09/014932)