KeyValueCoding protocol provides a mechanism by which you can access the properties of pure Swift struct or class instances indirectly by name or key.
The basic methods of KeyValueCoding
protocol for accessing an instance’s values are setValue(_ value: Any?, key: String)
, which sets the value for the property identified by the specified key, and value(key: String) -> Any?
, which returns the value for the property identified by the specified key. Thus, all of an instance’s properties including properties with enum
and Optional
types can be accessed in a consistent manner.
In order to make your own instances key-value coding compliant just adopt them from the KeyValueCoding
protocol:
enum UserType {
case none
case guest
case user
case admin
}
class User: KeyValueCoding {
let id: Int = 0
let type: UserType = .none
let name: String = ""
let SSN: Int? = nil
}
var user = User()
user.setValue(123, key: "id")
user.setValue(UserType.guest, key: "type")
user.setValue("Bob", key: "name")
user.setValue(123456789, key: "SSN")
guard let id = user.value(key: "id") as? Int,
let type = user.value(key: "type") as? UserType,
let name = user.value(key: "name") as? String,
let ssn = user.value(key: "SSN") as? Int
else {
return
}
print(id, type, name, ssn) // 123 guest Bob 123456789
You can also use subscripts to set and retrieve values by key without needing separate methods for setting and retrieval:
var user = User()
user["id"] = 123
user["type"] = UserType.guest
user["name"] = "Bob"
user["SSN"] = 123456789
guard let id = user["id"] as? Int,
let type = user["type"] as? UserType,
let name = user["name"] as? String,
let ssn = user["SSN"] as? Int
else {
return
}
print(id, type, name, ssn) // 123 guest Bob 123456789
KeyValueCoding
doesn't conflict with key-value conding of NSObject
class and they can work together:
class Resolution: NSObject, KeyValueCoding {
@objc var width = 0
@objc var height = 0
}
var resolution = Resolution()
// NSObject: setValue(_ value: Any?, forKey key: String)
resolution.setValue(1024, forKey: "width")
// KeyValueCoding: setValue(_ value: Any?, key: String)
resolution.setValue(760, key: "height")
print(resolution.width, resolution.height) // 1024 760
The same works with structs as well:
struct Book: KeyValueCoding {
let title: String = ""
let ISBN: Int = 0
}
var book = Book()
book["title"] = "The Swift Programming Language"
book["ISBN"] = 1234567890
print(book) // Book(title: "The Swift Programming Language", ISBN: 1234567890)
In additional there are also global functions to set and get values of properties without adopting KeyValueCoding
protocol:
struct Song {
let name: String = ""
let artist: String = ""
}
var song = Song()
swift_setValue("Blue Suede Shoes", to: &song, key: "name")
swift_setValue("Elvis Presley", to: &song, key: "artist")
guard let name = swift_value(of: &song, key: "name"),
let artist = swift_value(of: &song, key: "artist")
else {
return
}
print(name, "-", artist) // Blue Suede Shoes - Elvis Presley
Swift instances of struct
or class
that adopt KeyValueCoding
protocol are key-value coding compliant for their properties and they are addressable via essential methods value(key:)
and setValue(_: key:)
.
Returns the metadata kind of the instance.
let user = User()
print(user.metadataKind) // MetadataKind.class
let book = Book()
print(book.metadataKind) // MetadataKind.struct
Returns the array of the instance properties.
let user = User()
user.properties.forEach {
print($0)
}
Outputs:
PropertyMetadata(name: "id", type: Swift.Int, isStrong: true, isVar: false, offset: 16)
PropertyMetadata(name: "type", type: KeyValueCodingTests.UserType, isStrong: true, isVar: false, offset: 24)
PropertyMetadata(name: "name", type: Swift.String, isStrong: true, isVar: false, offset: 32)
PropertyMetadata(name: "SSN", type: Swift.Optional<Swift.Int>, isStrong: true, isVar: false, offset: 48)
Returns a value for a property identified by a given key.
var user = User()
if let type = user.value(key: "type") as? UserType {
print(type) // none
}
Sets a property specified by a given key to a given value.
var user = User()
user.setValue(UserType.admin, key: "type")
if let type = user.value(key: "type") as? UserType {
print(type) // admin
}
Gets and sets a value for a property identified by a given key.
var user = User()
user["type"] = UserType.guest
if let type = user["type"] as? UserType {
print(type) // guest
}
Global functions to set, get and retrieve metadata information from any instance or type without adopting KeyValueCoding
protocol.
Returns the metadata kind of the instance or type.
var song = Song()
print(swift_metadataKind(of: song)) // MetadataKind.struct
// OR
print(swift_metadataKind(of: type(of: song))) // MetadataKind.struct
// OR
print(swift_metadataKind(of: Song.self)) // MetadataKind.struct
Returns the array of the instance or type properties.
var song = Song()
let properties = swift_properties(of: song)
// OR
swift_properties(of: type(of:song))
// OR
swift_properties(of: Song.self)
properties.forEach {
print($0)
}
Outputs:
PropertyMetadata(name: "name", type: Swift.String, isStrong: true, isVar: false, offset: 0)
PropertyMetadata(name: "artist", type: Swift.String, isStrong: true, isVar: false, offset: 16)
- Select
Xcode > File > Add Packages...
- Add package repository:
https://github.com/ikhvorost/KeyValueCoding.git
- Import the package in your source files:
import KeyValueCoding
Add KeyValueCoding
package dependency to your Package.swift
file:
let package = Package(
...
dependencies: [
.package(url: "https://github.com/ikhvorost/KeyValueCoding.git", from: "1.0.0")
],
targets: [
.target(name: "YourPackage",
dependencies: [
.product(name: "KeyValueCoding", package: "KeyValueCoding")
]
),
...
...
)
KeyValueCoding is available under the MIT license. See the LICENSE file for more info.