en
de

What I Like in Swift 2 — The Return (Part 3 of 3)

10 July 2015
| |
Reading time: 5 minutes

This is the last part, 3 of 3, of this post series about Swift 2. You can read part 1 or part 2, if it is not already the case.

Today we will be discussing the following topics:

  • Early access with guard
  • Error handling
  • Swift as open-source
  • String slicing
  • Playground slowness
  • Selectors as plain strings

Early Exit with Guard

We have often the case where we try to downcast an object to a type, or try to access a property which can be nil. We want to exit in case it does not work, but continue with the result in case it worked. Without the guard statement, you would end up with the following kind of code.

class Person {
    var name: String?
    var age: Int?
}

// without guard statement
func printOutPerson(p: Person) {
    if let name = p.name,
       let age = p.age {
        print("I am \(name) and \(age) years old")
    }
    else {
        // do smth in case we cannot print 
        // the name and age
    }
}

Using a guard statement, we pull the else case at the beginning of the method, and have a smoother reading flow where the normal case is placed below, with no curly brackets around it.

// with guard statement
func printOutWithGuard(p: Person) {
    guard let name = p.name,
          let age = p.age else { return }
   
    print("I am \(name) and \(age) years old")
}

More info: WWDC talk ’What’s New in Swift’

Error Handling

Swift 1 was lacking a modern error handling mechanism and was still relying on the old Objective-C approach with NSError out parameters in methods which could produce an error.

 

It was looking like this — implementing a simple regular expression replacement function.

// Swift 1
func stringByReplacingFoo(str: String) -> String? {
    var error: NSError? = nil
    let regex = NSRegularExpression(pattern: "(foo)+",
                  options: .CaseInsensitive,
                  error:&error)

    if (error != nil) {
        print("an error occured: \(error!)")
        return nil
    }
    
    let range = NSMakeRange(0, count(str))
    return regex!.stringByReplacingMatchesInString(str,
                    options: .allZeros,
                    range: range,
                    withTemplate: "bar")
}

We go one level up on the elegance scale with Swift 2. In our previous example we don’t want to propagate the error outside of the function – which we could do using throws in the method signature, hence we catch it. Please note in the catch block the weird error variable which is implicitly instantiated.

// Swift 2
func stringByReplacingFoo2(str: String) -> String? {
    do {
        let regex = try NSRegularExpression(pattern: "(foo)+",
                          options: .CaseInsensitive)
        
        let range = NSMakeRange(0, str.characters.count)
        return regex.stringByReplacingMatchesInString(str,
                       options: .ReportCompletion,
                       range: range,
                       withTemplate: "bar")
        
    }
    catch {
        print("an error occured: \(error)")
        return nil
    }
}

Of course we could have matched a given type of error, where we do something special (like tolerating it and returning a special value) in addition to the catch all case.

 

Super weird in the error handling is the fact that we don’t know which errors we are getting from a given method. The method only specifies throws but there is no type, unlike in a language like Java. That is in fact very similar to error handling in Objective-C: we get some instance of NSError in case something goes wrong in a method that we call, it is then up to us to identify some standard error that we know might have happened during the call. At best the possible errors from a call are in the documentation. At worst they are not and you are left on your own. In any case you can always get a general error, so you always have to include a general error case handling.

 

Of course the given example is somehow artificial since we might get an error for a regular expression that is known, since the developer is providing it. Hence we might actually just let the app crash in case our own regular expression is wrong. That can also be done elegantly by the forced try! statement in the following code. As a side note, it makes the do … catch blocks unnecessary.

func stringByReplacingFooBis(str: String) -> String? {
    let regex = try! NSRegularExpression(
                       pattern: "(foo)+",
                       options: .CaseInsensitive)
    
    let range = NSMakeRange(0, str.characters.count)
    return regex.stringByReplacingMatchesInString(str,
                   options: .ReportCompletion,
                   range: range,
                   withTemplate: "bar")
}

A final word about error handling:

  • Unlike other programming languages like Java, there is, according to what was said in the corresponding WWDC video, no huge penality when using exception handling – in particular in the regular case.
  • A defer statement placed inside the do block indicates that the subsequent statement should always be called at the end of the do block. It is similar to finally in Java.
  • There is a nice bridging between Swift, that can use a do … catch block, and Objective-C which cannot. In Objective-C the signature of a Swift method which can throw an error (declared with throws) has an additional error parameter of type NSError**.

 

