- 投稿日:2020-06-19T20:12:45+09:00
ウォークスルー後の通知許可とフォアグラウンド 通知を両立しようとしたら,うまくいかなかった件について
TL;DR
- よくあるチュートリアルの後に通知許可の処理をしたらアプリ内通知が動かなかった
- これをするときは
appDelegateにフォアグラウンド処理を書いてもダメ- 通知許可の
viewControllerにフォアグラウンド処理を書こう何があったのか(前提条件)
- appDelegateの
application(_ application: UIApplication, didFinishLaunchingWithOptions)に通知許可の処理を書かない -> アプリ立ち上げでは通知許可は出さない- ウォークスルー終了後のメイン画面で通知許可を記述.
- フォアグラウンド通知のための
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification:〜以下略)はappDelegateに書いていた.実行環境
機材 バージョン Xcode 11.5 iOS 13.5.1 Swift 5.2.4
解決方法
上に書いてあるとおり,appDelegateには通知系の処理を書かず,通知許可を取りたい画面のコードに,
MainViewController.swiftimport UIKit import UserNotifications class MainViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() //通知許可 let center = UNUserNotificationCenter.current() center.requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] (granted, _) in if granted { let center = UNUserNotificationCenter.current() center.delegate = self } } //~~~~~~ (中略) //~~~~~~ } extension MainViewController: UNUserNotificationCenterDelegate { //フォアグラウンド通知処理 func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { //通知とサウンドのみ completionHandler([.alert, .sound]) } }と記述する.
最後に
なんでこんな仕様なんだろう・・・
原理や「お前の勘違いや」,「もっとこうすべき」などのご指摘がございましたらコメントにてよろしくお願いいたします.
- 投稿日:2020-06-19T16:39:36+09:00
SwiftでiOS開発している時に、Timerを利用したコードを、モダンでいい感じにUnitTestする方法
0. 初めに
画面タップイベントや、APIアクセスといった処理は、(ユーザーが連打するなどの場合を除けば)多くの場合一度きりの処理であることが多く、これらのロジックをUnitTestでテストするのは、そこまで大変ではないケースが多くを占めると思います。
しかしながら、一度きりではない処理を実装しないといけない(画面上にカウントダウンを表示させる場合など)ケースがあるのもまた事実かと思います。
いざTimerを使ったコードを書いてみると、実際に動かす場合はまぁよいにしろ、UnitTestを書くところで、
「どうやってテストしたらいいんだろう?
まさか待つわけにはいかないしな...」
といった感じで、思った以上にTimer部分のテストを書くのが難しく、Timer部分のUnitTestをスキップしてしまうことがあったり・・・するのではないでしょうか???
この記事では、そんなTimerのUnitTestが難しいなと思っている皆さんに向けて、僕はこんなふうにやったよという感じで、僕なりのテスト方法を提案しようと思います。
大まかに分けて下記のような流れで進めていきます。
ApplicationTimerProviderProtocolを作るTimerそのものではなく、ApplicationTimerProviderProtocolに依存するようにする(差し替え可能にする)- UnitTest用の
FakeApplcationiTimerProviderを作る- 実際のテストを書いてみる(ここではQuick/Nimbleを利用したコードを載せます)
1.
ApplicationTimerProviderProtocolを作るUnitTestが書きやすいプログラムとは外部から差し替え可能なクラスに依存しているクラスである、と僕は思います。
Timerを使おう〜!と思って下記のようにTimerを使ってしまっても実際の動きとしては良いのですが、Testコードを書こうとすると、TimeInterval分待つしか無くなってしまい、綺麗なUnitTestが書けないことになります。
import Foundation final class TimerClass { var timer: Timer? func check() { timer = Timer.scheduledTimer( withTimeInterval: 1.0, repeats: false, block: { timer in // do something... }) } }「実際にTimeInterval分待たなくてはならない」問題は、上記のプログラムが
Timer自体に依存しているため発生しています。そのため、下記のような
ApplicationTimerProviderProtocolを作成し、Timerへの依存を外しましょう。protocol ApplicationTimerProviderProtocol { func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer } struct ApplicationTimerProvider { } extension ApplicationTimerProvider: ApplicationTimerProviderProtocol { func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer { return Timer.scheduledTimer(withTimeInterval: interval, repeats: repeats, block: block) } }2.
ApplicationTimerProviderProtocolに依存させる作成した、
ApplicationTimerProviderProtocolを利用することで、UnitTestの時に差し替えを行うことが可能になります。UnitTestの時に差し替えを行うため、作成した
ApplicationTimerProviderを利用するようにTimerClassのコードを下記のように修正します、final class TimerClass { let timerProvider: ApplicationTimerProviderProtocol var timer: Timer? init(timerProvider: ApplicationTimerProviderProtocol) { self.timerProvider = timerProvider } func check() { timer = Timer.scheduledTimer( withTimeInterval: 1.0, repeats: false, block: { timer in // do something... }) } }3. UnitTest用の
FakeApplcationiTimerProviderを作る下記のようにUnitTestの際に利用する
FakeApplicationTimerProviderを作成します。class FakeApplcationiTimerProvider: ApplcationiTimerProviderProtocol { final class DummyTimer: Timer { override func invalidate() { } } var blocks = [(Timer) -> Void]() var scheduledTimer_callCount = 0 func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer { scheduledTimer_callCount += 1 blocks.append(block) return DummyTimer() } var fire_callCount = 0 func fire() { fire_callCount += 1 guard let first = blocks.first else { return } first(DummyTimer()) _ = blocks.removeFirst() } }4. 実際のテストを書いてみる
ここまでくれば下記のように、UnitTestを書くことができるようになっていると思います。
class TimerClassSpec: QuickSpec { override func spec() { var fakeTimerProvider: FakeApplcationiTimerProvider! var subject: TimerClass! beforeEach { fakeTimerProvider = FakeOrderAppliTimerProvider() subject = TimerClass(timerProvider: fakeTimerProvider) } it("check") { subject.check() fakeTimerProvider.fire() // ここまでくればクロージャーが発火しているのでテストをすることができる } } }終わりに
Timerを利用したことは何度かあったのですが、上手なUnitTestの書き方がわからず、悩んでおりました。
僕なりのテスト方法ではありますが皆様のお力になれれば幸いです。
- 投稿日:2020-06-19T07:59:51+09:00
WKWebView オブジェクトの破棄後にデバッグ領域に "Could not find specified service" というメッセージが出力される
概要
WKWebViewオブジェクトを破棄してから30秒ほど経過したら、以下の2つのエラーメッセージが Xcode のデバッグ領域に出力されます。Could not signal service com.apple.WebKit.WebContent: 113: Could not find specified serviceCould not signal service com.apple.WebKit.Networking: 113: Could not find specified serviceこのメッセージが出力される原因について調査したところ、いろいろな情報を見つけたのですが、Apple Developer Forums のページで Apple Staff が返信しているものが最も信頼できる情報であると判断しました。そのページによると、このメッセージについては特に気にする必要はないとのことです。
確認環境
ビルド環境
- macOS 10.15.5
- Xcode 11.5 (11E608c)
- Deployment Target: iOS 13.0
実行環境
- iPhone 11 Pro Max (iOS 13.5.1)
Apple Developer Forums での情報
下記リンク先の Apple Developer Forums のページで Apple Staff が返信しています。
com.apple.WebKit errors after deallocating WKWebView |Apple Developer Forums
返信内容の一部を抜粋します。
These are two XPC Services used by the web view to implement core functionality (page rendering and networking, respectively). I suspect what’s going on here is that the web view has shut down and, 30 seconds later, its XPC Services are shut down. That does not seem particularly worrying in and of itself.この返信を読む限りでは、
com.apple.WebKit.WebContentとcom.apple.WebKit.Networkingについての113: Could not find specified serviceは特に気にする必要はなさそうです。その他の情報
他の情報源の中には、 次のリンク先の記事のように、Xcode プロジェクトの設定や
WKWebViewの扱いに問題がある可能性に言及しているものもありました。このリンク先のページでは、
com.apple.WebKit.WebContentとcom.apple.WebKit.Networkingについての113: Could not find specified serviceの原因として、以下2つを挙げています。
addSubviewをする前にloadHTMLStringを呼び出していたInfo.plistでNSAppTransportSecurityを設定せずにhttpのURLに接続していた今回はこれらの問題がない状態でもエラーメッセージが出力されていたので、最終的には上の Apple Developer Forums のページが最も信頼できる情報であると判断しました。けれども、原因として挙げられているこれら2つについても、これはこれで気をつけるべき点かもしれませんので、本記事で紹介しておきます。