These great posts (part1, part2) from Dave DeLong show off all sorts of cool tricks we can use to get the most out of our .xcconfig
files. They’re of particular interest to those targeting disperate plaforms and architectures, but we can all benefit from a little extra xcconfig-fu.
H/T: Michel Tsai (who, by the way, just launched a patrion worthy of your consideration).
Most modern apps have to deal with URLs in one way or another. Let’s say we have an embedded WKWebView
, for example, and we need to decide how to route tapped links. A naive implementation might look something like this:1
func webView(_ webView: WKWebView,
decidePolicyFor action: WKNavigationAction,
decisionHandler: @escaping [...]) {
guard let url = action.request.url else {
decisionHandler(.cancel)
}
let app = UIApplication.shared
//If the URL has one of these special
//schemes, let the OS deal with it.
if "tel" == url.scheme
|| "sms" == url.scheme
|| "mailto" == url.scheme {
app.open(url, options: [:]) {_ in}
decisionHandler(.cancel)
//Natively handle these paths that
//have special meaning.
} else if url.path == "/logout" {
performLogout()
decisionHandler(.cancel)
} else if url.path == "/about" {
showAbout()
decisionHandler(.cancel)
//Otherwise, just load it.
} else {
decisionHandler(.allow)
}
}
As time passes and our code grows, long chains of if...else
statements like these are just asking for trouble. They encourage a very imperative style that can be hard to decipher after the fact. And it’s hard to know where to insert new conditionals or how said conditions will affect existing flow.
A better solution might be to arrange everything in a switch
… but things get ugly fast:
switch url {
case let u where u.scheme == "tel"
|| u.scheme == "sms"
|| u.scheme == "mailto":
app.open(url, options: [:]) {_ in}
decisionHandler(.cancel)
case let u where u.path == "/logout":
performLogout()
decisionHandler(.cancel)
case let u where u.path == "/about":
showAbout()
decisionHandler(.cancel)
default:
decisionHandler(.allow)
}
The problem here is we have no way to directly match the parts of the URL we’re interested in. We have to bind the whole thing and then use a where
clause tease out the components we care about. But where
is essentially just an if
with a different name. The conditional clauses we give it are the same as the ones we passed to if
— complete with all the imperative and readability issues we outlined above.
What we’d really like is a more declarative way to simply tell switch
exactly what we want:
switch url {
case "mailto:":
//...
case "/logout":
//...
}
The big problem here, though, is most URL
components we care about are indistinguishable by type. They’re all String
s in Swift. And not only do they all sort of blend together as we read them, the compiler can’t make heads or tails of it.2
So let’s unique everything by adding a few types of our own:
extension URL {
struct Scheme {
let value: String
init(_ value: String) {
self.value = value
}
}
struct Path {
let value: String
init(_ value: String) {
self.value = value
}
}
}
Now we can write:
switch url {
case URL.Scheme("tel"),
URL.Scheme("sms"),
URL.Scheme("mailto"):
app.open(url, options: [:]) {_ in}
decisionHandler(.cancel)
case URL.Path("/logout"):
performLogout()
decisionHandler(.cancel)
case URL.Path("/about"):
showAbout()
decisionHandler(.cancel)
default:
decisionHandler(.allow)
}
And it looks gorgeous! But, of course, none of this compiles because Swift doesn’t understand how to match a Scheme
or a Path
to a URL
:
Expression pattern of type ‘URL.Scheme’ cannot match values of type ‘URL’
No worries. As we talked about earlier we can teach Swift new expression patterns by overloading the ~=
operator:
//Given a control expression that evaluates
//to a URL, and our Scheme type for the pattern,
//Swift will call this to determine a match:
func ~=(pattern: URL.Scheme, value: URL) -> Bool {
//We can then use it to compare our `Scheme`
//pattern directly to URL’s `scheme` property.
return pattern.value == value.scheme
}
//Ditto for a URL and our custom Path type:
func ~=(pattern: URL.Path, value: URL) -> Bool {
return pattern.value == value.path
}
One nice side-effect of this “define a type and matching operator” alternative to endless if...else
or case...where
clauses is it provides a better path for composability.
Imagine our domain logic has some need to match a secure path; that is, a URL with a given path and the added requirement that the scheme is “https”. switch
s are good at handling one pattern or another:
switch url {
case URL.Path("/logout"),
URL.Path("/signout"):
//kill user credentials when *either*
//path is encountered.
}
But they’re not so great when it comes to and:
switch url {
case URL.Scheme("https"):
switch url {
case URL.Path("/about"):
//we have to nest our statements to
//compose a scheme *and* a path.
}
}
Thankfully we can just define a new type that represents our domain requirements:
extension URL {
struct SecurePath {
let value: String
init(_ value: String) {
self.value = value
}
}
}
And then compose our ~=
overload from existing matchers:
func ~=(pattern: URL.SecurePath, value: URL) -> Bool {
return URL.Scheme("https") ~= value
&& URL.Path(pattern.value) ~= value
}
It’s hard to argue with the readability of the results:
switch url {
case URL.Scheme("tel"),
URL.Scheme("sms"),
URL.Scheme("mailto"):
app.open(url, options: [:]) {_ in}
decisionHandler(.cancel)
case URL.Path("/logout"),
URL.Path("/signout"):
performLogout()
decisionHandler(.cancel)
case URL.SecurePath("/about"):
showAbout()
decisionHandler(.cancel)
default:
decisionHandler(.allow)
}
1: If you’re not familiar with WKWebView
’s navigation delegate, don’t fret. The important thing to know is the action
contains a URL, and based on that we decide whether we want the web view to load it (decisionHandler(.allow)
) or not load so we can do something else instead (decisionHandler(.cancel)
).↩︎
2: We could do something “clever” and regex the string looking for a trailing :
to denote a scheme, a leading /
for a path, or an internal .
for a host, etc. Edge cases abound, however. And at that point, we’re essentially reimplementing our own type system on top of String
. Swift has a perfectly good type system already, so I’ll leave the implementation (and attendant disillusionment) as an exercise for the reader.↩︎
Sometimes when working on a thing, I get the sense I’m swimming up stream — that it’s just a little too hard and there has to be a better way.
Sometimes this prompts me to do a little more research and I’ll find the solution staring me in the face. But sometimes I continue to miss the obvious, post about it, and rely on the kindness of smarter people to straighten me out.
In the case of my last post on applying optional values to failable initializers, the role of smarter people is played by Ole Begemann, Will Hains, and a bunch of helpful redditors.
I re-invented the wheel with applyOptional
, which is functionally identical to calling flatMap
on an optional type.
And given this, Haskell already has an operator for calling flatMap
(a.k.a. bind
) on an Optional
(a.k.a. Maybe
) and it’s >>=
. To avoid confusion, we should probably use this if an operator proves necessary.
Further, this all sounds familiar enough that I think I might have known it at one point or another. But I never really understood it, not on an intuitive level. When the time came, binding an optional via flatMap
never occurred to me.
So in an effort to make the lesson stick this time, I’ve tried to go back to basics. Thus, the rest of this post.1
First, how on earth does flatMap
apply to optionals anyway? Isn’t it supposed to deal with nested arrays or something?
Well, sort of. Let’s start by looking a little more into that nested Array
scenario. We can think of a plain ol’ map
on an Array
like this:
[T].map((T)->U) -> [U]
Which is to say, map
is a method on an array of T
s that, given a function that turns a lone T
into a U
, will return an array of U
s.
Now, we can imagine giving our map
a function that returns an array of U
s instead of a single U
, and then we would end up with something like:
[T].map((T)->[U]) -> [[U]]
See how now we’re returned a nested [[U]]
? That’s not always super useful. Sometimes we just want [U]
.
flatMap
deals with this by adding an operation that “flattens” the output before returning it. In this case it will unwrap our [[U]]
into a simple [U]
:
[T].flatMap((T)->[U]) -> [U]
This seems like a useful (if rather niche) utility. But how does it apply to Optional
s?
My big mistake was thinking about optionals as just another enum
when, in several situations, Swift treats them more like a collection of zero or one elements.
Viewed in this light, the same examples we used above for arrays can be applied to optionals. A map
works like this:
T?.map((T)->U) -> U?
That is, given a collection of zero or one T
s and a function that turns a T
into a U
, map
will return a collection of zero or one U
s.2
And just as with our array example, we can imagine a scenario where we pass this map
a function that returns a collection of zero or one U
s instead of a lone U
. This would result in:
T?.map((T)->U?) -> U??
Note the Optional<Optional<U>>
at the end there. We’d pretty much always want this to be unwrapped or “flattened” a level before we’d use it, and that’s exactly what flatMap
on an optional does:3
T?.flatMap((T)->U?) -> U?
If we think back to the previous post, I was bemoaning how some failable initializers in Foundation such as URL(string:)
don’t take optional parameters. But looking at it now, we see the signatures of these existing initializers exactly fit what flatMap
expects as a parameter:
//Takes a String, returns a URL?:
URL.init?(string: String)
//Takes a T, returns a U?:
(T)->U?
With no further modification we can pass these initializers into a flapMap
on an optional like so:
let urlString: String? = textField.text
let maybeURL = urlString.flatMap(URL.init)
And so it’s now clear to me why URL(string:)
doesn’t support optional parameters. It would break compatibility with a crucial part of Swift’s collection-processing toolkit.
But moreover, adding an optional param would be akin to asking URL.init
to accept an array of strings. It’s not URL
’s responsibility to know which elements of what collections to use to instantiate itself. Instead, it’s our program’s job to whip our collections into the accepted element(s), and pass those on to URL
.
And while it’s sometimes difficult to remember Optional
can, in this sense, be a collection in need of whipping, it none the less has a full complement of higher order functions (including flatMap
) making it up to the challenge, regardless.
1: As the Field Notes folks say, “I’m not writing it down to remember it later, I’m writing it down to remember it now.”↩︎
2: We don’t see this used much in practice because Swift gives us the optional chaining operator as a language feature. Functionally,
let str = maybeURL.map { $0.absoluteString }
and
let str = maybeURL?.absoluteString
are equivalent.↩︎
3: Note this is also the rational for why the recently deceased Sequence.flatMap
worked the way it did. If:
[T].map((T)->[U]) -> [[U]]
[T].flatMap((T)->[U]) -> [U]
and
T?.map((T)->U?) -> U??
T?.flatMap((T)->U?) -> U?
then
[T].map((T)->U?) -> [U?]
[T].flatMap((T)->U?) -> [U]
makes a certain amount of sense. Especially if we consider both arrays and optionals to be sequences of zero or more elements.↩︎
UPDATE: Ole Begemann, Will Hains, and a bunch of helpful redditors all point out a fundamental oversight I’ve made here:
My implementation of applyOptional
, below, is functionally identical to calling flatMap
on an optional type. And given this, there’s already prior art for an operator that calls flatMap
(a.k.a. bind
) on an Optional
(a.k.a. a Maybe
monad): >>=
.
I talk a little more about this in this post, but the takeaway is, just use the built-in flatMap
:
let urlString: String? = textField.text
let maybeURL = urlString.flatMap(URL.init)
And if we want to define an operator for this, we should use >>=
instead of my made up |?>
:
infix operator >>=
func >>= <T, U>(x: T?, f: (T)->U?) -> U? {
return x.flatMap(f)
}
let urlString: String? = textField.text
let maybeURL = urlString >>= URL.init
The original post follows:
Swift’s out-of-the-box compatibility with existing ObjC frameworks is an amazing technical feat and one of its greatest strengths. But that doesn’t mean I’m never annoyed when using, say, Foundation. Among my biggest pet peeves is the lack of optional overloads to common failable initializers like URL(string:)
:
let urlString: String? = textField.text
let maybeURL: URL?
if let someString = urlString {
maybeURL = URL(string: someString)
} else {
maybeURL = nil
}
// Do something with `maybeURL`...
It’s wordy. It’s got that dangling uninitialized let
just sort of hanging out. It has branching execution for no good reason.
And ultimately it just feels unnecessary. The whole idea of a failable initializer is that it can return a nil
in response to invalid input. It seems a small jump to extended the input to include optional types — the .none
values of which we would presume to be invalid. Then nil
s would just pass through.
But for whatever reason, that’s not how the API were translated to Swift. I’ve worked around this in a number of ways over the years. I started out using the nil coalescing operator:
let urlString: String? = textField.text
let maybeURL = URL(string: urlString ?? "🚫")
“🚫” is an invalid URL, so when urlString
is nil
, maybeURL
is nil
as well.
I don’t hate this — it’s concise and has a sort of literal readability to it. But it’s always felt like a hack based on undefined behavior. Eventually I moved to a more conventional solution:
extension URL {
init?(maybeString: String?) {
guard let string = maybeString else {
return nil
}
self.init(string: string)
}
}
let urlString: String? = textField.text
let maybeURL = URL(maybeString: urlString)
This works well enough. But it’s just as wordy as our original solution. The only difference is we give it a name behind an extension.
And therein lies another weakness: we have to define an extension like this for every failable initializer we use. There’s a fair amount of boiler plate in each, and there’s always the chance for collisions when extending Foundation like this.
Eventually I settled on generic solution in the form of a free function:
func applyOptional<T, U>
(_ x: T?, to f: (T)->U?) -> U? {
guard let someX = x else {
return nil
}
return f(someX)
}
let urlString: String? = textField.text
let maybeURL = applyOptional(urlString,
to: URL.init(string:))
This has a lot going for it. Not only can it generically handle optional initializers, it can be applied to functions returning optionals generally. This enhances its potential for reuse and thus its value as an abstraction.
But it’s never felt quite right. The first parameter feels buried. “Optional” isn’t descriptive enough to give me a sense of its purpose. And I keep reading URL.init(string:)
as a newly created URL rather than a function reference. Something is missing.
I didn’t realize what until I started watching the excellent Point•Free video series.1 Early on they create a Swift version of F#’s forward application operator, |>
. I realized their implementation was very similar to my applyOptional(_:to:)
, and that got me thinking about free functions in terms of operators:
infix operator |?>
public func |?> <T, U>(x: T?, f: (T)->U?) -> U? {
guard let someX = x else {
return nil
}
return f(someX)
}
let urlString: String? = textField.text
let maybeURL = urlString |?> URL.init(string:)
So yeah, I took the triangle operator and put a question mark in it to make it more optional-looking. Nothing ground-breaking, but it feels loads better to use. Freed from their parentheses, the bare urlString
and URL.init(string:)
stand out in their purpose. And the way the operator points the optional into the function ref makes their relation clear in a way the to:
label never quite captured.
Not that there aren’t tradeoffs, of course. applyOptional
is way easier to google for than |?>
, for one thing. But until Foundation adds init?(string: String?)
initializers,2 expect my code to be dotted with these little FP gems.
1: If you’re reading this blog, I can’t imagine you wouldn’t find entertainment and value in these videos.
My favorite part? Each one is accompanied by a carefully edited transcription. I don’t always have the time or energy to watch a presentation. Reading is sometimes more comfortable. And transcriptions make searching for half-remembered concepts a snap, as well.↩︎
2: Which I’m still not ruling out…↩︎
This post by Oleg Dreyman is so clever, clear, and useful, I’m temped to call it a must read.
In a nutshell, using closures for delegates puts the responsibility of ownership management on the delegatee rather than the delegator. But by packaging the delegate into a setter, Oleg shows us we can control how it’s captured with respect to the closure (and, as a bonus, reduce our free-floating closures with more trailing closure syntax).
While I’m not sold this needing to be its own dependency, I’m absolutely going to adopt the pattern post haste. Thanks, Oleg!
Imagine we’ve written a UIControl
that wraps a button to do some animations whenever it’s tapped:
class BouncyButton: UIControl {
private weak var button: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
do {
let b = UIButton(type: .system)
addSubview(b)
button = b
}
button.addTarget(self,
action: #selector(handleTap),
for: .touchUpInside)
// Set frame or constraints...
}
@objc func handleTap() {
//Do some bouncy animations here...
sendActions(for: .touchUpInside)
}
}
We could hardcode our animation in the handleTap()
method, but maybe we need our button to bounce different ways in different contexts. Encapsulating the behavior of a thing in a swappable container is a common enough practice in software design that it has its own pattern — the strategy pattern.
In Cocoa, strategies have traditionally be implemented via delegates:
protocol BouncyDelegate {
func animateBounce(for view: UIView)
}
class BouncyButton: UIControl {
let delegate: BouncyDelegate //🤔
//...
init(frame: CGRect, delegate: BouncyDelegate) {
self.delegate = delegate
//...
}
@objc func handleTap() {
delegate.animateBounce(for: button)
sendActions(for: .touchUpInside)
}
}
This seems straight-forward enough, but there’s a problem. The delegate is strongly held by our control. You can imagine a view controller with this button in its hierarchy setting itself as the button’s delegate. Then the view controller would own the button, which owns the delegate, which owns the button, which owns… a retain cycle!
For this reason, the Cocoa convention is to make all delegates weak
. No problem:
weak let delegate: BouncyDelegate
//🛑: 'weak' must be a mutable variable
Oh, I mean:
weak var delegate: BouncyDelegate
//🛑: 'weak' may not be applied to
// non-class-bound protocol
Hmm. We can fix1 this with:
protocol BouncyDelegate: class { ... }
But this will prevent us from using struct
s or enum
s to implement our strategy and, at this point, ought make us a little suspicious of what’s going on.
We have to limit our delegate to class
implementations because delegates are assumed to hold mutable state.
Since they have state, they need to be instantiated. If they need to be instantiated, we have to provision storage for them. If we have to provision storage for them, questions around ownership need to be resolved. Qualifiers like weak
and (the implicit) strong become necessary.
If we could somehow define our strategies to be stateless things, all of this would cease to be relevant.
But moreover, the thought of mixing state and strategies ought to give us the heebie-jeebies.2 Storing state means a strategy might behave differently depending on the values kept within it. Unless we’re writing a parser, no one wants to have think about every other thing that might have mutated our delegate before us and in what order.3
So we want to do away with state in our strategies. And if we’re going to do away with state, then instances are nothing more than chunks of memory we don’t use but still need to manage ownership of. We should get rid of those, too.
So rather than holding our strategy’s implementation in instance methods that need to be instantiated, we’re going to move it all up into type methods on the type.
Let’s start with the protocol:
protocol BouncyDelegate {
static func animateBounce(for view: UIView)
}
The static
here indicates a type conforming to BouncyDelegate
must have animageBounce(for:)
defined on its type. Anything can technically conform to this, but I like to use enum
s because they can never be instantiated, even accidentally.
enum ShakeStrategy: BouncyDelegate {
static func animateBounce(for view: UIButton) {
let base = CGAffineTransform.identity
let offset = base.translatedBy(x: 30, y: 0)
view.transform = offset
UIView.animate(withDuration: 0.5, delay: 0,
usingSpringWithDamping: 0.2,
initialSpringVelocity: 0,
options: [],
animations: { view.transform = base }
completion: nil)
}
}
Great! We have our strategy type defined. Now we have to change our button a bit to use it:
class BouncyButton: UIControl {
let delegate: BouncyDelegate.Type
//...
init(
frame: CGRect,
delegate: BouncyDelegate.Type) {
self.delegate = delegate
//...
}
@objc func handleTap() {
delegate.animateBounce(for: button)
sendActions(for: .touchUpInside)
}
}
Note we’ve had to change the type of delegate
from BouncyDelegate
, which would be an instance of a type conforming to our protocol, to BouncyDelegate.Type
, which is the conforming type, itself.
We’ll initialize our button by passing in our strategy type like so:
let button = BouncyButton(
frame: someRect,
delegate: ShakeStrategy.self)
Note the use of .self
to indicate we want to use the type itself as our delegate.
If all this .Type
and .self
stuff feels a little awkward, it’s probably because Swift already supports this kind of thing as a language feature. It has a specific syntax just for passing around types that are used to specialize implementations. We know it as “generics”.
We can rewrite our button like so:
class BouncyButton<Strategy>: UIControl
where Strategy: BouncyDelegate {
//...
init(frame: CGRect) {
//...
}
@objc func handleTap() {
Strategy.animateBounce(for: button)s
sendActions(for: .touchUpInside)
}
}
Note that not only do we get to drop the ownership qualifiers around our delegate
property, we don’t need a delegate
property at all! This, in turn, simplifies our init
and ditches all the confusing .Type
stuff.4
The .self
stuff disappears, too. We now create our button like so:
let button =
BouncyButton<ShakeStrategy>(frame: someRect)
Cool! But a further, easy-to-overlook benefit is how we’ve moved responsibility for specifying strategy away from init params and into the type. Now if we ever find ourselves allergic to angle brackets5 we can alias the whole thing away with another of Swift’s type-related language features:
typealias ShakyButton = BouncyButton<ShakeStrategy>
let button = ShakyButton(frame: someRect)
1: Almost. We’ll still have to make BouncyDelegate
optional, as well.↩︎
2: The Cocoa convention of passing delegate methods the operative object as their first parameter (i.e. tableView(_:heightForRowAt:)
) could be seen as a suggestive push in the direction of stateless delegation.↩︎
3: And those writing parsers wish they didn’t have to.↩︎
4: Though, as Michael Tsai points out, “unlike delegates, the type cannot change at runtime.” Dynamism is a key feature not only of delegates, but also of the strategy pattern, generally. Be aware that once we move to generics, we’re giving that up (though we can still swap out types with .self
at runtime, as per the previous example).↩︎
5: Or if we’re shipping a framework and want to, say, restrict 3rd party customization of our controls beyond certain pre-defined combinations of strategies…↩︎
subscribe via RSS