如何用NSKeyedArchiver存储枚举与嵌套数组?iOS持久化求助
Hey there! I totally get how tricky iOS persistence can feel when you're just starting out—those crashers and failed saves are so frustrating. Let's walk through fixing the two main issues you're hitting with NSKeyedArchiver: enums breaking saves and nested arrays crashing your IDE.
Fixing Enum-Related Save Failures
Enums don't play nice with NSCoding out of the box because they aren't NSObject subclasses. The fix is to use their raw values (like String or Int) to encode/decode them explicitly. Here's a step-by-step example:
First, define your enum with a serializable raw type:
enum ItemStatus: String { case active, completed, archived }
Then, in your custom class that uses this enum, implement NSCoding by converting the enum to its raw value when encoding, and converting back when decoding:
class TodoItem: NSObject, NSCoding { var title: String var status: ItemStatus init(title: String, status: ItemStatus) { self.title = title self.status = status } // Encode the enum's raw value instead of the enum itself func encode(with coder: NSCoder) { coder.encode(title, forKey: "title") coder.encode(status.rawValue, forKey: "status") } // Decode the raw value and safely convert it back to the enum required init?(coder: NSCoder) { guard let title = coder.decodeObject(forKey: "title") as? String, let statusRaw = coder.decodeObject(forKey: "status") as? String, let status = ItemStatus(rawValue: statusRaw) else { // If any step fails, return nil to avoid invalid objects return nil } self.title = title self.status = status } }
The key here is never trying to encode the enum directly—always use its raw value, and add guard clauses to handle cases where decoding might fail (like if the raw value doesn't match any enum case).
Fixing Nested Array IDE Crashes
Crashes when adding nested arrays almost always come down to one of two issues:
- The objects inside your nested arrays don't conform to
NSCodingproperly - Accidental recursive encoding (infinite loops) in your
encode(with:)method
Let's fix this with an example. Suppose you have a TodoList class that contains a nested array of TodoItem objects:
class TodoList: NSObject, NSCoding { var sections: [[TodoItem]] // Nested array of TodoItem init(sections: [[TodoItem]]) { self.sections = sections } func encode(with coder: NSCoder) { // Since TodoItem conforms to NSCoding, we can encode the nested array directly coder.encode(sections, forKey: "sections") } required init?(coder: NSCoder) { // Safely decode the nested array, defaulting to an empty array if decoding fails self.sections = coder.decodeObject(forKey: "sections") as? [[TodoItem]] ?? [] } }
Critical Checks to Avoid Crashes:
- All nested objects must conform to NSCoding: If you have a struct instead of a class, you'll need to make it conform to
NSCoding(or wrap it in anNSObjectsubclass if you're sticking with older iOS versions). - Watch for retain cycles: If your objects reference each other (e.g., a
TodoItemhas a reference to its parentTodoList), useweakreferences to prevent memory leaks that can lead to crashes during archiving. - Avoid recursive encoding: Never call
encode(with:)on the same object inside its ownencodemethod—this creates an infinite loop that will crash your IDE or app.
Quick Sanity Check for Your Archive/Unarchive Flow
Make sure your save/load code is set up correctly too—here's a standard implementation:
// Get the path to save your archive private func getArchiveURL() -> URL { let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! return documentsDirectory.appendingPathComponent("todoList.archive") } // Save your data func saveTodoList(_ list: TodoList) { let data = NSKeyedArchiver.archivedData(withRootObject: list, requiringSecureCoding: false) do { try data.write(to: getArchiveURL()) } catch { print("Save failed with error: \(error.localizedDescription)") } } // Load your data func loadTodoList() -> TodoList? { guard let data = try? Data(contentsOf: getArchiveURL()), let list = NSKeyedUnarchiver.unarchiveObject(with: data) as? TodoList else { return nil } return list }
Note: Setting requiringSecureCoding to false is fine for testing, but for production apps, you'll want to implement secure coding by conforming your classes to NSSecureCoding.
A quick side note: If you're feeling overwhelmed by Core Data, once you get past these NSKeyedArchiver issues, check out SwiftData (iOS 16+)—it's a much simpler, Swift-native persistence framework that removes a lot of the boilerplate of Core Data.
内容的提问来源于stack exchange,提问作者Leifster

