A value-binding pattern binds matched values to variable or constant names. Value-binding patterns that bind a matched value to the name of a constant begin with the let keyword; those that bind to the name of variable begin with the var keyword.
Identifiers patterns within a value-binding pattern bind new named variables or constants to their matching values. For example, you can decompose the elements of a tuple and bind the value of each element to a corresponding identifier pattern.
let point = (3, 2)
switch point {
// Bind x and y to the elements of point.
case let (x, y):
print("The point is at ((x), (y)).")
}
// Prints "The point is at (3, 2)."
In the example above, let distributes to each identifier pattern in the tuple pattern (x, y). Because of this behavior, the switch cases case let (x, y): and case (let x, let y): match the same values.
A value-binding pattern binds matched values to variable or constant names. Value-binding patterns that bind a matched value to the name of a constant begin with the let keyword; those that bind to the name of variable begin with the var keyword.
Identifiers patterns within a value-binding pattern bind new named variables or constants to their matching values. For example, you can decompose the elements of a tuple and bind the value of each element to a corresponding identifier pattern.
let point = (3, 2)
switch point {
// Bind x and y to the elements of point.
case let (x, y):
print("The point is at ((x), (y)).")
}
// Prints "The point is at (3, 2)."
In the example above, let distributes to each identifier pattern in the tuple pattern (x, y). Because of this behavior, the switch cases case let (x, y): and case (let x, let y): match the same values.
In Objective-C, a key is a string that identifies a specific property of an object. A key path is a string of dot-separated keys that specifies a sequence of object properties to traverse.
Significantly, the discussion of #keyPath is found in a section titled “Interacting with Objective-C APIs”. KVO and KVC are Objective-C features.
All the examples in the docs show Swift classes which inherit from NSObject.
Finally, when you type #keyPath in Xcode, the autocomplete tells you it is expecting an @objc property sequence.
Expressions entered using #keyPath will be checked by the compiler (good!), but this doesn’t remove the dependency on Objective-C.
I love video games. That may not come as a surprise, given that I am a programmer by trade. And for years I’ve been plagued by one tiny problem in the domain. The current Xbox One’s power button is so easy to accidentally tap and cut your precious gaming session short. It’s flat, hidden and not very practical. So, during E3 2016 — when Microsoft proudly displayed the next Xbox, it was telling that I was most taken with one tiny detail.
A tactile power button. A small, incremental, and applicable improvement that just makes thing so much better.
So today — we’ll talk about the Swift 3 equivalent of that — #keyPath().
The Typo — No Longer
Let’s agree on two simple truths in iOS programming. One will inevitably make a typo, and one will also use K.V.O somewhere down the beaten path. The former is true regardless of your programming language of choice.
But, since the API is driven (mostly) off of a fragile string literal — things go wrong far too easily:
True, some of this is mitigated by global variables, constants and enumerations. It’s a shame, though — since Foundation, UIKit and everything else in between makes clever use of the tech. This is like giving your freshly minted 16 year old son a Bugatti Chiron for his first vehicle.
And, being able to observe changes for any key-path on an ad-hoc basis can yield flexibility and promote abstraction, but the truth remains that it’s far too easy to scrub. Swift 3 smashes this problem to absolute oblivion by enforcing compile time checks on such key-paths.
Using #keyPath(), a static type check will be performed by virtue of the key-path literal string being used as a StaticString or StringLiteralConvertible. At this point, it’s then checked to ensure that it A) is actually a thing that exists and B) is properly exposed to Objective-C.
So, with our first code sample, clang would kindly tell us that “OH_GEEZ_HOPE_DIS_RIGHT” doesn’t exist in the current context. Before, this was assuredly Objective-C Roulette, as the alternative was finding out the key-path was bogus by the way of a runtime exception. No longer:
let aController = UIViewController()
//This line produces a compile time warning since it's now //A compiler checked expression aController.addObserver(self, forKeyPath: #keyPath(“OH_GEEZ_HOPE_DIS_RIGHT”), options:[], context: nil)
Level with me and think about just how easy it is to get this wrong:
Unless one reads with extra intent, it’s easy for the eyes to scan right past the error in the previous code, “Foo.aPop”, which is not a valid key-path. Using the #keyPath() expression, we wouldn’t even compile — which is exactly what we’d want to happen.
By ensuring matches to valid key-paths — even the common refactor is no longer a dangerous proposition:
class Foo { let aRenamedProp:String = “A Value” }
let anObject = NSObject()
//Compile time error is produced since #keyPath() contains the //Old property name anObject.addObserver(anObject, forKeyPath: #keyPath(“Foo.aProp”), options: [], context: nil)
Better yet — #keyPath() packs in autocompletion as well, so it’s even less likely for one to author such code to begin with. Bravo.
I know this piece is lighter on content than most entries, but that’s telling in of itself. I love this feature, and it’s elementary to explain — a hallmark of beautiful refactoring in the world or programming. There really isn’t much to say, other than it’s great and certainly a delightful enhancement.
Be sure to say hi to #keyPath()’s close cousin while you’re in town too, #selector().
Final Thoughts
Listen, I think #keyPath() is my spirit animal.
I’ve spent an embarrassing amount of time chasing down a bug that was produced by a misspelled string. KVO and KVC were always great tools for any iOS developer to wield, but now one can utilize such techniques with a greater peace of mind that only clang can bring.
To me, this Swift 3 tweak is just begging to yield war stories to the next generation of Swift developers. I can see us all now, “Back in my day, you had to just type a key-path and hope it was right! None of this sticky, compile time checking. That’s right, we were so alive back then! And — we didn’t even have ABI compatibility!”
In Objective-C, a key is a string that identifies a specific property of an object. A key path is a string of dot-separated keys that specifies a sequence of object properties to traverse.
Significantly, the discussion of #keyPath is found in a section titled “Interacting with Objective-C APIs”. KVO and KVC are Objective-C features.
All the examples in the docs show Swift classes which inherit from NSObject.
Finally, when you type #keyPath in Xcode, the autocomplete tells you it is expecting an @objc property sequence.
Expressions entered using #keyPath will be checked by the compiler (good!), but this doesn’t remove the dependency on Objective-C.
I love video games. That may not come as a surprise, given that I am a programmer by trade. And for years I’ve been plagued by one tiny problem in the domain. The current Xbox One’s power button is so easy to accidentally tap and cut your precious gaming session short. It’s flat, hidden and not very practical. So, during E3 2016 — when Microsoft proudly displayed the next Xbox, it was telling that I was most taken with one tiny detail.
A tactile power button. A small, incremental, and applicable improvement that just makes thing so much better.
So today — we’ll talk about the Swift 3 equivalent of that — #keyPath().
The Typo — No Longer
Let’s agree on two simple truths in iOS programming. One will inevitably make a typo, and one will also use K.V.O somewhere down the beaten path. The former is true regardless of your programming language of choice.
But, since the API is driven (mostly) off of a fragile string literal — things go wrong far too easily:
True, some of this is mitigated by global variables, constants and enumerations. It’s a shame, though — since Foundation, UIKit and everything else in between makes clever use of the tech. This is like giving your freshly minted 16 year old son a Bugatti Chiron for his first vehicle.
And, being able to observe changes for any key-path on an ad-hoc basis can yield flexibility and promote abstraction, but the truth remains that it’s far too easy to scrub. Swift 3 smashes this problem to absolute oblivion by enforcing compile time checks on such key-paths.
Using #keyPath(), a static type check will be performed by virtue of the key-path literal string being used as a StaticString or StringLiteralConvertible. At this point, it’s then checked to ensure that it A) is actually a thing that exists and B) is properly exposed to Objective-C.
So, with our first code sample, clang would kindly tell us that “OH_GEEZ_HOPE_DIS_RIGHT” doesn’t exist in the current context. Before, this was assuredly Objective-C Roulette, as the alternative was finding out the key-path was bogus by the way of a runtime exception. No longer:
let aController = UIViewController()
//This line produces a compile time warning since it's now //A compiler checked expression aController.addObserver(self, forKeyPath: #keyPath(“OH_GEEZ_HOPE_DIS_RIGHT”), options:[], context: nil)
Level with me and think about just how easy it is to get this wrong:
Unless one reads with extra intent, it’s easy for the eyes to scan right past the error in the previous code, “Foo.aPop”, which is not a valid key-path. Using the #keyPath() expression, we wouldn’t even compile — which is exactly what we’d want to happen.
By ensuring matches to valid key-paths — even the common refactor is no longer a dangerous proposition:
class Foo { let aRenamedProp:String = “A Value” }
let anObject = NSObject()
//Compile time error is produced since #keyPath() contains the //Old property name anObject.addObserver(anObject, forKeyPath: #keyPath(“Foo.aProp”), options: [], context: nil)
Better yet — #keyPath() packs in autocompletion as well, so it’s even less likely for one to author such code to begin with. Bravo.
I know this piece is lighter on content than most entries, but that’s telling in of itself. I love this feature, and it’s elementary to explain — a hallmark of beautiful refactoring in the world or programming. There really isn’t much to say, other than it’s great and certainly a delightful enhancement.
Be sure to say hi to #keyPath()’s close cousin while you’re in town too, #selector().
Final Thoughts
Listen, I think #keyPath() is my spirit animal.
I’ve spent an embarrassing amount of time chasing down a bug that was produced by a misspelled string. KVO and KVC were always great tools for any iOS developer to wield, but now one can utilize such techniques with a greater peace of mind that only clang can bring.
To me, this Swift 3 tweak is just begging to yield war stories to the next generation of Swift developers. I can see us all now, “Back in my day, you had to just type a key-path and hope it was right! None of this sticky, compile time checking. That’s right, we were so alive back then! And — we didn’t even have ABI compatibility!”
Swift 4 is almost upon us, and I thought I would explore one of its features that I haven’t had the opportunity to that much: KeyPaths. There’s a lot of interesting nuance in here that I previously didn’t realize existed which I’d love to share.
In short, KeyPaths are a type-safe way to separate referencing a type’s property from evaluating that property and getting a result back. You can already do that today in Swift 3 with functions, but until Swift 4 is released you can’t do so with properties without wrapping them in a closure, or with using the old unsafe #keyPath() syntax if your code is running on ios/mac/etc:
struct Person {
let name: String
func greet() {
print("Hello (name)!")
}
}
let p = Person(name: "Samus")
let greeter = p.greet // stores the method without evaluating it.
greeter() // calls the stored method
// this is the only way in Swift 3.1 and below to defer evaluating the name property of a Person.
let getName = { (p: Person) in p.name }
print(getName(p)) // evaluate the property
With Swift 4 you can rewrite the last two lines above like so:
let getName = Person.name
print(p[keyPath: getName])
// or just this:
print(p[keyPath: Person.name])
Person.name is the way to construct a KeyPath<Person, String> where the first generic parameter is the root type (what type we are querying), and the second generic parameter is the type of the value we are asking for. Using this, we can ask for the value of the name property from any instance of Person, but without the overhead of defining a closure every time we might want to ask for it. You can query more than one level as well. If you wanted to get a KeyPath for the length of a person’s name, you could write Person.name.count.
In this example we created a KeyPath which is read-only, because the nameproperty on Person was defined as a let. What happens if we change it to a var?
struct Person {
var name: String
func greet() {
print("Hello (name)!")
}
}
let kp = Person.name
var p = Person(name: "Samus")
p[keyPath: kp] = "Ridley"
p.greet() // prints "Hello Ridley!"
In this case, since name is defined as a var, Person.name is a WritableKeyPath<Person, String> which means we can use the subscript to set values in addition to getting them. If we tried to use the subscript setter for a let property we’d actually get a compiler error since settable subscripts are not defined for the plain KeyPath type, only WritableKeyPathwhich is a pretty nice way to make sure you can’t do something that isn’t possible.
There are more KeyPath types than just KeyPath and WritableKeyPath as well. If you look at the Swift Evolution proposal for KeyPaths or the generated interface for the KeyPath classes in Xcode, you’ll see that the KeyPath types are actually a 5-deep linear class hierarchy:
AnyKeyPath
|
v
PartialKeyPath<Root>
|
v
KeyPath<Root, Value>
|
v
WritableKeyPath<Root, Value>
|
v
ReferenceWritableKeyPath<Root, Value>
We went over the third and fourth type which are pretty easy to grok, but the others may not be. ReferenceWritableKeyPath is a subclass of WritableKeyPath which means that it can also be used both in a KeyPath getter and setter, but that’s the type of KeyPath you get if you referenced a mutable property on a class:
class BankAccount {
var balance: Decimal = 0
var owner: Person
}
// Creates a ReferenceWritableKeyPath<BankAccount, Decimal>:
let kp = BankAccount.balance
ReferenceWritableKeyPath is necessary so that the compiler can know whether it’s safe to allow you to use a KeyPath setter on a value that’s stored in a constant. If that value is a class type, then mutating the value’s properties is possible in any context, therefore this KeyPath is the only one that can be used for it. If it was a struct or other value type stored in a constant, you would not be able to set something with the keyPath:subscript since that would change the value itself, which is not allowed for let constants.
Moving up the tree we have PartialKeyPath<Root>. This KeyPath has a concrete root, but an unknown value type at compile time. It could be useful in cases where you might want to store all of a type’s KeyPaths in a generic data structure or if you want to do other things that don’t rely on a KeyPath’s evaluated value, since the resulting value is returned as the Any type:
이코드에 오타가 있는 것 같아 수정
let kp: PartialKeyPath<Person>: Person.name
let kp: PartialKeyPath<Person>= Person.name
이코드에 아래 내용이 없으므로 추가 해야 할듯
let person = Person();
struct Person {
var name: String
let birthdate: Date
}
let kp: PartialKeyPath<Person>: Person.name
type(of: person[keyPath: kp]) // returns Any
let personPaths: [PartialKeyPath<Person>] = [
Person.name,
Person.birthdate,
] // only possible with PartialKeyPath since name and birthdate are different types
Finally there’s the base class for everything, AnyKeyPath. Here we don’t know the type of the value OR the type of the root at compile time. These can be queried at runtime though if necessary:
let kp: AnyKeyPath = Person.birthdate
kp.rootType // evaluates to Person.self
kp.valueType // evaluates to Date.self
These properties are available to all the subclasses as well, though they’re not very interesting once you get to KeyPath since you already have the compile time generic type parameters at that point.
Combining KeyPaths
There’s another cool thing you can do with KeyPaths: combining them together! Every KeyPath type has a number of appending methods that let you stick together two KeyPaths to make one mega-KeyPath:
let accountOwnerPath = BankAccount.owner
let namePath = Person.name
let accountOwnerNamePath = accountOwnerPath.appending(namePath) // returns KeyPath<BankAccount, String>
let account: BankAccount = ...
account[keyPath: accountOwnerNamePath] // returns the name of the account owner
You can only append two KeyPaths if the Value of the first one in the chain is of the same type as the Root of the second one in the chain. If you try to append two wholly-unrelated KeyPaths, you’ll get different behavior depending on which type of KeyPath you’re working with. If you’re working with KeyPath and its subclasses exclusively, the compiler can check your work and give you a compiler error if the types don’t match up nicely. However if you’re working with AnyKeyPath or PartialKeyPath, appending those together with any KeyPath type will result in returning an optional KeyPath, where you’ll get nil at runtime if the KeyPaths’ types don’t line up properly.
Combining KeyPaths has some other interesting behavior around how the types of the KeyPaths you combine affect the KeyPath you get back. For instance, appending a KeyPath and a WritableKeyPath gives you a read-only KeyPath since it’s not possible to mutate a property in the normal case either:
struct Person {
let birthPlanet: Planet
}
struct Planet {
var name: String
}
var person: Person = ...
person.birthPlanet.name = "SR388" // error: can't mutate birthPlanet.name
person[keyPath: Person.birthPlanet.name] = "Zebes" // error for the same reason
When working with ReferenceWritableKeyPath though, it doesn’t always take the least strict version. If you append a ReferenceWritableKeyPath to the end of anything else (except AnyKeyPath), the result is a ReferenceWritableKeyPath since the KeyPath can safely mutate anything at the end of the chain if there’s reference semantics somewhere along the way. If you append a read-only KeyPath to the end of a ReferenceWritableKeyPath though, it’s still just a KeyPath since the tail end of the chain is still immutable.
The full table of ways you can combine KeyPaths and get different types back is below, for reference:
You’ll notice that you can’t append KeyPath or its subclasses with AnyKeyPath or PartialKeyPath, unless you upcast the first KeyPath to one of those type erased variants too. I’m not sure why these weren’t included. My best guess is that the swift team didn’t want developers to accidentally move into a type-erased world when they didn’t intend to, and upcasting would force developers to consciously opt-in to that behavior.
And so much more!
Well, three things more. You can use optional chaining to model optional properties in the same way it works by directly referencing them:
struct Person {
var address: Address?
}
struct Address {
var fullAddress: String
}
let kp = Person.address?.fullAddress // returns WritableKeyPath<Person, String?>
However I haven’t found a way to append KeyPaths with optionals somewhere in the chain. If a KeyPath ends in an optional Value, it’s unclear to me how you would append a KeyPath to that since you can’t (as far as I know) create a KeyPath that would lift its types up to an optional Root and Value directly. I expect explicit compiler or standard library support would need to be added to make this work.
You can also ostensibly use subscripting in KeyPaths:
struct Person {
var previousAddresses: [Address]
}
let kp = Person.previousAddresses[0].fullAddress // WritableKeyPath<Person, String>
However as of the swift snapshot in Xcode 9 beta 6, this isn’t working (with an actually useful error message saying that it’s not implemented yet).
There’s also ostensibly a way to use inferred types in key paths:
let p: Person = ...
p[keyPath: .name] // .name should evaluate to WritableKeyPath<Person, String>,
// referencing the name property on Person since we're calling it on an instance of Person
However this also appears to not be implemented yet (with a much less useful error message). I expect it will make it into Swift 4.1 if it’s too late for it to get into Swift 4.0 (as of this article’s publishing).
Conclusion
KeyPaths are a really cool feature that I haven’t heard discussed that much in the Swift developer community. I expect that more people will get excited about them once Swift 4 is released and people have more chances to play with them. There’s a lot of cool stuff in Foundation that utilizes the feature too that’s beyond the scope of this post (which I’ll probably get to in the future).
I hope this was informative, and that it inspires you to find useful or interesting ways to take advantage of this feature.
Swift 4 is almost upon us, and I thought I would explore one of its features that I haven’t had the opportunity to that much: KeyPaths. There’s a lot of interesting nuance in here that I previously didn’t realize existed which I’d love to share.
In short, KeyPaths are a type-safe way to separate referencing a type’s property from evaluating that property and getting a result back. You can already do that today in Swift 3 with functions, but until Swift 4 is released you can’t do so with properties without wrapping them in a closure, or with using the old unsafe #keyPath() syntax if your code is running on ios/mac/etc:
struct Person {
let name: String
func greet() {
print("Hello (name)!")
}
}
let p = Person(name: "Samus")
let greeter = p.greet // stores the method without evaluating it.
greeter() // calls the stored method
// this is the only way in Swift 3.1 and below to defer evaluating the name property of a Person.
let getName = { (p: Person) in p.name }
print(getName(p)) // evaluate the property
With Swift 4 you can rewrite the last two lines above like so:
let getName = Person.name
print(p[keyPath: getName])
// or just this:
print(p[keyPath: Person.name])
Person.name is the way to construct a KeyPath<Person, String> where the first generic parameter is the root type (what type we are querying), and the second generic parameter is the type of the value we are asking for. Using this, we can ask for the value of the name property from any instance of Person, but without the overhead of defining a closure every time we might want to ask for it. You can query more than one level as well. If you wanted to get a KeyPath for the length of a person’s name, you could write Person.name.count.
In this example we created a KeyPath which is read-only, because the nameproperty on Person was defined as a let. What happens if we change it to a var?
struct Person {
var name: String
func greet() {
print("Hello (name)!")
}
}
let kp = Person.name
var p = Person(name: "Samus")
p[keyPath: kp] = "Ridley"
p.greet() // prints "Hello Ridley!"
In this case, since name is defined as a var, Person.name is a WritableKeyPath<Person, String> which means we can use the subscript to set values in addition to getting them. If we tried to use the subscript setter for a let property we’d actually get a compiler error since settable subscripts are not defined for the plain KeyPath type, only WritableKeyPathwhich is a pretty nice way to make sure you can’t do something that isn’t possible.
There are more KeyPath types than just KeyPath and WritableKeyPath as well. If you look at the Swift Evolution proposal for KeyPaths or the generated interface for the KeyPath classes in Xcode, you’ll see that the KeyPath types are actually a 5-deep linear class hierarchy:
AnyKeyPath
|
v
PartialKeyPath<Root>
|
v
KeyPath<Root, Value>
|
v
WritableKeyPath<Root, Value>
|
v
ReferenceWritableKeyPath<Root, Value>
We went over the third and fourth type which are pretty easy to grok, but the others may not be. ReferenceWritableKeyPath is a subclass of WritableKeyPath which means that it can also be used both in a KeyPath getter and setter, but that’s the type of KeyPath you get if you referenced a mutable property on a class:
class BankAccount {
var balance: Decimal = 0
var owner: Person
}
// Creates a ReferenceWritableKeyPath<BankAccount, Decimal>:
let kp = BankAccount.balance
ReferenceWritableKeyPath is necessary so that the compiler can know whether it’s safe to allow you to use a KeyPath setter on a value that’s stored in a constant. If that value is a class type, then mutating the value’s properties is possible in any context, therefore this KeyPath is the only one that can be used for it. If it was a struct or other value type stored in a constant, you would not be able to set something with the keyPath:subscript since that would change the value itself, which is not allowed for let constants.
Moving up the tree we have PartialKeyPath<Root>. This KeyPath has a concrete root, but an unknown value type at compile time. It could be useful in cases where you might want to store all of a type’s KeyPaths in a generic data structure or if you want to do other things that don’t rely on a KeyPath’s evaluated value, since the resulting value is returned as the Any type:
이코드에 오타가 있는 것 같아 수정
let kp: PartialKeyPath<Person>: Person.name
let kp: PartialKeyPath<Person>= Person.name
이코드에 아래 내용이 없으므로 추가 해야 할듯
let person = Person();
struct Person {
var name: String
let birthdate: Date
}
let kp: PartialKeyPath<Person>: Person.name
type(of: person[keyPath: kp]) // returns Any
let personPaths: [PartialKeyPath<Person>] = [
Person.name,
Person.birthdate,
] // only possible with PartialKeyPath since name and birthdate are different types
Finally there’s the base class for everything, AnyKeyPath. Here we don’t know the type of the value OR the type of the root at compile time. These can be queried at runtime though if necessary:
let kp: AnyKeyPath = Person.birthdate
kp.rootType // evaluates to Person.self
kp.valueType // evaluates to Date.self
These properties are available to all the subclasses as well, though they’re not very interesting once you get to KeyPath since you already have the compile time generic type parameters at that point.
Combining KeyPaths
There’s another cool thing you can do with KeyPaths: combining them together! Every KeyPath type has a number of appending methods that let you stick together two KeyPaths to make one mega-KeyPath:
let accountOwnerPath = BankAccount.owner
let namePath = Person.name
let accountOwnerNamePath = accountOwnerPath.appending(namePath) // returns KeyPath<BankAccount, String>
let account: BankAccount = ...
account[keyPath: accountOwnerNamePath] // returns the name of the account owner
You can only append two KeyPaths if the Value of the first one in the chain is of the same type as the Root of the second one in the chain. If you try to append two wholly-unrelated KeyPaths, you’ll get different behavior depending on which type of KeyPath you’re working with. If you’re working with KeyPath and its subclasses exclusively, the compiler can check your work and give you a compiler error if the types don’t match up nicely. However if you’re working with AnyKeyPath or PartialKeyPath, appending those together with any KeyPath type will result in returning an optional KeyPath, where you’ll get nil at runtime if the KeyPaths’ types don’t line up properly.
Combining KeyPaths has some other interesting behavior around how the types of the KeyPaths you combine affect the KeyPath you get back. For instance, appending a KeyPath and a WritableKeyPath gives you a read-only KeyPath since it’s not possible to mutate a property in the normal case either:
struct Person {
let birthPlanet: Planet
}
struct Planet {
var name: String
}
var person: Person = ...
person.birthPlanet.name = "SR388" // error: can't mutate birthPlanet.name
person[keyPath: Person.birthPlanet.name] = "Zebes" // error for the same reason
When working with ReferenceWritableKeyPath though, it doesn’t always take the least strict version. If you append a ReferenceWritableKeyPath to the end of anything else (except AnyKeyPath), the result is a ReferenceWritableKeyPath since the KeyPath can safely mutate anything at the end of the chain if there’s reference semantics somewhere along the way. If you append a read-only KeyPath to the end of a ReferenceWritableKeyPath though, it’s still just a KeyPath since the tail end of the chain is still immutable.
The full table of ways you can combine KeyPaths and get different types back is below, for reference:
You’ll notice that you can’t append KeyPath or its subclasses with AnyKeyPath or PartialKeyPath, unless you upcast the first KeyPath to one of those type erased variants too. I’m not sure why these weren’t included. My best guess is that the swift team didn’t want developers to accidentally move into a type-erased world when they didn’t intend to, and upcasting would force developers to consciously opt-in to that behavior.
And so much more!
Well, three things more. You can use optional chaining to model optional properties in the same way it works by directly referencing them:
struct Person {
var address: Address?
}
struct Address {
var fullAddress: String
}
let kp = Person.address?.fullAddress // returns WritableKeyPath<Person, String?>
However I haven’t found a way to append KeyPaths with optionals somewhere in the chain. If a KeyPath ends in an optional Value, it’s unclear to me how you would append a KeyPath to that since you can’t (as far as I know) create a KeyPath that would lift its types up to an optional Root and Value directly. I expect explicit compiler or standard library support would need to be added to make this work.
You can also ostensibly use subscripting in KeyPaths:
struct Person {
var previousAddresses: [Address]
}
let kp = Person.previousAddresses[0].fullAddress // WritableKeyPath<Person, String>
However as of the swift snapshot in Xcode 9 beta 6, this isn’t working (with an actually useful error message saying that it’s not implemented yet).
There’s also ostensibly a way to use inferred types in key paths:
let p: Person = ...
p[keyPath: .name] // .name should evaluate to WritableKeyPath<Person, String>,
// referencing the name property on Person since we're calling it on an instance of Person
However this also appears to not be implemented yet (with a much less useful error message). I expect it will make it into Swift 4.1 if it’s too late for it to get into Swift 4.0 (as of this article’s publishing).
Conclusion
KeyPaths are a really cool feature that I haven’t heard discussed that much in the Swift developer community. I expect that more people will get excited about them once Swift 4 is released and people have more chances to play with them. There’s a lot of cool stuff in Foundation that utilizes the feature too that’s beyond the scope of this post (which I’ll probably get to in the future).
I hope this was informative, and that it inspires you to find useful or interesting ways to take advantage of this feature.