You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift Firebase 自定义对象存储问题:如何将UnlockingCharacters结构体持久化到Firestore用户文档

解决Firebase自定义结构体持久化与类型错误问题

看起来你现在卡了几个关键问题:解锁的角色没法持久化到Firebase、结构体编码出错,还有初始化时的类型不匹配问题。我一步步帮你梳理解决:

1. 修正结构体的Codable兼容性与结构优化

首先,你的UnlockingCharacters其实可以简化——它只包裹了一个Character,没必要多一层嵌套,直接在UserModel里存[Character]会更清爽(如果坚持原结构也可以,后面会适配)。另外要确保所有结构体的Codable实现正确,尤其是@DocumentID的使用:

struct Character: Identifiable, Codable {
    @DocumentID var id: String?
    var character_name: String
    var character_type: String
    var character_image: String
    var character_details: String
    var character_usersUnlocking: Int
    var character_totalPoints: Int
    // 去掉user字段避免循环引用(UserModel里也包含Character,会导致编码失败)
    var didUnlock: Bool? = false
    var isUnlocking: Bool = false
    
    // 可选:手动指定编码键,避免Firestore字段名混乱
    enum CodingKeys: String, CodingKey {
        case id
        case character_name
        case character_type
        case character_image
        case character_details
        case character_usersUnlocking
        case character_totalPoints
        case didUnlock
        case isUnlocking
    }
}

// 如果坚持保留UnlockingCharacters,确保Codable正确:
struct UnlockingCharacters: Identifiable, Codable {
    var id = UUID().uuidString
    var character: Character
    
    enum CodingKeys: String, CodingKey {
        case id
        case character
    }
}

struct UserModel: Identifiable, Codable {
    var username : String
    var pic : String
    var bio: String
    var uid : String
    var id: String { uid }
    var activeUnlockingCharacters: [UnlockingCharacters]
    
    // 新增便捷构造器,解决ViewModel初始化错误
    init(username: String = "", pic: String = "", bio: String = "", uid: String = "", activeUnlockingCharacters: [UnlockingCharacters] = []) {
        self.username = username
        self.pic = pic
        self.bio = bio
        self.uid = uid
        self.activeUnlockingCharacters = activeUnlockingCharacters
    }
}

2. 修正ProfileViewModel的初始化错误

你之前把类型[SupportingCharities]直接传给参数,这是错误的,应该传空数组实例:

// 错误写法:
// @Published var userInfo = UserModel(username: "", pic: "", bio: "", uid: "", activeSupportingCharities: [SupportingCharities])

// 修正后(用便捷构造器自动填充默认值):
@Published var userInfo = UserModel()
// 或者手动传空数组:
@Published var userInfo = UserModel(username: "", pic: "", bio: "", uid: "", activeUnlockingCharacters: [])

3. 修复fetchUser函数的Firestore读写错误

你的fetchUser有两个严重问题:一是调用setData(from: UnlockingCharacters)时传的是类型而非实例;二是completion里传的也是类型而非数组实例。正确逻辑应该是读取用户数据,写入操作要放在角色解锁/移除的函数里:

let ref = Firestore.firestore()
// 假设你有全局或可访问的SharedDataModel实例
let sharedData = SharedDataModel()

func fetchUser(uid: String, completion: @escaping (UserModel) -> ()){
    ref.collection("Users").document(uid).getDocument { (doc, err) in
        if let err = err {
            print("获取用户数据失败:\(err)")
            return
        }
        guard let doc = doc, doc.exists else {
            print("用户文档不存在")
            return
        }
        
        do {
            // 用Firestore自带的Codable解析UserModel
            let user = try doc.data(as: UserModel.self)
            DispatchQueue.main.async {
                completion(user)
                // 同步到SharedDataModel,让UI显示已解锁的角色
                sharedData.unlockingCharacters = user.activeUnlockingCharacters.map { $0.character }
            }
        } catch let error {
            print("解析用户数据失败:\(error)")
        }
    }
}

4. 实现角色解锁/移除的持久化逻辑

用户点击按钮时,不仅要更新SharedDataModel,还要同步更新UserModel并保存到Firebase:

// 假设你在ViewModel里有当前用户的实例
@Published var currentUser: UserModel?

func addToUnlocking(characterData: Character) {
    guard var currentUser = currentUser else { return }
    
    if let index = sharedData.unlockingCharacters.firstIndex(where: { $0.id == characterData.id }) {
        // 移除角色
        sharedData.unlockingCharacters.remove(at: index)
        currentUser.activeUnlockingCharacters.removeAll { $0.character.id == characterData.id }
    } else {
        // 添加角色
        sharedData.unlockingCharacters.append(characterData)
        let unlockingChar = UnlockingCharacters(character: characterData)
        currentUser.activeUnlockingCharacters.append(unlockingChar)
    }
    
    // 保存更新后的用户数据到Firebase
    saveUserToFirebase(user: currentUser)
}

func saveUserToFirebase(user: UserModel) {
    ref.collection("Users").document(user.uid).setData(from: user) { error in
        if let error = error {
            print("保存用户数据失败:\(error)")
        } else {
            print("解锁角色已成功持久化")
        }
    }
}

5. 关键注意事项

  • 循环引用规避:Character里的user字段会和UserModel形成循环引用,导致Codable编码失败,必须移除或用@Ignored标记(如果不需要存储到Firebase)。
  • Firestore权限配置:确保你的Firestore规则允许用户读写自己的文档,示例规则:
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /Users/{uid} {
          allow read, write: if request.auth != null && request.auth.uid == uid;
        }
      }
    }
    
  • UUID稳定性:UnlockingCharacters的id用UUID().uuidString每次初始化都会生成新ID,若需要追踪同一解锁记录,可调整为基于Character.id生成。

按照上面的步骤修改后,解锁的角色就能正确持久化到Firebase,重启APP后也能从Firestore读取并显示了。

内容的提问来源于stack exchange,提问作者Mxyb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 00:59:08