Key-value coding (KVC) for pure Swift.
KeyValueCoding
provides a mechanism by which you can access the properties of pure Swift struct
or class
instances indirectly by a string key or a key path.
The basic approach of KeyValueCoding
protocol for accessing an instance’s properties values is subscripting by string key or a key path. In order to make your types key-value coding compliant just adopt them from this protocol, for instance:
import KeyValueCoding
struct Resolution: KeyValueCoding {
let width: Int
let height: Int
}
var resolution = Resolution(width: 640, height: 480)
resolution["width"] = 1920
resolution["height"] = 1080
print(resolution) // Prints: Resolution(width: 1920, height: 1080)
NOTE: An instance variable must be declared as
var
, otherwise you’ll have the following error:Cannot use mutating getter on immutable value
.
The same works with the key paths as well:
resolution[\Resolution.width] = 2560
resolution[\Resolution.height] = 1440
print(resolution) // Prints: Resolution(width: 2560, height: 1440)
You can also read properties values in the same way:
if let width: Int = resolution[\Resolution.width], let height: Int = resolution[\Resolution.height] {
print("\(width)x\(height)") // Prints: 2560x1440
}
The following properties can be accessible for classes and structs in a consistent manner including:
let
and variable var
properties.lazy
, @objc
and optional properties.public
, internal
, private
etc.enum
, struct
, class
, tuple etc.But there are some limitations:
willSet
and didSet
observers aren’t being called on changing values.weak
, unowned
and the property wrappers are not supported.KeyValueCoding
can access to relationship properties by a string key (“relationship.property”) or a key path, for example:
import KeyValueCoding
struct Resolution {
let width: Int
let height: Int
}
class VideoMode: KeyValueCoding {
let name: String
let resolution: Resolution
init(name: String, resolution: Resolution) {
self.name = name
self.resolution = resolution
}
}
var videoMode = VideoMode(name: "HD", resolution: Resolution(width: 1920, height: 1080))
print("\(videoMode.name) - \(videoMode.resolution.width)x\(videoMode.resolution.height)")
// Prints: HD - 1920x1080
videoMode[\VideoMode.name] = "4K"
videoMode[\VideoMode.resolution.width] = 3840
videoMode[\VideoMode.resolution.height] = 2160
print("\(videoMode.name) - \(videoMode.resolution.width)x\(videoMode.resolution.height)")
// Prints: 4K - 3840x2160
NOTE: Your parent instance can access to its children’s properties without conforming the children to
KeyValueCoding
protocol.
Properties from inherited classes are also accessible by KeyValueCoding
protocol:
import KeyValueCoding
class Mode {
let name: String
init(name: String) {
self.name = name
}
}
class VideoMode: Mode, KeyValueCoding {
let frameRate: Int
init(name: String, frameRate: Int) {
self.frameRate = frameRate
super.init(name: name)
}
}
var videoMode = VideoMode(name: "HD", frameRate: 30)
print("\(videoMode.name) - \(videoMode.frameRate)fps")
// Prints: HD - 30fps
videoMode[\VideoMode.name] = "4K"
videoMode[\VideoMode.frameRate] = 25
print("\(videoMode.name) - \(videoMode.frameRate)fps")
// Prints: 4K - 25fps
You can inherit any protocol from KeyValueCoding
and then all instances of this protocol will be to accessible to read and write their properties:
import KeyValueCoding
protocol Size: KeyValueCoding {
var width: Int { get }
var height: Int { get }
}
struct Resolution: Size {
let width: Int
let height: Int
}
var resolution: Size = Resolution(width: 1920, height: 1080)
print(resolution)
// Prints: Resolution(width: 1920, height: 1080)
resolution[\Resolution.width] = 3840
resolution[\Resolution.height] = 2160
if let width: Int = resolution[\Resolution.width], let height: Int = resolution[\Resolution.height] {
print("\(width)x\(height)")
// Prints: 3840x2160
}
In additional you can use pure API functions for getting and setting values of an instance’s properties without adopting KeyValueCoding
protocol at all:
import KeyValueCoding
struct Resolution {
let width: Int
let height: Int
}
var resolution = Resolution(width: 1920, height: 1080)
print(resolution)
// Prints: Resolution(width: 1920, height: 1080)
swift_setValue(3840, to: &resolution, keyPath: \Resolution.width)
swift_setValue(2160, to: &resolution, keyPath: \Resolution.height)
if let width = swift_value(of: &resolution, keyPath: \Resolution.width) as? Int,
let height = swift_value(of: &resolution, keyPath: \Resolution.height) as? Int
{
print("\(width)x\(height)")
// Prints: 3840x2160
}
Swift instances of struct
or class
that adopt KeyValueCoding
protocol are key-value coding compliant for their properties and they are addressable via essential subscriptions [key]
and [keyPath]
.
Returns the metadata of the instance which includes its type, kind, size and a list of accessible properties:
import KeyValueCoding
struct Resolution: KeyValueCoding {
let width: Int
let height: Int
}
let resolution = Resolution(width: 1920, height: 1080)
print(resolution.metadata)
Prints:
Metadata(type: Resolution, kind: .struct, size: 16, properties: [
Property(name: 'width', isStrong: true, isLazy: false, isVar: false, offset: 0),
Property(name: 'height', isStrong: true, isLazy: false, isVar: false, offset: 8)
])
Gets and sets a value for a property identified by a string key.
import KeyValueCoding
struct Resolution: KeyValueCoding {
let width: Int
let height: Int
}
var resolution = Resolution(width: 1920, height: 1080)
resolution["width"] = 2048
if let width: Int = resolution["width"] {
print(width) // Prints: 2048
}
Gets and sets a value for a property identified by a key path.
import KeyValueCoding
struct Resolution: KeyValueCoding {
let width: Int
let height: Int
}
var resolution = Resolution(width: 1920, height: 1080)
resolution[\Resolution.width] = 2048
if let width: Int = resolution[\Resolution.width] {
print(width) // Prints: 2048
}
Global API functions to set, get and retrieve metadata information from any instance or type even without adopting KeyValueCoding
protocol.
Returns the metadata of an instance or a type which includes its type
, kind
, size
and a list of accessible properties
:
import KeyValueCoding
struct Resolution {
let width: Int
let height: Int
}
let resolution = Resolution(width: 1920, height: 1080)
var metadata = swift_metadata(of: resolution)
// OR
metadata = swift_metadata(of: type(of: resolution))
// OR
metadata = swift_metadata(of: Resolution.self)
print(metadata)
Prints:
Metadata(type: Resolution, kind: .struct, size: 16, properties: [
Property(name: 'width', isStrong: true, isLazy: false, isVar: false, offset: 0),
Property(name: 'height', isStrong: true, isLazy: false, isVar: false, offset: 8)
])
Returns the value for the instance’s property identified by a given string key or a key path.
import KeyValueCoding
struct Resolution {
let width: Int
let height: Int
}
var resolution = Resolution(width: 1920, height: 1080)
if let width = swift_value(of: &resolution, key: "width") as? Int {
print(width) // Prints: 1920
}
// OR
if let width = swift_value(of: &resolution, keyPath: \Resolution.width) as? Int {
print(width) // Prints: 1920
}
Sets a property of an instance specified by a given string key or a key path to a given value.
import KeyValueCoding
struct Resolution {
let width: Int
let height: Int
}
var resolution = Resolution(width: 1920, height: 1080)
swift_setValue(2048, to: &resolution, key: "width")
// OR
swift_setValue(2048, to: &resolution, keyPath: \Resolution.width)
print(resolution) // Prints: Resolution(width: 2048, height: 1080)
Xcode > File > Add Packages...
https://github.com/ikhvorost/KeyValueCoding.git
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.