Swift conciseness through operators, extensions, and unnamed arguments (Part 1)

In a toy project, I had the need to intercept all external keyboard keypresses.

Something I am always trying to keep a critical eye on when coding in swift is 'Have I made this as concise as possible without sacrificing readability in the name of cleverness/magic/etc? I want to be sure to jettison any overly verbose habits from coding in Objective-C for the last 5 years. Elegance and readability are critical in creating easily maintainable code.

In order to intercept external keyboard keypresses in iOS, one has to implement a function

func keyCommands() -> [UIKeyCommand]!  

in a ViewController

From that function you return an array of valid UIKeyCommand's. When iOS receives a keypress, it checks, in order, your array of valid UIKeyCommand's, and if it finds a match, invokes a method on the class to alert you to the keypress.

If you wanted to invoke the 'keyPressed:' method on your class when Command-A was pressed, you would create a UIKeyCommand instance:

UIKeyCommand("A", UIKeyModifierFlags.Command, Selector("keyPressed:"))  

And to build an entire keyboard worth of possible keypresses, you could make an Array of characters, and put them all together like this:

var keys = [UIKeyCommand]()  
for digit in Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ")  
{
   keys.append(UIKeyCommand(input: String(digit), modifierFlags: UIKeyModifierFlags.Command, action:  Selector("keyPressed:"))
   keys.append(UIKeyCommand(input: String(digit), modifierFlags: UIKeyModifierFlags.Control, action:  Selector("keyPressed:"))
   keys.append(UIKeyCommand(input: String(digit), modifierFlags: nil, action: Selector("keyPressed:"))
}

How can we make this code more concise?

It turns out swift offers a number of things out of the box like Strings are already enumerable for their elements (drop Array())

var keys = [UIKeyCommand]()  
for digit in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
{
    keys.append(UIKeyCommand(input:  String(digit), modifierFlags: .Command, action:  Selector("keyPressed:"))
    keys.append(UIKeyCommand(input: String(digit), modifierFlags: .Control, action:  Selector("keyPressed:"))
    keys.append(UIKeyCommand(input: String(digit), modifierFlags: nil, action:  Selector("keyPressed:"))
}

and dropping Enum prefixes (UIKeyModifierFlags in this case):

var keys = [UIKeyCommand]()  
for digit in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
{
    keys.append(UIKeyCommand(input:  String(digit), modifierFlags: .Command, action:  Selector("keyPressed:"))
    keys.append(UIKeyCommand(input: String(digit), modifierFlags: .Control, action:  Selector("keyPressed:"))
    keys.append(UIKeyCommand(input: String(digit), modifierFlags: nil, action:  Selector("keyPressed:"))
}

and automatic String to Selector conversion (drop Selector())

var keys = [UIKeyCommand]()  
for digit in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
{
    keys.append(UIKeyCommand(input: String(digit), modifierFlags: .Command, action:  "keyPressed:"))
    keys.append(UIKeyCommand(input: String(digit), modifierFlags: .Control, action:  "keyPressed:"))
    keys.append(UIKeyCommand(input: String(digit), modifierFlags: nil,  action: "keyPressed:"))
}

and of course we could only convert the letter Character to a String type once:

var keys = [UIKeyCommand]()  
for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
{
    let sletter = String(letter)
    keys.append(UIKeyCommand(input: sletter, modifierFlags: .Command, action: "keyPressed:")
    keys.append(UIKeyCommand(input: sletter, modifierFlags: .Control, action: "keyPressed:")
    keys.append(UIKeyCommand(input: sletter, modifierFlags: nil,  action: "keyPressed:")
}

Ok . . . this is looking a little bit more concise than Objective-C, and fairly easy to read although you could argue, and I would, that passing a String to reference a function callback seems brittle. Alas it's a carry over from Objective-C interoperability.

How can we make this code tighter?

UIKeyCommand is a fairly unambiguous class. It's purpose is to hold a key w/modifiers and the action it should trigger. In this case, those parameter names ('input', 'modifierFlags', 'action:') don't seem to be adding much value to the readability of our code.

So it seems appropriate here to cut things down even further by creating a new convenience constructor for UIKeyCommand which allows using that same original constructor, but without the parameter names.

extension UIKeyCommand {  
    convenience init( _ input: String!, _ modifierFlags: UIKeyModifierFlags, _ action: Selector)
    {
        self.init(input: input, modifierFlags: modifierFlags, action: action)
    }
}

The underscores (_) are what makes this work. Swift allows you to specifiy a outwardly visible parameter name seperately from parameter name that is inwardly referencable in the function. Underscore (_) basically means 'Allow nothing' here.

With this new nameless parameter convenience constructor, our code can be further refined to drop input: and modifierFlags: and action::

var keys = [UIKeyCommand]()  
for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
{
    let sletter = String(sletter)
    keys.append(UIKeyCommand(sletter, .Command, "keyPressed:"))
    keys.append(UIKeyCommand(sletter, .Control, "keyPressed:"))
    keys.append(UIKeyCommand(sletter, nil, "keyPressed:"))
}

Because the parameters .Command, .Control, 'keyPressed:', and sletter are so recognizable for their intent, I would argue we have only gained readability and conciseness here.

A little bit more tidyness can be attained by using the built in swift operator overload += (part of Array)

var keys = [UIKeyCommand]()  
for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  
{
    let sletter = String(sletter)
    keys += [UIKeyCommand(sletter, .Command, "keyPressed:"),
             UIKeyCommand(sletter, .Control, "keyPressed:"),
             UIKeyCommand(sletter, nil, "keyPressed:")]
}

Another area that could be tightened up is converting of the String to an Array, looping through the Sequence of Characters, and then converting each Character to a String. It is necessary but does not seem core to the problem we're solving here. We can extract those gruntwork details into a String extension to further simplify our code.

Adding a String extension:

extension String {  
    func each(closure: (String) -> Void ) {
        for digit in self
        {
            closure(String(digit))
        }
    }
}

allows us to further refine our code to:

var keys = [UIKeyCommand]()

"ABCDEFGHIJKLMNOPQRSTUVWXYZ".each({  
    keys += [UIKeyCommand($0, .Command, "keyPressed:"),
             UIKeyCommand($0, .Control, "keyPressed:"),
             UIKeyCommand($0, nil, "keyPressed:")]
})

In swift, $0 is a special keyword that means 'the first parameter passed to the closure' which in this case would be each letter contained in the A-Z String.

Happy Swifting!

UPDATE: I wrote a follow-up to this blog entry here which covers some awesome feedback I received and new ideas.