IOS editor bug. archivedData renamed

Viewed 12495

Please help me! I am stuck in a loop and can't find my way out. I am trying to learn IOS programming for work so I thought I would start with their tutorial app the Meal list application. I am at the part where you are supposed to start saving persistent data and now the editor has me stuck in a never ending loop. I have a line of code...

let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(meals, toFile: Meal.ArchiveURL.path)

That gives me a warning that says...

'archiveRootObject(_:toFile:)' was deprecated in iOS 12.0: Use +archivedDataWithRootObject:requiringSecureCoding:error: instead

OK, so I change the line of code to...

let isSuccessfulSave = NSKeyedArchiver.archivedDataWithRootObject(meals)

Which then gives me the warning...

'archivedDataWithRootObject' has been renamed to 'archivedData(withRootObject:)'

OK, so I change the line of code to...

let isSuccessfulSave = NSKeyedArchiver.archivedData(withRootObject: meals)

Which tells me...

'archivedData(withRootObject:)' was deprecated in iOS 12.0: Use +archivedDataWithRootObject:requiringSecureCoding:error: instead

OK... So... archivedData was deprecated and I have to use archivedDataWithRootObject, but using archivedDataWithRootObject has been renamed to archivedData, but archivedData is deprecated so use archivedDataWithRootObject which is renamed to archivedData which is deprecated... ad infinitum.

I have tried looking on the developer docs but they just tell me the same thing, one is deprecated, with no links or anything and searching google just gives me a bunch of pages showing me the syntax of using any of them. I am still really new to IOS programming and have no idea how to get out of this endless loop of deprecated to renamed to deprecated to...

Please help, I am lost and not sure how to continue. Thank you.

4 Answers

I am following the same example you are trying to do, and I figured out how to update the methods for storing and retrieving values in iOS 12, this should help you:

//MARK: Private Methods
private func saveMeals() {

    let fullPath = getDocumentsDirectory().appendingPathComponent("meals")

    do {
        let data = try NSKeyedArchiver.archivedData(withRootObject: meals, requiringSecureCoding: false)
        try data.write(to: fullPath)
        os_log("Meals successfully saved.", log: OSLog.default, type: .debug)
    } catch {
        os_log("Failed to save meals...", log: OSLog.default, type: .error)
    }
}

func getDocumentsDirectory() -> URL {
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    return paths[0]
}

private func loadMeals() -> [Meal]? {
    let fullPath = getDocumentsDirectory().appendingPathComponent("meals")
    if let nsData = NSData(contentsOf: fullPath) {
        do {

            let data = Data(referencing:nsData)

            if let loadedMeals = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Array<Meal> {
                return loadedMeals
            }
        } catch {
            print("Couldn't read file.")
            return nil
        }
    }
    return nil
}

Also you will find that you need to update ViewDidLoad as this:

override func viewDidLoad() {
    super.viewDidLoad()

    // Use the edit button item provided by the table view controller.
    navigationItem.leftBarButtonItem = editButtonItem

    let savedMeals = loadMeals()

    if savedMeals?.count ?? 0 > 0 {
        meals = savedMeals ?? [Meal]()
    } else {
        loadSampleMeals()
    }

}

I hope this helps, for me the app is now working, storing and retrieving data.

FYI: This doesn't work with Xcode 11 beta and iOS 13 is should work with anything before those versions.

A general solution for iOS 12 would be:

class SettingsArchiver {
    static func setData(_ value: Any, key: String) {
        let ud = UserDefaults.standard
        let archivedPool = try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true)
        ud.set(archivedPool, forKey: key)
    }

    static func getData<T>(key: String) -> T? {
        let ud = UserDefaults.standard
        if let val = ud.value(forKey: key) as? Data,
            let obj = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(val) as? T {
            return obj
        }
        return nil
    }
}

You need

try {
   let data = try NSKeyedArchiver.archivedData(withRootObject:meals,requiringSecureCoding:true)
   try data.write(to:fullPath)
}
catch {
   print(error)
}

Here in Docs it's IOS 11+

I would say, the answer directly addressing your question is to use the ArchiveURL defined in your Meal.swift data model (think MVC pattern) and reimplement the saveMeals() function in your MealTableViewController.swift controller using the recommended replacement to the deprecated archiveRootObject method this way:

private func saveMeals(){
    do {
        let data = try NSKeyedArchiver.archivedData(withRootObject: meals, requiringSecureCoding: true)
        try data.write(to: Meal.ArchiveURL)
    }
    catch {
        print("Couldn't save to file")
    }
}

Although this answer is a little late :-) I hope it helps whomever may come across this issue.

Related