OAuth2.0とscene-based lifecycleについておさらいしながら、SpotifyLoginのアップデートをする

こんにちは！

今回は、githubで公開されている**SpotifyのOauth認証を通すためのswiftフレームワーク"SpotifyLogin"** をiOS13以降に対応させる作業を通じて、OAuth2.0とscene-based lifecycleについておさらいしていこうと思います。

なお、本記事はiOS13以降のこの現代世界で、ひょんなことからSpotifyLoginを使うこととなった方たち向けにも書いています(そんな人いますか？)。SpotifyLoginはOSSであるため、コードの修正案はpullリクを送るのが正攻法かとは思います。しかし私のリクエストの書き方が悪いのか、作成者の方が更新に力を割けていないのか、送ったリクエストが反映されないままなので、簡単にではありますが他の方のハマりを防ぐためにも一旦ここにまとめておこうかと思いました！

もちろん私の修正案が適切でない可能性もあるため、もし何かお気づきの点があればコメントにてご指摘頂けますと幸いです🙆

# SpotifyLoginって何？
github上で公開されているSpotifyのOAuth認証を通すためのswiftライブラリです。
SpotifyLogin: [Link to github](https://github.com/spotify/SpotifyLogin)

OAuth認証を通すためのswiftフレームワークとしては他にも多くあります。例えば有名どころとしてOAuthSwift([Link to github](https://github.com/OAuthSwift/OAuthSwift))があります。こちらはTwitterからFacebook、Instagram等に幅広く対応しており、非常に有用かと思われますが、如何せん私には動かせませんでした……泣

なお、上記のような悲しい事情から本稿ではSpotifyLoginを扱いますが、最終更新はSpotifyLoginが2017年であるのに対して、OAuthSwiftは2020年であることを考えると冷静に後者を使ったほうがいい気がします！この記事はSpotifyLoginの使用を進めているわけではありません🙋(私にとってはとても使いやすいフレームワークだったことは間違いありません！)

# 基本的な使い方の流れ
公式のReadmeにあるように、フレームワークとしてXcodeに入れていきましょう

# (iOS13以降を使う場合)ライフサイクルの変更に対応させよう
先述の通り、**SpotifyLoginは2017年11月が最後の更新**です。
github上、このフレームワークはiOS9+laterに対してcomaptibleと記されていますが、時は流れ**iOS13が2019年にリリース**され、**それ以降のiOSについてはcompatibleではなくなってしまっています**。というのも、**iOS13以降ではアプリのライフサイクルが大きく変化した**からです。
ライフサイクルについては別の記事でまとめ、ここではiOS13以降での変更点の要旨およびとにかく動かすために必要なことを考えます。

## iOS13以降でリダイレクト時の処理が変わった
iOSのライフサイクルはバージョン12までApp-Based Life-Cycleと呼ばれるものであったのが、**13以降ではScene-Based Life-Cycle**と呼ばれるものになりました。
この変更はiPadの使用を念頭に置いており、sceneと呼ばれる概念を導入することによってアプリのマルチウィンドウ化を実現することが主な目的です。多くの解説記事がありますが、[Appleの公式ドキュメント](https://developer.apple.com/documentation/uikit/app_and_environment/scenes)にもそのようなことが書かれています。
>A scene contains the windows and view controllers for presenting one instance of your UI. Each scene also has a corresponding UIWindowSceneDelegate object, which you use to coordinate interactions between UIKit and your app. Scenes run concurrently with each other, sharing the same memory and app process space. As a result, a single app may have multiple scenes and scene delegate objects active at the same time.

ライフサイクルの変更により、**アプリがアクションやイベントに対して反応するタイミングや方法が変わる**こととなります。SpotifyLoginを使う上では、アプリにURLを使ってアクセスした際に呼び出されるメソッド、つまり**アプリへのリダイレクト時に呼び出されるメソッドが変更された**ことに対応しなければなりません。

## リダイレクトとは
アプリへのリダイレクトという言葉の意味を理解するために、iOSアプリにおけるOAuth認証の流れのうちwebブラウザを介して認可コードを取得するまでを復習します。
![](https://storage.googleapis.com/zenn-user-upload/86qiil1fqw2kv0gmb7nrbbydkym7)

認可コード取得以後も含んだOAuth2.0の全体像は[こちら](https://qiita.com/TakahikoKawasaki/items/8567c80528da43c7e844#%E9%81%B7%E7%A7%BB%E5%85%88%E3%82%92%E4%BA%8B%E5%89%8D%E3%81%AB%E7%99%BB%E9%8C%B2%E3%81%97%E3%81%A6%E3%81%8A%E3%81%8F)や[こちら](https://murashun.jp/article/programming/oauth2.html)を読むと理解できます。

OAuth2.0ではエンドユーザーの認可を得るために専用のページを表示することになっており、そのためクライアントアプリケーションと認可サーバーとの間にwebブラウザを仲介させます。しかし**もちろん最終的にはクライアントアプリケーションが認可コードを得る必要があり**、そのため、上記8番のように**webブラウザからアプリにリダイレクトする**ことになります。

## iOS13以降リダイレクト時に呼び出されてほしい関数はscenedelegateに入れよう
クライアントアプリケーションは、webブラウザからのリダイレクトで渡された文字列から認可コードを取得する必要があります(取得するとは言っても、渡された文字列に埋め込まれた部分文字列を取り出すだけであることが[こちら](https://qiita.com/TakahikoKawasaki/items/8567c80528da43c7e844#%E9%81%B7%E7%A7%BB%E5%85%88%E3%82%92%E4%BA%8B%E5%89%8D%E3%81%AB%E7%99%BB%E9%8C%B2%E3%81%97%E3%81%A6%E3%81%8A%E3%81%8F)を読むとわかります)。
当たり前のことですが上記を実現するためには、**リダイレクト時に実行される処理として、渡された文字列から部分文字列を抽出する処理を仕込んでおく**必要があります。

渡された文字列から部分文字列を抽出する処理はプログラムを書いたことのある方なら、本質的に難しいものではないはずです。よって私のようなiOSアプリ初心者にとってのポイントは「**じゃあどうやってそれをリダイレクト時に実行されるようにするのか**」ということに尽きます。

そしてiOS13以降では、**scenedelegateの中に下記の関数が含まれる場合、アプリはリダイレクトされた際それを最初に呼び出します**。

``` swift
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {

*/ ここに処理を書き込む /*

}
```

一方SpotifyLoginでは、2021年3月20日時点でSpotifyLoginのREADME.mdの中でappdelegateに下記のコードを追加してねと言っています。

``` swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    SpotifyLogin.shared.configure(clientID: <#T##String#>, clientSecret: <#T##String#>, redirectURL: <#T##URL#>)
    return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    let handled = SpotifyLogin.shared.applicationOpenURL(url) { (error) in }
    return handled
}
```

ここまで読んでくださった方であれば分かるかと思いますが、**要するにこの2つめのかたまりをscenedelegateに移しかえれば動作するようになります**。

# なぜリダイレクト時の呼び出しがscenedelegateで行われるようになったのか
scene-based lifecycleの基本思想はアプリをマルチウィンドウ化することです。先ほどのApple公式ドキュメントの引用からもわかるように、個別のウィンドウを制御するのはsceneです。
これに伴い、scene-based lifecycleの中では**appdelegateはアプリの起動時および終了時にのみ呼び出されるようになり**、**アプリが操作されている間に生じる状態遷移ではscenedelegateが都度呼び出されるようになりました**([こちら](https://qiita.com/KenNagami/items/cbbe98b736fbdb24fef8)で新旧のライフサイクルを比較するとわかりやすいです)。それはそうでしょう、アプリが操作されているということはすなわちウィンドウを操作するということであり、そしてウィンドウを制御するのはsceneだからです。
私はこれらの事情から「**リダイレクトもscenedelegateに任されるようになった**」と自分なりに結論付けます。

以上、初めてちゃんと調べて技術的なことを書いた記事でしたが、やはり自分で説明するのは本当に勉強になるなと感じました！今後もたくさん書いていこうと思います😊