Key Path
Last updated
Was this helpful?
Last updated
Was this helpful?
Keypaths allow you to refer to properties without invoking them – you hold a reference to the property itself, rather than reading its value.
Key paths 主要三種變體(還有其他)
KeyPath: read-only access to a property.
WritableKeyPath: readwrite access to a mutable property with value semantics (so the instance in question also needs to be mutable for writes to be allowed).
ReferenceWritableKeyPath: can only be used with reference types, and provides readwrite access to any mutable property.
Keypaths in Swift have a few more types, which mostly revolve around type-erasure, like with . When you combine or allow multiple keypaths, for example in an array, you can use the and to construct types that fit multiple keypaths.
Swift Evolution ⟩
: Smart KeyPaths: Better Key-Value Coding for Swift
: Key Path Expressions as Functions
Swift ⟩ Standard Library ⟩
- type-erased key path, from any root type to any resulting value type.
// declarations: inheritance chain
class AnyKeyPath
class PartialKeyPath<Root> : AnyKeyPath
class KeyPath<Root, Value> : PartialKeyPath<Root>
class WritableKeyPath<Root, Value> : KeyPath<Root, Value>
class ReferenceWritableKeyPath<Root, Value> : WritableKeyPath<Root, Value>
operator (~=) - pattern matching using keypath
// ⭐ 下面兩種型別雖然都有「可以當作 ID」的屬性,但屬性名稱不一樣。
struct Person {
// social security number
var ssn: String // ⭐ id key for `Person`
var name: String
}
struct Book {
var isbn: String // ⭐ id key for `Book`
var title: String
}
/* -------- HasID protocol -------- */
// ⭐ 用 `HasID` 協定來統一規範這種有「ID 屬性」的類別。
// ⭐ 這種做法的好處是:「ID 屬性」不需要真的叫 `id`,叫其他名稱也可以。
protocol HasID {
// ⭐ 2. 用 `IDType` 來稱呼此「ID 屬性」的類別
associatedtype IDType
// ⭐ 1. 用 keypath 來指定哪個屬性是「ID 屬性」
static var idKey: WritableKeyPath<Self, IDType> { get }
}
/* -------- protocol extension (default behaviors) -------- */
extension HasID {
func printID(){
print(self[keyPath: Self.idKey])
}
}
/* -------- protocol conformances -------- */
extension Person: HasID {
static let idKey = \Person.ssn // ⭐ WritableKeyPath<Person, String>
}
extension Book: HasID {
static let idKey = \Book.isbn // ⭐ WritableKeyPath<Book, String>
}
/* -------- main -------- */
let taylor = Person(ssn: "555-55-5555", name: "Taylor Swift")
let book = Book(isbn: "1234-5678", title: "Snow White")
taylor.printID() // 555-55-5555
book.printID() // 1234-5678
print(type(of: Person.idKey)) // WritableKeyPath<Person, String>
print(type(of: Book.idKey)) // WritableKeyPath<Book, String>
// User
struct User {
var fullName: String
var email: String?
var age: Int
}
let user = User(fullName: "Mike", age: 16)
let fullNamePath = \User.fullName // ⭐️ KeyPath<User, String>
user[keyPath: fullNamePath] // get: user.fullName
user[keyPath: fullNamePath] = "John" // set: user.fullName
// appending key paths: ╭─ key path ─╮ ╭─────╮
let fullNameEmptyPath = \User.fullName.isEmpty // ⭐️ KeyPath<User, Bool>
user[keyPath: fullNameEmptyPath] // user.fullName.isEmpty
let isEmptyPath = \String.isEmpty
// ⭐️ appending key path
let fullNameIsEmpty = fullNamePath.appending(path: isEmptyPath)
user[keyPath: fullNameIsEmpty] // user.fullName.isEmpty
var user = User(username: "Hello")
// ⭐️ type-erase to `AnyKeyPath`
let keyPath: AnyKeyPath = \User.username // ⭐️ read-only
/* -------- ⭐️ type-cast -------- */
// ⭐️ if let ... as? ...
if let writableUsername = keyPath as? WritableKeyPath<User, String> {
user[keyPath: writableUsername] = "World" // ⭐️ read-write
}
// ⭐️ case let ... as ...
switch keyPath {
case let a as KeyPath<User, String>: print(user[keyPath: a])
case let a as KeyPath<User, Int> : print(user[keyPath: a])
default: print("Unknown keypath type")
}
- Sundell ⭐️