What's New in Swift 3 - Part 3

Curious about what’s new in Swift 3? In this final part of a three-post series, Daniel Steinberg covers Clarity, Good Looking C, and API Guidelines.

You can find the other parts here:


Clarity

 

Move where clause to end of declaration (Proposal 0081)

Many of the Swift 3 proposals revisited existing Swift code and said, “How can we make this easier for humans to parse this code?” For example, in this function anyCommonElements, it takes two generic types:

func anyCommonElements<T : SequenceType, U : SequenceType where
    T.Generator.Element: Equatable,
    T.Generator.Element == U.Generator.Element>(lhs: T, _ rhs: U) -> Bool {
    ...
}

But the parameters for the function come way there at the end. There is an awful lot in between the declaration of the generic types and the declaration of the parameters in the return type for this function. In between is this where clause that is important, but it is not as important as the parameters in the return type of the function.

In Swift 3, this where clause will move to the end, after the parameters and the return type of the function, and before the opening curly brace:

func anyCommonElements<T : SequenceType, U : SequenceType>(lhs: T, _ rhs: U) -> Bool
    where
        T.Generator.Element: Equatable,
        T.Generator.Element == U.Generator.Element {
    ...
}

Restructuring condition clauses (Proposal 0099)

In guard clauses and if clauses, we could have multiple, comma-separated conditions. The rule so far has been: we could have a Boolean expression followed by various lets:

