When Apple introduced Swift in June 2014, the initial fanfare centered on the brand-new language’s emphasis on performance, approachability, and ease. Now, with Swift 2 out more than a year later, Apple has made good on that emphasis, pushing Swift closer to the kind of readability and maintainability development shops and IT organizations expect of a mature language.
Many of the enhancements to Swift, through both the Swift 2.0 update and subsequent Swift 2.1 update, have made the language more explicit and intentional, and in turns, Swift 2 code will be safer and easier to maintain for years to come (especially now that Swift is open source). New language constructs (keywords) in Swift 2 improve the readability of control flow -- the order in which lines of code are executed. Thanks to these new keywords, collaborating on Swift code will be much more productive and efficient.
Apple has also enhanced Xcode 7 with new features and modernized its SDKs across OS X 10.11, iOS 9, and watchOS 2. The API overhaul even includes new features for Objective-C, including lightweight generics and OptionSetType
, which makes Objective-C APIs Swift-like when invoked from Swift code.
Easy-to-follow code is a vital concern for development teams, especially in business contexts, as “witty” or complex code reduces maintainability, introduces and hides serious bugs, and can lead to security leaks. Here are seven new features available with Swift 2 that will help you and your team collaborate more effectively on a more efficient, secure code base.
guard
keyword
Swift 2’s new guard keyword provides precondition checking in a method -- exactly like you might find with an if/else
statement in Objective-C.
There are two wins here:
Firstly, the guard
keyword makes it clear that you, as the programmer, are asserting that certain preconditions must be met before executing the code that follows.
Secondly, the guard
keyword makes the control flow easier to read. Instead of injecting a gigantic if/else
structure with no clear beginning or end, all of the assertion and cleanup code is compacted into one location.
This makes early exits from Swift methods and functions easy for fellow programmers to follow and explicitly clear -- a win for code maintainability.
Code that follows a guard
statement can safely assume all of the parameters required to perform an action are ready to use:
// A value type struct to store data (passed by value i.e. copied!)
struct EmailSubscriber {
var firstName: String
var email: String
}
// A supporting function that illustrates valid/invalid values
func validateEmail(email: String) -> Bool {
return true// false // Implementation as a reader exercise
}
// Create a new subscriber if the parameters meet the requirements
func createEmailSubscriber(firstName: String, email: String) -> EmailSubscriber? {
// Prevent empty text input from the user
guard firstName.characters.count > 0 else {
print("Invalid First Name")
return nil
}
// Assert it's an accepted email format: i.e. Paul@SuperEasyApps.com
guard validateEmail(email) else {
print("Email Format Error: Invalid Email")
return nil
}
// Any code path that reaches this point has been validated
return EmailSubscriber(firstName: firstName, email: email)
}
// Missing data results in a nil value
let invalid = createEmailSubscriber("", email: "Paul@SuperEasyApps.com")
print("Subscriber is invalid, returned: ", invalid)
// Attempts to force unwrap and use nil values causes crashes!
// Tip: Use the if/let syntax to work safely with optional types
//print("Subscriber is invalid, returned: ", invalid!.firstName) // ERROR!
// Complete data results in a new digital subscriber value/object
let valid = createEmailSubscriber("Paul", email: "Paul@SuperEasyApps.com")
print(valid!.firstName, "'s email is: \(valid!.email)")
This enhancement reinforces one of Swift’s core strengths: It provides safety mechanisms to protect against invalid or optional values. Thanks to guard
, your core app logic will be simpler to express (and maintain) because you will have blocked the invalid state in advance.
defer
keyword
Swift 2’s new defer
keyword adds a level of control flow that didn’t previously exist in Swift. The defer
keyword allows you to easily clean up an expensive or limited resource. For example, if you are making the next Dropbox app in which you are reading and writing thousands of files, you would hit the maximum number of file descriptors for your app’s process if your code fails to close files after reading and writing completes. Using the defer
statement, you can be sure that all files are closed either when the file processing completes or if an error occurs.
The key strength of defer
is that it allows you to keep related code together, rather than having to split code into segments at the top and bottom of a method. Cleanup code (file close) can be written directly below the creation of an expensive or limited resource (file open) in a defer
closure that will execute at the end of the current scope:
func fileProcessing() {
print("1. Create file descriptor #1 and start file processing")
defer {
print("5. Close and cleanup file descriptor #1 (I/O resource)")
}
print("2. Create file descriptor #2 and start file processing")
defer {
print("4. Close and cleanup file descriptor #2 (I/O resource)")
}
print("3. Finish file processing")
}
// Look at Xcode's Console output for order: 1, 2, 3, 4, 5.
fileProcessing()
In the previous code block, the messages will print to the Xcode console in the order 1, 2, 3, 4, 5. The defer
keyword causes your code in the defer
block to execute after the current scope’s closing curly bracket.
Multiple defer
code blocks can be added inside your methods and functions. Each defer
closure will execute in the reverse order it is called -- this ensures cleanup safety when combined with the new error handling model in Swift 2.
repeat/while
and do
scope
Apple recently made a surprise change to Swift’s control flow: The do/while
loop is now the repeat/while
loop.
It’s a small change, and it goes against the convention of established languages like Objective-C, JavaScript, C++, and Java. The rationale is that the repeat
keyword provides an instant cue that the block of code will repeat, enhancing code readability:
// A method that counts down and outputs to Xcode’s Console
func repeatWhile() {
var x = 10
repeat {
print("T-minus", x, "seconds")
x = x - 1// x-- is being deprecated in Swift 3.0!
} while x > 0
print("Blast off!")
}
// Call the method in Playgrounds
repeatWhile()
The do
keyword has been repurposed to create new scope, unlike every other popular language that uses it for loops. Now, when you want to create an inner scope inside an existing scope, you use do
. This is important when you need to deallocate expensive resources in a tight loop or if you want to help ARC (Automatic Reference Counting) effectively clean up expensive resources:
// Outer scope
let x = 7
do {
// Inner scope (new x variable masks outer scope x variable)
let x = 10
do {
// Inner inner scope ... inception
let x = 200
print("x:", x) // x: 200
}
print("x:", x) // x: 10
}
// outer scope
print("x:", x) // x: 7
As with C++ or Objective-C, you can nest do
scopes to enhance your local variable lifetime control.
Where is this useful? If you process 15-megapixel images or large data files sequentially in a tight loop, you can run out of memory, which will cause iOS to force quit your app. There’s a hard memory footprint limit on iOS -- once you pass it, your app “crashes” and you have an unhappy customer. Using the new do
scope, you can avoid the crash and deallocate memory (using ARC) after each filter pass, instead of at the end of a tight loop.
These new and repurposed Swift keywords play perfectly with the new error handling model in Swift 2.
error
handling
Swift 2’s new error handling is one of the pillars of the improved language. It provides a feature that has been missing from Swift and is expensive (performance-wise) to use in Objective-C.
Error handling in Swift creates an implicit control flow structure for dealing with unexpected issues from networking, input, and data. Swift uses the new do
keyword for scoping the traditional try/catch
structure popular in Java. In Swift, you use do/catch
blocks.
Swift 2’s error handling isn’t a cure-all. Instead, it’s designed to help you deal with recoverable issues, such as invalid input from user forms or invalid (or unexpected) response data from a Web server. Error handling can also be used to handle file I/O issues, networking availability, and other common problems associated with poor cellphone reception and limited disk storage on mobile phones.
Error handling is built into the method signature using the throws
keyword. With native language support for error propagation, the additional NSError
pointer parameter is dropped from all APIs because it is redundant.
Every line of code that can fail (unsafe code) must be marked with the new try
keyword. This makes it easier for everyone working on the code to quickly know that a particular line of code could fail.
In dealing with code that can fail, you have three options:
- Ignore the error using the
try?
keyword, thereby unifying the optional type system with Swift 2’s error handling model. The use oftry?
ensures that all errors (if thrown) are consumed and an optional value is returned as a right-hand value. - Handle any errors that are thrown from unsafe code using the
do/catch
statements. - Propagate errors by adding the
throws
keyword to the method or function signature. Unlike Java, errors do not propagate by default.
Error handling in Swift 2 improves on the Objective-C error model -- which relies on documentation and comments. Swift 2’s error-handling model provides safety through compile-time checks that prevent unsafe code from entering your code base.
For example, you can leverage error handling in code that validates a customer has typed in an authentic name and email address:
// Create a custom error type by conforming to the ErrorType protocol
enum ValidationError: ErrorType {
case InvalidName
case InvalidEmail
case InvalidAge(age: Int)
}
// Check name against your name policies
func validateName(name: String) throws {
// Use guard statements to prevent invalid user input
guard name.characters.count > 0 else {
throwValidationError.InvalidName
}
}
// Process a new customer using required attributes
func onboardNewCustomer(name: String, email: String, age: Int) {
do {
print("Started onboarding")
// You must use the try keyword for any method that can throw an error
try validateName(name)
// Exercise: Validate other required attributes (age, email, etc.)
// Finished processing if no errors
print("Finished onboarding")
} catch let error as ValidationError {
// Using a local variable you can catch all ValidationErrors
// The local error variable can be handled with a switch
switch(error) {
case ValidationError.InvalidName:
print("Invalid name!")
case ValidationError.InvalidEmail:
print("Invalid birthday!")
case ValidationError.InvalidAge(let age):
print("Invalid age \(age)!")
}
}
catch { // default error catch
print("Catch errors here:", error) // error is default name
}
}
onboardNewCustomer("", email: "", age: 12223) // Invalid!
onboardNewCustomer("paul", email: "Paul@SuperEasyApps.com", age: 29)
The new error handling model works well with the new defer
keyword in recovering and running additional lines of code if there is an error thrown in your codebase. This is similar to Java’s finally
keyword.
protocol
extensions
Apple’s v1.0 release of Swift came with some quirks, like the plethora of global free-standing functions that were poorly documented and hard to find within the Swift Standard Library. Developers who rely on code completion to code faster (myself included) had trouble figuring out how to write code after years of experience with Objective-C.
Protocol extensions allow any protocol (for example, interface in Java) to be enhanced with both new behavior and state. This is huge for Swift and it’s going to change the face of the Swift Standard Library, open source code, and how you approach developing your code base.
The introduction of protocol extensions also brings new APIs, and with this, most free-standing global functions have been removed. As a result, the standard dot syntax variableName.methodName()
is now readily available for you to use, thereby enhancing API discoverability and coder productivity.
With protocol extensions in Swift 2, if a feature or method doesn’t exist, you can write your own. For example, you can extend the Array structure that Apple provides with new functions or methods. In Objective-C, the NSMutableArray
has a method to remove multiple elements as a single line of code, named removeObjectsInArray:
. In Swift (2.1) there is no method to remove multiple elements from the Swift Array
structure using a single method call. Using a protocol extension, you can add this method to the Array
structure.
// Extend the Array class when Elements are comparable
extensionArraywhere Element: Equatable {