复杂的自定义对象存储在NSUserDefaults中。编码解决方案Swift 4

时间:2018-07-02 08:22:27

标签: swift swift4 nsuserdefaults

我已经阅读并搜索了一段时间,以了解如何将自定义对象存储到“ NSUserDefaults”中。到目前为止,我已经有了一些解决方案,可以通过在自定义对象中实现“ NSCoding”来实现这一目标。我得到的示例基于非常简单的对象,但就我而言,我正面临着将这种挑战转化为具有复杂结构以及其他自定义类的现有自定义类的问题。

class MyCustomClass:NSObject, NSCoding{

   let codingTagSecondClass = "codingTagSecondClass"
   var mySecondClass:MySecondCustomClass?
   ...

   let codeingTagaString = "codeingTagaString"
   var aString = "aString"
}

我已经实现了NSCoding方法,例如:

required init?(coder aDecoder: NSCoder) {
   aString = aDecoder.decodeObject(forKey: codeingTagaString) as! String
   mySecondClass = aDecoder.decodeObject(forKey: codingTagSecondClass) as? mySecondClass:MySecondCustomClass
}

func encode(with aCoder: NSCoder) {

   aCoder.encode(aString, forKey: codeingTagaString)
   aCoder.encode(mySecondClass, forKey: codingTagSecondClass)

}

这就是我存储到NSUserDefaults中的方式

let archivedObject = NSKeyedArchiver.archivedData(withRootObject: myCustomObject!)
let defaults = UserDefaults.standard
defaults.set(archivedObject, forKey: defaultUserCurrentServerProxy)

此实现仅适用于String var,但是当我尝试使用secondCustomClass时会崩溃...

我可以想象这是因为“ MySecondCustomClass”未实现“ NSCoding”。那是对的吗?有没有其他方法可以实现我要完成的任务?我的Custom类的结构比我在此处显示的结构大,因此在我开始编码或思考我需要知道的其他替代方法之前。

非常感谢您。

2 个答案:

答案 0 :(得分:0)

使用Codable代替,here关于如何使用它的非常不错的帖子

答案 1 :(得分:0)

我将提供一个已实现的自定义助手的工作示例,该助手用于管理不同可编码对象的路径

My Class:private class StoredMediaItem:Codable{
    var metadata: String?
    var url: URL?
    var trackID:UInt32 = 0

    init(metadata:String?, url:URL?, trackID:UInt32){

        self.metadata = metadata
        self.url = url
        self.trackID = trackID

    }

}

阅读:

func readStoredItemArray(fileName:String)->[StoredMediaItem]?{

    do {
        let storedMediaItemArray = try StorageHelper.retrieve(fileName, from: .caches, as: [StoredMediaItem].self)

        print("\(logClassNameOH) Read Stored Item Array from \(fileName) SUCCESS")

        return storedMediaItemArray

        } catch {

            print("\(logClassNameOH) Read Stored Item Array from\(fileName) ERROR -> \(error)")
            return nil

        }

}

写作:

private func saveMediaItemArray(_ mediaItemArrayTemp:[storedMediaItemArray], as fileName:String){

    do {

        try StorageHelper.store(storedMediaItemArray, to: .caches, as: fileName)

        print("\(logClassNameOH) Read Stored Item Array from \(fileName) SUCCESS")

    } catch {
        print("\(logClassNameOH) save MediaItem Array ERROR -> \(error)")
    }

}

我的StorageHelper:

class StorageHelper{

    //MARK: - Variables
    enum StorageHelperError:Error{
        case error(_ message:String)
    }

    enum Directory {
        // Only documents and other data that is user-generated, or that cannot otherwise be recreated by your application, should be stored in the <Application_Home>/Documents directory and will be automatically backed up by iCloud.
        case documents

        // Data that can be downloaded again or regenerated should be stored in the <Application_Home>/Library/Caches directory. Examples of files you should put in the Caches directory include database cache files and downloadable content, such as that used by magazine, newspaper, and map applications.
        case caches
    }



    //MARK: - Functions
    /** Store an encodable struct to the specified directory on disk
    *  @param object      The encodable struct to store
    *  @param directory   Where to store the struct
    *  @param fileName    What to name the file where the struct data will be stored
    **/
    static func store<T: Encodable>(_ object: T, to directory: Directory, as fileName: String) throws {

        let url = getURL(for: directory).appendingPathComponent(fileName, isDirectory: false)

        let encoder = JSONEncoder()
        do {
            let data = try encoder.encode(object)
            if FileManager.default.fileExists(atPath: url.path) {
                try FileManager.default.removeItem(at: url)
            }
            FileManager.default.createFile(atPath: url.path, contents: data, attributes: nil)
        }
        catch {
            throw(error)
        }

    }

    /** Retrieve and convert an Object from a file on disk
    *  @param fileName    Name of the file where struct data is stored
    *  @param directory   Directory where Object data is stored
    *  @param type        Object type (i.e. Message.self)
    *  @return decoded    Object model(s) of data
    **/
    static func retrieve<T: Decodable>(_ fileName: String, from directory: Directory, as type: T.Type) throws -> T{
        let url = getURL(for: directory).appendingPathComponent(fileName, isDirectory: false)

        if !FileManager.default.fileExists(atPath: url.path) {
            throw StorageHelperError.error("No data at location: \(url.path)")
        }

        if let data = FileManager.default.contents(atPath: url.path) {
            let decoder = JSONDecoder()
            do {
                let model = try decoder.decode(type, from: data)
                return model
            } catch {
                throw(error)
            }
        }
        else {
            throw StorageHelperError.error("No data at location: \(url.path)")
        }
    }

    /** Remove all files at specified directory **/
    static func clear(_ directory: Directory) throws {

        let url = getURL(for: directory)
        do {
            let contents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [])
            for fileUrl in contents {
                try FileManager.default.removeItem(at: fileUrl)
            }
        }
        catch {
            throw(error)
        }

    }

    /** Remove specified file from specified directory **/
    static func remove(_ fileName: String, from directory: Directory) throws {
        let url = getURL(for: directory).appendingPathComponent(fileName, isDirectory: false)
        if FileManager.default.fileExists(atPath: url.path) {
            do {
                try FileManager.default.removeItem(at: url)
            } catch {
                throw(error)
            }
        }
    }


    //MARK: Helpers
    /** Returns BOOL indicating whether file exists at specified directory with specified file name **/
    static fileprivate func fileExists(_ fileName: String, in directory: Directory) -> Bool {

        let url = getURL(for: directory).appendingPathComponent(fileName, isDirectory: false)

        return FileManager.default.fileExists(atPath: url.path)

    }

    /** Returns URL constructed from specified directory **/
    static fileprivate func getURL(for directory: Directory) -> URL {

        var searchPathDirectory: FileManager.SearchPathDirectory

        switch directory {
        case .documents:
            searchPathDirectory = .documentDirectory
        case .caches:
            searchPathDirectory = .cachesDirectory
        }

        if let url = FileManager.default.urls(for: searchPathDirectory, in: .userDomainMask).first {
            return url
        } else {
            fatalError("Could not create URL for specified directory!")
        }

    }

}