Compatibility.swiftpm

Compatibility 是一组旨在提高兼容性的代码,以便在旧平台上使用现代 API,并为某些操作提供简化的语法。还包括 Debug 模块和 Test harness 模块。Debug 是一组旨在轻松编写和启用/禁用代码中的调试语句的代码。这允许轻松地仅中断某些级别的语句,并在输出中具有一致的标志,以便于阅读调试输出。还包括 Int 上的兼容性函数。

主要目标是易于多人维护,采用可在所有平台上使用的一致 API,以及可以使用 iPad 和 macOS 上的 Swift Playgrounds 进行维护。API 通常即使在不支持所有功能的平台上也存在,因此不必在外部代码中执行可用性检查,并且在不相关的情况下,代码可以简单地返回可选值。

这是积极维护的项目,因此如果有功能请求或更改,我们将力争在一周内解决。

功能

要求

已知问题

请参阅 CHANGELOG.md 以了解已知问题和路线图 请注意,DataStore 测试仅在包含 entitlements 和 privacyinfo 时才有效(因此,它们在 Previews 和 Swift Playgrounds 中将不起作用)。

安装

通过将其作为包依赖项添加到您的代码中进行安装。这可以在 Xcode 或 Swift Playgrounds 中完成!

Swift Package Manager

Swift 5+

您可以通过添加包在 Swift Playground 中尝试这些示例:https://github.com/kudit/Compatibility

如果存储库是私有的,请使用以下链接导入:https://<your-PAT-string>@github.com/kudit/Compatibility.git

或者您可以手动在 Package.swift 文件中输入以下内容

dependencies: [
    .package(url: "https://github.com/kudit/Compatibility.git", from: "1.0.0"),
]

用法

首先确保导入框架

import Compatibility

以下是一些用法示例。

获取导入的 Compatibility 版本。

let version = Compatibility.version

简单的调试语句(可以用于替换调试 print 语句),使用默认调试级别(可以更改)。

debug("This is a test string \(true ? "with" : "without") interpolation")

指定级别的调试语句

debug("Fatal error!  This will be output to console typically even in production code.", level: .ERROR)

仅调试代码

如果您有只想在调试中显示的功能,可以添加以下内容

if Application.isDebug {
    // execute test/debug-only code.  This will only run in DEBUG configurations and will be removed during RELEASE compilations.
}

在各种线程上运行代码并延迟代码执行

background {
    // run long-running code on background thread
    sleep(4) // wait for 4 seconds before continuing
    delay(0.4) { // run this code after 0.4 seconds (similar to calling await sleep() and then executing code)
        main {
            // run this code back on the main thread
        }
    }
}

生成警告的技巧,可以轻松禁用,而无需使用 #warning()

默认为 true,因此应用程序需要在 init/启动期间调用函数

if false { // this will generate a warning if left as false
    debug("This should be run in init.")
}

应用程序版本跟踪和 firstRun 检查

这应在 App init 中运行。

init() {
    Application.track() // ensures Application.main.isFirstRun and Application.main.versions variables are properly set.
    if Application.main.isFirstRun {
        debug("First Run!")
    }
    debug("All versions run: \(Application.main.versionsRun)")
}

如果可用,将数据存储到 iCloud 键值存储。

这非常有用,因此我们不必担心用户是否启用了 iCloud 或他们注销时会发生什么。但是,这将使用 iCloud 或 UserDefaults。切换时,它不会尝试合并或迁移两者之间的数据。请注意,即使您不使用 iCloud,您也需要包含 entitlements 文件,否则这将尝试使用 iCloud 存储,但如果用户登录到 iCloud,则无法保存。应用程序应自动执行正确的操作,并将使用每个键的最后更新值来解决冲突。如果您需要更精细的控制,请缓存该值并监视更改以更新缓存的值。如果您使用此功能,则需要添加 entitlement,因为这使用了 iCloud 键值存储。示例 entitlement 文件可以在 Compatibility.swiftpm/Development/Resources/Entitlements.entitlements 中找到。如果您使用与开发 Xcode 项目类似的结构,您将需要将 Code Signing Entitlements 构建设置设置为 Resources/Entitlements.entitlements。如果您有 watchOS 应用程序,您可能需要手动设置密钥,而不是从标识符中拉取,以便 watchOS 应用程序和 iOS 应用程序使用相同的存储:例如:$(TeamIdentifierPrefix)com.kudit.CompatibilityTest(请注意,com 之前没有句点)如果您希望它自动设置并且没有 watchOS 应用程序,请将 iCloud Key-Value Store entitlement 条目设置为:$(TeamIdentifierPrefix)$(CFBundleIdentifier)

此功能需要 iOS 13、tvOS 13 和 watchOS 9 才能使用云,因为这是 NSUbiquitousKeyValueStore 的最低要求。如果代码使用旧版本,您将需要添加 PrivacyInfo 文件,因为 UserDefaults 可用于指纹识别。示例文件可以在包中的 Compatibility.swiftpm/Development/Resources/PrivacyInfo.xcprivacy 中找到

// either specify a default value or make it an optional
@CloudStorage("keyString") var myKey: Bool = false
@CloudStorage("key2String") var myOtherKey: Double?

在添加到先前使用 @AppStorage 或 UserDefaults 的应用程序时,将旧的 UserDefaults 值迁移到新的 @CloudStorage 存储(在 model/app init 期间执行)

// check that existing value exists in UserDefaults and that this value hasn't already been migrated.
if let existingString1 = UserDefaults.standard.object(forKey: .string1Key) as? String, !string1.contains(existingString1) { // legacy support
    debug("Migrating local string1: \(existingString1) to cloud string1: \(string1)", level: .NOTICE)
    string1 = "\(existingString1),\(string1)"
    // zero out local version so we don't do this again in the future.  If we log out of iCloud, we will lose the data but that is expected behavior.
    UserDefaults.standard.removeObject(forKey: .string1Key)
}

@CloudStorage 添加自定义协议存储支持

public protocol CustomProtocol {
    init(string: String)
    var stringValue: String { get }
}
@available(iOS 13, tvOS 13, watchOS 6, *)
extension CloudStorage where Value: CustomProtocol {
    public init(wrappedValue: Value, _ key: String) {
        self.init(
            keyName: key,
            syncGet: { CloudStorageSync.shared.string(for: key).flatMap(Value.init) ?? wrappedValue },
            syncSet: { newValue in CloudStorageSync.shared.set(newValue.stringValue, for: key) })
    }
}

所有这些测试都可以使用预览或通过运行模块 Development 文件夹中捆绑的应用程序可执行文件来演示。

贡献

如果您需要实现特定功能或遇到错误,请打开一个 issue。如果您自己扩展了功能并希望其他人也使用它,请提交 pull request。

捐赠

这花费了很多精力。如果您发现这很有用,特别是如果您在商业产品中使用它,请考虑捐赠至 http://paypal.me/kudit

许可证

欢迎在项目中使用此代码,但是,请在应用程序的某处包含指向此项目的链接并注明出处。版本示例 Markdown 和字符串插值

Text("Open Source projects used include [Compatibility](https://github.com/kudit/Compatibility) v\(Compatibility.version)

贡献者

贡献此项目的完整人员列表可在此处找到:此处。非常感谢所有做出贡献的人!🙏 特别感谢此项目提供的 CloudStorage 基础(此处已清理以获得更广泛的兼容性):https://github.com/nonstrict-hq/CloudStorage