More info: WWDC talk ’What’s New in Swift’ and ‘Swift and Objective-C Interoperability’.

Swift Open-Source

Swift 2 will be open-sourced later this year. This is definitely cool news. I am curious to see what it will be used for (e.g. on Linux), if the Swift API will evolve because of contributions back from the community or what advantages an open-source Swift language will bring.

What I do not like

String Slicing

I am not a big fan of string slicing the Swift way. It is still quite complicated compared to what it could be – and compared to what it is in Groovy for instance. The reason for that is a string subscript notation in Swift which does not accept a standard range of integer (type Range<Int>), being easily constructed, but requires a range of indices (type Range<Index>).

 

Let’s have a look at an example.

let str = "hello world"

str[str.startIndex...advance(str.startIndex, 4)]
// yields “hello"

str[advance(str.startIndex, 6)..<str.endIndex]
// yields “world"

You are welcome! That’s far from readable. And index is not easily constructed so you need to take the start or end index from the string you are manipulating and shift it with the advance function.

 

I could imagine a far easier syntax using plain range of int. Let’s start by defining an extension over String.

extension String {
   
    subscript(range: Range<Int>) -> String {
        let startIndex = range.startIndex
        let endIndex = range.endIndex - 1
        let start = startIndex < 0 ? 
                    advance(self.endIndex, startIndex) : 
                    advance(self.startIndex, startIndex)
        let end   = endIndex < 0 ? 
                    advance(self.endIndex, endIndex) : 
                    advance(self.startIndex, endIndex)
        let newRange = (start)...(end)
        return self.substringWithRange(newRange)
    }
   
}

With this extension in place we can slice a string a lot more easily.

"hello world"[0...4]
// yields “hello"

"hello world"[6...10]
// yields “world"

Of course there is still room for improvement on the second one. We could use negative indices to reference distances from the end of the string. Our extension supports that.

"hello world"[(-5)...(-1)]
// yields “world"

Groovy even goes one step further and allows reverse ranges like [1…-1] which means taking all characters from the second to the last one, but I was not able to easily implement such an extension in Swift – reverse ranges are not directly supported, reversing a range is allowed (the notation becomes ugly) but the reversed range is not a range anymore and things become complicated.

Playgrounds Painfully Slow

There is still room for improvement about how the playground files are evaluated. In Xcode 7 but also in Xcode 6 the playground execution is still very laggy. We have regularly to restart Xcode, or comment out previous part of the playground in order to trigger execution. Sometimes we update a few lines and nothing happens. When the playground file becomes large, things get messy. Up to some point where playground execution completely stops and we have to comment out or remove code to clear things up.

Selector are Plain Strings

In Objective-C the existence of a specified selector is checked by the compiler, we get a warning in case it does not exist. For instance if we want to add an observer for being notified when a given app will enter into foreground we can do it like that.

// Objective-C
[[NSNotificationCenter defaultCenter] 
    addObserver:self 
    selector:@selector(someMethod) 
    name:UIApplicationDidBecomeActiveNotification
    object:nil];

If someMethod does not exist in the current class, we get a polite yet indicative warning from the compiler.

 

In Swift a selector is a plain string and is not checked. The equivalent syntax is the following.

// Swift
NSNotificationCenter.defaultCenter().addObserver(self, 
  selector: "someMethod", 
  name:UIApplicationDidBecomeActiveNotification, 
  object: nil)

That is far from ideal and error prone.

Where to Go from Here?

There is a lot more to Swift 2 than the features I am mentioning in this post series. Make sure you check the new Swift 2 book, the various WWDC presentations about Swift – some of which are mentioned in this post series, and the references below.

 

As always, don’t hesitate to give me your opinion in the comment section below.

References

Comments (0)

×

Sign up for our Updates

Sign up now for our updates.

This field is required
This field is required
This field is required

I'm interested in:

Select at least one category
You were signed up successfully.

Receive regular updates from our blog

Subscribe

Or would you like to discuss a potential project with us? Contact us »