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!”
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.
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]
There are more ways than one to do concurrent operations on iOS. In this article, we’ll cover how to use Operations and OperationQueue to execute concurrent operations. There’s a lot to be said (and written) about concurrency on iOS and I don’t really want to bore you to death by writing a massive article about it, so I’ll focus here on Operations because chances are these will cover most of your needs for concurrent operations. We’ll touch a bit on GCD because sometimes you just need to dig in deep and do it yourself.
Concurrency
This is just a fancy way of saying ‘doing stuff at the same time’. The iPhone is capable of executing multiple threads at the same time. I could go into the nitty gritty details about threads and queues, but you don’t really care about them. You just want to use your OperationQueue… Fair enough
Just remember one thing – on your iOS device you’ll see two types of queues; the main queue, and everything else. In your day-to-day development, you can split all of your operations as being run on the main queue or off the main queue. Clearly, the main queue is important… So what is it exactly? Well, it’s where your UI gets updated. All the user interactions and all the animations get executed on the main queue. You don’t really want to run your code on the main queue because you’ll be blocking the UI and the user will see the UI as being unresponsive so they might even kill your app.
The Queues
All your UI updates should be performed on the main queue and you should do as much work as possible in the background, which is sometimes referred to as ‘off the main queue’. You want to keep the main queue clear.
The main queue is like a priority boarding queue at an airport. Some people buy priority boarding but most don’t and the people in the priority boarding queue board the plane before the people in the other queue (at least that’s how Ryanair does it ) Imagine what would happen if everyone bought a priority boarding pass. That queue would be full and the other queue would be empty so the priority queue would lose its purpose. The priority queue, just like the main queue, works only if you use it sparingly. There can be only one main queue and you can have as many background queues as you want.
The most common example would be refreshing a table view. You pull to refresh, start animating the spinner, kick off your networking request in the background and then when you’re finished processing and parsing the response you reload the table on the main queue.
Let’s use this example to demonstrate the usage of Operations and OperationQueue. I’ll use one of my projects on GitHub – DADependencyInjection – to demonstrate this.
Operation
Operation is an abstract class and represents a logical unit of work. For example, in your code you probably have a method that fetches data from the API. If you want to use OperationQueue you would have to create your class that subclasses Operation and move that method in your new class. Doing this has an added benefit of forcing you to use the single responsibility principle.
Once you have your class you add it to your OperationQueue and wait for it to finish executing. If you just want to execute a small piece of code or call a method you can use BlockOperation and NSInvocationOperation instead of subclassing Operation.
In our example we’ll use two operations, but first, we’ll create a base class for our operations just to get some of the clutter out of the way:
executing(true) provider.restCall(urlString: urlString) { (data) in self.responseData = data self.executing(false) self.finish(true) } } }
We’re initializing this class with a URL string and a networking provider. We’re exposing a ‘responseData’ property where we’ll save the networking response. This class is nothing more than a wrapper around our networking provider. Parse operation is even simpler:
executing(true) guard let data = moviesData, let jsonObject = try? JSONSerialization.jsonObject(with: data, options: [JSONSerialization.ReadingOptions.allowFragments]) else { executing(false) finish(true) return }
movies = factory.movieItems(withJSON: jsonObject)
executing(false) finish(true) } }
We have two important properties at the top: ‘moviesData’ and ‘movies’. Networking operation will set the ‘moviesData’ property. We’ll feed this data into our factory and save the movies array into the instance variable ‘movies’.
When you start your operation the main method gets called. This method will be called by the OperationQueue automatically so all you have to do is add your operations to the queue.
OperationQueue
You create your OperationQueue and start adding your operations to it. OperationQueue is a prioritised FIFO queue which means you can set priority on individual operations. Those with the highest priority get pushed ahead, but not necessarily to the front of the queue, as iOS determines when to actually execute the operation. Operations with the same priority get executed in the order they were added to the queue unless an operation has dependencies.
You can set the priority of your operations by setting your queuePriority property of the operation. You can also control how much of the system resources will be given to your operation by setting qualityOfService property. OperationQueue can execute multiple operations at the same time. You can set the number of maxConcurrentOperationCount or you can let the system set this property to the default value which is different for each device.
A beautiful thing about operations is that they can be cancelled which is something you can’t do with blocks. Let’s say you have three operations: networking call, parsing of data and saving in the database. If your networking call fails, there’s little sense in executing the remaining two operations so you can simply cancel them.
In our example we’ll create a new data provider for movies. We’ll create a new property, ‘operationQueue’, and initialize it to the OperationQueue:
private let operationQueue: OperationQueue = OperationQueue()
Now all we need to do is create our operations and add them to the queue:
let networkingOperation = GetDataOperation(withURLString: urlString, andNetworkingProvider: networkingProvider) let parsingOperation = ParseDataOperation(withFactory: moviesFactory)
As soon as you add your operations to the queue they will start executing. As I said before the main method will get called for each operation when it starts executing.
Dependencies
In some cases your operations might depend on other operations to finish.
Your ParseDataOperation depends on the successful completion of the GetDataOperation. If your operation has dependencies it will not start until they all finish. This little feature is really powerful if you’re dealing with complex logic because it greatly simplifies your code.
We have our two operations again: parsingOperation and networkingOperation, but this time parsingOperation has a dependency set to networkingOperation. So parsingOperation will never start before networkingOperation.
Every operation has a ‘completionBlock’ that gets called when the operation finishes. We’ll use these completion blocks to pass data between operations. When the networking operation completes it sets the ‘moviesData’ property on the parsing operation. And when the parsing operation completes it calls the closure and passes in the movies.
At the end of the code listing, we’re setting our dependency.
What About GCD?
GCD stands for Grand Central Dispatch. You might think that with OperationQueues and Operations there’s no need for GCD but they actually work together. Fun fact: Operations are built on top of GCD.
I primarily use GCD to dispatch my closures back on the main thread. We talked a lot about Operations and OperationQueues here, but sooner or later you will have to update your UI to reflect the state of your app. UI updates must happen on the main thread so you need a way to dispatch code back to the main thread. One of the easiest ways to do that is to use GCD.
GCD is great if you need to use advanced things like semaphores and barriers. I won’t go into details on how to use them here but you can see an example of using barriers in my article on Singletons in Swift.
The most common use of GCD is to run a piece of code on the main thread – like the ‘onCompleted’ closure in our example here:
OperationQueue is a great tool to use if you have complex logic in your app and if you have to manage dependencies between the operations. It’s very elegant at that. Passing data between operations is a bit messy. When your operation is finished you store the data in an instance variable which you read from the dependent operation. That’s not very elegant at all. There are much neater solutions out there like, for example, PromiseKit, which I’ll cover in one of the next articles.
I hope I gave you enough of an intro into Operations so you can use them in your apps where appropriate. You can find all the example code in my GitHub repo.