Swift is flexible. You just won’t believe how vastly, hugely, mindbogglingly flexible it is. I mean, you might think it takes just a wee bit of code to populate some array, but that’s just peanuts compared to how many amazing ways you can use everything from closures to protocols to functional programming to build small collections for testing, prototyping, etc.
These kinds of tiny challenges open you to Swift’s enormous design space. Just as it’s a mistake to think you can solve any major problem with potatoes, Swift’s power features help you move past obvious code to the expert, the arcane, and the reusable. Swift’s simplicity is an illusion. Move past that illusion.
Introduction
My name is Erica Sadun. I’m a writer, blogger, and coder. I write about the Apple ecosystem, and specifically about developer topics. Today, it’s my pleasure to talk to you about Swift’s amazing flexibility and power.
I’m going to focus on a simple array. I’ll be showing you many ways Swift helps you build an ordinary collection. I’ll showcase Swift language features that will inspire you in your own development work.
Multiple Views
A few weeks ago, I was helping a friend stress test his user interface. He needed a collection of test views. I asked him, “How many views do you need?” He said, “I don’t know, a bunch.” I gave him a bunch…
[ UIView(), UIView(), UIView(), UIView() ]
This was not what he was looking for. Although I was joking, my response showcases several problems:
- Fixed
- Not reusable or generalizable
- Hard to audit: You have to count how many items are in the array to see if you have the right number - that breaks down fast when you start working with hundreds or thousands of views at a time.
- Fails with more complex constructors: When you switch to more complex constructors than this default UIView initializer, it gets even uglier. Because you can’t fit them all on a single line. You have to repeat them over and over again.
This is a terrible way to generate your array, but Swift has some great ways to build fixed count arrays, and that’s what we’re going to talk about today.
Being Swifty
If you come from a procedural language background, you may be tempted to use iteration to build your collection. There’s nothing wrong with this approach, but it’s not very Swifty.
let aBunch = 5 // Bunchiness will vary var views: [UIView] = [] for _ in 1 ... aBunch { views.append(UIView()) }
This code feels migrated, not native. There are lots of other ways you can handle this particular challenge.
Swift is the Frankenstein baby of many different languages.
Get more development news like this
Swift has roots in Haskell, Ruby, Scala, Python, Rust, C#, and other languages, as well as of course, Objective-C. Swift has procedural roots. But it also has other capabilities that are baked right in. These include objects, value types, protocols, functional programming and more. There’s a lot to explore, even when building simple arrays.
When you see code like the example above, you may think “How can I make this more Swifty?” In this particular example we can achieve it by initializing UIView instances and collecting them into an array. The
for loop doesn’t use a variable because it’s a counter. It is pushing the repeated clause. You run it many times to collect a number of views and it feels overly complex.
Functional Swiftiness
Many, if not most, experienced Swift developers will prefer a functional programming approach. They’ll substitute the map function for the
for loop, for example.
let views = (1 ... aBunch) .map({ UIView() })
This particular map function applies its functionality across its source range. Which is that 1 through
aBunch. It returns the results in an array like
map always does, and in this case the domain is the range from one to five and the array that’s returned contains five new views. The
for loop before it, this closure doesn’t use the numbers one through five.
To make this code compile, you must explicitly tell Swift to ignore those numbers. You do that by adding an underscore notation that says, “Hey Swift, ignore the closure arguments and just build me a new view.”
let views = (1 ... aBunch) .map({ _ UIView() })
I’d say that a lot of Swift developers would use this approach and build their array of views. It’s simple, it gets the job done, and it doesn’t make you write new methods or protocols or do anything fancy. It’s a very reliable way to collect a bunch of views. I personally have some minor issues with this approach, and I will spare you my normal rant. The function
map has a very specific meaning in math.
In functional programming, when you map something you apply a function. A function creates a relation from a value in a domain to a value in a range.
This Swift shortcut breaks that contract. It produces a different view for each member of the source range – that’s a little bit aesthetically displeasing…
Should this stop you from using map? Probably not. There’s no Swift purity test. The compiler doesn’t care. But there are many other ways to build your array, so let’s look at a few more.
UIView.init()
Consider what
UIView() does – it’s a constructor. It creates a new view. It’s a short hand for calling a view initializer, specifically
UIView.init(). When you want to create a view instance, you’re saying this can be constructed. Why can’t you use that notion in Swift to create an operator that constructs types?
First, look at protocols. Protocols create a blueprint for a single semantic notion, e.g. equatable or hashable or collection.
/// A type that can be instantiated without arguments public protocol Constructible { init() }
I’ve created a new protocol that I call
Constructible. It means something that can be constructed using
init(). It contains that one requirement: a zero argument initializer. Any type that supports
init() without arguments can conform to my constructible protocol.
// Make views constructible extension UIView: Constructible {}
This is the entirety of the code it takes to conform UIView and all it’s subtypes to constructible. Simple. Then, UIView becomes constructible, it conforms to that protocol, and you can do some fun things with it.
One of the things you can do is extend the integer type
Int:
extension Int { /// Return array containing `self` instances of a constructed type /// extension NSObject: Constructible {} /// 3.of(NSObject.self) public func of<T: Constructible>( _ this: T.Type) -> [T] { return (0 ..< self) .map ({ _ in this.init() }) } }
By using constructible, you can build a new method and you can see this method which is called of uses the constructible type. It creates an array of instances of that constructible type.
This code still uses range mapping because I haven’t talked about any other solutions. But, you can use any solution you want in the inside of the function.
Inside the braces is a difference from the map that you saw earlier. It’s not exactly the same map. Instead of calling the view constructor, it calls the initializer. That initializer is for whatever type you’re constructing.
UIview.self
Here’s a real column. Here you can make an array of five views:
5.of(UIView.self)
Or, you can make an array of five UILabels:
5.of(UILabel.self)
You pass it any view types and it returns new instances in an array. This makes it very cool and convenient and readable. You’re returning five of UILabels. The fact that UILabel has the self after it makes it clear that you’re referring to a type.
Let’s say you wanted to make this even cuter. Let’s introduce an operator instead of using a method on
Int.
extension Int { /// Returns n constructed instances of this type public static func * <T: Constructible>( count: Int, this: T.Type) -> [T] { return count.of(this) } }
Here’s some code that does exactly that. This code creates an operator that builds multiple instances and returns an array with them. As an operator, I have implemented it as a static method on the
Int type, so you see it’s
public static func. And the reason it has to be static and a class type is that otherwise, it would be a global function. But in Swift 3, it now comes under the umbrella of
Int, can be declared within the extension, and is a cleaner way of doing operator implementations.
If you see what it does, is this operator calls
count.of(this), which is exactly the same function that I showed you a few seconds ago. But, it’s where you use it that you get the greatest benefit:
views = 5 * UIView.self views = 5 * UISwift.self views = 5 * UITableViewCell.self
You can create view arrays by using an integer and a type, and the times operator. It’s clear that you’re not multiplying
UIView. You’re creating five times
UIView. It’s simple – you can do five views, five switches, five tableview cells. You have an integer, you have the operator, and then you have the type that you’re constructing.
arc4random_uniform(n)
What if you’re not interested in constructing an instance, but in generating any type? What if you wanted to build an array of random numbers? Using an initializer won’t get you very far unless you build a new random number type, and honestly I don’t want to build a new random number type. Surely there’s a better approach. I generate random numbers using
arc4random_uniform(n).
This returns integers between zero and n. It doesn’t hit n, it’s up to but not including n. That’s its maximum value. Because arc4random and all of its little variations, because they live in the BSD world, and we live in Swift world, we have to do things to convert from its numbers to ours.
Getting an integer out of
arc4random_uniform(n) is not the cleanest call in the universe:
let randomNumber = Int(arc4random_uniform(UInt32(maxNumber)))
If you want to create an array with several random numbers, this can get ugly, fast. First, move it more Swiftwards. C or convert this call to a closure. Make that a closure that generates random numbers one by one on command.
let randomNumberGenerator = { Int(arc4random_uniform( UInt32(maxNumber))) } randomNumberGenerator()
I’ve built this generator simply by surrounding the call with braces. All the type conversions, everything is there. But it’s has braces around it and that means that to generate a new random number over and over however many times I want to. All I have to do is call it with a pair of parentheses because it takes no arguments – it’s a zero argument closure.
Then, I can call the
randomNumberGenerator as many times as I want. But since I’m working with a generator, instead of using functional programming, or an operator, the generator naturally lends itself to sequences. Whenever you hear the word generator, you think sequences. They go together in Swift.
Sequences
The simplest way to turn this generator into a sequence is to surround the generator and calls to any sequence and any iterator, and grab the first end items using prefix.
let randomNumberGenerator = { Int(arc4random_uniform( UInt32(maxNumber))) } let randomSequence = AnySequence( AnyIterator(randomNumberGenerator)) Array(randomSequence.prefix(5))
It works well, but it’s complex because you don’t need to build a sequence to grab those first five numbers. You only need an iterator. And you can do the same thing by dropping any sequence.
Array(AnyIterator(randomNumberGenerator) .prefix(5))
This is simpler. And it returns five random numbers in an array. It does it in a very Swifty way. I’m still not crazy about having to convert the sequence to an array, but you can hide that. If you’re working with views, you can use a similar approach with
UILabel.init, instead of using the random number generator. And this would give you five fresh UILabels.
Array(AnyIterator(UILabel.init).prefix(5))
Alternatively, you can use a closure argument, and you could build a label within that closure, as part of any iterator. It’s the same approach and the same result, it looks different.
Array(AnyIterator({ UILabel() }).prefix(5))
When working with sequences, you can get ridiculous…
func countedArray <T>(_ count: Int, generator: @escaping () -> T) -> [T] { return Array(sequence(state: count, next: { (state: inout Int) -> T? in defer { state -= 1 } return state == 0 ? nil : generator() })) }
This example builds a state based sequence that decriments accounter, reduces it by one and then reduces it again, all the way until it reaches zero. And when it hits zero, it terminates a sequence, throws it into an array, and then returns the result. And this is fine, you can call it. There are many ways in Swift to create a counted array – this is just one, but shows that there are some esoteric ways out there.
Initializers
I’ve shown you some functional programming, some protocols. You’ve seen generators and iterators and sequences. But I want to conclude with initializers. They get the job done the best.
Array already has a built in initializer that lets you use account and a repeated element.
/// Creates a new array containing the specified number of a single, repeated value. /// /// - Parameters: /// - repeatedValue: The element to repeat. /// - count: The number of times to repeat the value passed in the `repeating` parameter. `count` must be zero or greater. public init (repeating repeatedValue: Array.Element, count: Int)
Unfortunately you cannot use this initializer for this exact problem. The repeating initializer repeats exactly the same value for every single array position. Instead of five views, you get five of the exact same view. Instead of five random numbers, you get five of the exact same number. You might say, “Let’s fix the language. Let’s change that repeated value from array element, to some closure that lets you pass a generator, rather than a value.”
But, unfortunately, you can’t do that without changing the language, or changing routine to a different name. In other words, you can’t overload this with different functionality and different arguments.
This was recently brought up on the Swift evolution list which is where Swift evolves and becomes the new version, and it’s run by Apple. The idea was brought up and it was immediately shouted down. Even though this is a common workflow, I use it for user interface stress testing, in mathematical simulations, and other places where you want to create arrays of values.
The members of the Swift evolution list felt that this change would be too disruptive. One reason for this is that you always want to put closure arguments last. And, the built in API in its current state, puts its repeated value first.
Instead of breaking Swift, or offering an alternative API through the standard library, its left up to you to create your own array extension to provide this functionality. Fortunately this is simple to do:
public extension Array { /// Creates a new array containing the specified number of values created by repeating a generating closure. /// /// - Parameters: /// - count: The number of times to apply the closure. `count` must be zero or greater. /// - generator: The closure to execute public init(count: Int, generator: @escaping () -> Element) { precondition(count >= 0, "Cannot initialize array with negative count") self.init(AnyIterator(generator).prefix(count)) } }
Here’s my great way of doing it. This is an initializer. It’s part of a
public extension, and it takes two parameters: a
count and a
generator. Because the generator is the last argument, you can use it directly or as a trailing closure.
There’s not much code. There’s a precondition that ensures you don’t pass a negative number. I use the
AnyIterator approach with the prefix, because that’s a nice clean way to get the results.
Here’s what those calls look like from the calling side:
let views = Array(count: 5, generator: UIView.init) // or let views = Array(count: 5) { UIView() } let numbers = Array(count: 5, generator: randomNumberGenerator)
With this approach, you’re guaranteed that you get however many items you need, they’re placed into an array, and a new value is generated for each element. Using the closure or call you give it. It gives you the best of both worlds.
You can either with UIView pass, UIView init, or you can use a trailing closure, or you can use that closure as an argument. I could’ve easily given you more approaches, but I wanted to showcase Swift’s flexibility even for a simple problem like this.
Swift is super flexible.
I’m hoping that you’ve gotten a glimpse of how freely flexible it is. It takes a tiny bit of code to populate an array. But that’s peanuts compared to how many different ways you can use closures and protocols, sequences and constructors, initializers and functional programming, in your everyday development.
If you’d like to learn more about Swift, and specifically about Swift Style, pick up a copy of my latest book from Pragmatic Press.
About the content
This talk was delivered live in September 2017 at try! Swift NYC. The video was recorded, produced, and transcribed by Realm, and is published here with the permission of the conference organizers.