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

显式设置Core Data对象后部分属性仍为Nil值问题排查

问题

学习《100 Days of SwiftUI》时,实现从JSON接口下载数据并解析为Core Data对象的功能,遇到异常:

  • 通过FetchRequest获取的CachedUser对象中,name属性已显式赋值,但界面显示为“Unknown”(空值合并后的结果),而邮箱、公司、地址等其他属性正常显示
  • printCache函数遍历FetchedResults能正常打印名称,但界面使用同一对象时name为Nil
  • CachedFriend的name属性存在相同问题

相关代码

ContentView

import SwiftUI

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(sortDescriptors: []) var cachedUsers: FetchedResults<CachedUser>
    @State private var users = [User]()
    
    var body: some View {
        NavigationView {
            List(cachedUsers) { cachedUser in
                
                NavigationLink {
                    VStack(alignment: .leading){
                        Text("Name: \(cachedUser.unwrappedName)")
                            .font(.largeTitle)
                        
                        Spacer()
                        
                        Section {
                            Text("Email:")
                            Text(cachedUser.uEmail)
                                .font(.body)
                                .foregroundColor(.secondary)
                            
                            Text("Company")
                            Text(cachedUser.uCompany)
                                .font(.body)
                                .foregroundColor(.secondary)
                            
                            Text("Address:")
                            Text(cachedUser.uAddress)
                                .font(.body)
                                .foregroundColor(.secondary)
                        } header: {
                            Text("Contact")
                                .font(.title)
                        }
                        Spacer()
                        Spacer()
                    }
                    
                } label: {
                    VStack(alignment: .leading){
                        Text("\(cachedUser.unwrappedName)")
                            .font(.headline)
                            .foregroundColor(cachedUser.isActive ? .white : .red)
                    }
                }
            }
            .task {
                if cachedUsers.isEmpty {
                    do {
                        if let retrievedUsers = try await loadData() {
                            users = retrievedUsers
                        }
                    } catch {
                        print(error)
                    }
                    
                    await MainActor.run {
                        saveToCoreData(users: users)                        
                    }
                }
                printCache()
            }
           .navigationTitle("Users")
           .foregroundColor(.white)
            
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
    
    func loadData() async throws -> [User]? {
        
        if users.isEmpty {
            
            let targetURL = "https://www.hackingwithswift.com/samples/friendface.json"
            guard let url = URL(string: targetURL) else {
                print("Bad URL. No biscuit.")
                return users
            }
            
            let (data, _) = try await URLSession.shared.data(from: url)
            do {
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .iso8601
                users = try decoder.decode([User].self, from: data)
            } catch {
                print(error)
            }
        }
        return users
    }
    
    func saveToCoreData(users: [User]){
        
        for user in users {
            
            let cd = CachedUser(context: moc)
            cd.about = user.about
            cd.address = user.address
            cd.age = Int16(user.age)
            cd.company = user.company
            cd.email = user.email
            cd.id = user.id
            cd.isActive = user.isActive
            cd.name = user.name
            cd.registered = user.registered
            
            for friend in user.friends {
                let cf = CachedFriend(context: moc)
                cf.id = friend.id
                cf.name = friend.name
                cd.addToFriend(cf)
            }
            
            do {
                if moc.hasChanges {
                    try moc.save()
                }
            } catch {
                print(error)
            }
        }
    }
    
    func printCache() {
        for cachedUser in cachedUsers {
            print("Name: \(cachedUser.unwrappedName) | \(cachedUser.uUUID) ")
        }
    }
    
}

User模型

import Foundation

struct User: Codable, Identifiable {
    enum CodingKeys: CodingKey {
        case id, isActive, name, age, company, email, address, about, registered, tags, friends
    }
    
    var id = UUID()
    var isActive: Bool = false
    var name: String = ""
    var age: Int16 = 20
    var company: String = ""
    var email: String = ""
    var address: String = ""
    var about: String = ""
    var registered: Date = Date.now
    var tags: [String] = []
    var friends: [Friend] = []
    
    init() { }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(isActive, forKey: .isActive)
        try container.encode(name, forKey: .name)
        try container.encode(age, forKey: .age)
        try container.encode(company, forKey: .company)
        try container.encode(email, forKey: .email)
        try container.encode(address, forKey: .address)
        try container.encode(about,forKey: .about)
        try container.encode(registered,forKey: .registered)
        try container.encode(tags, forKey: .tags)
        try container.encode(friends, forKey: .friends)
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(UUID.self, forKey: .id)
        isActive = try container.decode(Bool.self, forKey: .isActive)
        name = try container.decode(String.self, forKey: .name)
        
        age = try container.decode(Int16.self, forKey: .age)
        company = try container.decode(String.self, forKey: .company)
        
        email = try container.decode(String.self, forKey: .email)
        address = try container.decode(String.self, forKey: .address)
        about = try container.decode(String.self, forKey: .about)
        registered = try container.decode(Date.self, forKey: .registered)
        
        tags = try container.decode([String].self, forKey: .tags)
        friends = try container.decode([Friend].self, forKey: .friends)
    }
}


class UserData: ObservableObject, Codable {
    @Published var user = User()
    
    enum CodingKeys: CodingKey {
        case user
    }
    
    init() { }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(user, forKey: .user)
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        user = try container.decode(User.self, forKey: .user)
    }
}

Core Data实体及printCache结果截图

Core Data实体及printCache()结果


排查与解决

1. 检查Core Data实体属性配置

确认CachedUser和CachedFriend的name属性是否勾选了Optional:

  • 如果是可选属性,可能因上下文同步延迟,界面读取时未完成持久化
  • 将name等必填属性的“Optional”取消勾选,确保属性非空

2. 验证unwrappedName扩展逻辑

确认CachedUser的扩展代码无疏漏,示例正确实现:

extension CachedUser {
    var unwrappedName: String {
        name ?? "Unknown"
    }
    
    var uEmail: String {
        email ?? "Unknown"
    }
}
  • 检查是否存在拼写错误(Core Data属性名区分大小写)
  • 确认unwrappedName确实绑定name属性,而非其他字段

3. 修正Core Data保存时机

当前saveToCoreData在循环内频繁保存上下文,易引发状态不一致,改为批量创建后统一保存:

func saveToCoreData(users: [User]){
    for user in users {
        let cd = CachedUser(context: moc)
        cd.about = user.about
        cd.address = user.address
        cd.age = Int16(user.age)
        cd.company = user.company
        cd.email = user.email
        cd.id = user.id
        cd.isActive = user.isActive
        cd.name = user.name
        cd.registered = user.registered
        
        for friend in user.friends {
            let cf = CachedFriend(context: moc)
            cf.id = friend.id
            cf.name = friend.name
            cd.addToFriend(cf)
        }
    }
    
    // 所有对象创建完成后统一保存
    do {
        if moc.hasChanges {
            try moc.save()
        }
    } catch {
        print(error)
    }
}

4. 强制上下文同步刷新

保存完成后,强制刷新上下文确保FetchRequest获取最新数据:

await MainActor.run {
    saveToCoreData(users: users)
    moc.refreshAllObjects() // 添加这行
}

5. 验证数据赋值有效性

在saveToCoreData中添加日志,确认user.name非空:

cd.name = user.name
print("Saving user name: \(user.name)")

排除JSON解析阶段name属性为空的问题


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 09:41:17