guard
    x == 0,
    let y = optional where z == 2
    else {

In this example, we are testing that x is zero, and we are binding y where z is two. We need to use this where clause because we cannot put a Boolean expression after the let.

In Swift 3, we can comma-separate, and move the where clause down as just a standard Boolean condition:

guard
    x == 0,
    let y = optional
    z == 2
    else {

And so regarding the x is zero, we are binding y, and regarding that z is 2.

In Swift 2, this would be confusing because we do not have to repeat the let for various bindings. In Swift 3, we cannot do something like this, where we guard let, and we bind three things at the same time:

guard let x = opt1,
          y = opt2,
          z = opt3,
          booleanAssertion else {}

x is optional 1 and y is optional 2, and z is optional 3. Instead, in Swift 3, we must repeat the keyword let:

guard let x = opt1,
      let y = opt2,
      let z = opt3,
          booleanAssertion else {}

And although that makes it just a little bit wordier, it makes it clearer, and it allows us to intersperse lets in Boolean expressions.

The same is true of case, we have to repeat the keyword case, but again that adds flexibility to the condition clauses:

if case let x = a,
   case let y = b {

Improved operator declarations (Proposal 0077)

Operator declarations have been a mess:

infix operator <> { precedence 100 associativity left }

There is nothing that says “100 goes with precedence”, there is nothing that helps us parse this.

In Swift 3, we can create a precedencegroup:

precedencegroup ComparativePrecedence {
    associativity: left
    higherThan: LogicalAndPrecedence
}
infix operator <> : ComparativePrecedence

In this case, we assign the associativity to be left, and notice the colon in between that says, That is the value of the associativity. We set the precedence level to be higherThan LogicalAndPrecedence, we are comparing it with numbers like 100 and 120 as we did in the past. That is what makes up the definition of this ComparativePrecedence precedence group, and now we can declare our operator and specify that it has this associativity and precedence level.

Replacing equal signs with colons for attribute arguments (Proposal 0040)

You saw how much cleaner it was to use colons to map the associativity and precedence level, the same is going to be done in this proposal to attribute arguments that use =:

introduced=version-number
deprecated=version-number
obsoleted=version-number
message=message
renamed=new-name
mutable_variant=method-name

The equals sign will be replaced by the colon:

introduced: version-number
deprecated: version-number
obsoleted: version-number
message: message
renamed: new-name
mutable_variant: method-name

This is less about one looking better than the other, and more about consistency across the APIs.

Clarify interaction between comments & operators (Proposal 0077)

What happens when you put a comment in place?

if/* comment */!foo { ... }

When you remove the comment, is there a white space between If and the exclamation point, or not? In Swift 3, a comment will always be equivalent to a white space:

if !foo { ... }

The comment is interpreted as if there was just a white space between the if and what follows.

Tuple comparison operators (Proposal 0015)

If you have a tuple of comparables, why can you not compare the two tuples? In Swift 3 now you can, up to arity 6. The rule is simple: for equals (==) and non-equals (!=), you just compare element by element. For order comparisons, you do it in lexicographical order, starting with the first element and comparing; then, if you need a tiebreaker, going on to the next element.

Good Looking C

 

“Classic” CoreGraphics code (Proposal 0044)

When we connect to C code from Swift, it does not look very Swifty, and some attention has been paid to taking care of this.

For instance, let’s start with some classic core graphics code:

override func drawRect(rect: CGRect) {
    let context: CGContext = UIGraphicsGetCurrentContext()!
    let toCenter = CGPoint(x: bounds.width/2.0, y:bounds.height/2.0)
    let angle = CGFloat(M_PI / 16)
    
    var transform = CGAffineTransformIdentity
    for _ in 0..<32 {
        triangulateRect(bounds, inputTransform: transform, context: context)
        transform = CGAffineTransformTranslate(transform, toCenter.x, toCenter.y)
        transform = CGAffineTransformRotate(transform, angle)
        transform = CGAffineTransformTranslate(transform, -toCenter.x, -toCenter.y)
    }
    CGContextSetLineWidth(context, bounds.size.width / 100)
    CGContextSetGrayStrokeColor(context, 0.5, 1.0)
    CGContextDrawPath(context, .Stroke)
}

In this case, we have got a drawRect method, and we create a context. Now when we do things like set the line width, we have to pass in the context as one of the parameters, that is traditional C code. We do that when we set the line width, the stroke color, and when we actually draw the path.

Similarly, when we create an AffineTransform, when we go to use that, we have to pass in the transform as the first argument. We do it because transforming contexts or C constructs, and the functions that deal with them are just plain old C functions.

The great renaming (Proposal 0023)

There are many things we can do to change this code:

override func draw(_ rect: CGRect) {
    let context: CGContext = UIGraphicsGetCurrentContext()!
    let toCenter = CGPoint(x: bounds.width/2.0, y:bounds.height/2.0)
    let angle = CGFloat(M_PI / 16)
    
    var transform = CGAffineTransformIdentity
    for _ in 0..<32 {
        triangulateRect(bounds, inputTransform: transform, context: context)
        transform = CGAffineTransformTranslate(transform, toCenter.x, toCenter.y)
        transform = CGAffineTransformRotate(transform, angle)
        transform = CGAffineTransformTranslate(transform, -toCenter.x, -toCenter.y)
    }
    CGContextSetLineWidth(context, bounds.size.width / 100)
    CGContextSetGrayStrokeColor(context, 0.5, 1.0)
    CGContextDrawPath(context, .Stroke)
}

To start with, there is The Great Renaming where drawRect has been renamed. There is this redundancy where it is drawRect and then we pass in a Rect, so drawRect just becomes draw. And similarly, we do not have to say “draw” what, and then pass it in a Rect, and we put in this _. And when the system calls this method, it is called as draw, and passes in the Rect to be drawn.

Import as Member (Proposal 0044)

The magic of changing the context and the transform and how they are used comes with this proposal to import as member. We take these C structures and these functions, and we specify, when we bring these into Swift, that we import them as if they’re a member of a structed self.

Of course, in Swift, structs can have behavior. When we transform our C API’s into Swift, we want to treat these structs as if they have this behavior. Instead of having to pass in context and transform, it should be context. and transform.

override func draw(_ rect: CGRect) {
    ...
    context.setLineWidth(bounds.size.width / 100)
    context.setGrayStrokeColor(0.5, 1.0)
    context.drawPath(.Stroke)
}

Instead of passing context in as the first parameter for ContextSetLineWidth, it is now context.setLineWidth. Similarly, context comes out front for the next two methods; we do not pass in as the first parameter in our function call. Instead, it becomes context.. And then while we are at it, we do clean these up a bit:

override func draw(_ rect: CGRect) {
    ...
    context.setLineWidth(bounds.size.width / 100)
    context.setStrokeColor(gray: 0.5, alpha: 1.0)
    context.drawPath(using: .stroke)
}

setGrayStrokeColor becomes setStrokeColor and then gray as the label for the first parameter, and drawPath gets the label using and .stroke goes to lowercase.

Similarly, for transform, we are going to change all these times that we use transform, and it is going to be transform.:

override func draw(_ rect: CGRect) {
    ...
    var transform = CGAffineTransform.identity
    for _ in 0..<32 {
        triangulateRect(bounds, inputTransform: transform, context: context)
        transform = transform.translateBy(x: toCenter.x, y: toCenter.y)
                             .rotate(angle)
                             .translateBy(x: -toCenter.x, y: -toCenter.y)
    }
    ...
}

What is used to create these import as members is specified in the NS_SWIFT_NAME which has been around for a while, but is now being used in much deeper ways to make the imported APIs more Swifty.

Modernize libdispatch for Swift 3 naming conventions (Proposal 0088)

Outside of this general work being done on C-based APIs is an additional proposal for modernizing libdispatch for Swift 3. Here we create a queue using a typical C way of doing things, dispatch_queue_create, and then pass in the arguments:

let queue = dispatch_queue_create("com.test.myqueue", nil)

dispatch_async(queue) {
    print("Hello World")
}

That does not look very Swifty; instead, we are going to create a queue using something that looks much more like a constructor for dispatch_queue:

let queue = DispatchQueue(label: "com.test.myqueue")

dispatch_async(queue) {
    print("Hello World")
}

Similarly, we are going to use it instead of making this call to dispatch_async and passing in queue. The dispatch queue class is going to include the synch and the asynch methods, and we will call these by just using queue.synch and queue.asynch`:

class DispatchQueue : DispatchObject {
    func synch(execute block: @noescape () -> Void)
    
    func asynch(
        group: DispatchGroup? = nil,
        qos: DispatchQoS = .unspecified,
        flags: DispatchWorkItemFlags = [],
        work: @convention(block) () -> Void)
}

API Guidelines

 

API Design Guidelines (Proposal 0023)

Of all of the Swift proposals, the API Design Guidelines is the most important and informs many of the others. Some of the advice is general, and some of it is very specific. For example, one of the overall goals for an API is that there should be clarity at the point of use. There is an emphasis on the simplicity and the clarity at the call site as opposed to laboring over the method signature itself.

Although Swift is a more concise language than Objective-C, one of the important guidelines is that clarity is more important than brevity; if you have to lengthen a method name or add another label to make use of the method clear, you should do so.

Naming - Promote Clear Usage

There are various categories in the design guidelines, the first of which is naming. One of your guiding principles for naming should be to promote clear usage. This will require that you include all the words needed to avoid ambiguity. For example, here we have employees remove something:

employees.remove(x)

employees is an array, and what do we mean by removes something? Is the “something” the item to be removed? In this case, x stands for the index of the array this API becomes clearer if we instead say:

employees.remove(at: x)

Omit needless words

The first principle is to add in words for clarity; the second principle is to omit needless words:

allViews.removeElement(view: cancelButton)

In this example, allViews is a collection of views and when we removeElement, we do not have to specify that the element we are removing is a view - all of the elements are views. We can remove the label view: and still be completely clear. We could go further and remove the word element:

allViews.remove(cancelButton)

It almost reads like an English phrase and is completely descriptive.

Name variables, parameters, and associated types according to their roles

class ProductionLine {
    func restockFrom(widgetFactory: WidgetFactory)
}

In this example, this might be clearer if you think of it from the call site as restock as the name of the method and we move from inside:

class ProductionLine {
    func restock(from widgetFactory: WidgetFactory)
}

widgetFactory is the internal label - we use it within the function; even though we only use widgetFactory inside of the function, this is an odd choice for an internal label. Perhaps, instead of “widgetFactory” that is too specific, and we want to say “supplier.”:

class ProductionLine {
    func restock(from supplier: WidgetFactory)
}

We are restocking from whatever the supplier is and in this case, the supplier is a WidgetFactory.

Precede each weakly typed parameter with a noun describing its role

func add(_ observer: NSObject, for keyPath: String) {}

grid.add(self, for: graphics)

In the “add” function, we have an observer, and we have a keyPath. The observer does not have an external label and the keyPath has the external label for. When we call this it is not clear what self refers to, or what graphics refers to. Instead, we add a noun describing the role, and we can do that to the method name or to the label, and we end up with:

func addObserver(_ observer: NSObject, forKeyPath path: String) {}

grid.addObserver(self, forKeyPath: graphics)

Naming - Strive for Fluent Usage

Those are some guidelines for promoting clear usage we also strive for fluent usage.

Prefer method and function names that make use sites from grammatical English phrases

This example looks like very traditional code:

x.insert(y, position: z)

But that is not the way we speak. We would be more likely to say:

x.insert(y, at: z)

And that is the API that we prefer.

The following are some of the most controversial parts of these API design guidelines.

Naming functions and methods for their side effects

Functions or methods without side effects should read as noun phrases.:

x.distance(to: y)
i.successor()

Those that have side effects should read as imperative verb phrases:

print(x)
x.sort()
x.append(y)

Mutating/Non-Mutating pairs

Here was the controversial part: sometimes we have mutating and non-mutating parts; and when the operation is naturally described by a verb, we use the ed/ing ending for non-mutating:

x.sort()
z = x.sorted()

x.append(y)
z = x.appending(y)

The partner to “sort” and to “append” are “sorted” and “appending”. x.sort() is the mutating version z = x.sorted() is the non-mutating; x.append() is the mutating version, z = x.appending() is the non-mutating.

This principle caused problems with some of the terms, and a second principle was added: when the operation is naturally described by a noun, use form for mutating:

x = y.union(z)
y.formUnion(z)

j = c.successor(i)
c.formSuccessor(&i)

This went a long way to making people feel better about the earlier principle.

Update API naming guidelines and rewrite set APIs accordingly (Proposal 0059)

The examination of union and that whole discussion led to a sister proposal for updating the API Naming Guidelines for Set APIs.

x = y.union(z)
y.formUnion(z)

Now back to the API Design Guidelines.

API Design Guidelines (Proposal 0023)

Now that we have protocols extensions, we can prefer methods and properties to free functions. Before protocol extensions, we were stuck using free functions because we did not know where to park that functionality.

When you are naming the various parts of a function or method, you should chose parameter names to serve as documentation:

func move(from start: Point, to end: Point)

Here the external name is move from one place to another place. Internally it is start and end as you are working with these points, internal to the function - you know which one refers to the start, and which one refers to the end.

In Swift, we can have default values for parameters. Many people forget that we can do this in inits as well. Instead of having a whole family of inits with different numbers of parameters, we can have default values for parameters, and have a smaller set of inits.

It is not a requirement in Swift that we locate the parameters with defaults towards the end, but it is a preference; we prefer to put them towards the end of the parameter list.

Apply API Guidelines to the Standard Library (Proposal 0006)

In light of these new API design guidelines, it is important to go back to the Swift standard library and make sure that the Swift standard library conforms to these guidelines. It was essential that those writing the importers for the Objective-C APIs made sure that the Swift versions conformed to these API design guidelines.

Resources


Daniel Steinberg

Daniel Steinberg

Daniel is the author of the books 'A Swift Kickstart' and 'Developing iOS 7 Apps for iPad and iPhone', the official companion book to the popular iTunes U series from Stanford University. Daniel presents iPhone, Cocoa, and Swift training and consults through his company Dim Sum Thinking. He is also the host of the CocoaConf Podcast.