自分専用Web記事保存アプリのサムネイル画像がへんだ!に書いたWeb記事保存アプリですが、もう1つバグがありました。これも開発当初は無かったはずですが、iOSのバージョンアップで発生するようになったバグです。
バグの症状
このWeb記事保存アプリは新たに自分専用Web記事保存アプリの開発を始めたにも書きましたが、以下の画像のように
- Safariの共有(アクション)ボタンを押すとApp Extentionとして作られたコードが動き、現在Safariが表示している画面のURLをCustom URL Schemeを使いWeb記事保存アプリに渡す
- Web記事保存アプリが起動されて、渡されたURLを保存するダイアログが表示される
さてバグですが、2.でWeb記事保存アプリが起動されるのですが、保存するダイアログが表示されない事があるのです。ただし、もう一度1.から繰り返すと保存するダイアログが表示され保存できるので、我慢して使っていました。
よく調べてみると、既にWeb記事保存アプリが起動済みでバックグラウンドで動作している場合はOKですが、アプリが起動されてない状態で1.が実行された場合は保存ダイアログが表示されないことが判りました。
コードの解説とバグの原因
Custom URL Schemeでアプリケーションが起動されると、通常のアプリケーション起動とは異なりAppDelegateクラスのapplication(_:open:options:)メソッドが呼び出されます。また、このメソッドには引数としてCustom URLが渡ります、Web記事保存アプリではこのメソッド内で保存ダイアログ起動用のNotificationを送信します。
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
・・・
func application(_ app: UIApplication, open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
let urlPath = url.path
NotificationCenter.default.post(name: .riseArchive, object:
String(urlPath.dropFirst()))
return true
}
・・・
}
Notificationを使うには、Notification受け取り処理を行うオブザーバーを予め定義してをく必要があります。Web記事保存アプリでは保存記事一覧表示を行うMasterViewControllerクラス、生成時に呼び出されるviewDidLoadメソッドの中でオブザーバーを設定しています。
この中でNotification受け取ると、保存ダイアログ画面のArchiveViewControllerをダイアログとして作成・表示しています。
class MasterViewController: UITableViewController {
・・・
override func viewDidLoad() {
super.viewDidLoad()
・・・
NotificationCenter.default.addObserver(forName: .riseArchive, object: nil,
queue: OperationQueue.main, using: { notification in
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let archiveViewController = storyboard.instantiateViewController(
withIdentifier: "ArchiveViewControllerID") as! ArchiveViewController
archiveViewController.urlStrings = [notification.object] as? [String]
self.present(archiveViewController, animated: true, completion: nil)
})
・・・
}
・・・
}
上手く動作する予めWeb記事保存アプリが起動されている場合は、アプリ起動時にMasterViewControllerクラスのviewDidLoadメソッドが実行されNotificationのオブザーバーは設定されます。その後バックグラウンドで動作しているのでCustom URL Schemeでアプリケーションが起動されapplication(_:open:options:)の中のNotificationが送信され、保存ダイアログが表示されます。
上手く行かない予めWeb記事保存アプリが起動されていない場合は、Notificationオブザーバーの設定が完了する前に、application(_:open:options:)の中のNotificationが送信されてるのでは?と思い、ログを出して試してみました。
23:12:41: ++++ application(_:open:options:)
23:12:41: ++++ NotificationCenter post
23:12:41: ++++ viewDidLoad
やはり、予想通りでした! application(_:open:options:)内のNotification送信が、Notificationオブザーバーの設定前に動いています。
対応
さて、どのように対応するかですが、ちゃんとした形で対応するのは面倒そうです。ただしこのアプリは自分専用なので安易な方法で対応しても良いのでは!と思い、非常に安易な方法で対応しました。
application(_:open:options:)の中でNotificationを送信するタイミングを1秒ほど遅らせる事にしました。処理を遅らせるのはDispatchQueue.main.asyncAfter
メソッドで簡単にできました。
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
・・・
func application(_ app: UIApplication, open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let urlPath = url.path
NotificationCenter.default.post(name: .riseArchive, object:
String(urlPath.dropFirst()))
}
return true
}
・・・
}
ログを見るとNotificationオブザーバーの設定後にNotificationが送信されています。
23:14:02: ++++ application(_:open:options:)
23:14:02: ++++ viewDidLoad
23:14:03: ++++ NotificationCenter post
まとめ
今回のバグのように、作成時からバグが内在していても偶然動いていたコードが、iOSのバージョンアップ等でバグが顕在化する事はたまにあると思います。
ただしバグ対応には、実際にリリースするアプリでは使えないような安易な方法で対応できるのも自分専用アプリの嬉しい事かもしれません😊