Swift中Codable类weak属性JSONDecoder解码为nil问题咨询
friend Property Is Always nil After Decoding Yep, this approach definitely has an issue—here's why and how to fix it:
The Root Cause
Weak references in Swift don't hold a strong ownership of the object they point to. When JSONDecoder tries to decode the friend property:
- It creates a new
Userinstance for the friend. - But since your
friendproperty is markedweak, there's no strong reference keeping this new instance alive. - As soon as the decoding finishes creating the friend instance, ARC immediately deallocates it, leaving your
friendproperty asnil.
Fix Options
Option 1: Switch to a Strong Reference (and Handle Cycles Manually)
If your data model has potential cycles (like User A has friend User B, who also has friend User A), you can keep friend as a strong reference and use manual cleanup or unowned in your business logic to avoid retain cycles. For example:
class User: Codable { var firstName: String? var lastName: String? var friend: User? // Strong reference here enum CodingKeys: String, CodingKey { case firstName = "first" case lastName = "last" case friend } // Manually break the cycle when it's no longer needed func endFriendship() { friend = nil } }
This works for most cases unless you have long-lived objects with persistent cycles—then you'll need a more targeted approach.
Option 2: Custom Decoding to Preserve Weak References
If you must keep friend as weak, you need to first create strong references to all User instances during decoding, then assign the weak references afterward. Here's a practical implementation:
- Add a unique identifier (like
id) to yourUserclass to map instances. - Customize the
init(from:)method to use a shared strong reference store (like a dictionary) to hold all users before assigning weakfriendproperties.
Example code:
class User: Codable { var id: String var firstName: String? var lastName: String? weak var friend: User? enum CodingKeys: String, CodingKey { case id, firstName = "first", lastName = "last", friendId } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) firstName = try container.decodeIfPresent(String.self, forKey: .firstName) lastName = try container.decodeIfPresent(String.self, forKey: .lastName) // Pull the shared user map from decoder's userInfo if let friendId = try container.decodeIfPresent(String.self, forKey: .friendId), let userMap = decoder.userInfo[.userMapKey] as? [String: User] { friend = userMap[friendId] } } } // Define a custom key for the user map in decoder userInfo extension CodingUserInfoKey { static let userMapKey = CodingUserInfoKey(rawValue: "userMapKey")! } // How to use this setup: let jsonData = // Your JSON data (with `id` and `friendId` fields) var userMap: [String: User] = [:] // First: Decode all users and store them in the strong map let decoder = JSONDecoder() decoder.userInfo[.userMapKey] = userMap let users = try decoder.decode([User].self, from: jsonData) users.forEach { userMap[$0.id] = $0 } // Now all `friend` weak references point to strongly held instances in the map
This way, all User instances are kept alive by the userMap, so assigning the weak friend property won't result in immediate deallocation.
内容的提问来源于stack exchange,提问作者cherry_4

