What is a KeyPath used for?
Objective-C has the ability to reference a property dynamically rather than directly. These references, called keypaths. They are distinct from direct property accesses because they don't actually read or write the value, they just stash it away for use.
Let define a struct called Cavaliers and a struct called Player, then create one instance of each:
// an example struct
struct Player {
var name: String
var rank: String
}
// another example struct, this time with a method
struct Cavaliers {
var name: String
var maxPoint: Double
var captain: Player
func goTomaxPoint() {
print("\(name) is now travelling at warp \(maxPoint)")
}
}
// create instances of those two structs
let james = Player(name: "Lebron", rank: "Captain")
let irving = Cavaliers(name: "Kyrie", maxPoint: 9.975, captain: james)
// grab a reference to the `goTomaxPoint()` method
let score = irving.goTomaxPoint
// call that reference
score()
The last lines create a reference to the goTomaxPoint() method called score. The problem is, we can't create a reference to the captain's name property but keypath can do.
let nameKeyPath = \Cavaliers.name
let maxPointKeyPath = \Cavaliers.maxPoint
let captainName = \Cavaliers.captain.name
let cavaliersName = irving[keyPath: nameKeyPath]
let cavaliersMaxPoint = irving[keyPath: maxPointKeyPath]
let cavaliersNameCaptain = irving[keyPath: captainName]
Please test with Xcode 9 or capable snapshot.
What's the purpose of keyPath in IDBObjectStore.createIndex()?
A great way to understand things is to think how you would design it yourself.
Let's take a step back, and look at how a very simple NoSQL database would store data, then add indices.
Each Store would look like a JavaScript object (Or dictionary in python, a hash in C# ) etc...
{
"1": {"name": "Lara", "age": "20"},
"2": {"name": "Sara", "age": "22"},
"3": {"name": "Joe", "age": "22"},
}
This structure is basically a list of values, plus an index to retrieve the values, like so:
//Raw JS:
var sara = data["2"]
// IndexedDB equivalent:
store.get(2)
This is super fast for direct object retrieval, but sucks for filtering. If you want to get people of a specific age, you need to loop over every value.
If you knew in advance you would be doing your queries by age, you could really speed up such queries by creating a new object which indexes the value by age:
{
"20": [<lara>],
"22": [<joe>, <sara>],
...
}
But how would you query that? You can't use the default indexing syntax (e.g. data[22]
or store.get(22)
) because those expect the key.
One way would be to name that second index, e.g. by_age_index
and give our store object a way to access that index by name, so you could to this:
store.index('by_age_index').get(22) // Returns Joe and Sara
The last bit of the puzzle would be telling that index how to determine which records go against which key (because it has to keep itself updated when records are added/changed/removed)
In other words, how does it know Joe and Sara go against key 22, but Lara goes against key 20?
In our case, we want to use the field age from each record. This is what we mean by a keyPath.
So when defining an index, it makes sense that we would specify that as well as the name, e.g.
store.createIndex('by_age_index', 'age');
Of course, if you want to access your index like this:
store.index('age').get(22) // Returns Joe and Sara
Then you need to create your index like this:
store.createIndex('age', 'age');
Which is what most people do, which is why we see that in examples, which gives the impression that the first argument is the keyPath (whereas it's actually just the arbitrary name we give that index) leaving us unsure about what the second argument might be for.
I could have explained all this by saying:
The first parameter is the handle by which you access the index on the store, the second parameter is the name of the field on the record by which that index should group its records.
But maybe this rundown will help other people too :-)
what's the difference between single key and keypath?
A key is a string that identifies a property of an object. A key path is a list of keys separated by dots, used to identify a nested property.
Here's an example. If an object person
has a property address
, which itself has a property town
you could get the town value in two steps using keys:
id address = [person valueForKey:@"address"];
id town = [address valueForKey:@"town"];
or in one step using a keyPath:
id town = [person valueForKeyPath:@"address.town"];
Have a look at Apple's docs on Key-Value Coding for further details.
What is the use of #keyPath rather than directly passing key as a String?
There is no difference between
key:"dateCreated"
and
key: #keyPath(Note.dateCreated)
both will do the sort with Note
object's dateCreated
property
the latter has an advantage of avoiding hard coding problems e.x writing datCreated
instead of dateCreated
will throw a compile time error , so it'll safely avoid run-time crashes that definitely will happen with the former under same circumstances
https://www.klundberg.com/blog/swift-4-keypaths-and-you/
http://chris.eidhof.nl/post/sort-descriptors-in-swift/
How do I use #keyPath() in Swift 4?
According to the docs:
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.
Swift keyPath vs protocol
If you already know the type of the instance, you can access their properties easily. If you don’t, protocol already supports read-only, read-write properties. Can someone explain me what I am missing?
What you're missing is a sense of what's unknown.
In both your sentences you speak of knowing what the instance's properties are. That's not the problem key paths solve. Key paths have nothing to do with knowing the type; they are not in any kind of opposition to types or protocols. On the contrary, before you can use a key path, you have to know exactly what the instance's properties are.
Key paths are for when what is unknown is which property to access. They provide a way to pass a property reference so that someone else can be told to access that property.
For example, here's a Person type:
struct Person {
let firstName : String
let lastName : String
}
And here is a function that sorts an array of Persons by either the firstName
or the lastName
, without knowing which one to sort by:
func sortArrayOfPersons(_ arr:[Person], by prop: KeyPath<Person, String>) -> [Person] {
return arr.sorted { $0[keyPath:prop] < $1[keyPath:prop] }
}
A key path is how you tell this function what property to use as a basis for sorting.
how do you write a KeyPath to reference the value of a dictionary
Your question is unclear because you refer to User
in your text, but have Person
in your code. That suggests that you might have the syntax right but a hierarchy wrong.
You also may just be forgetting the question mark.
To get the type you're asking about, you also need a KidID
(the D is capitalized in Swift) value, not the KidID
type.
typealias KidID = String
enum KidName { case goatBoy }
\Person.kids["quot;] as WritableKeyPath<Person, KidName?>
Person(kids: ["quot;: .goatBoy])[keyPath: \.kids["quot;]] // .goatBoy
You don't have to supply a value in the key path, but that is a different type.
\Person.kids as WritableKeyPath<Person, [KidID: KidName]>
Person(kids: ["quot;: .goatBoy])[keyPath: \.kids]["quot;] // .goatBoy
Getting string from Swift 4 new key path syntax?
For Objective-C properties on Objective-C classes, you can use the _kvcKeyPathString
property to get it.
However, Swift key paths may not have String equivalents. It is a stated objective of Swift key paths that they do not require field names to be included in the executable. It's possible that a key path could be represented as a sequence of offsets of fields to get, or closures to call on an object.
Of course, this directly conflicts with your own objective of avoiding to declare properties @objc
. I believe that there is no built-in facility to do what you want to do.
map(keyPath) where keyPath is a variable
Array.map
expects a closure with signature (Element) throws -> T
.
In Swift 5.2, key paths were allowed to be passed in as functions/closures (here's an evolution proposal), but only as literals (at least, according to the proposal, it says "for now", so perhaps this restriction would be lifted).
To overcome this, you can create an extension on Sequence
that accepts a key path:
extension Sequence {
func map<T>(_ keyPath: KeyPath<Element, T>) -> [T] {
return map { $0[keyPath: keyPath] }
}
}
(credit to: https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/)
Then you can do what you wanted:
let keyPath = \(Int, Int).0
arr.map(keyPath)
Related Topics
How to Execute Code Once and Only Once in Swift
How to Cache Images Using Urlsession in Swift
String(Contentsofurl:) in Swift 3
Swift 4.0 Mapview Running Slow
Storing/Passing Function Types from Swift Protocols
How to Set the Alpha of an Uiimage in Swift Programmatically
Swift Combine: How to Create a Single Publisher from a List of Publishers
Swift 2 Migration Savecontext() in Appdelegate
Exporting Mp4 Through Avassetexportsession Fails
Swiftui - Possible Memory Leak
Only First Track Playing of Avmutablecomposition()
Can't Hook Up an Outlet Collection in Xcode 6 Using Storyboard
Function Throws and Returns Optional.. Possible to Conditionally Unwrap in One Line
How to Retrieve a Random Object from Firebase Using a Sequential Id
Could Not Find an Overload for '/' That Accepts the Supplied Arguments
What Is the Advantage of a Lazy Var in Swift
Binding an Element of an Array of an Observableobject:'Subscript(_:)' Is Deprecated