Great news: Xcode 8.3 and Swift 3.1 is now out of beta! This release contains some long-awaited Swift Package Manager features and improvements to the language itself.
If you haven’t been following the Swift Evolution Process closely, keep reading – this article is for you!
In this article, I’ll highlight the most significant changes in Swift 3.1 which will have a major impact on your code. Let’s dive in! :]
Getting Started
Swift 3.1 is source-compatible with Swift 3.0, so the new features won’t break your code if you’ve already migrated your project to Swift 3.0 using Edit\Convert\To Current Swift Syntax… in Xcode. However, Apple has dropped support for Swift 2.3 in Xcode 8.3. So if you haven’t migrated from Swift 2.3 yet, now is the time to do so!
In the sections below, you’ll see linked tags such as [SE-0001]. These are Swift Evolution proposal numbers. I’ve included the link to each proposal so you can discover the full details of each particular change. I recommend you try out the features we discuss in a playground, so you have a better understanding of everything that changes.
So, fire up Xcode, select File\New\Playground…. Choose iOS as the platform, call it whatever you want, and save it wherever you want. While you’re reading this article, try out each feature in this playground.
Language Improvements
First, let’s take a look at the language improvements in this release, including failable initializers for numeric types, new sequence functions, and more.
Failable Numeric Conversion Initializers
Swift 3.1 implements failable initializers for all numeric types (Int
, Int8
, Int16
, Int32
, Int64
, UInt
, UInt8
, UInt16
, UInt32
, UInt64
, Float
, Float80
, Double
) which either complete successfully without loss of information or simply return nil
[SE-0080].
This feature is useful, for example, when dealing with loosely typed data conversions from external sources in a safe and recoverable manner. For example, this is how you might process a JSON array of students:
class Student { let name: String let grade: Int init?(json: [String: Any]) { guard let name = json["name"] as? String, let gradeString = json["grade"] as? String, let gradeDouble = Double(gradeString), let grade = Int(exactly: gradeDouble) // <-- 3.1 feature here else { return nil } self.name = name self.grade = grade } } func makeStudents(with data: Data) -> [Student] { guard let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments), let jsonArray = json as? [[String: Any]] else { return [] } return jsonArray.flatMap(Student.init) } let rawStudents = "[{\"name\":\"Ray\", \"grade\":\"5.0\"}, {\"name\":\"Matt\", \"grade\":\"6\"}, {\"name\":\"Chris\", \"grade\":\"6.33\"}, {\"name\":\"Cosmin\", \"grade\":\"7\"}, {\"name\":\"Steven\", \"grade\":\"7.5\"}]" let data = rawStudents.data(using: .utf8)! let students = makeStudents(with: data) dump(students) // [(name: "Ray", grade: 5), (name: "Matt", grade: 6), (name: "Cosmin", grade: 7)] |
You use a failable initializer to convert the grade
property from Double
to Int
inside the Student
class designated failable initializer like so:
let grade = Int(exactly: gradeDouble) |
If gradeDouble
is a fractional value, such as 6.33
, it will fail. If it can be represented exactly with an Int
, such as 6.0
, it will succeed.
New Sequence Functions
Swift 3.1 adds two new functions for data filtering to the standard library’s Sequence
protocol: prefix(while:)
and drop(while:)
[SE-0045].
Consider the Fibonacci infinite sequence:
let fibonacci = sequence(state: (0, 1)) { (state: inout (Int, Int)) -> Int? in defer {state = (state.1, state.0 + state.1)} return state.0 } |
In Swift 3.0 you simply specified the iteration count to iterate through the fibonacci
sequence:
// Swift 3.0 for number in fibonacci.prefix(10) { print(number) // 0 1 1 2 3 5 8 13 21 34 } |
Swift 3.1 lets you use prefix(while:)
and drop(while:)
with a condition to get all elements of the sequence between two given values, like so:
// Swift 3.1 let interval = fibonacci.prefix(while: {$0 < 1000}).drop(while: {$0 < 100}) for element in interval { print(element) // 144 233 377 610 987 } |
prefix(while:)
returns the longest subsequence which satisfies a certain predicate. It starts at the beginning of the sequence and stops at the first element for which the given closure returns false.
drop(while:)
does the opposite: It returns the subsequence that begins with the first element for which the given closure returns false and finishes at the end of the sequence.
let interval = fibonacci.prefix{$0 < 1000}.drop{$0 < 100} |
Concrete Constrained Extensions
Swift 3.1 lets you extend a generic type with a concrete type constraint. Previously, you couldn’t extend a type like this because the constraint had to be a protocol. Let’s see an example.
For example, Ruby on Rails provides a very useful isBlank
method for checking for user input. Here is how you would implement it in Swift 3.0 as a computed property on the String
data type extension:
// Swift 3.0 extension String { var isBlank: Bool { return trimmingCharacters(in: .whitespaces).isEmpty } } let abc = " " let def = "x" abc.isBlank // true def.isBlank // false |
If you want the isBlank
computed property to work with optional strings as well, you would do the following in Swift 3.0:
// Swift 3.0 protocol StringProvider { var string: String {get} } extension String: StringProvider { var string: String { return self } } extension Optional where Wrapped: StringProvider { var isBlank: Bool { return self?.string.isBlank ?? true } } let foo: String? = nil let bar: String? = " " let baz: String? = "x" foo.isBlank // true bar.isBlank // true baz.isBlank // false |
This creates a StringProvider
protocol for String
to adopt. Which in turn you use to extend Optional
when the wrapped type is a StringProvider
, to add the isBlank
method.
Swift 3.1 lets you extend a concrete type instead of a protocol like this:
// Swift 3.1 extension Optional where Wrapped == String { var isBlank: Bool { return self?.isBlank ?? true } } |
This provides the same functionality as before, in much fewer lines of code!
Nested Generics
Swift 3.1 allows you to mix nested types with generics. As an exercise, consider this (not too crazy) example. Whenever a certain team lead at raywenderlich.com wants to publish a post on the blog, he assigns a team of dedicated developers to work on it so that it meets the website’s high quality standards:
class Team<T> { enum TeamType { case swift case iOS case macOS } class BlogPost<T> { enum BlogPostType { case tutorial case article } let title: T let type: BlogPostType let category: TeamType let publishDate: Date init(title: T, type: BlogPostType, category: TeamType, publishDate: Date) { self.title = title self.type = type self.category = category self.publishDate = publishDate } } let type: TeamType let author: T let teamLead: T let blogPost: BlogPost<T> init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost<T>) { self.type = type self.author = author self.teamLead = teamLead self.blogPost = blogPost } } |
You nest the BlogPost
inner class within its corresponding Team
outer class and make both classes generic. This is how the teams look for the tutorials and articles I’ve published on the website so far:
Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix", blogPost: Team.BlogPost(title: "Pattern Matching", type: .tutorial, category: .swift, publishDate: Date())) Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix", blogPost: Team.BlogPost(title: "What's New in Swift 3.1?", type: .article, category: .swift, publishDate: Date())) |
But actually, in this case you can simplify that code a bit more. If the nested inner type uses the generic outer one, it inherits the parent’s type by default. Therefore you don’t have to declare it, like so:
class Team<T> { // original code class BlogPost { // original code } // original code let blogPost: BlogPost init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) { // original code } } |
Availability by Swift Version
You may check for a specific Swift version with the #if swift(>= N)
static construct like this:
// Swift 3.0 #if swift(>=3.1) func intVersion(number: Double) -> Int? { return Int(exactly: number) } #elseif swift(>=3.0) func intVersion(number: Double) -> Int { return Int(number) } #endif |
However, this approach has one major drawback when used in something like the Swift standard library. It requires compiling the standard library for each and every supported old language version. That’s because when you run the Swift compiler in backwards compatibility mode, to say you want Swift 3.0 behaviour for example, it would need to use a version of the standard library compiled for that specific compatibility version. If you used one compiled with in version 3.1 mode, you simply wouldn’t have the right code there.
So, Swift 3.1 extends the @available
attribute to support specifying Swift version numbers in addition to its existing platform versions [SE-0141]:
// Swift 3.1 @available(swift 3.1) func intVersion(number: Double) -> Int? { return Int(exactly: number) } @available(swift, introduced: 3.0, obsoleted: 3.1) func intVersion(number: Double) -> Int { return Int(number) } |
This new feature gives the same behaviour in terms of which intVersion
method is available under which Swift version. However it allows libraries like the standard library to be compiled only once. The compiler then simply picks the features that are available for the given compatibility version selected.
Convert Non-Escaping Closures to Escaping Ones
Closure arguments to functions were made non-escaping by default in Swift 3.0 [SE-0103]. However, part of that proposal was not implemented at the time. In Swift 3.1, you can convert non-escaping closures to escaping ones temporarily by using the new withoutActuallyEscaping()
helper function.
Why would you ever want to do this? It’s probably not often but consider the example from the proposal.
func perform(_ f: () -> Void, simultaneouslyWith g: () -> Void, on queue: DispatchQueue) { withoutActuallyEscaping(f) { escapableF in // 1 withoutActuallyEscaping(g) { escapableG in queue.async(execute: escapableF) // 2 queue.async(execute: escapableG) queue.sync(flags: .barrier) {} // 3 } // 4 } } |
This function runs two closures simultaneously and then returns after both are done.
f
andg
come in as non-escaping and are converted toescapableF
andescapableG
.async(execute:)
calls require escaping closures. Fortunately you have those because of the previous step.- By running
sync(flags: .barrier)
, you ensure that theasync(execute:)
methods are completely done and the closures won’t be called later on. - Scope limits the use of
escapableF
andescapableG
.
If you squirrel either temporary escaping closures away (i.e. actually escaped them) it would be a bug. Future versions of the Standard Library may be able to detect this and trap if you tried to call them.
Swift Package Manager Updates
Ah, the long-awaited updates to the Swift Package Manager have arrived!
Editable Packages
Swift 3.1 adds the concept of editable packages to the Swift Package Manager [SE-0082].
The swift package edit
command takes an existing package and converts it to an editable one. The editable package replaces all of the canonical package’s occurrences in the dependency graph. Use the --end-edit
command to revert the package manager back to the canonical resolved package.
Version Pinning
Swift 3.1 adds the concept of version pinning package dependencies to specific versions to the Swift Package Manager [SE-0145]. The pin
command pins one or all dependencies like this:
$ swift package pin --all // pins all the dependencies $ swift package pin Foo // pins Foo at current resolved version $ swift package pin Foo --version 1.2.3 // pins Foo at 1.2.3 |
Use the unpin
command to revert to the previous package version:
$ swift package unpin —all $ swift package unpin Foo |
The package manager stores the active version pin information for each package in Package.pins. If the file doesn’t exist, the package manager creates it automatically based on the requirements specified in the package manifest, as part of the automatic pinning process.
Other Bits
The swift package reset
command resets a package back to a clean state, with no dependencies checked out or build artifacts present.
As well, the swift test --parallel
command executes tests in parallel.
Miscellaneous Bits and Pieces
There’s a few other tidbits in Swift 3.1 that don’t quite fit anywhere else:
Multiple-Return Functions
C functions which return twice such as vfork
and setjmp
don’t work anymore. They change the program’s control flow in interesting ways. So the Swift community has decided that using them should be banned and now results in a compile-time error.
Disable Auto-Linking
The Swift Package Manager disables the auto-linking feature of module maps for C language targets:
// Swift 3.0 module MyCLib { header “foo.h" link “MyCLib" export * } // Swift 3.1 module MyCLib { header “foo.h” export * } |
Where to Go From Here?
Swift 3.1 polishes up some Swift 3.0 features in preparation for more serious changes that will be bundled in Swift 4.0 later on this year. These include huge improvements to generics, regular expressions, a more ergonomic string design and more.
If you’re feeling adventurous, then head over and look at the Swift standard library diffs or look at the official Swift CHANGELOG where you can read more information about all of the changes. Or you can use this to keep an eye out for what’s coming in Swift 4.0!
And if you’re really curious about what changes are coming in Swift 4 and beyond, then head over to the Swift Evolution proposals where you can see what things are being proposed right now. If you’re really keen then why not give feedback on one of the current proposals under review or even pitch a proposal yourself ;].
What do you like or dislike about Swift 3.1 so far? Let us know in the forum discussion below!
The post What’s New in Swift 3.1? appeared first on Ray Wenderlich.