Question about including all project classes in ofClasses parameter when using NSKeyedUnarchiver.unarchivedObject(ofClasses:from:)

Hello,

I have a question about data deserialization using NSKeyedUnarchiver in iOS SDK development.

Current Situation:

  • Previously, we were using the NSKeyedUnarchiver.unarchiveObject(with: Data) function
  • We have changed to using the NSKeyedUnarchiver.unarchivedObject(ofClasses:from:) method to deserialize complex objects stored in UserDefaults
  • We need to include all types in the ofClasses parameter, including Swift primitive types as well as various custom classes and structs within the project

Questions:

  1. Implementation Approach: Is it correct pattern to include all classes defined in the project in the ofClasses array? Is this approach recommended?
  2. Runtime Stability: When using this approach, is there a possibility of runtime crashes? Are there any performance issues?
  3. Alternative Methods: If the current approach is not the correct pattern, what alternatives should we consider?

Current Code Structure:

  • All model classes conform to the NSSecureCoding protocol
  • We use the requiringSecureCoding: true parameter
  • We use a whitelist approach, explicitly listing only allowed classes

I would like to know if this structure is appropriate, or if we should consider a different approach.

Thank you.

Answered by DTS Engineer in 863543022
Is it correct pattern to include all classes defined in the project in the ofClasses array?

Probably not, but it depends on how the object graph was serialised in the first place. The general idea with coding is that each class is responsible for encoding and decoding its own values. With secure coding that means that each class is responsible for decoding its values securely. So, if a class X has an array property whose elements are expected to be of type Y, it calls decodeArrayOfObjects(ofClasses:forKey:) passing Y to the first parameter. If class Y has a propert of type Z, class X doesn’t need to list it here because it’s handled by Y internally.

So, when you call unarchivedObject(ofClasses:from:) you only have to list the class of the root object. In most setups that a single class, not a long list.

When using this approach, is there a possibility of runtime crashes?

That question is too general to answer.

Are there any performance issues?

If you’re asking whether secure coding is slower than regular coding then the answer is “yes”. It has to do more work and thus is guaranteed to be slower.

However, it’s likely to be a trivial difference. Moreover, if you’re concerned about performance, you shouldn’t be storing the resulting archive in UserDefaults.

If the current approach is not the correct pattern, what alternatives should we consider?

My general advice is that you move to Swift Codable. It’s generally less work and is secure by default.

Note that Codable does have limits, for example, it can only deal with hierarchies, not graphs. Moreover, to migrate from secure coding to Codable you have to sort out secure coding first, so whatever work you do on that front won’t be wasted.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Is it correct pattern to include all classes defined in the project in the ofClasses array?

Probably not, but it depends on how the object graph was serialised in the first place. The general idea with coding is that each class is responsible for encoding and decoding its own values. With secure coding that means that each class is responsible for decoding its values securely. So, if a class X has an array property whose elements are expected to be of type Y, it calls decodeArrayOfObjects(ofClasses:forKey:) passing Y to the first parameter. If class Y has a propert of type Z, class X doesn’t need to list it here because it’s handled by Y internally.

So, when you call unarchivedObject(ofClasses:from:) you only have to list the class of the root object. In most setups that a single class, not a long list.

When using this approach, is there a possibility of runtime crashes?

That question is too general to answer.

Are there any performance issues?

If you’re asking whether secure coding is slower than regular coding then the answer is “yes”. It has to do more work and thus is guaranteed to be slower.

However, it’s likely to be a trivial difference. Moreover, if you’re concerned about performance, you shouldn’t be storing the resulting archive in UserDefaults.

If the current approach is not the correct pattern, what alternatives should we consider?

My general advice is that you move to Swift Codable. It’s generally less work and is secure by default.

Note that Codable does have limits, for example, it can only deal with hierarchies, not graphs. Moreover, to migrate from secure coding to Codable you have to sort out secure coding first, so whatever work you do on that front won’t be wasted.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hello,

Thank you for your detailed response to my previous question.

I understand that including all classes used within the app in the ofClasses parameter is not the correct pattern.

Situation Update: Following your advice, I tested by including only the root object's class in ofClasses. Class X contains nested objects Y and Z.

// Including only root object and some nested objects NSKeyedUnarchiver.unarchivedObject(ofClasses: [X.self, Y.self], from: data)

Problem Encountered: However, I encountered the following error:

Error Domain=NSCocoaErrorDomain Code=4864 "value for key 'anotherNestedObject' was of unexpected class 'Z'. Allowed classes are: { 'X', 'Y' }"

The error was only resolved when I included class Z in ofClasses as well.

Current Implementation Verification:

  • Class X properly implements NSSecureCoding
  • In the init(coder:) method, it individually decodes nested objects using coder.decodeObject(ofClass: Y.self, forKey: "nestedObject") and coder.decodeObject(ofClass: Z.self, forKey:

"anotherNestedObject")

  • Classes Y and Z also properly implement NSSecureCoding

Question: Even though each class properly implements NSSecureCoding and individually decodes nested objects, why do I still need to include all nested classes in the root-level ofClasses parameter? Is this the intended behavior of the NSKeyedUnarchiver.unarchivedObject(ofClasses:from:) method, or is there a different implementation approach needed to apply the pattern where "you only have to list the class of the root object"?

Thank you.

Question about including all project classes in ofClasses parameter when using NSKeyedUnarchiver.unarchivedObject(ofClasses:from:)
 
 
Q