Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Swift Dynamic Properties
public struct Person {
public var firstName: String
public var middleName: String?
public var lastName: String
public var ageInYears: Int
public var fullName: String {
return [firstName, middleName, lastName].flatMap{ $0 }.joinWithSeparator(" ")
}
}
var john = Person(firstName: "John", middleName: nil, lastName: "Doe", ageInYears: 30)
/* Getting */
let firstNameState = john[.firstName] // Person.PropertyState.firstName("John")
let ageInYearsState = john[.ageInYears] // Person.PropertyState.ageInYears(30)
let fullNameState = john[.fullName] // Person.PropertyState.fullName("John Doe")
// Mutate ageInYears
john.ageInYears = 80
let ageInYearsState2 = john[.ageInYears] // Person.PropertyState.ageInYears(80)
/* Initializing keys from strings */
if let validKey = Person.Property(rawValue: "lastName") { // Person.Property.lastName
// Get value
let validKeyState = john[validKey] // Person.PropertyState.lastName("Doe")
}
let invalidKey = Person.Property(rawValue: "nonExistant") // nil
/* Creating from arbitrary values */
if let
validatedFirstName = Person.PropertyState(validating: "Bob", property: .firstName),
validatedAgeInYears = Person.PropertyState(validating: 50, property: .ageInYears)
{
var bob = john
// Make alterations
validatedFirstName.set(&bob)
validatedAgeInYears.set(&bob)
print(bob) // Person(firstName: "Bob", middleName: nil, lastName: "Doe", ageInYears: 50)
}
// Invalid values
let failedFirstName = Person.PropertyState(validating: ["Not going to work"], property: .firstName) // nil
let failedAgeInYears = Person.PropertyState(validating: "Expects an int", property: .ageInYears) // nil
/* Transfer from one to another */
var alice = Person(firstName: "Alice", middleName: nil, lastName: "Jones", ageInYears: 40)
var susan = Person(firstName: "Susan", middleName: nil, lastName: "Smith", ageInYears: 20)
// Transfer lastName "Jones" from alice to susan
alice[.lastName].set(&susan)
print(susan) // Person(firstName: "Susan", middleName: nil, lastName: "Jones", ageInYears: 20)
// Attempt to transfer get-only fullName
alice[.fullName].set(&susan) // Fatal error (not settable)
// Protocols
protocol PropertyIdentifierProtocol {
var settable: Bool { get }
static var publicProperties: [Self] { get }
}
protocol PropertyStateProtocol {
associatedtype Target
associatedtype PropertyIdentifier : PropertyIdentifierProtocol
init(target: Target, property: PropertyIdentifier)
init?<T>(validating: T, property: PropertyIdentifier)
var property: PropertyIdentifier { get }
func set(inout target: Target)
}
// Implementation automatically provided by compiler:
extension Person {
public enum Property : String, PropertyIdentifierProtocol {
case firstName = "firstName"
case middleName = "middleName"
case lastName = "lastName"
case ageInYears = "ageInYears"
case fullName = "fullName"
public var settable: Bool {
switch self {
case .firstName, .middleName, .lastName, .ageInYears: return true
case .fullName: return false
}
}
public static var publicProperties: [Property] {
return [.firstName, .middleName, .lastName, .ageInYears, .fullName]
}
}
public enum PropertyState : PropertyStateProtocol {
case firstName(String)
case middleName(String?)
case lastName(String)
case ageInYears(Int)
case fullName(String)
public typealias Target = Person
public typealias Identifier = Property
public init(target: Target, property: Property) {
switch property {
case .firstName: self = .firstName(target.firstName)
case .middleName: self = .middleName(target.middleName)
case .lastName: self = .lastName(target.lastName)
case .ageInYears: self = .ageInYears(target.ageInYears)
case .fullName: self = .fullName(target.fullName)
}
}
public init?<T>(validating: T, property: Property) {
switch validating {
case let string as String:
switch property {
case .firstName: self = .firstName(string)
case .middleName: self = .middleName(string)
case .lastName: self = .lastName(string)
default: return nil
}
case let integer as Int:
switch property {
case .ageInYears: self = .ageInYears(integer)
default: return nil
}
default:
return nil
}
}
public var property: Property {
switch self {
case .firstName: return .firstName
case .middleName: return .middleName
case .lastName: return .lastName
case .ageInYears: return .ageInYears
case .fullName: return .fullName
}
}
public func set(inout target: Target) {
switch self {
case let .firstName(value): target.firstName = value
case let .middleName(value): target.middleName = value
case let .lastName(value): target.lastName = value
case let .ageInYears(value): target.ageInYears = value
default:
fatalError("Property \(property) is not settable")
}
}
}
public subscript(property: Property) -> PropertyState {
return PropertyState(target: self, property: property)
}
}
@mrtj

This comment has been minimized.

Copy link

@mrtj mrtj commented Mar 3, 2017

Hi, I read your gist with great interest. In the third file you say it is "Implementation automatically provided by compiler". Could you give more information about what this compiler is?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment