[SwiftPM][iOS] ‘shared’ is unavailable in application extensions for iOS の対処法

SwiftPackageManagerEyeCatch

SwiftPackage としてライブラリを切り出して、'shared' is unavailable in application extensions for iOS: Use view controller based solutions where appropriate instead. というエラーが発生したケースの対応方法を説明します。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterery beta 4
  • Xcode 13 beta3
  • iOS 15 beta

SwiftPackage に切り出すとエラーになる

SwiftPackage に切り出す前の状態

UIApplication.shared から、デバイス情報を取得するアプリを作ったとします。

以下のようなコードで、スクリーンロックが設定されているかがチェックできます。


//
//  ApplicationSharedExampleApp.swift
//
//  Created by : Tomoaki Yagishita on 2021/08/08
//  © 2021  SmallDeskSoftware
//

import SwiftUI

@main
struct ApplicationSharedExampleApp: App {
    var body: some Scene {
        WindowGroup {
            VStack {
                Text("ScreenLock State: \(UIAppSharedWrapper.isIdleTimerDisabled ? "true" : "false" )")
            }
        }
    }
}

public class UIAppSharedWrapper {
    static public var isIdleTimerDisabled: Bool {
        return UIApplication.shared.isIdleTimerDisabled
    }
}

問題なく動作します。

スクリーンロックが設定されているかは、UIApplication.shared.isIdleTimerDisabled をチェックすることが確認しています。

SwiftPackage に切り出そうとすると・・・

ここでは、UIAppSharedWrapper という Package 名で作りました。

アプリケーション側:


//
//  ApplicationSharedExampleApp.swift
//
//  Created by : Tomoaki Yagishita on 2021/08/08
//  © 2021  SmallDeskSoftware
//

import SwiftUI
import UIAppSharedWrapper

@main
struct ApplicationSharedExampleApp: App {
    var body: some Scene {
        WindowGroup {
            VStack {
                Text("ScreenLock State: \(UIAppSharedWrapper.isIdleTimerDisabled ? "true" : "false" )")
            }
        }
    }
}

SwiftPackage側:


//
//  UIAppSharedWrapper.swift
//
//  Created by : Tomoaki Yagishita on 2021/08/08
//  © 2021  SmallDeskSoftware
//

import Foundation
import UIKit

public class UIAppSharedWrapper {
    static public var isIdleTimerDisabled: Bool {
        return UIApplication.shared.isIdleTimerDisabled
    }
}

単にコードを外部パッケージにコピーしただけなので、問題なく動くと思いきや、エラーが発生します。


/<path to source code>/UIAppSharedWrapper.swift:13:30: 'shared' is unavailable in application extensions for iOS: Use view controller based solutions where appropriate instead.

エラーの意味

エラーの意味は、「Application Extension には、UIApplication.shared は、使えないので、View Controller を使った解決策を使用しなさい」 というメッセージです。

最初、意味わかりませんでした。

普通のアプリケーションを開発していて、よく使うクラスを Swift Package に分離しただけなので、Application Extension がどう関係するの??? となりますし、

SwiftUI で作っているので、view controller ベースの解決策と言われて、さらに ??? となります。

調べて回ったところ、以下のような理由でエラーになっています。

  • Application extension は、App とは別プロセスで実行されるので、UIApplication にアクセスできない
  • Swift Package は、ターゲットが App なのか Application Extension なのか知らない
  • 知らないので、どちらでも使われることを想定してコンパイルする・・・ shared が使われているのでエラー!

作った側としては、「App につかうだけなので、不要なチェックでは?」とも思いますが・・・・

なぜこの段階でチェックされるのか?

該当 Swift Package を 意図せず Application Extension に使用してしまうかもしれません。

そのときには、不定な動作になりそうです。同一プロセス内にいない UIApplication にアクセスすることで、例外になりそうな気がします。
いずれにしても、動作させるまではチェックができない状態になってしまいます。

その点から考えると、コンパイル時に、チェックするのが妥当にも思えます。

App 向けのみで Application Extension を対象としない設定

対応方法ですが、@available という Swift 言語の持つ機能を使用して、該当部分が、App 向けのみであることを宣言することで、エラーを解消することができます。


//
//  UIAppSharedWrapper.swift
//
//  Created by : Tomoaki Yagishita on 2021/08/08
//  © 2021  SmallDeskSoftware
//

import Foundation
import UIKit

public class UIAppSharedWrapper {
    @available(iOSApplicationExtension, unavailable)     // <- 🆕
    static public var isIdleTimerDisabled: Bool {
        return UIApplication.shared.isIdleTimerDisabled
    }
}

こうすることで、コンパイル・リンク共に問題なく終了し、アプリも期待通りに動作します。

Swift の available

Swift の @available は、続く括弧のなかに、2つ以上のカンマで区切った引数を指定して使用します。

引数は、以下の要素のいずれかを最初の引数として持つ必要があります。

  • iOS
  • iOSApplicationExtension
  • macOS
  • macOSApplicationExtension
  • macCatalyst
  • macCatalystApplicationExtension
  • watchOS
  • watchOSApplicationExtension
  • tvOS
  • tvOSApplicationExtension
  • swift
  • * (全てのプラットフォーム相当、ただし、Swift のバージョン指定には使用できない)

2つ目以降の引数は、以下の要素を順不同で指定でき、この定義の有効な範囲を指定します。

unavailable
指定プラットフォームで使用できないということを定義します
introduced: <version number>
version number 以降で使用できることを定義します。
deprecated: <version number>
version number 以降で deprecated (非推奨)指定であることを定義します。version number を省略すると、現時点で deprecated であることを意味します。version number を省略するときは、コロン(:)も省略します
obsoleted: <version number>
version number 以降で、obsoleted (使用不可)指定であることを定義します。
message: <message>
deprecated や obsoleted に該当する場合に、指定されたメッセージを表示します。
renamed: <new name>
deprecated に該当する場合に、新しく置き換えるべき名前をメッセージとして表示します。

Note: version number は、1〜3個のピリオドで区切られた正の整数です

まとめ:'shared' is unavailable in application extension の解決方法 と @available

'shared' is unavailable in application extension の解決方法 と @available
  • UIApplication には、Application からのみアクセス可能で、Application Extension からはできない
  • Swift Package は、Application Extensions に組み込まれることを想定してチェックする
  • Swift 言語の @available を使用して、Application Extension からは使用できないことを明示するとエラーは解消できる

上記は、iOS を例に書いていますが、macOS であれば、NSApplication へのアクセスを同様に処理する必要があります。

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です