What's New In Swift 2.0
I recently presented at VanSwift about what is new in Swift 2.0. Here are my slides:
What’s New
There are 3 major features in Swift 2.0 that are new:
- Protocol Improvements
- Error Handling
- Control Flow
What Are Protocols?
A protocol defines an interface of methods, properties, and other requirements for a piece of functionality.
protocol SomeType {
var someVar: Int { get set }
static var someTypeVar: Int
func someFunc()
static func someTypeFunc()
}
The protocol doesn’t actually provide an implementation for any of these requirements—it only describes what an implementation will look like. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol.
Protocols can require that conforming types have specific instance properties, instance methods, type methods, operators, and subscripts.
That is the basics of protocols, but protocols have additional features:
- mutating keyword
- init, required init
- @objc and optional
The mutating keywords signifies that the inner state of the type may change. That means the type must be instantiated with a var
rather than a let
to call that function.
A protocol can define how a type must be instantiated by declaring an init
method or a required init
method.
The last major thing to note that methods in pure Swift protocols must always be implemented. To get optional
methods, the protocol must be marked as @objc
. This is for compatability with optional methods in Objective-C protocols.
What’s New in Protocols
Protocol Extensions allow protocols to contain method implementations, not just declarations. Protocol Extensions may also have type constraints:
extension CollectionType where Self.Generator.Element: IntegerArithmeticType {
var average: Self.Generator.Element {
var accumulate = 0
var count = 0
for c in self {
accumulate += c
count++
}
return count == 0 ? 0 : accumulate/count
}
}
The above code snippet implements an average
function when the conforming type is of type IntegerArithmeticType
. This saves you from implementing average for integers, unsigned integers, integers of different size, etc.
Swift 2.0 Error Model
The new error handling mechanism in Swift 2.0 looks like exception handling, but it can really be thought of as syntactic sugar around return values.
The old Objective-C/Cocoa way to return an error looked like:
NSError *error;
BOOL success = [object functionMayFailWithError:&error];
if (success == NO) {
// Check the error variable
}
Error handling was optional. How many times have you seen a nil
passed in as the NSError object?
In Swift, it is mandatory to handle errors:
do {
try object.firstFunctionThatMayFail() // the try is mandatory
try object.secondFunctionThatMayFail()
} catch {
}
Functions that may throw must be wrapped in a do
block. And the function call must be prepended with a try
. This is NOT optional. The compiler will complain if you do not do this.
Representing Errors
The way to represent an error is to conform to the empty ErrorType
protocol.
enum HTTPError : ErrorType {
case BadRequest = 400
case Unauthorized = 401
case Forbidden = 403
case NotFound = 404
}
And when you “throw” an error, you must mark your function as throws
. I’ve put scare quotes around “throw” because it can be thought of as syntactic sugar around returning an NSError.
struct WeirdError : ErrorType {
let error: String
}
func functionThatMayThrow() throws -> Int {
if someStateIsWeird {
throw WeirdError
}
return 0
}
And the way you catch an error looks like this:
do {
try functionThatMayThrow()
} catch WeirdError {
print("Weird Error")
}
Catching Errors
There are a few things to note about catching errors. catch
patterns are very much like patterns for the switch
statement.
Either a catch
must be exhaustive, it must catch all possible errors, or the catching function must be marked as throws
to propogate the error up the call stack.
An empty catch
pattern catches all errors.
Error propogation can be disabled with a force try:
try! functionThatMayThrow()
But note that this will result in an runtime exception if the function throws.
Control Flow: defer
and guard
The new defer
keyword guarantees that a piece of code will be executed at the end of a scope block:
func readFile(filename: String) throws {
let file = open(filename)
defer {
close(file)
}
while let line = readline(file) {
...
}
// defer is executed here
}
If there are multiple defer
blocks in a scope, they will be executed in reverse order. Also, a defer
block may NOT call break
or return
. This is because it is ambiguous where the defer
block would be returning to.
The new guard
keyword is like an if not:
func printName(name: String?) {
guard let name = name else {
return
}
print("Hello \(name)")
}
The optional bindings in a guard
are available for the rest of the scope block. And guard
works the opposite of defer
. A guard
block MUST call return
, break
, or continue
.
defer
and guard
are kind of like chocolate and peanut butter. When you see one, you will probably see the other, too:
func readFile(filename: String) {
guard let file = open(filename) else {
return
}
defer {
close(file)
}
while let line = readline(file) {
...
}
}
Conclusion
Those are the major features that were added to Swift 2.0. There are a bunch of other features that I have not covered. And of course there were also bug fixes to the language and to the tools.