A multiplatform Swift library bringing functional methods and type safety to .plist (Property List) files.
The challenges that Apple's standard PropertyListSerialization
presents:
PListKit solves these issues by:
PropertyListSerialization
The library is available as a Swift Package Manager (SPM) package.
To add PListKit to your Xcode project:
https://github.com/orchetect/PListKit
as the URL.import PListKit
// new empty plist object
let pl = PList()
The following initializers are available to load external data into the PList
object.
PList(file:)
- using a file path on diskPList(url:)
- using a local file URL or network resource URLPList(data:)
- using raw plist file dataPList(string:)
- using raw plist file stringPList(dictionary:)
- using raw dictionaryLoad method with a single error handler:
do {
let pl = try PList(file: "/Users/user/Desktop/file.plist")
} catch {
// handle failure
}
Load method with individual error handlers:
do {
let pl = try PList(file: "/Users/user/Desktop/file.plist")
} catch let err as PList.LoadError {
switch err {
case .fileNotFound:
// handle error here
case .formatNotExpected:
// handle error here
case .unexpectedKeyTypeEncountered:
// handle error here
case .unexpectedKeyValueEncountered:
// handle error here
case .unhandledType:
// handle error here
}
}
// can create intermediate dictionaries if nonexistent
pl.createIntermediateDictionaries = true // (note: defaults to true)
// create a new Int key within nested dictionaries
pl.root
.dict(key: "Dict")
.dict(key: "Nested Dict")
.int(key: "Int")
.value = 123
// read the value back
let val = pl.root
.dict(key: "Dict")
.dict(key: "Nested Dict")
.int(key: "Int")
.value // == Optional(123)
All valid property list value types map transparently to native Swift value types.
pl.root.string(key: "String").value = "a new string"
pl.root.int(key: "Int").value = 123
pl.root.double(key: "Double").value = 123.45
pl.root.bool(key: "Bool").value = true
pl.root.date(key: "Date").value = Date()
pl.root.data(key: "Data").value = Data([0x01, 0x02])
pl.root.array(key: "Array").value =
["a string",
123,
123.45,
true,
Date(),
Data([0x01, 0x02])]
// dictonaries can be modified directly if necessary,
// perhaps if you need to populate a large data set or copy a nested structure
// but otherwise it's much nicer to use the discretely typed methods above
pl.root.dict(key: "Dictionary").value =
["Key 1" : "a string",
"Key 2" : 123]
Arrays can, of course, be modified in-place using native Swift subscripts.
pl.root.array(key: "Array").value?[0] = "replaced string value"
pl.root.array(key: "Array").value?.append("new string value")
Since property list arrays can contain any valid plist value type simultaneously, when reading arrays you need to conditionally cast values to test their type.
// returns type PListArray, aka [PListValue]
let arr = pl.root.array(key: "Array").value ?? [] // defaulted since the key may not exist
// if you need to test each value in the array, type them in a switch block:
for element in arr {
switch element {
case let val as String: print("String: \(val)")
case let val as Int: print("Int: \(val)")
case let val as Double: print("Double: \(val)")
case let val as Bool: print("Bool: \(val)")
case let val as Date: print("Date: \(val)")
case let val as Data: print("Data with \(val.count) bytes")
case let val as PList.PListArray: print("Array with \(val.count) elements")
case let val as PList.PListDictionary: print("Dictionary with \(val.count) elements")
default: break // technically, this should never happen
}
}
// delete a key
pl.root.string(key: "String").value = nil
// delete a dictionary or array and all of its contents, in the same fashion
pl.root.array(key: "Array").value = nil
pl.root.dict(key: "Dict").value = nil
A full set of chainable subscripts are also available if you choose to use them, mirroring the functional methods. To use them, reference the storage
property directly instead of root
.
pl.storage[any: "Keyname"] // reads key value as PListValue
pl.storage[string: "Keyname"]
pl.storage[int: "Keyname"]
pl.storage[double: "Keyname"]
pl.storage[bool: "Keyname"]
pl.storage[date: "Keyname"]
pl.storage[data: "Keyname"]
pl.storage[array: "Keyname"]
pl.storage[dict: "Keyname"]
The subscripts are usable to both get and set.
pl.storage[string: "Keyname"] = "string value"
let str = pl.storage[string: "Keyname"] ?? "" // "string value"
Nested dictionaries can easily be accessed through chaining subscripts.
// sets nested string key if the intermediate dictionaries already exist
pl.storage[dict: "Dict"]?[dict: "Nested Dict"]?[string: "Keyname"] = "string value"
// alternative subscript creates nested dictionaries if they don't exist
pl.storage[dictCreate: "Dict"]?[dictCreate: "Nested Dict"]?[string: "Keyname"] = "string value"
Arrays can be read by index, conditionally casting to a strong type in process:
// safely attempt to read indexes
// if index does not exist, returns nil
// if index exists but is of wrong type, returns nil
pl.storage[array: "Array"]?[any: 0] // read index 0 as PListValue; you must cast it yourself
pl.storage[array: "Array"]?[string: 0] // read index 0 and cast it as? String
pl.storage[array: "Array"]?[int: 0] // read index 0 and cast it as? Int
pl.storage[array: "Array"]?[double: 0] // read index 0 and cast it as? Double
pl.storage[array: "Array"]?[bool: 0] // read index 0 and cast it as? Bool
pl.storage[array: "Array"]?[date: 0] // read index 0 and cast it as? Date
pl.storage[array: "Array"]?[data: 0] // read index 0 and cast it as? PList.PListArray
pl.storage[array: "Array"]?[dict: 0] // read index 0 and cast it as? PList.PListDictionary
// save to disk in-place, if the file was previously loaded
// from PList(fromURL:) / PList(fromFile:) or .load(fromURL:) / .load(fromFile:)
try? pl.save()
// save to a new file on disk using file URL
guard let url = URL(string: "file:///Users/user/Desktop/file.plist") else { return }
try? pl.save(toURL: url, format: .xml)
// save to a new file on disk using path
try? pl.save(toFile: "/Users/user/Desktop/file.plist", format: .xml)
The PList
class conforms to NSCopying
if you need to copy the entire plist object in memory.
let pl = PList(file: "/Users/user/Desktop/file.plist")
let pl2 = pl.copy() as! PList
More methods are available in addition to what is outlined here in the documentation. Use code completion in the Xcode IDE code editor to discover them.
.root
objectsDictionary
(can be any supported PList value type)Coded by a bunch of 🐹 hamsters in a trenchcoat that calls itself @orchetect.
Licensed under the MIT license. See LICENSE for details.
Contributions are welcome. Feel free to post an Issue to discuss.
This library was formerly known as OTPList.
link |
Stars: 12 |
Last commit: 3 weeks ago |
Many improvements across the library.
Note that there are API-breaking changes, but they are minimal. In all cases, API simply changed or was improved.
.load(...)
methods are now inits instead(string:)
and (dictionary: RawDictionary)
initsConvertToPListArray
is now RawArray
category method convertedToPListArray()
ConvertToPListDictionary
is now RawDictionary
category method convertedToPListDictionary()
Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics