简体   繁体   中英

Write array content (of custom type) to plist as keyed array in swift

MacOS, Swift 4.

I want to store data in a plist. I can load the values, but when writing them out they get formatted incorrectly.

With this I'm loading the plist (to display it in a table view), which works fine:

    func loadBooks() {
        let path = dataFilePath() // /com.name.books/Data/Documents/

        do {
            let data = try Data(contentsOf: path)
            let decoder = PropertyListDecoder()
            let plist = try decoder.decode([String: [Book]].self, from: data)
            books = plist["BooksData"]!
        } catch {
            print("Error occurred: \(error.localizedDescription)")
        }
    }

This is the plist I'm loading:

<plist version="1.0">
    <dict>
    <key>BooksData</key>
    <array>
        <dict>
            <key>author</key>
            <string>First Author</string>
            <key>title</key>
            <string>First Title</string>     
            <key>lentBy</key>
            <string>Person</string>
        </dict>       
        <dict>
            <key>author</key>
            <string>Second Author</string>  
            <key>title</key>
            <string>Book Title2</string>
            <key>lentBy</key>
            <string>Another Person</string>
        </dict>
    </array>  
    </dict>
</plist>

I'm trying to write the array back to the plist like this:

  func saveBooks() {
        let data = books

        do {
            let encoder = PropertyListEncoder()
            var plist = try encoder.encode(data)
            try plist.write(to: dataFilePath())
        } catch {
            print("Error occured: \(error.localizedDescription)")
        }
    }

The plist content looses its "wrapping" and key ("BooksData") and subsequently cannot be read anymore with the loadBooks() function.

This is the output:

<plist version="1.0">
    <array>    
        <dict>
            <key>author</key>
            <string>First Author</string>
            <key>title</key>
            <string>First Title</string>     
            <key>lentBy</key>
            <string>Person</string>
        </dict>       
        <dict>
            <key>author</key>
            <string>Second Author</string>  
            <key>title</key>
            <string>Book Title2</string>
            <key>lentBy</key>
            <string>Another Person</string>
        </dict>  
    </array>
</plist>

I've played with NSMutableDictionary() , trying to rewrap it with dict.setObject(plist, forKey: "BooksData") and writing it with dict.write(toFile: dataFilePath().absoluteString, atomically: true) .

            let encoder = PropertyListEncoder()
            var plist = try encoder.encode(data)

            dict.setObject(plist, forKey: "BooksData" as NSCopying)
            dict.write(toFile: dataFilePath().absoluteString, atomically: true)
            print(dict)

printing the dict returns

{
    BooksData = <62706c69 73743030 a401080c 10d30203 04050607 55746974 6c655661 7574686f 72566c65 6e744279 59427563 68746974 656c5b46 696e6b2c 20457567 656e546e 6f6e65d3 02030409 0a0b5f10 14457266 61687275 6e672075 6e642055 72746569 6c5f100f 48757373 65726c2c 2045646d 756e645a 50617472 69636b20 5379d302 03040d0e 0f5f101a 4b726974 696b2064 65722072 65696e65 6e205665 726e756e 66745e4b 616e742c 20496d6d 616e7565 6c50d302 03041112 0f5b4369 74612041 63746976 615e4172 656e6474 2c204861 6e6e6168 080d141a 2128323e 434a6173 7e85a2b1 b2b9c500 00000000 00010100 00000000 00001300 00000000 00000000 00000000 0000d4>;
}

And the file isn't even written out, ie the old file at dataFilePath() stays the same.

Could the answer lie in PropertyListSerialization.propertyList(from:options:format:) ? Or CodingKeys ? Or encode(title, forKey:"BooksData") (?) etc.? NSMutableArray ? KeyedDecodingContainer , unkeyedContainer etc.? NSKeyedArchiver ?

This seems like such a simple task, but I just cannot get it right. There appear to be some related threads about this around, but nothing really applied or gave me the crucial information to fix this. The documentation also elucidates nothing for me, I would need some concrete pointers.

Please help.

As the expected type is [String: [Book]] you have to create the root dictionary manually

func saveBooks() {
    do {
        let encoder = PropertyListEncoder()
        let plistData = try encoder.encode(["BooksData":books])
        try plistData.write(to: dataFilePath())
    } catch {
        print("Error occured:", error) // never ever print `localizedDescription`
    }
}

But if the plist contains only the books array load and save only the array. Further I recommend to hand over a possible error to the caller

var books = [Book]()

func loadBooks() throws {
    let path = dataFilePath() // /com.name.books/Data/Documents/
    let data = try Data(contentsOf: path)
    let decoder = PropertyListDecoder()
    books = try decoder.decode([Book].self, from: data)
}

Then the save method matches the type

func saveBooks() throws {
    let encoder = PropertyListEncoder()
    let plistData = try encoder.encode(books)
    try plistData.write(to: dataFilePath())
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM