はじめに
Xcode 16.1 で Swift 6 環境を使用していると、UNUserNotificationCenter.current().requestAuthorization
を使った通知許可リクエストでクラッシュが発生する場合があります。しかもエラーログに原因が出力されずAIに聞いても解決できませんでした。最新の環境限定のエラーはAIはそれらしい返答は返してくるので余計に時間かかりますよね。
この記事では、この問題の原因と、その解決方法を解説します。
問題の概要
以下のコードは、通知許可をリクエストする一般的な方法ですが、このコードを Swift 6 環境で実行すると、クラッシュが発生します。
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
func configureNotification() {
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { granted, error in
if let error = error {
print("通知許可エラー: \(error.localizedDescription)")
} else if granted {
print("通知が許可されました")
} else {
print("通知が拒否されました")
}
}
}
クラッシュの原因
Appleの DTS Engineer によると、このクラッシュの原因は、Swift 6 のコンパイラがクロージャの実行スレッドを誤ってメインアクターと認識してしまうことにあります。
この問題により、メインアクター上で実行されていないとクラッシュが発生します。
解決方法: 非同期APIの利用
この問題を解決するには、UNUserNotificationCenter
の 非同期関数版 を使用します。
private func configureNotification() {
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
Task {
do {
// 非同期で通知許可をリクエスト
let granted = try await UNUserNotificationCenter.current().requestAuthorization(options: authOptions)
if granted {
print("通知が許可されました")
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
} else {
print("通知が拒否されました")
}
} catch {
print("通知許可エラー: \(error.localizedDescription)")
}
}
}
解説
ポイント1: 非同期処理でクラッシュを回避
-
Task
を利用して非同期コンテキストを作成し、await
を使用して非同期関数を安全に呼び出します。 - これにより、Swift 6 のメインアクター関連のバグを回避できます。
ポイント2: メインスレッドでの操作
通知が許可された後の UIApplication.shared.registerForRemoteNotifications()
はメインスレッドで実行する必要があるため、DispatchQueue.main.async
を使用しています。