5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SwiftPMのpreviewでBundle.moduleにアクセスしてクラッシュした話

Last updated at Posted at 2021-11-13

概要

  • Swift Package Manager の Preview で Bundle.module にアクセスしてクラッシュした状況と、手元の環境で解決できた方法の話です

環境

  • Xcode 13.1
  • macOS 11.3.1

何があったのか?

  • SwiftPM を使って AppMain と Assets のターゲットを作成しました
  • Assets の Resources の中に、Assets.xcassets を配置し、aka の Color Set を追加しました
  • SwiftGenを利用して、Asset.aka.color で追加した aka にアクセスできるようにしました
  • AppMain 内の preview で、Asset.aka.color を使って View を表示させようとしました

スクリーンショット 2021-11-13 22.05.42.png

  • 上の画像が期待する結果ですが、クラッシュしたと言われました↓

スクリーンショット 2021-11-13 22.23.15.png

  • エラーはこちらです
  • Libraries_Assets.bundle にアクセスできなくなっているようです

Assets/resource_bundle_accessor.swift:27: Fatal error: unable to find bundle named Libraries_Assets

  • SwiftGen の生成したコードを確認すると、Bundle.module を使って bundle にアクセスしようとしているようです↓(一部抜粋)
public extension ColorAsset.Color {
  @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
  convenience init?(asset: ColorAsset) {
    let bundle = BundleToken.bundle
    #if os(iOS) || os(tvOS)
    self.init(named: asset.name, in: bundle, compatibleWith: nil)
    #elseif os(macOS)
    self.init(named: NSColor.Name(asset.name), bundle: bundle)
    #elseif os(watchOS)
    self.init(named: asset.name)
    #endif
  }
}

private final class BundleToken {
  static let bundle: Bundle = {
    #if SWIFT_PACKAGE
    return Bundle.module
    #else
    return Bundle(for: BundleToken.self)
    #endif
  }()
}

  • Bundle.module のパスを確認すると確かに、Libraries_Assets.bundle は存在しないようです
  • 正しいパスを指定して、Bundle をロードさせてやればよさそうです

対処法

  • Bundle(for: BundleToken.self) を確認した所、2階層上に、Libraries_Assets.bundle を確認しました
  • そこで、Preview の場合は、Bundle.module ではなく、パスを指定して Bundle をロードさせることにしました
  • NSStringFromClass を使って、モジュール名を取得しました
  • Bundle が入っているはずのパスをモジュール名で検索して、Bundle の取得を試みています
internal final class MyBundleToken {
    private static let previewBundlePath: URL = {
        let typeName = type(of: MyBundleToken())
        // "\(ModuleName).MyBundleToken"
        let nsClassString = NSStringFromClass(typeName)
        guard let bundleName = nsClassString.components(separatedBy: ".").first else {
            fatalError("failed get bundle name: \(nsClassString)")
        }

        let searchUrl: URL = Bundle(for: MyBundleToken.self)
            .bundleURL
            .deletingLastPathComponent()
            .deletingLastPathComponent()
        let list: [URL]
        do {
            list = try FileManager.default.contentsOfDirectory(at: searchUrl, includingPropertiesForKeys: nil)
        } catch let error {
            fatalError("failed get contents: \(searchUrl)")
        }
        let firstMatch: URL? = list.first { (url: URL) in
            let name = url.lastPathComponent
            return name.contains("_\(bundleName).bundle")
        }
        guard let res = firstMatch else {
            let jointed = list.map { $0.relativeString }
                .joined(separator: "\n")
            fatalError("no url contains: _\(bundleName).bundle\n\(jointed)")
        }
        return res
    }()
    static let bundle: Bundle = {
        #if SWIFT_PACKAGE
        if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
            return Bundle(url: previewBundlePath)!
        } else {
            return Bundle.module
        }
        #else
        return Bundle(for: MyBundleToken.self)
        #endif
    }()
}

  • 合わせて swiftgen.yml も更新します
  • bundle を先ほど作成した MyBundleToken.bundle に差し替えます
  • パラメータの詳細はこちら
xcassets:
  inputs:
    - Modules/Sources/Assets/Resources/Assets.xcassets
  outputs:
    - templateName: swift5
      output: Modules/Sources/Assets/Generated.swift
      params:
        publicAccess: true
        bundle: MyBundleToken.bundle
  • 再度、SwiftGen でコードを生成すると、確かに MyBundleToken.bundle になっている事を確認できます
public extension ColorAsset.Color {
  @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
  convenience init?(asset: ColorAsset) {
    let bundle = MyBundleToken.bundle
    #if os(iOS) || os(tvOS)
    self.init(named: asset.name, in: bundle, compatibleWith: nil)
    #elseif os(macOS)
    self.init(named: NSColor.Name(asset.name), bundle: bundle)
    #elseif os(watchOS)
    self.init(named: asset.name)
    #endif
  }
}
  • 手元の環境ではこれで問題が解決できました

まとめ

  • Swift Package Manager の Preview で Bundle.module にアクセスしてクラッシュした状況と、手元の環境で解決できた方法の話でした
  • SwiftGen はパラメータが細かく指定できるので、簡単に問題を解決することができました、とても便利です
5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?