iosapple-watchwatchconnectivity

Can't unarchive a file sent by watch


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.


Solution

  • 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.