14

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.

Neglected Sanity
  • 1,194
  • 1
  • 17
  • 36
  • Another question to ask is why use `NSKeyedArchiver` at all in Swift? Why not use modern Swift APIs based around `Codable`? – rmaddy Nov 17 '18 at 03:25
  • 1
    The 2 above comments are literally trash , first deprecation should be avoided anyway in code , second the answer is very related to the zone of the question – Sh_Khan Nov 17 '18 at 03:29
  • 2
    @matt So you're suggestion for a completely new programmer to a language is to ignore warning and don't ask questions? Thank you, that's helpful. I DON'T know IOS programming. – Neglected Sanity Nov 17 '18 at 17:56
  • 1
    What I’m saying is don’t call it a bug and don’t vent. Just ask. – matt Nov 17 '18 at 20:08
  • 1
    I understand and I apologize, I just didn't know how else to describe it. The editor is of no help since it just sends me in a loop and the docs are extremely non helpful to a completely new IOS developer. I didn't quite understand the whole :requireingSecureCoding:error: piece of the whole thing, so it just looks like a bug to a complete newbie, where you get stuck in a loop. – Neglected Sanity Nov 20 '18 at 01:16

4 Answers4

24

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.

Bhavin p
  • 63
  • 9
Daspuru
  • 274
  • 3
  • 6
4

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
    }
}
Ani Farsh
  • 43
  • 3
2

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+

Sh_Khan
  • 86,695
  • 6
  • 38
  • 57
  • What about the "toFile" part of the original call that is in their tutorial app? Does it all go to the same place now? I had to add static members to my Meal class of... static let DocumentsDirectory = FileManager().urls(for: .documentDirecory, in: .userDomainMask).first! static let ArchiveURL = DocumentsDirectory.appendingPathComponent("meals") – Neglected Sanity Nov 17 '18 at 01:51
  • yes but it's deprecated https://developer.apple.com/documentation/foundation/nskeyedarchiver/1410621-archiverootobject , you need to use the secure one in answer and write the returned data to the file – Sh_Khan Nov 17 '18 at 01:58
  • So I still have to write the data to a file after I run the archivedData function? I have no idea how to do that. Their tutorial is obviously outdated and tells you to use deprecated methods that are supposed to write to file automatically – Neglected Sanity Nov 17 '18 at 02:06
  • Check my answer buddy. – user2430797 Jul 21 '20 at 00:38
0

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.

user2430797
  • 260
  • 4
  • 15
  • Also, you will have to add 'NSSecureCoding' to your Meal class declaration and also the following varible declaration: 'static var supportsSecureCoding: Bool = true' – user2430797 Jul 21 '20 at 02:26