NSCoding
requires the NSObjectProtocol
, which is a class protocol, and can’t be conformed to by structs. If we have anything we want to encode using NSCoding
, the easiest thing do has always been to make it a class, and make it inherit from NSObject
.
I’ve found a decent way to wrap a struct in an NSCoding
container, and save that without much fuss. I’ll use Coordinate
as an example.
struct Coordinate: JSONInitializable {
let latitude: Double
let longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
It’s a simple type, with two scalar properties. Let’s create a class that conforms to NSCoding
and wraps a Coordinate
.
class EncodableCoordinate: NSObject, NSCoding {
var coordinate: Coordinate?
init(coordinate: Coordinate?) {
self.coordinate = coordinate
}
required init?(coder decoder: NSCoder) {
guard
let latitude = decoder.decodeObject(forKey: "latitude") as? Double,
let longitude = decoder.decodeObject(forKey: "longitude") as? Double
else { return nil }
coordinate = Coordinate(latitude: latitude, longitude: longitude)
}
func encode(with encoder: NSCoder) {
encoder.encode(coordinate?.latitude, forKey: "latitude")
encoder.encode(coordinate?.longitude, forKey: "longitude")
}
}
It’s nice to have this logic in another type, which adheres more strictly to the single responsibility principle. An astute reader of the class above can see that the coordinate
property of the EncodableCoordinate
is Optional
, but it doesn’t have to be. We could make our intializer take a non-optional Coordinate
(or make it failable), and our init(coder:)
method is already failable, and then we would guarantee that we will always have a coordinate
if we have an instance of the EncodableCoordinate
class.
However, because of a peculiarity in the way NSCoder
works, when encoding Double
types (and other primitives), they can’t be extracted with decodeObject(forKey:)
(which returns an Any?
. They have to use their specific corresponding method, which for a Double
is decodeDouble(forKey:)
. Unfortunately, these specific methods don’t return optionals, and they return 0.0
for any keys that are missing or otherwise corrupt. Because of this, I chose to keep the coordinate
property as an optional, and encode it as optional, so that I could get objects of type Double?
out using decodeObject(forKey:)
, ensuring a little extra safety.
Now, to encode and decode Coordinate
objects, we can create an EncodableCoordinate
, and write that to disk with NSKeyedArchiver
:
let encodable = EncodableCoordinate(coordinate: coordinate)
let data = NSKeyedArchiver.archiveRootObject(encodable, toFile: somePath)
Having to make this extra object isn’t ideal, and I’d like to work with an object like SKCache
from Cache Me If You Can, so if I can formalize the relationship between the encoder and the encoded, maybe I can avoid having to create the NSCoding
container manually each time.
To that end, let’s add two protocols:
protocol Encoded {
associatedtype Encoder: NSCoding
var encoder: Encoder { get }
}
protocol Encodable {
associatedtype Value
var value: Value? { get }
}
And two conformances for our two types:
extension EncodableCoordinate: Encodable {
var value: Coordinate? {
return coordinate
}
}
extension Coordinate: Encoded {
var encoder: EncodableCoordinate {
return EncodableCoordinate(coordinate: self)
}
}
With these, the type system now knows how to convert back and forth between the types and values of the pair of objects.
class Cache<T: Encoded> where T.Encoder: Encodable, T.Encoder.Value == T {
//...
}
The SKCache
object from that blog post has been upgraded to be generic over some Encoded
type, with the constraint that its encoder’s value’s type is itself, which enables bidirectional conversion between the two types.
The last piece of the puzzle is the save
and fetch
methods for this type. save
involves grabbing the encoder
(which is the actual NSCoding
-conformant object), and saving it to some path
:
func save(object: T) {
NSKeyedArchiver.archiveRootObject(object.encoder, toFile: path)
}
Fetching involves a slight compiler dance. We need to cast the unarchived object to T.Encodable
, which is the type of the encoder, and then grab its value
, and dynamically cast it back to T
.
func fetchObject() -> T? {
let fetchedEncoder = NSKeyedUnarchiver.unarchiveObject(withFile: storagePath)
let typedEncoder = fetchedEncoder as? T.Encoder
return typedEncoder?.value as T?
}
Now, to use the cache, we instantiate one and make it generic over Coordinate
:
let cache = Cache<Coordinate>(name: "coordinateCache")
Once we have that, we can transparently save and retrieve coordinate structs:
cache.save(object: coordinate)
Through this technique, we can encode structs through NSCoding
, maintain conformance to the single responsibility principle, and enforce type-safety.
This article is also available in Chinese.
Refactoring is continual process. However, this process needs to leave the code in a functional state at frequent intervals. Refactorings that don’t stay functional can’t be deployed regularly, and it’s harder for them to be kept up-to-date with the rest of your team’s code.
Some refactorings are tougher than others, though. The nature of singletons in particular causes them reach their tendrils into many different objects, and this can make them difficult to remove from your project.
Lots of singletons, especially the poorly named ones, have a tendency to accumulate unrelated behaviors, data, and responsibilities, simply because its easier to put them there than anywhere else.
If you want to break up a far-reaching singleton, or if you want to be able to test code that uses that singleton, you have a lot of work to do. You want to slowly replace references to your singleton with smaller, better objects, but you can’t remove the singleton itself until you are completely finished, since other objects rely on it.
Worst of all, you can’t extract a singleton’s behavior and methods into another object, because they’re reliant on the shared state within the singleton. Put another way, if the singleton didn’t have any shared state, you could just make a new instance at each call site and your problem would go away instantly.
So we have a singleton with many disparate responsibilities and a bunch of shared state, being touched from many parts of your app. How can we remove this singleton without actually removing it?
We need a new way to refer to our singleton: a view on the singleton that represents a slice of its many responsibilities, without actually changing the structure of the singleton itself. This slice of behavior and data can be represented with a protocol.
Imagine this kind of singleton in a hypothetical shopping app:
class SessionController {
static let sharedController: SessionController
var currentUser: User
var cart: Cart
func addItemToCart(item: Item) { }
var fetchedItems: [Item]
var availableItems: [Item]
func fetchAvailableItems() { }
}
This singleton has at least three responsibilities. We’d love to break it up, but dozens of classes from all over the codebase refer to the properties and functions on this object. If we make a protocol for each “slice” of responsibility, we can start breaking this up.
protocol CurrentUserProvider {
var currentUser: User { get }
}
protocol CurrentCart {
var cart: Cart { get }
func addItemToCart(item: Item)
}
protocol ItemFetcher {
var fetchedItems: [Item] { get }
var availableItems: [Item] { get }
func fetchAvailableItems()
}
The SessionController
can conform to these protocols without any extra work:
class SessionController: CurrentUserProvider, CurrentCart, ItemFetcher {
//...
Because of Swift’s protocol extensions, we can move anything that relies purely on the things provided in the protocol into the extension. For example, availableItems
might be any items in the fetchedItems
array that has a status
of .available
. We can move that out of the singleton and into into the specific protocol:
extension ItemFetcher {
var availableItems: [Item] {
return fetchedItems.filter({ $0.status == .available })
}
}
By doing this, we begin the process of slimming down the singleton and extracting irrelevant bits.
Now that we have these protocols, we can begin using them around the app. A fine first step is to extract any usage of the singleton into an instance variable:
class ItemListViewController {
let sessionController = SessionController.sharedController
//...
}
Next, we can change its type to the type of a specific protocol:
class ItemListViewController {
let itemFetcher: ItemFetcher = SessionController.sharedController
//...
}
Now, while the class technically still accesses the singleton, it’s done in a very limited way, and it’s clear that this class should only be using the ItemFetcher
slice of the singleton. We’re not done, however. The next step is intialize this class with an ItemFetcher
:
let itemFetcher: ItemFetcher
init(itemFetcher: ItemFetcher) {
self.itemFetcher = itemFetcher
super.init(nibName: nil, bundle: nil)
}
Now, the class has no idea what kind of ItemFetcher
it was intialized with. It could be the singleton, but it could also some other type! This is called dependency injection. It lets us inject alternate dependencies into our view controllers and other objects, which will let us test these objects more easily. The places where this view controller is initialized across the app have to be updated to use this new intializer.
Ideally, view controller initalization is only done inside of Coordinators, which should simplify how much you need to pass those singletons around. If you don’t use coordinators, any dependency that the fourth view controller in a navigation controller might need has to be passed through the first, second, and third view controllers as well. This is not ideal.
Now, this is the hard part: this process has to be repeated across every reference to this singleton across your app. It’s a tedious process, but it’s mostly rote work, once you’ve figured out what the various responsibilities and protocols are. (One tip: if you have a CurrentUserProvider
, you might want a separate MutableCurrentUserProvider
. The objects that only need to read from the current user don’t need to have access to write to that storage as well.)
Once you’ve changed all your references to your singleton, you’ve removed a lot of its teeth. You can remove its static singleton accessor property, and then view controllers and other objects will only be able to play with the objects that they are passed.
From here, you have a few options. You can now easily actually break up the singleton itself into all of the little responsibilities that you’ve created.
- You can make
ItemFetcher
a class instead of a protocol, and move all the code fromSessionController
into the newItemFetcher
class, and pass the class around instead of the singleton. - You can keep
ItemFetcher
as a protocol, and make a class calledConcreteItemFetcher
, and move the singleton code into this new class. This solution gives you a few more options, since you can inject other objects that conform to theItemFetcher
protocol, suitable for unit testing, screenshot testing, demoing the app, and other purposes. This is a little more work, but it also has more flexibility.
By creating “slices” of responsibility on top of your singletons, you can break them up into their constitutive responsibilities without changing the structure of the singleton itself. From there, you can use dependency injection to give objects only those things that you expect them to use. Finally, you can make your singletons no longer have singleton accessors, and ride off into the glorious sunset of well-factored code.
Back in the heady days of iOS 2, before NSURLSession
, developers had to build their own networking stack. There was a class called NSURLConnection
and it had a delegate, the aptly-named NSURLConnectionDelegate
.
An object conformed to NSURLConnectionDelegate
with a few key methods:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
While the new NSURLSession
APIs are much nicer, these methods gave you a lot of control (mostly control that you didn’t need). They have another quality that I think is somewhat enlightening. This protocol suggests its own implementation.
You can tell by looking at the API that didReceiveResponse
will get called once, didReceiveData
will get called at least once and maybe even more than once, and connectionDidFinishLoading
will get called once as well. didFailWithError
might get called once, and you probably won’t get any more callbacks after that one. And since you know that you’re going to have to keep track of some NSMutableData
instance variable to glue all of these disparate pieces of data together. With that knowledge, you can put together an implementation:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
self.receivedData = [NSMutableData data];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
id object = [NSJSONSerialization JSONObjectWithData:self.receivedData options:0 error:nil];
//call completion block or delegate
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
self.error = error
//call completion block or delegate
}
(This code is actually from an old app of mine, from around 2011.)
Because you need to keep track of this instance variable, it’s glaringly obvious that you can’t keep it around in a view controller or another high-level object. Every single request that a view controller needed to send would need its own NSMutableData
variable. So just by the nature of the design of this API, it almost forces you to create a new object for a request. (This is a pit of success. Make the right way and the easy way line up with each other.)
And so even those of us who were writing huge view controllers knew that it was wrong to keep that kind of data in a view controller, so we made a custom little object for interfacing with a web server. Sometimes that custom object got out of hand and started doing way too much, but that’s a blog post for another day.
Eventually, we all moved on to AFNetworking, which handled all those messy bits for you. But somewhere, deep in the bowels of AFNetworking, there was an object whose primary purpose was to act as this delegate.
While there’s plenty more I could say about the institutional reasons that caused Apple gave us this low-level API instead of something a little simpler to use, I’ve always been more intrigued by the idea of a protocol that gently forces you to create a new object, just by the virtue of its API. What if more protocols pushed you in that direction? What if we took existing protocols and forced ourselves to implement them as wholly new objects?
I want to examine three of these cases.
First, UITableViewDataSource
. This is a really common protocol to break out of a view controller. This extraction is a frustrating one, because the table view protocols are somewhat poorly factored. Why is cell height calculation in UITableViewDelegate
, whereas cell generation (like cellForRowAtIndexPath:
) is in UITableViewDataSource
?
Still, it can be a worthwhile object to extract, but remember that the object conforming to this protocol probably should have its own collaborators and child objects.
Second, I want to take a look at UIAlertViewDelegate
. Because UIAlertView
has been deprecated in favor of UIAlertController
, which has a block-based API, this protocol isn’t common anymore. However, we can still learn from it. The protocol suggests making an object whose job it is to present the alert, conform to its delegate, and handle the button taps. Even though the new block-based API doesn’t suggest this implementation in the same way as the protocol-based API did, it’s a smart place to start thinking about breaking out a responsibility. I proposed this in 8 Patterns to Destroy Massive View Controller, as the “Interaction” pattern.
Lastly, I want to talk about UINavigationControllerDelegate
. This delegate is a weird one, and I struggled for a long time with this one. Because UINavigationController
objects don’t really have a parent, it’s not obvious who should be their delegate. Most of the work they do (pushing and popping) is usually handled by their child view controllers. If a second child view controller in the stack needed access to the delegate of the navigation controller, it could just call self.navigationController.delegate = self
in viewDidLoad
, and that would work. But then if the third view controller in the stack also needed access to that information, it would take that delegateness away from the second view controller, and the second view controller wouldn’t work as expected anymore.
Traditional navigation-based iOS development has an upside-down view of the app’s control hierarchy. The child (a view controller) grabs hold of the parent (the navigation controller) and tells it how to behave. With the navigation controller having no logical parent, its delegate doesn’t naturally fit in anywhere.
The solution to both of these problems took some time for me to figure out, but it ended up being the kernel of the idea of Coordinators. Coordinators are an object suggested by the idea of a specialized object for a UINavigationControllerDelegate
, and as the natural parent for a UINavigationController
are the right place for those delegate methods to get fired.
Every object needs a purpose, and conforming to a complex delegate can be one purpose. Next time you see a delegate, ask yourself if it makes sense for this delegate to be its own object. Break it out, and see if you like it.
This article is also available in Chinese.
Swift is a successor to Objective-C only because it has the same vendor. It doesn’t look the same, doesn’t act the same, and doesn’t feel the same. Paradigms that worked great in Objective-C, like method names, are slowly being updated for a bright new Swift world. For example, in Objective-C, this method was called with its full form:
[string dataUsingEncoding:NSUTF8StringEncoding];
In Swift 2.2, it was somewhat ungainly:
string.dataUsingEncoding(NSUTF8StringEncoding)
In Swift 3, the method became much more streamlined:
string.data(using: .utf8)
The Swift 3 version of this method is right for Swift, in the same way that the Objective-C version was right for Objective-C. I think this article does a good job covering how to update your own method names for this new world.
There are other parts of the frameworks and the idioms that we use to write our apps that also need to be updated in a Swiftier world. Today, I’d like to focus on delegate naming.
Delegates in Swift don’t translate very well from Objective-C. Objective-C was very much about “senders” and “receivers”. Much of Apple’s Objective-C documentation is written in these terms. For example, the method isFirstResponder
on UIResponder
has documentation that reads:
Returns a Boolean value indicating whether the receiver is the first responder.
And of course, when setting up a target-action selector, the idiomatic name for the first argument has historically been sender
:
- (void)buttonTapped:(UIButton *)sender {
Delegates work much the same way: the first argument for a delegate method in Objective-C has always been the sender. You can see why this is useful: if you (the receiver) are the delegate of multiple objects of the same type, you’ll need a way to differentiate them. The delegate provides you with the first parameter, and you can switch on it.
I’m going to use some examples from the Backchannel SDK with some class names simplified for brevity.
There are two main types of delegate methods. First, those that just indicate that an event happened.
- (void)messageFormDidTapCancel:(BAKMessageForm *)messageForm;
In Swift, this translates to:
func messageFormDidTapCancel(_ messageForm: BAKMessageForm)
This doesn’t look right in Swift 3 anymore. In Swift 3, redundancy is eliminated (the two messageForm
names), and first arguments should generally be named rather than removed using the underscore.
The second type of delegate method indicates that an event happened and includes some data. I want to look at two examples of this one.
- (void)messageForm:(BAKMessageForm *)messageForm didTapPostWithDraft:(BAKDraft *)draft;
- (void)messageForm:(BAKMessageForm *)messageForm didTapAttachment:(BAKAttachment *)attachment;
In Swift, these translate to:
func messageForm(_ messageForm: BAKMessageForm, didTapPostWithDraft draft: BAKDraft)
func messageForm(_ messageForm: BAKMessageForm, didTapAttachment attachment: BAKAttachment)
These are horrible. Why are both of these methods called messageForm
? Also, starting a method with a noun here doesn’t make sense: it usually suggests that you’ll be returning an object of that type (think about data(using:)
on NSString
, which returns a Data
). We’re not returning any message form objects here. That “message form” is actually the name of the first parameter. These are very confusing method names!
Both of these types of delegate methods can be fixed by moving the “sender” to the back of the line, and bringing the verbs forward. For the first one, the event that the sender is informing the delegate about is didTapCancel
, instead of messageFormDidTapCancel
. Let’s start with that:
func didTapCancel(messageForm: BAKMessageForm)
This is already much better. The action is brought to the front, and becomes the name of the method, and it’s a lot clearer what this method does. I think we could use a preposition instead for the parameter name, to make it read a little nicer at the call site:
func didTapCancel(on messageForm: BAKMessageForm)
I haven’t found a hard and fast rule for which preposition to use yet. I’ve found “on”, “for”, “with”, and “in” to all be useful in different circumstances. Users tap “on” a form, so I think “on” is appropriate here.
Let’s also take a look at delegate methods that have data to pass back. Bringing the verb to the front helps, and switching to a preposition for the delegate name also cleans up these types of methods. Instead of:
func messageForm(_ messageForm: BAKMessageForm, didTapPostWithDraft draft: BAKDraft)
we have the much Swiftier:
func didTapPost(with draft: BAKDraft, on messageForm: BAKMessageForm)
and
func didTap(attachment: BAKAttachment, on messageForm: BAKMessageForm)
These rules aren’t endorsed by anyone except for me, but I think they make much more sense than the current rules by which we write delegate methods. Going forward, I’ll probably start writing my Swift delegate methods with this structure.
I’ll leave you with some UITableView
delegate and data source methods and what they could look like in this awesome future:
func numberOfSections(in tableView: UITableView) -> Int
numberOfSections
follows this scheme and looks pretty good already.
These methods, however, don’t look so great:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
Here they are with a little love:
func numberOfRows(inSection section: Int, in tableView: UITableView) -> Int
func cellForRow(at indexPath: IndexPath, in tableView: UITableView) -> UITableViewCell
func didSelectRow(at indexPath: IndexPath, in tableView: UITableView)
A few weeks ago, Matt Gallagher wrote about Swift’s error model, in comparison with the Result
type. He puts it quite succinctly:
Swift’s error handling has a major limitation: it can only be used to pass an error to an enclosing catch scope or the previous function on the stack.
If you want to handle errors across asynchronous boundaries or store value/error results for later processing, then Swift error handling won’t help.
This is the clearest explanation of the problem with Swift’s error handling that I’ve seen. Having a dedicated Result
type, Matt argues, would solve the async problem and the error storage problem. John McCall, on the mailing list, wrote about why the team didn’t include Result
:
We considered it, had some specifics worked out, and then decided to put it on hold. Part of our reasoning was that it seemed more like an implementation detail of the async / CPS-conversion features we’d like to provide than an independently valuable feature, given that we don’t want to encourage people to write library interfaces using functional-style error handling instead of
throws
.
The Swift team considers Result
not useful enough to include in the standard library. The only reason to add it, they argue, is as an implementation detail of async features that they would add in the future.
Let’s examine what those potential async features might look like, design them in the same way that the error handling was designed, and see how these new features could fill all the gaps of the current error handling model.
If we use C#’s async/await model that I discussed in the last post, a Swifty version might look something like this:
async func getCurrentUsersFollowers() throws -> [User] {
let user = try await APIClient.getCurrentUser()
let followers = try await APIClient.getFollowers(for: user)
return followers
}
This is the closest I can get it to how the error model works. In the same way that Swift’s throwing functions don’t return a Result
, this function doesn’t return an Async<[User]>
or any kind of wrapped type (e.g, Task<[User]>
, à la C#), but rather the raw value itself, and it uses the async
decorator on the function (like the throws
decorator) to express that the function must be awaited upon.
func updateUI() {
do {
self.followers = try await getCurrentUsersFollowers()
self.tableView.reloadData()
} catch let error as APIError {
self.presentError(error)
} catch error {
print(error)
}
}
To use getCurrentUsersFollowers()
, we need a function that returns Void
, so that it won’t need to be marked as async
. This is the equivalent of wrapping function marked throws
in do
/catch
blocks.
Lastly, we need some way to define a new async task. The Swift error model uses the throw
keyword opposite the return
keyword to handle the distinction between success and failure. I’m not sure how exactly Swift syntax would define a new async task, but for simplicity’s sake, let’s imagine it as a closure that runs on some background thread. When the closure is complete, it returns a value, and that’s the result of the async function:
async func getCurrentUser() {
let url = buildURLForCurrentUser()
return await {
let data = Data(contentsOf: url)
let user = //parse JSON, construct User
return user
}
}
This is some super fake syntax I made up. The goal is to stay as true to the model of Swift’s error handling as possible. Specifically, I want to prevent users from constructing a type that would represent the async task, in the same way you don’t create a Result
when using the error handling constructs.
Since we don’t have a type to work with, we can’t do anything other than perform a sequential series of asynchronous, potentially erroring tasks, such as in the getCurrentUsersFollowers()
function. I’m happy to grant that sequential, erroring tasks are 90% of the kind of work that we do with asynchronicity, in the same way that passing errors up the stack is 90% of what we need error handling for. But the remaining 10% of stuff is important too. As Larry Wall says, “make the easy jobs easy, without making the hard jobs impossible”. Swift’s error handling and this new, fake async syntax both make the hard jobs literally inexpressible, requiring programmers to pull in their own abstractions, like Result
and Promise
, to get the job done.
Promise.all
is probably the second most common use case for async tasks: given an array of async tasks, create one async task that completes when every subtask has completed. If we wanted to implement Promise.all
with this new async syntax we’d need some combination of an array of functions that are async, a concurrent dispatch queue, a dispatch group, and a bunch of messy synchronization. On the other hand, its implementation on Promise
almost trivial, complete with thread-safety and type-safety.
Other, more infrequently-used behaviors, like recover
, delay
, or race
, are similarly easy with Promises, but tougher with this kind of async feature. Here’s another one that I found useful, Promise.zip
:
public static func zip<T, U>(first: Promise<T>, second: Promise<U>) -> Promise<(T, U)> {
return Promise<(T, U)>(work: { fulfill, reject in
let resolver: (Any) -> () = { _ in
if let firstValue = first.value, secondValue = second.value {
fulfill((firstValue, secondValue))
}
}
first.then(resolver, reject)
second.then(resolver, reject)
})
}
Where Promise.all
requires that every promise have the same type, Promise.zip
(and any higher arity versions of it you might want) combines two promises with two different types (T
and U
) into one promise of type (T, U)
as a tuple. This way, if you need two things to finish that return different types, you can maintain their types while still waiting for them to both complete. Promise.all
is used frequently, and there will almost definitely be some kind of facility for it built into Swift’s async features (even if they’re hard-coded into the syntax). Promise.zip
, on the other hand, would hardly ever be used, so the odds of it being added to the standard library are nil. But if one of our projects eventually did need it, it’s nice to know it’s easy enough to add ourselves.
As easy as it is to write helpers on a fully-fledged abstraction like Promise
, it’s tricky and complicated when the feature is built into the language’s syntax. Without the manipulability of fully-blown types that represent these constructs, we can’t build useful one-off functions like zip
.
Consider if optionality were built into the language the way that errors are. You could never extend the Optional type. There’d be no more using flatMap
to unwrap. There’d be no way to turn a tuple with two optionals into one optional tuple with two non-optional values (the equivalent of our Promise.zip
above). My fake async
/await
syntax and the very real Swift 2 error handling are hard-coded into the language, and they both prevent this kind of extensibility and flexibilty that you get with Optional
.
This extensibility is often limited and even removed with excuses of safety and soundness; we’ve certainly seen those arguments made for removing features like mutability, metaprogramming, and reflection. However, in many of these cases, these features are restricted without any increase in type safety or the other guarantees that we subscribe to Swift for. In those cases, the reasoning for why we can’t have nice things falls apart. As I wrote a few weeks ago:
Being able to conform my own types SequenceType shows that the language trusts me to make my own useful abstractions (with no loss of safety or strictness!) on the same level as its own standard library.
Perhaps I’m wrong about how the syntax will look. I truly hope that’s the case. As I’ve shown, this syntax is purposely almost crippled in order to make two points: first, that without the proper supporting abstractions, async
/await
develops holes very similar to the current error handling model. The Swift team wants to fill in one of those holes (async) with more syntax, but that merely kicks the can down the road.
Second, the goal of this fictional async
/await
syntax was to design it so that it’s limited in similar ways to the error handling model; given these limitations, if async
/await
sans abstractions falls short, then doesn’t our current error model also deserve an overhaul?
I don’t know if the ship has sailed for redesigning the way that Swift error handling works; it probably has. The async ship is still in the docks, to stretch the metaphor, and I’d like to make sure the language that we’ll be writing code in for the next 20 years has as solid of a foundation as possible.
This article is also available in Chinese.
There’s a lot to love about Swift, which I’ve written about before. Today, however, I want to write about where the language falls short. This is a complicated issue with lots of nuance, so I’ll go into a couple of examples of where I think the language gets it right, where it gets it wrong, and what the future holds.
Defining within the language vs without
Take a look at Ruby.
Ruby’s attr_accessor
is a way to define a setter and a getter for an instance variable. You use it like so:
class Person
attr_accessor :first_name, :last_name
end
At first blush, this looks like a language feature, like Swift’s let
and var
property declarations. But Ruby’s functions can be called without parentheses, and this is just a function defined in the class scope (which we’d call a static function in Swift):
def self.attr_accessor(*names)
names.each do |name|
define_method(name) {instance_variable_get("@#{name}")} # This is the getter
define_method("#{name}=") {|arg| instance_variable_set("@#{name}", arg)} # This is the setter
end
end
If you can’t read Ruby, that’s okay. It uses a function called define_method
to create a getter and setter for the keys that you pass in. In Ruby, @first_name
means the instance variable named first_name
.
This is one of the reasons I love Ruby’s language design — they first create the meta-tools to create useful language features, and then they use those tools to implement the language features that they want. Yehuda Katz explores how Ruby applies this idea to its blocks. Because Ruby’s language features are written with the same tools and in the same language that users have access to, users can also write features similar in style and scope to the ones that define the language.
Optionals
This brings us to Swift. One of Swift’s core features is its Optional
type. This allows users to define whether a certain variable can be null or not. It’s defined within the system with an enum:
enum Optional<WrappedType> {
case Some(WrappedType)
case None
}
Like attr_accessor
, this feature uses a Swift language construct to define itself. This is good, because it means users can create similar things with different semantic meanings, such as this fictional RemoteLoading
type:
enum RemoteLoading<WrappedType> {
case Loaded(WrappedType)
case Pending
}
It has the exact same shape as Optional
but carries different meanings. (Arkadiusz Holko takes this enum a step further in a great blog post.)
However, the Swift compiler knows about the Optional
type in a way it doesn’t know about RemoteLoading
, and it lets you do special things. Take a look at these identical declarations:
let name: Optional<String> = .None
let name: Optional<String> = nil
let name: String? = nil
var name: String?
Let’s unpack them. The first one is the full expression (with type inference). You could declare your own RemoteLoading
property with the same syntax. The second uses the NilLiteralConvertible
protocol to define what happens when you set that value to the literal nil
. While this piece of syntax is accessible for your own types, it doesn’t seem quite right to use it with RemoteLoading
. This is the first of a few language features that are designed to make Swift feel more comfortable to writers of C family languages, which we’ll come back to in a moment.
The third and fourth declarations are where the compiler starts using its knowledge of the Optional
type to allow us to write special code that we couldn’t write with other types. The third one uses a shorthand for Optional<T>
where it can be written as T?
. This is called syntactic sugar, where the language lets you write common bits of code in simpler ways. The final line is another piece of syntactic sugar: if you declare an optional type, but don’t give it a value, the compiler will infer that its value should be .None
/nil
(but only if it’s a var
reference).
You can’t access these last two optimizations with your own types. The language’s Optional
type, which started out awesomely, by being defined within the existing constructs of language, ends up with special-cased compiler exceptions that only this type can access.
Families
Swift is defined to “feel at home in the C family of languages”. This means having for loops and if statements.
Swift’s for..in
construct is special. Anything that conforms to SequenceType
can be iterated over in a for..in
loop. That means I can define my own types, declare that they’re sequential, and use them in for..in
loops.
Although if
statements and while
loops currently work this way in Swift 2.2 with BooleanType
, this functionality has been removed in Swift 3. I can’t define my own boolean types to use within if
statements like I can with for..in
.
These are two fundamentally different approaches to a language feature, and they define a duality in Swift. The first creates a meta-tool that can be used to define a language feature; the other creates a explicit and concrete connection between the feature of the language and the types of that language.
You could argue that types conforming to SequenceType
are more useful than types conforming to BooleanType
. Swift 3 fully removes this feature, though, so you have to fully commit: you have to argue that BooleanType
is so useless that it should be completely disallowed.
Being able to conform my own types SequenceType
shows that the language trusts me to make my own useful abstractions (with no loss of safety or strictness!) on the same level as its own standard library.
Operations
Operators in Swift are also worth examining. Syntax exists within the language to define operators, and all the arithmetic operators are defined within that syntax. Users are then free to define their own operators, useful for if they create their own BigInt type and want to use standard arithmetic operators with it.
While the +
operator is defined within the language, the ternary operator ?:
isn’t. Command-clicking on the +
operator jumps you to its definition. Command-clicking on either the ?
or the :
of the ternary operator yields nothing. If you want to use a sole question mark or colon as an operator for your code, you can’t. Note that I’m not saying that it would be a good idea to use a colon operator in your code; all I’m saying is that this operator has been special-cased, hard-coded into the compiler, to add familiarity to those weaned on C.
In each of these three cases, we’ve compared two things: the first, a useful language syntax which the standard library uses to implement features; and the second, a special-case which privileges standard library code over consumer code.
The best kinds of syntax and syntactic sugar can be tapped into by the writers of the language, with their own types and their own systems. Swift sometimes handles this with protocols like NilLiteralConvertible
, SequenceType
, and the soon-defunct BooleanType
. The way that var name: String?
can infer its own default (.None
) crucially isn’t like this, and therefore is a less powerful form of syntactic sugar.
I think it’s also worth noting that even though I love Ruby’s syntax, two places where it doesn’t have very much flexibility are operators and falsiness. You can define your own implementations for the Ruby’s existing operators, but you can’t add new ones, and the precedences are fixed. Swift is more flexible in this regard. And, of course, it was more flexible with respect to defining falsiness as well, until Swift 3.
Errors
In the same way that Swift’s Optional type is a shade of C’s nullability, Swift’s error handling resembles a shade of C’s exception handling. Swift’s error handling introduces several new keywords: do
, try
, throw
, throws
, rethrows
, and catch
.
Functions and methods marked with throws
can return
a value or throw
an ErrorType
. Thrown errors are land in catch
blocks. Under the hood, you can imagine Swift rewriting the return type for the function
func doThing(with: Property) throws -> Value
as
func doThing(withProperty) -> _Result<Value, ErrorType>
with some internal _Result
type (like antitypical/Result
) that represents potential success or failure. (The reality is this _Result
type isn’t explicitly defined, but rather implicitly handled in the bowels of the compiler. It doesn’t make much of a difference for our example.) At the call site, this is unpacked into its successful value, which is passed through the try
statement, and the error, which jumps execution to the catch
block.
Compare this to the previous examples, where useful features are defined within the language, and then syntax (in the case of operators or SequenceType
) and syntactic sugar (in the case of Optional
) are added on top of them to make the code look the way we expect it. In contrast, the Swift’s error handling doesn’t expose its internal _Result
model, so users can’t use it or build on it.
Some cases for error handling works great with Swift’s model, like Brad Larson’s code for moving a robot arm or my JSON parsing code. Other code might work better with a Result
type and flatMap
.
Still other code might rely on asynchronicity and want to pass a Result
type to a completion block. Apple’s solution only works in certain cases, and giving users of the language more flexibility in the error model would help cover this distance. Result
is great, because it’s flexible enough to build multiple things on top of it. The try
/catch
syntax is weak, because it’s very rigid and can only be used in one way.
The Future
Swift 4 promises language features for asynchronous work soon. It’s not clear how these features will be implemented yet, but Chris Lattner has written about the road to the Swift 4:
First class concurrency: Actors, async/await, atomicity, memory model, and related topics.
Async/await is my leading theory for what asynchronicity in Swift will look like. For the uninitiated, async/await involves declaring when functions are async
, and using the await
keyword to wait for them to finish. Take this simple example from C#:
async Task<int> GetIntAsync()
{
return new Task<int>(() =>
{
Thread.Sleep(10000);
return 1
});
}
async Task MyMethodAsync()
{
int result = await GetIntAsync();
Console.WriteLine(result);
}
The first function, GetIntAsync
returns a tasks that waits for some amount of time, and then returns a value. Because it returns a Task
, it is marked as async
. The second function, MyMethodAsync
, calls the first, using the keyword await
. This signals to the system that it can do other work until the Task
from GetIntAsync
completes. Once it completes, control is restored to the function, and it can write to the console.
Judging from this example, Task
objects in C# seem a lot like promises. Also, any function that uses the await
keyword must itself be declared as async
. The compiler can enforce this guarantee. This solution mirrors Swift’s error model: functions that throw must be caught, and if they don’t, they must be marked with throws
as well.
It also has the same flaws as the error model. Rather than being mere syntactic sugar over a more useful tool, a brand new construct and a bunch of keywords are added. This construct is partially dependent on types within defined in the standard library and partially dependent on syntax baked into the compiler.
Properties
Property behaviors are another big feature that might come in Swift 4. There is a rejected proposal for property behaviors, which is set to be examined more closely for Swift 4.
Property behaviors let you attach a behavior like lazy
to a property. The lazy
property, for example, would only set up a value the first time it’s accessed. While you currently can use this particular behavior, it’s hard-coded into the Swift compiler. Property behaviors as proposed would allow the facility for the standard library to implement some behaviors and for users to define others entirely.
Perhaps this is the best of all worlds. Start with a feature that’s hard-coded in the compiler, and after the feature has gained some prominence, create a more generic framework which lets you define that feature through the language itself. At that point, any writer of Swift can create similar functionality, tweaked precisely to suit their own requirements.
If Swift’s error model followed that same path, Swift’s standard library might expose a Result
type, and any function returning a Result
would be able to use the do
/try
/catch
syntax when it is most useful (like for many parallel, synchronous actions that can each fail). For error needs that don’t fit in to the currently available syntax, like async errors, users would have a common Result
type that they can use. If the Result
requires lots of chaining, users can flatMap
.
Async/await could work the same way. Define a Promise
or Task
protocol, and things that conform to that would be await
-able. then
or flatMap
would be available on that type, and depending on user’s needs, they could use the language feature at as high or as low of a level as needed.
Metaprogramming
I’d like to close with a note on metaprogramming. I’ve written extensively about metaprogramming in Objective-C, but it’s similar to what we’re working with here. The lines between code and metacode are blurry. The code in the Swift compiler is the meta code, and Swift itself is the code. If defining an implementation of an operator (as you do in Ruby) is just code, then defining a whole new operator seems like it has to be metacode.
As a protocol-oriented language, Swift is uniquely set up to let us tap into the syntax of the language, as we do with BooleanType
and SequenceType
. I’d love to see these capacities expanded.
The line where keywords stop and syntax starts, or where syntax stops and syntactic sugar starts, isn’t very well defined, but the engineers who write code in the language should have the ability to work with the same tools as those who develop the standard library.
This article is also available in Chinese.
Last week, I wrote about Promises, which are a high-level building blocks for dealing with asynchronous operations. Using just the fulfill()
, reject()
, and then()
, functions, we can build up lots of functionality in a simple and composable way. I’d like to explore some of those here.
Promise.all
Probably the poster child for the value of promisifying all of your asynchronous callbacks is Promise.all
. What this static function does is wait for all the promises you give it to fulfill, and once they have, Promises.all
will fulfill itself with the array of all fulfilled values. For example, you might want to write code to hit an API endpoint once for each item in an array. map
and Promise.all
make that super easy:
let userPromises = users.map({ user in
APIClient.followUser(user)
})
Promise.all(userPromises).then({
//all the users are now followed!
}).onFailure({ error in
//one of the API requests failed
})
To write Promise.all
, we first need to create a new Promise the represents the state of all the promises combined. If the array is empty, we can fulfill immediately.
static func all<T>(promises: [Promise<T>]) -> Promise<[T]> {
return Promise<[T]>(work: { fulfill, reject in
guard !promises.isEmpty else { fulfill([]); return }
})
}
Inside this promise, we need to loop through each promise and add a handler for success and one for failure. If any of the promises have failed, we can reject
the larger promise.
for promise in promises {
promise.then({ value in
}).onFailure({ error in
reject(error)
})
}
Only when all of the promises are complete should it fulfill
the larger promise. With a simple check to make sure none of the promises are rejected or pending and a little flatMap
magic, we can fulfill the promise with the values of all the promises combined. The whole function together looks like this:
static func all<T>(promises: [Promise<T>]) -> Promise<[T]> {
return Promise<[T]>(work: { fulfill, reject in
guard !promises.isEmpty else { fulfill([]); return }
for promise in promises {
promise.then({ value in
if !promises.contains({ $0.isRejected || $0.isPending }) {
fulfill(promises.flatMap({ $0.value }))
}
}).onFailure({ error in
reject(error)
})
}
})
}
Note that promises can only be fulfilled or rejected once. If fulfill
or reject
were called a second time, it wouldn’t have any effect on the state of the promise.
Promises, like NSOperations, are state machines, but they store all the important state representing their completion in a thread-safe way. This is a different approach than NSOperation
, where you have to provide your own state, storage, and thread-safety mechanisms. Operations don’t store the resulting value, so you have to manage that yourself too.
NSOperation
also holds data about threading models and priority order, whereas promises don’t make any guarantees about how the work will be completed, just that it will be completed. This is borne out by looking at the Promise class itself. Its only instance variables are the state
, which holds information about whether it’s pending, fulfilled, or rejected (and the corresponding data), and an array of callbacks. (It also holds an isolation queue, but that’s not really state.)
delay
One useful promise is one that resolves itself after some delay.
static func delay(delay: NSTimeInterval) -> Promise<()> {
return Promise<()>(work: { fulfill, reject in
let nanoseconds = Int64(delay*Double(NSEC_PER_SEC))
let time = dispatch_time(DISPATCH_TIME_NOW, nanoseconds)
dispatch_after(time, dispatch_get_main_queue(), {
fulfill(())
})
})
}
Internally, this could be implemented with usleep
or some other method of delaying, but dispatch_after
is simple enough for our usage. This promise will become more useful as we build up other interesting promises.
timeout
Next, we’ll use delay
to build timeout
. This promise will be rejected after a delay.
static func timeout<T>(timeout: NSTimeInterval) -> Promise<T> {
return Promise<T>(work: { fulfill, reject in
delay(timeout).then({ _ in
reject(NSError(domain: "com.khanlou.Promise", code: -1111, userInfo: [ NSLocalizedDescriptionKey: "Timed out" ]))
})
})
}
This isn’t that useful of a promise by itself, but it will be useful in building a few of the other behaviors that we want.
race
The partner to Promise.all
, which fulfills when all promises are fulfilled, is Promise.race
, which fulfills or rejects with the first promise that completes.
static func race<T>(promises: [Promise<T>]) -> Promise<T> {
return Promise<T>(work: { fulfill, reject in
guard !promises.isEmpty else { fatalError() }
for promise in promises {
promise.then({ value in
fulfill(value)
}).onFailure({ error in
reject(error)
})
}
})
}
Because promises can only be fulfilled or rejected once, calling fulfill
or reject
on the outer promise after it’s already been moved out of the .Pending
state won’t have any effect.
With this function, timeout
, and Promise.race
, we can now create a new promise that either succeeds, fails, or times out within a certain limit. We’ll make it an extension on Promise
.
extension Promise {
func addTimeout(timeout: NSTimeInterval) -> Promise<Value> {
return Promise.race([self, Promise.timeout(timeout)])
}
}
It can be used within a normal promise chain, like so:
APIClient
.getUsers()
.addTimeout(0.5)
.then({
//we got our users within 0.5 seconds
})
.onFailure({ error in
//maybe our timeout error, maybe a network error
})
One of the reasons I like promises so much is that their composability lets us build up behaviors easily. Promises usually require a guarantee that they will be fulfilled or rejected at some point, but our timeout function allows us to correct that behavior in a general fashion.
recover
recover
is another useful function. It lets us catch an error and easily recover from it without messing up the rest of the promise chain.
We know the form we want our function to take: it should accept a function that takes our error and returns a new promise. Our method will also return a promise that we can use to continue chaining off of.
extension Promise {
func recover<T>(recovery: (ErrorType) -> Promise<T>) -> Promise<T> {
}
}
For the body of the method, we know that we need to return a new promise, and if the current promise (self
) succeeds, we should forward that success to the new promise.
func recover<T>(recovery: (ErrorType) -> Promise<T>) -> Promise<T> {
return Promise(work: { fulfill, reject in
self.then({ value in
fulfill(value)
}).onFailure({ error in
}
}
}
onFailure
, however, is a different story. If the promise fails, we should call the recovery
function that was provided. That will give us a new promise. If that recovery promise succeeds, we can pass on that success to the new promise, and same if it fails.
//..
}).onFailure({ error in
recovery(error).then({ value in
fulfill(value)
}).onFailure({ error in
reject(error)
}
})
//...
The completed function looks like this:
extension Promise {
func recover(recovery: (ErrorType) -> Promise<Value>) -> Promise<Value> {
return Promise(work: { fulfill, reject in
self.then({ value in
fulfill(value)
}).onFailure({ error in
recovery(error).then({ value in
fulfill(value)
}).onFailure({ error in
reject(error)
})
})
})
}
}
With this new function, we can recover from errors. For example. if the network doesn’t load the data we expect to see, we can load the data from a cache:
APIClient.getUsers()
.recover({ error in
return cache.getUsers()
}).then({ user in
//update UI
}).onFailure({ error in
//handle error
})
retry
Retrying is another capability we can add. To retry, we need a number of times to retry and a function that will generate the promise to be retried (so we can create the promise multiple times).
static func retry<T>(count count: Int, delay: NSTimeInterval, generate: () -> Promise<T>) -> Promise<T> {
if count <= 0 {
return generate()
}
return Promise<T>(work: { fulfill, reject in
generate().then({ value in
fulfill(value)
}).recover({ error in
return self.delay(delay).then({
retry(count: count-1, delay: delay, generate: generate)
})
}).onFailure({ error in
reject(error)
})
})
}
- If the count is 1 or less, just generate the promise and return it.
- Otherwise, create a new promise which generates the promise, and recovers it with a
delay
followed by a retry ofcount-1
.
Creating retry builds on both delay
and recover
, which we wrote above.
In each of these examples, small, composable pieces come together to make simple and elegant solutions. All of these behaviors are built on the simple .then
and .onFailure
functions provided by the core of the promise implementation. Simply by formalizing what completion blocks look like, we can solve problems like timeouts, recovery, and retrying, and we can do so in a simple and reusable way. These examples still need some tests and verification, but I’ll be adding them slowly to the GitHub repo in the coming days and weeks.
This article is also available in Chinese.
Promises are a way to chain asynchronous tasks. Normally, asynchronous tasks take a callback (or sometimes two, one for success and one for failure), in the form of a block, that is called when the asynchronous operation is completed. To perform more than one asynchronous operation, you have to nest the second one inside the completion block of the first one:
APIClient.fetchCurrentUser(success: { currentUser in
APIClient.fetchFollowers(user: currentUser, success: { followers in
// you now have an array of followers
}, failure: { error in
// handle the error
})
}, failure: { error in
// handle the error
})
Promises are a way of formalizing these completion blocks to make chaining asynchronous processes much easier. If the system knows what success and what failure look like, composing those asynchronous operations becomes much easier. For example, it becomes trivial to write reusable code that can:
- perform a chain of dependent asynchronous operations with one completion block at the end
- perform many independent asynchronous operations simultaneously with one completion block
- race many asynchronous operations and return the value of the first to complete
- retry asynchronous operations
- add a timeout to asynchronous operations
The code sample above, when converted into promises, looks like this:
APIClient.fetchCurrentUser().then({ currentUser in
return APIClient.fetchFollowers(user: currentUser)
}).then({ followers in
// you now have an array of followers
)}.onFailure({ error in
// hooray, a single failure block!
})
(You’ll note that promises are a thing that turns nested/indented code into flat code: a promise is a monad.)
Promises grew to ascendence in the JavaScript community. Because Node.js was designed to have lots of asynchronous capabilities, even simple tasks would require chains of method calls with asynchronous callbacks. This grew unwieldy even after only 3 or 4 of these actions. Promises saved the day, and they’re now part of the official JavaScript ES6 spec. This blog post goes into great detail on how JavaScript’s promises work.
One of the great things about the JavaScript Promise implementation is that there is a very clearly defined spec, called A+, which can be found at promisejs.org. This meant that multiple promise implementations could sprout, and they were fully interoperable, due to JavaScript’s weak type system. As long as your Promise implementation has a then
function that conforms to the spec, it can be chained with promises from other libraries. This is awesome.
While writing the Backchannel API (which was in Node), I grew to love Promises. The A+ spec has a really nice API, eschewing the functional names you would expect on a monad for the simpler, easier-to-understand then
(which is overloaded to act as both flatMap
and map
) . While this API isn’t for everyone, (in particular, I can fully understand why you’d prefer the explicitness of the functional names), I really do like it, and I set out to implement a similar library in Swift.
You can find this library on Github. The process of writing it was enlightening, and I’d like to share some of the things I learned here.
Enums are awesome
Yeah, everyone knows. Enums are great. But because Promises are essentially a state machine, enums are a particularly excellent fit here. The reference implementation of JavaScript’s Promise starts out like this:
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise() {
// store state which can be PENDING, FULFILLED or REJECTED
var state = PENDING;
// store value or error once FULFILLED or REJECTED
var value = null;
// store success & failure handlers attached by calling .then or .done
var handlers = [];
}
I couldn’t contrive a more perfect example for Swift’s enums if I tried. Here’s the same code in Swift:
enum State<Value> {
case Pending
case Fulfilled(value: Value)
case Rejected(error: ErrorType)
}
final class Promise<Value> {
private var state: State<Value>
private var callbacks: [Callback<Value>] = []
}
The additional data, because it is contingent on the specific state the promise is in, is stored as an associated value on each of the enum cases. Since it doesn’t make any sense for a promise to be in the .Pending
state but have a value, the enum makes that completely inexpressible in the type system.
My only criticism is that generic types can’t be nested inside other types, and this is fixed in Swift 3.
Type systems are nice
When creating a new JavaScript promise, you can use a convenience initializer:
var promise = new Promise(function(resolve, reject) {
someAsyncRequest(function(error, result) {
if (error) {
reject(error);
}
resolve(result);
});
});
You pass it a function that takes two functions: one for if the promise should succeed, and one for if it should fail. For these two functions, order matters. And because JavaScript isn’t type-safe, if you mis-order the functions in the first line above, writing reject, resolve
(which I did more often than I’d like to admit), you can easily pass an error into the resolve
function. Swift’s type-safety, on the other hand, means that the reject
function has the type (ErrorType) -> Void)
and won’t accept your successful result. Gone is the worry that I’ll mess up the order of the reject
and resolve
functions.
Too much types can be frustrating
My Promise
type is generic over Value
, which is the type of the value that comes out of it. This means you can rely on type inference to write code with no types.
let promise = Promise(value: "initialValue") // a fulfilled Promise<String>
Because promises are often chained, relying on inference to figure out what your types will be is especially useful. Having to add explicit types to each step in the chain would be very frustrating, and ultimately not particularly Swift-like.
My first crack at this was generic over Error
as well. This strictness meant that creating a fulfilled promise required you to specify your error’s type up-front, every time.
let promise = Promise<String, APIError>(value: "initialValue")
This adds a lot of unnecessary baggage to what used to be a simple line of code, so I remove the ability to specify what the type of the error was.
Unfortunately, removing explicit error types means that there’s one small type-system goodie that I have to miss out on. If you make an empty enum called NoError
, it effectively expresses that the promise can’t fail. Since empty enums can’t be initialized, there’s no way to make the promise enter the rejected state. This is a sad loss, but ultimately, I decided it was worth it, since it made using promises in every other context way simpler. I hope using the class in practice will give me the insight into whether this was a good decision or not.
Relatedly, Swift’s generics manifesto includes “default generic arguments”, which would be a great way to get around this problem: you’d be able to say the default is ErrorType
, and if anyone wants to get more specific, they have that capacity.
Functional methods are hard to grok
The promise type is a monad, meaning you can call flatMap
on it. The function that you pass into flatMap
returns a new promise, and that promise’s state becomes the state of the chain.
The name of the flatMap
function is completely inscrutable, though. It doesn’t express what’s actually happening here, in an easy-to-read way. This is part of the reason I prefer A+’s Promise API. The then
function in JavaScript overloaded to act as both flatMap
(returning a new promise for the chain) and map
(returning a new value for the next promise in the chain). then
now just means “do this thing next” without respect to exactly how the next thing works.
Since Swift’s type system knows when functions return Void
, then
can also be overloaded to accept functions that don’t return anything, to allow you to “tap” into the chain. I’ll know more when I use this class in a project, but I look forward to seeing if this “tap” version of then
is useful. A triple overload might be too much, but I think it’ll be nice to be able to use then
without having to think about the precise functional term for what I’m trying to do.
Tests are good
Once I wrote a basic implementation of the class, I wrote a few tests. I got some experience with XCTest’s expectationWithDescription
and waitForExpectationsWithTimeout
, which are pretty nice APIs to work with.
Like the cookbook project, having a full suite of tests for the Promise
class was extremely useful. As always, there was some up front cost in writing the tests, but it was totally worth it. While I was refactoring and cleaning up this code, the tests caught numerous errors. A promise implementation is very fickle, and tiny details about the order that code is executed change the behavior of the class in subtle ways. Having a test suite that confirms that a refactor is truly isomorphic is great.
Threading is hard
Because it inherently deals with threading and asynchronicity, Promise
needs to be a thread-safe class. To make the class thread-safe, its instance variables need to be accessed all from the same queue. This was harder than I expected it to be. Even after I’d thought I’d done it right, there were still a few places where I’d screwed it up.
Two of the tests in particular were very flaky and would fail every 5-10 times I ran the test suite. There’s nothing scarier than a flaky test, because it’s so easy to just assume it was a cosmic ray that hit your computer’s RAM at the exact right moment to cause your test to fail.
One of the flaky tests was resulting in an EXC_BAD_ACCESS
, which was very confusing, because I couldn’t think of a way in Swift that I would be accessing bad memory. It took me a while, but I got finally got a log message that suggested it was a threading issue. I was appending things to an array from multiple threads simultaneously. I corrected the code that accesses the instance variables to use the dispatch queue correctly, and the flaky tests became reliable.
You can find the code on Github. I haven’t made it a full library yet, with public
declarations and a podspec yet. I want to see what it’s like to use it in a real app first.
Promises seem complex and magical, but the implementation flows almost naturally from the types that each version of then
has. Once I had an implementation that worked, I could write tests against it, and those tests enabled me slowly refactor my code and find edge case bugs.
This article is also available in Chinese.
While Swift’s actual native Dictionary type has a pretty complex implementation (undoubtedly for performance reasons), Swift gives us the tools to write a beautiful and simple one with very little code. We’ll start small and add features as we go.
A quick recap of how dictionaries work: dictionaries allow you to set and retrieve a value using a key, which can be any type. They are usually backed by an array, although they can be backed by a tree as well. We will explore an array-backed dictionary in this post, mostly because I don’t know how tree-backed dictionaries work yet.
Since our dictionary will be backed with an array, we need a way to convert the key that’s given into an integer, and then a way to force that integer into the bounds of our array. These two methods are the hashing function and the modulus operation, respectively. By hashing, we can consistently turn our key (usually a string, but can be any type that is Hashable
) into a number, and by taking the modulus with respect to the length of the array, we’ll get a consistent slot in the array to set and retrieve the value.
I took some inspiration from Mike Ash’s Let’s Build NSMutableDictionary
, particularly the rules for resizing.
Let’s get started. We know that we want this to be generic over Key
and Value
, and that Key
has to be Hashable
. Something that is Hashable
is also Equatable
, which we will need as well, but we will get for free.
struct Dictionary<Key, Value where Key: Hashable> {
private var storage = //some Array...
subscript(key: Key) -> Value? {
get {
return nil
}
set {
}
}
}
This is the basic structure of our type. We know our array has to be generic over …something, but we don’t know what yet. Because there may be collisions, which are two different Key
objects that hash and mod to the same position in the array, each placeholder object will need to support storing multiple values. Let’s design Placeholder
:
struct Placeholder<Key, Value where Key: Hashable> {
var values: [(Key, Value)] = []
}
This object will hold many keys and values that have all hashed and modded to the same spot in the dictionary. If we’ve designed our dictionary well, there won’t be more than one key-value pair in each Placeholder
often, but it will happen. A nice implementation of this Dictionary
might use a linked list for the values
property of the Placeholder
. I’ll leave that as an exercise for the reader.
Now that we know roughly what Placeholder
looks like, we know what the storage will look like.
private var storage = Array(count: 8, repeatedValue: Placeholder<Key, Value>())
We start with a randomly chosen size for the array. It helps to pick a power of two, because modding by a power of 2 is a little bit faster than any other number. Until we implement resizing, it won’t really matter what size we pick. With the Array(count:repeatedValue:)
constructor, each spot in the array now has a placeholder that we can add values to.
To set values in the dictionary, we need to hash the key (and absolute value it, since the hash can sometimes come back as negative), and then mod it by the size of the array.
set {
let position = abs(key.hashValue) % storage.count
//...
}
To actually add this value to the dictionary, we need to a) make sure the new value is not nil, b) find the placeholder at position
, and c) add the key and value to the placeholder.
set {
guard let value = newValue else { return }
let position = abs(key.hashValue) % storage.count
storage[position].values.append((key, value))
}
For a basic implementation of the setter, that’s really all we need. (I’ve left out some small details, like what happens when you try to set the same key twice. We’ll tackle that soon.)
For fetching the value, process is pretty similar. Hash the key, absolute value, mod, and then we have the placeholder at that position. To retrieve the value from the placeholder, I’m going to delegate that to the placeholder itself.
get {
let position = abs(key.hashValue) % storage.count
return storage[position].firstValue(matchingKey: key)
}
That method is where the real magic will happen. We need to find the first key-value pair that has that key. A method on SequenceType
called first(where:)
will land in Swift 3, but until we have that bounty, we will need to use the longhand lazy.filter( /* block */ ).first
func firstValue(matchingKey key: Key) -> Value? {
let matchingKeyValuePair = values.lazy.filter({ $0.0 == key }).first
//...
}
Once we have the tuple that represents the pair, we can call .1
on it to extract the value.
func firstValue(matchingKey key: Key) -> Value? {
return values.lazy.filter({ $0.0 == key }).first?.1
}
That’s pretty much it for a basic implementation of Dictionary
. 23 lines of Swift. You can find all the code together as a gist here.
There are a few interesting things left to implement on our type. First, an extremely lazy implementation of generate()
so that our Dictionary
conforms to SequenceType
:
extension Dictionary: SequenceType {
typealias Generator = IndexingGenerator<[(Key, Value)]>
func generate() -> Dictionary.Generator {
return storage.flatMap({ $0.values }).generate()
}
}
Next, a way to remove keys. First, from the placeholder:
extension Placeholder {
mutating func removeValue(key key: Key) {
values = values.filter({ $0.0 != key })
}
}
and then on Dictionary
itself:
extension Dictionary {
mutating func remove(key key: Key) {
let position = abs(key.hashValue) % storage.count
storage[position].removeValue(key: key)
}
}
We’ll add a call to remove(key:)
before we set any new values. This will ensure the same key can’t point to two different values.
Lastly, let’s take a look at resizing. When a dictionary has too many objects in it, its backing storage should resize itself. Typically, “too many objects” is defined as having a load factor (number of objects divided by backing array length) above 2/3 or 3/4. I chose 0.7.
extension Dictionary {
private let maxLoadFactor = 0.7
private var size: Int {
return storage.count
}
var count: Int {
return storage.flatMap({ $0.values }).count
}
var currentLoadFactor: Double {
return Double(count) / Double(size)
}
}
(The implementation of count
is extremely lazy, again. Ideally, the Dictionary
would keep track of how many objects have been added to — and removed from — it, but that is tougher than it looks.)
mutating func resizeIfNeeded() {
if currentLoadFactor > maxLoadFactor {
//resize storage
}
}
This is the part where Swift’s value semantics get really really weird. When Mike Ash built his NSMutableDictionary
, he created a fixed-size dictionary, and wrapped it in a mutable size dictionary. When he needed to resize, he would generate a new fixed-size dictionary (with double the size), and copy all the items to it manually.
We don’t have to do this in Swift. In a Swift struct, assigning self to a variable makes a full copy of it.
let oldDictionary = self
//...
Once we have a copy of the dictionary, we can reset our storage
variable to an array with double the size. (Doubling the size ensures that we remain a power of two.)
//...
storage = Array<Placeholder<Key, Value>>(count: size*2, repeatedValue: Placeholder<Key, Value>())
//...
Once that’s done, our dictionary is empty, and we have to copy all the values from the oldDictionary
to the current one:
//...
for (key, value) in oldDictionary {
self[key] = value
}
Here’s the complete resizeIfNeeded()
function:
mutating func resizeIfNeeded() {
if Double(count) / Double(size) > maxLoadFactor {
let oldDictionary = self
storage = Array<Placeholder<Key, Value>>(count: size*2, repeatedValue: Placeholder<Key, Value>())
for (key, value) in oldDictionary {
self[key] = value
}
}
}
self
, inside a Swift struct, is a way of accessing the values and functions of the current type, but it’s also a very moldable and mutable thing. You can set it to new values, copy it to others, and generally treat it as if it were just another variable reference.
I joked two weeks ago that Swift is a more dynamic language than Objective-C, Ruby, or Python because you can change its falsiness behavior, but here we have another situation where you can mutate something in Swift that you couldn’t in Objective-C: the reference to self
itself. We could have written self = Dictionary(size: size*2)
in this method, and it would have been perfectly valid Swift. To developers who are used to writing object-oriented code where an object’s identity is paramount, this is fundamentally weird.
The full implementation, with sequencing, removing, and resizing can be found as a gist. Besides the lazy count
/generate()
implementations, I’m pleased with the way this little project turned out.
If you are thinking that the performance of this Dictionary is must be absolutely treacherous, you would be right. This dictionary is written to prefer simple code and concepts to performance. If you want to write a much more performant Dictoinary, you can use a more advanced technique called linear probing..
This article is also available in Chinese.
I’ve had a few beginner programmers ask me how to go from being able to write some code to being able to write nice code. I have a few book recommendations to this end. These books are great for people who are just starting out, but they also taught me a good bit when I was a few years into my programming career. A lot of them are in different languages — C, Ruby, Java — so reading them can be a bit of a challenge. For the most part, though, the concepts translate cleanly into whatever any language, and developing your polyglot skills early is never a bad thing.
Practical Object-Oriented Design in Ruby
by Sandi Metz
Longtime readers know about about my partiality to Sandi Metz. I think she does some of the best work out there explaining simple concepts to smart people. Practical Object-Oriented Design in Ruby, or POODR, is no exception.
Using a simple example of bike repair, she shows explains object-oriented theory in tiny examples, slowing building them up from an object with one method on it, to a full implementation of Fowler’s Replace Condtional with Polymorphism pattern from Refactoring (which I’ll talk about it in a moment). The concepts in this book are not mindblowing, but they’re patiently explained, start slow, and build on each other perfectly.
Design Patterns
by Gamma, Helm, Johnson, and Vlissides
Colloquially known as Gang of Four, this book was published in 1994. It’s one of the first books that just lays out a list of commonly used design patterns, explains when and where they should be used, and shows examples of how to use them. Several of the books I’ll recommend are like this. Even though they look like textbooks, they’re worth reading straight through like a normal book. When the time comes to use one of these patterns, you’ll know which book and chapter to flip to to get the details you need.
Gang of Four was written for a world of desktop GUI applications, and this is made clear by some of the patterns. For example, the Command pattern is clearly useful for undoable menu commands. These kinds of actions are rarer on iOS and the web, so the pattern has fallen out of favor. Nevertheless, seeing the path from problem to solution has helps you come up with creative solutions to your own problems.
Patterns of Enterprise Application Architecture
by Martin Fowler
If Gang of Four was written in an age of GUI desktop apps, Patterns of Enterprise Application Architecture was written in an age of web services. While its title suggests that it’s extremely dry, I’ve found it to be a very useful collection of patterns. It reads as practically a cookbook for a framework like Ruby on Rails, so much so that I wouldn’t be surprised if DHH read this book before setting out to write Rails.
The patterns, having been written for the web, deal with forms, HTML, and databases. While the former two categories are interesting, the database patterns can be used when writing modern iOS apps. If you want to think about how an ORM like Core Data (or ActiveRecord) is written, this book is the place to start. For example, Core Data uses the Identity Map, Lazy Load, Metadata Mapping, and Query Object patterns. Like Gang of Four, we don’t need to use all these patterns in our day-to-day, but seeing how its authors solved their problems is enlightening.
Refactoring
by Martin Fowler
This book is also by Martin Fowler. It gives a precise definition for refactoring:
I’ve been asked, “Is refactoring just cleaning up code?” In a way the answer is yes, but I think refactoring goes further because it provides a technique for cleaning up code in a more efficient and controlled manner.
And describes how refactoring fits into the process of normal development:
When you use refactoring to develop software, you divide your time between two distinct activities: adding function and refactoring. When you add function, you shouldn’t be changing existing code; you are just adding new capabilities.
After the introductions and defintions, Fowler dives into a list of refactorings. He starts from simple ones, like Extract Method, and onto more complex ones like Introduce Null Object. Like with the previous two books, it pays to read this one straight through.
Domain-Driven Design
by Eric Evans
While the other books are mostly lists of patterns, this book follows a slightly more narrative arc. A developer works with a domain expert in shipping goods overseas to build an application to manage boat itineraries. In the process, you learn about how to model a domain from the initial research phases to the actual coding. This book taught me about value types two years before Swift and its value types were announced.
The made-up Socratric dialogues between Evans’s programmer and domain expert are helpful too. Some people think that in an ideal world, a product manager can sit in-between the developers and the stakeholders. In the real world, you (as the developer) are ultimately responsibilty for clearly expressing the abilities and limitations of the software, and this book shows what those conversations can and should look like.
How to think vs what to think
Each of these five books is valuable in a special way — not just for teaching you their contents, but also for meta lesson of teaching you how to think about the problems you face. They all follow a general structure: given this problem, here’s a solution. Connecting the problems to the solutions helps you see the thought process that leads to that solution, and ultimately helps you apply that process to other problems.