Skip to main content

Command Palette

Search for a command to run...

SwiftPM中使用CoreData在SwiftUI进行Preview的小纸条

Published
2 min read
SwiftPM中使用CoreData在SwiftUI进行Preview的小纸条

使用SwiftPM重构英语小助手一段时间了,遇到了一个问题,就是在SwiftUI里使用一个在SwiftPM中定义的CoreData时,如果进行Preview就会直接挂掉。这是它的提示信息:

image.png

如果点Preview中的Diagnostics按钮会得到这样的信息:

image.png

这是我在CoreData中对于加载数据的定义代码:

class PersistentContainer: NSPersistentContainer {
    init(name: String, bundle: Bundle = .module,
        inMemory: Bool = false) {
        guard let mom = NSManagedObjectModel.mergedModel(from: [bundle]) else {
          fatalError("Failed to create mom")
        }
        super.init(name: name, managedObjectModel: mom)
      }
}

如果不使用SwiftUI进行Preview它的工作完全正常。所以我从来没有认真的想过这个.module是如何工作的。在苹果的官方文档里,我也没有找到这个.module的定义,哪么它会是在哪里定义的呢?于是我发现了SwiftPM会在你需要访问资源时自动生成一个resource_bundle_accessor.swift文件,内容如下:

import class Foundation.Bundle

private class BundleFinder {}

extension Foundation.Bundle {
    /// Returns the resource bundle associated with the current Swift module.
    static var module: Bundle = {
        let bundleName = "CommomLibrary_CommomLibrary"

        let candidates = [
            // Bundle should be present here when the package is linked into an App.
            Bundle.main.resourceURL,

            // Bundle should be present here when the package is linked into a framework.
            Bundle(for: BundleFinder.self).resourceURL,

            // For command-line tools.
            Bundle.main.bundleURL,
        ]

        for candidate in candidates {
            let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
            if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
                return bundle
            }
        }
        fatalError("unable to find bundle named CommomLibrary_CommomLibrary")
    }()
}

所以这个.module其实就是通过一个空的class以及你的bundleName来找到可以加载的Bundle的位置。而SwiftUI在进行Preview时模拟器这些路径有些变化,而生成的代码在几千工程师工作的时候根本没有想到你变了而我没想过要将CoreData放在SwiftPM里还要去被SwiftUI进行Preview的变化。找了很久,发现在stackoverflow上的这个线索参考这个,我修改了一下代码,形成了这个extension Foundation.Bundle:

import Foundation

import class Foundation.Bundle

private class CurrentBundleFinder {}

extension Foundation.Bundle {
    /// Returns the resource bundle associated with the current Swift module.
    static var swiftUIPreviewsCompatibleModule: Bundle = {
#if DEBUG
        let bundleNameIOS = "LocalPackages_CommomLibrary"
        let bundleNameMacOs = "CommomLibrary_CommomLibrary"

        let candidates = [
            // Bundle should be present here when the package is linked into an App.
            Bundle.main.resourceURL,

            // Bundle should be present here when the package is linked into a framework.
            Bundle(for: CurrentBundleFinder.self).resourceURL,

            // For command-line tools.
            Bundle.main.bundleURL,

            // Bundle should be present here when running previews from a different package (this is the path to "…/Debug-iphonesimulator/").
            Bundle(for: CurrentBundleFinder.self).resourceURL?.deletingLastPathComponent().deletingLastPathComponent()
                .deletingLastPathComponent(),
            Bundle(for: CurrentBundleFinder.self).resourceURL?.deletingLastPathComponent().deletingLastPathComponent(),
        ]

        for candidate in candidates {
            let bundlePathiOS = candidate?.appendingPathComponent(bundleNameIOS + ".bundle")
            let bundlePath = candidate?.appendingPathComponent(bundleNameMacOs + ".bundle")

            if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
                return bundle
            }else if let bundle = bundlePathiOS.flatMap(Bundle.init(url:)){
                return bundle
            }
        }
        fatalError("unable to find bundle named LocalPackages_CommomLibrary")
#else
        return Bundle.module
#endif
    }()
}

考虑到在生产环境还会使用SwiftPM生成的Bundle.module,而在Debug时使用我们的代码。最后,把加载NSManagedObjectModel的代码做一下修改:

class PersistentContainer: NSPersistentContainer {
    init(name: String, bundle: Bundle = .swiftUIPreviewsCompatibleModule,
        inMemory: Bool = false) {
        guard let mom = NSManagedObjectModel.mergedModel(from: [bundle]) else {
          fatalError("Failed to create mom")
        }
        super.init(name: name, managedObjectModel: mom)
      }
}

Preview运行正常了。

More from this blog

Https 本地服务器小纸条

最近想要尝试一下Telegram mini app,在调试时需要使用https的服务。试来试去发现在Mac下完成一个简单的HTTPS服务器还是需要点奇奇怪怪的工具的。但是整体来讲非常简单。 准备证书 主要会使用mkcert来创建证书。首先安装mkcert。 brew install mkcert brew install nss # if use Firefox 将mkcert加入到本地root CA。 mkcert -install 生成证书 本地调试可以使用localhost或127.0...

Feb 21, 20241 min read
Https 本地服务器小纸条

macOS中使用Docker发布一个python项目的小纸条

最近写了一个Telegram Bot,它可以使用语音和文字与GPT进行交互,成为了我日常重度使用的工具。从练习英语的听说读,到日常的搜索使用上都让我有了不少收获。终于,日常跑在我笔记本上的日子就要过去了,我需要它能日常跑在我的服务器上,所以准备使用Docker整个image,使得我日常的更新和服务器的迁移更为简单些。所以写下这个小纸条,方便以后自己回来查看。 安装Docker 使用Homebrew安装简单方便: brew install --cask docker Homebrew会视你的机器...

Mar 10, 20233 min read
macOS中使用Docker发布一个python项目的小纸条

老房东的纸条箱

39 posts