Swift Regex Deep Dive
iOS MacOur introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
When Apple announced Swift 2.0 at this year’s WWDC, Swift’s main architect, Chris Lattner,
indicated that the 2.0 update to the language focused on three main areas: fundamentals, safety and beautiful code.
Out of the list of new features, improvements, polishes and beautifications, one that may impact your Swift 1.x
code the most is error handling.
That’s because you cannot opt out of it. You must embrace error handling if you want to write Swift 2.0 code, and it will change the way you interact with methods that use NSError
in the Cocoa and Cocoa Touch frameworks.
As we all know, Swift was created as a modern replacement for Objective-C, the lingua franca for writing OS X and iOS applications. In its earliest releases, Objective-C did not have native exception handling. Exception handling was added later through the NSException
class and the NS_DURING
, NS_HANDLER
and NS_ENDHANDLER
macros. This scheme is now known as “classic exception handling,” and the macros are based on the setjmp()
and longjmp()
C functions.
Exception-catching constructs looked as shown below, where any exception thrown within the NS_DURING
and NS_HANDLER
macros would result in executing the code between the NS_HANDLER
and NS_ENDHANDLER
macros.
NS_DURING
// Call a dangerous method or function that raises an exception:
[obj someRiskyMethod];
NS_HANDLER
NSLog(@"Oh no!");
[anotherObj makeItRight];
NS_ENDHANDLER
A quick way to raise an exception is (and this is still available):
- (void)someRiskyMethod
{
[NSException raise:@"Kablam"
format:@"This method is not implemented yet. Do not call!"];
}
As you can imagine, this artisanal way of handling exceptions caused a lot of teasing for early Cocoa programmers.
However, those programmers kept their chins high, because they rarely used it. In both Cocoa and Cocoa Touch,
exceptions have been traditionally relegated to mark catastrophic, unrecoverable errors, such as programmer errors. A good
example is the -someRiskyMethod
above, that raises an exception because the implementation is not ready. In the Cocoa
and Cocoa Touch frameworks, recoverable errors are handled with the NSError
class discussed later.
I guess the teasing arising from the classic exception handling in Objective-C got bothersome enough that Apple released
native exception handling with OS X 10.3, before any iOS version. This was done by essentially grafting C++ exceptions
onto Objective-C. Exception handling constructs now look something like this:
@try {
[obj someRiskyMethod];
}
@catch (SomeClass *exception) {
// Handle the error.
// Can use the exception object to gather information.
}
@catch (SomeOtherClass *exception) {
// ...
}
@catch (id allTheRest) {
// ...
}
@finally {
// Code that is executed whether an exception is thrown or not.
// Use for cleanup.
}
Native exception handling gives you the opportunity to specify different @catch
blocks for each exception type, and
a @finally
block for code that needs to execute regardless of the outcome of the @try
block.
Even though raising an NSException
works as expected with native exception handling, the more explicit way to throw
an exception is with the @throw <expression>;
statement. Normally you throw NSException
instances, but any object may
be thrown.
Despite the many advantages of native versus classic exception handling in Objective-C, Cocoa and Cocoa Touch developers still rarely use exceptions, restricting them to unrecoverable programmer errors. Recoverable errors use the NSError
class that predates exception handling. The NSError
pattern was also inherited by Swift 1.x.
In Swift 1.x, Cocoa and Cocoa Touch methods and functions that may fail return either a boolean false
or nil
in place of an object to indicate failure. Additionally, an NSErrorPointer
is taken as an argument to return specific information about the failure. A classic example:
// A local variable to store an error object if one comes back:
var error: NSError?
// success is a Bool:
let success = someString.writeToURL(someURL,
atomically: true,
encoding: NSUTF8StringEncoding,
error: &error)
if !success {
// Log information about the error:
println("Error writing to URL: (error!)")
}
Programmer errors can be flagged with the Swift Standard Library function fatalError("Error message")
to log an error message to the console and terminate execution unconditionally. Also available are the assert()
, assertionFailure()
, precondition()
and preconditionFailure()
functions.
When Swift was first released, some developers outside of Apple platforms readied the torches and pitchforks. They claimed Swift could not be a “real language” because it lacked exception handling. However, the Cocoa and Cocoa Touch communities stayed calm, as we knew that NSError
and NSException
were still there. Personally, I believe that Apple was still pondering the right way to implement error/exception handling. I also think that Apple deferred opening the Swift source until the issue was resolved (remember the pitchforks?). All this has been cleared up with the release of Swift 2.0.
In Swift 2.0, if you want to throw an error, the object thrown must conform to the ErrorType
protocol. As you may have expected, NSError
conforms to this protocol. Enumerations are used for classifying errors.
enum AwfulError: ErrorType {
case Bad
case Worse
case Terrible
}
Then, a function or method is marked with the throws
keyword if it may throw
one or several errors:
func doDangerousStuff() throws -> SomeObject {
// If something bad happens throw the error:
throw AwfulError.Bad
// If something worse happens, throw another error:
throw AwfulError.Worse
// If something terrible happens, you know what to do:
throw AwfulError.Terrible
// If you made it here, you can return:
return SomeObject()
}
In order to catch errors, a new do-catch
statement is available:
do {
let theResult = try obj.doDangerousStuff()
}
catch AwfulError.Bad {
// Deal with badness.
}
catch AwfulError.Worse {
// Deal with worseness.
}
catch AwfulError.Terrible {
// Deal with terribleness.
}
catch ErrorType {
// Unexpected error!
}
The do-catch
statement has some similarities with switch
in the sense that the list of caught errors must be exhaustive and you can use patterns to capture the thrown error. Also notice the use of the keyword try
. It is meant to explicitly label a throwing line of code, so that when you read the code you can immediately tell where the danger is.
A variant of the try
keyword is try!
. That keyword may be appropriate for those programmer errors again. If you mark a throwing call with try!
, you are promising the compiler that that error will never happen and you do not need to catch it. If the statement does produce an error, the application will stop execution and you should start debugging.
let theResult = try! obj.doDangerousStuff()
The issue now is, how do you deal with grandpa’s NSError
API in Swift 2.0? Apple has done a great job of unifying behavior in Swift 2.0, and they have prepared the way for future frameworks written in Swift.
Cocoa and Cocoa Touch methods and functions that could produce an NSError
instance have their signature automatically converted to Swift’s new error handling.
For example, this NSString
initializer has the following signature in Swift 1.x:
convenience init?(contentsOfFile path: String,
encoding enc: UInt,
error error: NSErrorPointer)
In Swift 2.0 the signature is converted to:
convenience init(contentsOfFile path: String,
encoding enc: UInt) throws
Notice that in Swift 2.0, the initializer is no longer marked as failable, it does not take an NSErrorPointer
argument, and it is marked with throws
to explicitly indicate potential failures. An example using this new signature:
do {
let str = try NSString(contentsOfFile: "Foo.bar",
encoding: NSUTF8StringEncoding)
}
catch let error as NSError {
print(error.localizedDescription)
}
Notice how the error is caught and cast as an NSError
instance, so that you can access information with its familiar API. As a matter of fact, any ErrorType
can be converted to an NSError
.
Attentive readers may have noticed that Swift 2.0 introduced a new do-catch
statement, not a do-catch-finally
. How do you specify code that must be run regardless of errors? For that, you now have a defer
statement that will delay execution of a block of code until the current scope is exited.
// Some scope:
{
// Get some resource.
defer {
// Release resource.
}
// Do things with the resource.
// Possibly return early if an error occurs.
} // Deferred code is executed at the end of the scope.
Swift 2.0 does a great job of coalescing the history of error handling in Cocoa and Cocoa Touch into a modern idiom that will feel familiar to many programmers. Unifying behavior leaves the Swift language and the frameworks it inherits in a good position to evolve.
Our introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
The Combine framework in Swift is a powerful declarative API for the asynchronous processing of values over time. It takes full advantage of Swift...
SwiftUI has changed a great many things about how developers create applications for iOS, and not just in the way we lay out our...