I have a class containing data that is being produced on the Apple Watch. I use the following method to archive the class, store the data in a file and then send the file to the iPhone.
func send(file counter: CounterModel) {
let session = WCSession.default
let fm = FileManager.default
let documentsDirectory = fm.urls(for: .documentDirectory, in: .userDomainMask).first!
let transferStore = documentsDirectory.appendingPathComponent("transferfile").appendingPathExtension("cnt")
do {
let counterData = try NSKeyedArchiver.archivedData(
withRootObject: counter,
requiringSecureCoding: false
)
try counterData.write(to: transferStore)
if session.activationState == .activated {
session.transferFile(transferStore, metadata: nil)
}
} catch {
print("Oops")
}
}
Sending the file to the iPhone works fine, the delegate method is being called and the file is received. However, I can't unarchive the data and get the error message "The data couldn’t be read because it isn’t in the correct format." The delegate is simple:
func session(_ session: WCSession, didReceive file: WCSessionFile) {
do {
let contents = try Data(contentsOf: file.fileURL)
if let newValue = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(contents) as? CounterModel {
listOfCounters.append(newValue)
} else {
print("The content could not be decoded.")
}
} catch {
print("Failed to retrieve the file with error \(error.localizedDescription).")
}
}
Apparently, I'm doing something wrong. The un-archiving of the data on the iPhone works, so this is not the problem. Perhaps the file send has another format, but I can't get any information on that.
I opened the problem as a ticket to DTS and got the following answer:
The culprit is that your Model class has a different (full) class name in different targets. A Swift class has a module name, which by default is tied to the target name. When your Model class is compiled for your WatchKit extension, its full name is “TagetName_watchkit_extension.Model”; when it is compiled for your iOS app, it becomes “TargetName.Model”.
When your WatchKit extension archives an object Model, it uses “Target_watchkit_extension.Model” as the class name, which is not recognized by your iOS app, and triggers the failure.
You can use
@objc
to give your Model class a full name, which prevents the compiler from adding the module name, like below:@objc(Model) class Model: NSObject, NSCoding, ObservableObject {
I implemented this advice and it worked. However, on my MacBook I got an error message from the preview, that stated, that I needed to change some methods of my model with a prefix of "@objc dynamic". This might, however, happen, because DTS at Apple, didn't get this error.
The response on the problem was:
“@objc dynamic” is required for KVO (key-value observation) support. Since a “@Published" variable relies on KVO as well, adding that does sound reasonable for me.
This solved my problem and I'm happy.