如何保持 iOS 应用程序的干净生产版本?

问题描述

我开发了一个名为 Swordy Quest 的 iOS 应用: https://apps.apple.com/us/app/swordy-quest-an-rpg-adventure/id1446641513

它包含用于排行榜、成就、玩家对玩家 (PVP) 匹配和氏族的 Game Center 集成。

我有一个在开发时使用的本地测试版本(带有测试 bundleID)。我也有我的游戏的生产版本,我用它来玩游戏和进步,就好像我是客户一样。但是,为了升级/实现上面的 Game Center 功能,我需要使用我的生产 bundleID 进行测试。然后这会用我所有的测试数据覆盖我的“客户游戏”(破坏我的“自然”进度)。

所以我想知道,是否有可能有一个应用程序的“干净”生产版本,并且仍然有一个单独的测试版本,允许我测试 Game Center 功能。或者有什么方法可以在 Xcode 中恢复以前的应用程序状态,以便我可以在使用测试数据污染它之前保存我的生产干净版本?我知道在 Mac Apps 中您可以更改自定义工作目录,但我认为在 iOS 中您不能?

在进行 Game Center 升级之前,我曾考虑过备份我的应用程序的生产版本,但看起来这可能是不可能的?有没有人想出一个聪明的方法解决这个问题?

请注意,我已经在应用中存储了 CoreData 和 UserDefaults。

解决方法

自定义工作目录 只是命令行工具项目。 ChangeCurrentDirectoryPath 选项在此位置不再可用,如下面的 XCode 4.6.1 屏幕截图所示。听起来很疯狂,但您可以尝试降级到 Xcode 4 并实现它。

enter image description here


或者你需要使用 Cocoa 的 NSBundle 类或 Core Foundation 的 CFBundle 函数加载文件。因此,为您的 Swordy Quest 测试制作重复目标。它不会影响您的干净副本。

管理方案:

ss

最后点击小齿轮按钮创建一个干净的副本,以避免触及您的生产代码。

enter image description here

设置好密钥后,产品和测试位置

构建设置 > 打包(写入过滤器打包)

enter image description here

在您的逻辑函数中实现以下代码(例如,在其中实现一个从 LoginPlayerVC 触发 GameHomeVC 的函数)

    var key: String?
    #if TARGET_PROD || TARGET_STORE
    key = @"prodKey";
    #else
    key = @"testKey";
,

Targets 就是为此而设计的。您设置预处理器宏值以使编译器根据目标/宏值编译特定代码。

在您的情况下,您根据选定的目标/宏组合更改客户游戏/测试数据文件的路径。

您还可以为每个目标设置不同的 bundleID。

设置完成后,您只需在目标和编译之间切换即可。整个过程应该可以无缝运行。

备份您的项目,然后按照本教程进行操作,该教程涵盖了具体如何执行此操作: https://www.appcoda.com/using-xcode-targets/

如果以后上面的链接失效了,只需搜索“Xcode target tutorials”

,

作为前身,我对 Game Center 并不熟悉,因此可能存在我没有考虑到的问题。所以,有了这个,我解决这个问题的直觉是从启动参数开始的。此处有一篇关于如何执行此操作的精彩文章:https://www.swiftbysundell.com/articles/launch-arguments-in-swift/

现在您可以根据不同方案的启动参数开始改变行为,您可以开始研究如何分割您的测试/生产数据。

由于我不是 CoreData 专家,我不能 100% 确信这是可能的(或容易的),但我会研究如何根据启动参数设置单独的持久存储。使用 this article 作为参考,在为新的 -testGameCenter 方案创建 TestGameCenter 启动参数以在测试时创建内存数据存储后,您似乎可以粗略地执行以下操作游戏中心

lazy var persistentContainer: NSPersistentContainer = {
  let container = NSPersistentContainer(name: "YourDataStore")

  if CommandLine.arguments.contains("-testGameCenter") {
    let description = NSPersistentStoreDescription()
    description.url = URL(fileURLWithPath: "/dev/null")
    container.persistentStoreDescriptions = [description]  
  }

  container.loadPersistentStores(completionHandler: { _,error in
    if let error = error as NSError? {
      fatalError("Failed to load stores: \(error),\(error.userInfo)")
    }
  })

  return container
}()

如果您能够解决上面的 CoreData 问题,那么是时候开始研究如何分割您的 UserDefaults 数据了。立即想到的这个粗略但简单的解决方案是在从您的测试方案运行时为您的 UserDefault 键添加 test 前缀。下面是一个示例,说明如何围绕 UserDefaults 构建一个包装器来管理它

struct UserDefaultsWrapper {
    let userDefaults: UserDefaults
    let keyPrefix: String

    init(userDefaults: UserDefaults,keyPrefix: String) {
        self.userDefaults = userDefaults
        self.keyPrefix = keyPrefix
    }

    func setValue(_ value: Any?,forKey key: String) {
        self.userDefaults.setValue(value,forKey: prefixedKey(forKey: key))
    }

    func value(forKey key: String) -> Any? {
        self.userDefaults.value(forKey: prefixedKey(forKey: key))
    }

    func prefixedKey(forKey key: String) -> String {
        return "\(keyPrefix)\(key)}"
    }
}

你可以像这样使用包装器

    let userDefaultsPrefix = CommandLine.arguments.contains("-testGameCenter") ? "testGameCenter_" : ""

    let userDefaultsWrapper = UserDefaultsWrapper(userDefaults: .standard,keyPrefix: userDefaultsPrefix)

为了获得更优雅的东西,您可以更多地查看 UserDefaults,看看是否可以应用类似于 CoreData 的解决方案,其中有两个完全独立的存储。快速浏览一下 this initializer,也许你可以用你的包装器做这样简单的事情

struct UserDefaultsWrapper {
    let userDefaults: UserDefaults

    init(userDefaults: UserDefaults) {
        self.userDefaults = userDefaults
    }

    func setValue(_ value: Any?,forKey: key)
    }

    func value(forKey key: String) -> Any? {
        self.userDefaults.value(forKey: key)
    }
}

你在哪里构建它

    let userDefaultsSuiteName: String? = CommandLine.arguments.contains("-testGameCenter") ? myTestingGameCenterSuiteName : nil

    let userDefaults = UserDefaults(suiteName: userDefaultsSuiteName)
    let userDefaultsWrapper = UserDefaultsWrapper(userDefaults: userDefaults)

最后,从您对另一个回复的评论来看,您似乎也关心全新安装方案。也就是说,我概述的方法对跨删除/安装保存数据没有帮助(至少我不认为)。但是,我认为您应该考虑的是,是否有必要从您的生产包 ID 中测试那些删除/安装问题。您可以改为从您的测试包 ID 手动测试这些问题和/或围绕涉及这些问题的组件编写单元测试吗?当你接近你的测试策略时,确保你在正确的层测试正确的东西很重要;在错误的层测试错误的东西会使每个测试层执行起来更加困难

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...