How do you archive mixed objects that conforms to NSSecureCoding for later retrieval?

How do you archive mixed objects that conforms to NSSecureCoding (SFTranscription) for later retrieval?

I am utilizing SFSpeechRecognizer and attempting to save the results of the transcription for later analysis and processing. My issue isnt specifically with speech but rather with archiving.

Unfortunately, I haven't archived before and after Googling, I have encountered challenges.

Code Block
struct TranscriptionResults: Codable {
var currTime : Double // Running start time from beginning of file
var currSegStart : Double // start from beginning of segment
var currSegSecs : Double // segment length in seconds
var currSegEnd : Double // end = currStart + segmentSecs, calculate dont need to save
var elapsedTime : Double // how much time to process to this point
var fileName : String
var fileURL : URL
var fileLength : Int64
var transcription : SFTranscription //* does not conform to Codable **
}


Type 'TranscriptionResults' does not conform to protocol 'Decodable'
Type 'TranscriptionResults' does not conform to protocol 'Encodable'

When I add the SFTranscription type with " var transcription : SFTranscription" I get the above error

I looked it up and SFTranscription is the following
open class SFTranscription : NSObject, NSCopying, NSSecureCoding {...}

SFTranscription is my issue is with complying with Codable, as it doe not look like you can mix with NSSecureCoding.

I don't think my issue is specifically with SFTranscription but understanding how to save the results that include a mix of NSSecureCoding to disk.

How do you save the result for later retrieval?
Answered by DTS Engineer in 674977022

You may be able to smooth the path by using a property wrapper to
handle the coding of transcription. This isn’t something I’ve
personally explored, but I wouldn’t be surprised if someone out there
on the ’net has done so.

Well, DevForums was relatively quiet today, and I’ve been looking for an excuse to spend some quality time with property wrappers, so here we go…



Here’s the property wrapper itself:

Code Block
@propertyWrapper
struct QSecureCodable<T> where
T: NSObject & NSSecureCoding
{
var wrappedValue: T
}
extension QSecureCodable: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let uuidData = try container.decode(Data.self)
let t = try NSKeyedUnarchiver.unarchivedObject(ofClass: T.self, from: uuidData)
self.wrappedValue = t!
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let tData = try NSKeyedArchiver.archivedData(withRootObject: self.wrappedValue, requiringSecureCoding: true)
try container.encode(tData)
}
}


And here’s an example of it in action:

Code Block
struct Account: Codable {
var name: String
@QSecureCodable
var uuid: NSUUID
}
let uuid = NSUUID(uuidString: "3FE1B7EC-9238-48A7-B533-3FE1AED97CAB")!
let a1 = Account(name: "Mr Gumby", uuid: uuid)
let d = try JSONEncoder().encode(a1)
let a2 = try JSONDecoder().decode(Account.self, from: d)
print(a1.name == a2.name) // -> true
print(a1.uuid == a2.uuid) // -> true


I’m using NSUUID rather than SFTranscription because it’s easier to create a placeholder that way, but the wrapper should work with any type that supports NSSecureCoding.

Neat-o!

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
You seem to be mixing SecureCoding and Codable, which are two very different serialisation schemes. The easiest way forward here is to stick with one, and because SFTranscription supports SecureCoding the obvious choice is SecureCoding.

If, however, you want to stick with Codable then you’ll need to write the code to adapt SFTranscription into that scheme. Pasted in below is a rough outline of how to make that work.

You may be able to smooth the path by using a property wrapper to handle the coding of transcription. This isn’t something I’ve personally explored, but I wouldn’t be surprised if someone out there on the ’net has done so.

Share and Enjoy

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

Code Block
struct TranscriptionResults {
var fileLength: Int64
var transcription: SFTranscription
}
extension TranscriptionResults: Decodable {
enum CodingKeys: CodingKey {
case fileLength
case transcription
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.fileLength = try container.decode(Int64.self, forKey: .fileLength)
let transcriptionData = try container.decode(Data.self, forKey: .transcription)
let t = try NSKeyedUnarchiver.unarchivedObject(ofClass: SFTranscription.self, from: transcriptionData)
self.transcription = t!
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.fileLength, forKey: .fileLength)
let transcriptionData = try NSKeyedArchiver.archivedData(withRootObject: self.transcription, requiringSecureCoding: true)
try container.encode(transcriptionData, forKey: .transcription)
}
}

Accepted Answer

You may be able to smooth the path by using a property wrapper to
handle the coding of transcription. This isn’t something I’ve
personally explored, but I wouldn’t be surprised if someone out there
on the ’net has done so.

Well, DevForums was relatively quiet today, and I’ve been looking for an excuse to spend some quality time with property wrappers, so here we go…



Here’s the property wrapper itself:

Code Block
@propertyWrapper
struct QSecureCodable<T> where
T: NSObject & NSSecureCoding
{
var wrappedValue: T
}
extension QSecureCodable: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let uuidData = try container.decode(Data.self)
let t = try NSKeyedUnarchiver.unarchivedObject(ofClass: T.self, from: uuidData)
self.wrappedValue = t!
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let tData = try NSKeyedArchiver.archivedData(withRootObject: self.wrappedValue, requiringSecureCoding: true)
try container.encode(tData)
}
}


And here’s an example of it in action:

Code Block
struct Account: Codable {
var name: String
@QSecureCodable
var uuid: NSUUID
}
let uuid = NSUUID(uuidString: "3FE1B7EC-9238-48A7-B533-3FE1AED97CAB")!
let a1 = Account(name: "Mr Gumby", uuid: uuid)
let d = try JSONEncoder().encode(a1)
let a2 = try JSONDecoder().decode(Account.self, from: d)
print(a1.name == a2.name) // -> true
print(a1.uuid == a2.uuid) // -> true


I’m using NSUUID rather than SFTranscription because it’s easier to create a placeholder that way, but the wrapper should work with any type that supports NSSecureCoding.

Neat-o!

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
This is absolute extraordinary. I've been staring at this code for a while, to understand its elegance, scalability and effectiveness. I have more to study, as I read through materials on property wrappers.

>Neat-o!

That is an understatement.

Thank you for all your help.
How do you archive mixed objects that conforms to NSSecureCoding for later retrieval?
 
 
Q