显式设置Core Data对象后部分属性仍为Nil值问题排查
问题
学习《100 Days of SwiftUI》时,实现从JSON接口下载数据并解析为Core Data对象的功能,遇到异常:
- 通过
FetchRequest获取的CachedUser对象中,name属性已显式赋值,但界面显示为“Unknown”(空值合并后的结果),而邮箱、公司、地址等其他属性正常显示 printCache函数遍历FetchedResults能正常打印名称,但界面使用同一对象时name为NilCachedFriend的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结果截图

排查与解决
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
相关产品推荐
相关产品推荐

