Swift 4 has been in the works for the last few months. If you're like me, you might follow Swift Evolution to stay up to date with all the proposals and changes. Even if you do, now is a good time to review all the additions and changes to the language in this new iteration.
A snapshot of Swift 4 was already available a few weeks before Xcode 9 was announced at WWDC 2017. In this post you'll learn all about the new features introduced in Swift 4—from brand new APIs to improvements to the language syntax.
Let's first see how you can get the new compiler installed on your machine.
Xcode Setup
There are two ways to run Swift 4. You can either install the Xcode 9 beta if you have a developer account with access to it or you can set up Xcode 8 to run with a Swift 4 snapshot. In the former case, download the beta from your developer account download page.
If you prefer to use Xcode 8, simply head over to Swift.org to download the latest Swift 4.0 Development snapshot. Once the download finishes, double-click to open the .pkg file, which installs the snapshot.
Switch to Xcode now and go to Xcode > Toolchains > Manage Toolchains. From there, select the newly installed Swift 4.0 snapshot. Restart Xcode and now Swift 4 will be used when compiling your project or playground. Note that all the code presented in this tutorial is also available in a GitHub repo.
New Features
Let's take a look at the new features added to Swift 4. One caveat: the language is still in beta, and we will most likely see more changes and bug fixes before the official version is released. Moreover, some of the most recently approved proposals may still not be implemented at this time, so keep an eye on future release notes to see what will be implemented and fixed.
Encoding and Decoding
JSON parsing is one of the most discussed topics in the Swift community. It's great to see that someone finally took care of writing proposals SE-0166 and SE-0167 and pushed the idea to refresh the archival and serialization APIs in the Foundation framework. In Swift 4, there is no longer any need to parse or encode your class, struct or enum manually.
New Encodable
and Decodable
protocols have been added, and you can make your classes conform to them by simply adding Codable
(which is an alias for Decodable & Encodable
) to the class's inheritance list. Then you can use the JSONEncoder
to encode an instance of the class:
let wwdc = Conference(name: "WWDC", city: "San Jose", date: Date(timeIntervalSince1970: 0)) let jsonEncoder = JSONEncoder() let jsonEncoded = try jsonEncoder.encode(wwdc) let jsonString = String(data: jsonEncoded, encoding: .utf8) // {"name":"WWDC","city":"San Jose","date":-978307200}
As you can see, you instantiate a JSONEncoder
object to convert the struct to a JSON string representation. There are a few settings that you can tweak to get the exact JSON format you need. For example, to set a custom date format, you can specify a dateEncodingStrategy
in the following way:
let jsonEncoder = JSONEncoder() let dateFormatter = DateFormatter() dateFormatter.dateStyle = .long dateFormatter.timeStyle = .long jsonEncoder.dateEncodingStrategy = JSONEncoder.DateEncodingStrategy.formatted(dateFormatter) let jsonEncoded = try jsonEncoder.encode(wwdc) let jsonString = String(data: jsonEncoded, encoding: .utf8) // {"name":"WWDC","city":"San Jose","date":"January 1, 1970 at 1:00:00 AM GMT+1"}
The reverse process to decode a string works very similarly, thanks to the JSONDecoder
class.
let jsonDecoder = JSONDecoder() let decodedWWDC = try jsonDecoder.decode(Conference.self, from: jsonEncoded) // `decodedWWDC` is already of type Conference!
As you can see, by passing the type of the object to the decode
method, we let the decoder know what object we expect back from the JSON data. If everything is successful, we'll get an instance of our model object ready to be used.
That's not even all the power and the modularity of the new API. Instead of using a JSONEncoder
, you can use the new PropertyListEncoder
and PropertyListDecoder
in case you need to store data in a plist file. You can also create your own custom encoder and decoder. You only need to make your decoder conform to the Decoder
and your encoder to the Encoder
protocol.
Strings
As part of the String Manifesto, the String
type also received quite a big refresh. It now conforms once again (after being removed in Swift 2) to the Collection
protocol thanks to proposal SE-0163. So now you can simply enumerate over a string to get all characters.
let text: String = "Hello from Tutsplus!" for character in text.reversed() { print(character) }
Substring
is a new type that conforms to the same StringProtocol
to which String
also conforms. You can create a new Substring
by just subscripting a String
. The following line creates a Substring
by omitting the first and last character.
let substring = text[text.index(after: text.startIndex)..<text.index(before: text.endIndex)]
A nice addition that should make it easier to work with big pieces of text is multi-line strings. If you have to create a block of text which spans across multiple lines, you previously had to manually insert \n
all over the place. This was very inelegant and difficult to manage. A better way now exists to write multi-line strings, as you can see from the following example:
let welcomeMessage = """ Hey there, Welcome to Envato Tuts+. We hope you enjoy learning with us! """
There are few rules that go along with this new syntax. Each string begins with a triple quotation mark ("""
). Then, if the entire string is indented, the spacing of the closing characters decides the spacing to be stripped from each line in the string. For example, if the closing character is indented by 2 tabs, the same amount will be removed from each line. If the string has a line that doesn't have this amount of spacing, the compiler will throw an error.
Key Paths
Key paths were added in Swift 3 to make it easier to reference properties in an object. Instead of referencing an object key with a simple string literal, key paths let us enforce a compile-time check that a type contains the required key—eliminating a common type of runtime error.
Key paths were a nice addition to Swift 3, but their use was limited to NSObject
s and they didn't really play well with structs. These were the main motivations behind proposal SE-0161 to give the API a refresh.
A new syntax was agreed by the community to specify a key path: the path is written starting with a \
. It looks like the following:
struct Conference { var name = "" let city: String } let nameKeyPath = \Conference.name let wwdc = Conference(name: "WWDC", city: "San Jose") wwdc[keyPath: nameKeyPath] // "WWDC"
The nameKeyPath
object describes a reference to the name
property. It can then be used as a subscript on that object.
If you change the variable from let
to var
of wwdc
, you can also modify a specific property via the key-path subscript syntax.
wwdc[keyPath: nameKeyPath] = "AltConf" let name = wwdc[keyPath: nameKeyPath] // "AltConf"
One-Sided Ranges
SE-0172 proposed to add new prefix and postfix operators to avoid unnecessarily repeating a start or end index when it can be inferred. For example, if you wanted to subscript an array from the second index all the way to the last one, you could write it in the following way:
let numbers = [-2, -1, 0, 1, 2] let positive = numbers[2..<numbers.endIndex]
Previously, the endIndex
had to be specified. Now, a shorter syntax exists:
let positive = numbers[2...]
Or, if you want to begin with the start index:
let negative = numbers[...1]
The same syntax can also be used for pattern matching in a switch
statement.
Generic Subscripts
Before Swift 4, subscripts were required to define a specific return value type. SE-0148 proposed the possibility of defining a single generic subscript that would infer the return type based on the defined result value. Aside from the type annotation, it works pretty much the same way as before.
struct Conferences { let conferences: [String: Any] // Returns the conference models given its key. subscript<T>(key: String) -> T? { return conferences[key] as? T } } // Define the return type of the variable and access the object via subscript. let thing: Conference? = conferences["WWDC"]
As you can see, this really improves the readability of your objects in the cases where you need to access them via the subscript syntax.
Class and Subtype Existentials
One of the missing features from the Swift type system to date has been the ability to constrain a class to a specific protocol. This has been fixed in Swift 4—you can now specify the type of an object and the protocols to which it has to conform, thanks to SE-0156. You can, for example, write a method that takes a UIView
that conforms to the Reloadable
protocol with the following syntax:
func reload(view: UIView & Reloadable) { }
Dictionary and Set Improvements
Dictionary
and Set
also received a nice refresh in Swift 4. They are much more pleasant to use thanks to a few utility methods that have been added.
mapValues
Dictionary now has a mapValues
method to change all values, avoiding the use of the generic map
method that requires working with key, value tuples.
let conferences = ["WWDC": "Very Good", "AltConf": "Good", "Firebase Party": "Very Fun"] // Map over all values in the dictionary. let newConferences = conferences.mapValues { value in return value + " 👍" }
filter
Return Type
The filter
method now returns an object of the same type you're filtering with.
// The type of this variable is [String: String] let wwdc = conferences.filter { $0.key == "WWDC" }
Defaults for Dictionary Lookup
When working with dictionaries, you can provide a default value when using the subscript syntax to avoid having to later unwrap an optional.
// Default value if key is not found. let talkShow = conferences["The Talk Show", default: "🤷♂️"]
Dictionary Grouping Initializer
Finally, a Dictionary(grouping:)
initializer has been introduced to facilitate creating a new dictionary by grouping the elements of an existing collection according to some criteria.
In the following examples, we create a dictionary by grouping together all conferences
that have the same starting letter. The dictionary will have a key for each starting letter in the conferences
collection, with each value consisting of all keys that start with that letter.
let dictionary = Dictionary(grouping: conferences.values) { $0.uppercased().first! }
Resources
If you are interested in going deeper into the new Swift 4 features, here are a few more resources:
- What's New In Swift - Session 402 - WWDC 2017
- Apple Swift Guide
- Ole Begemann's What's New in Swift 4 Playground
- Migration Guide to Swift 4
Conclusion
Now that you have taken a look at some of the major new features in Swift 4, you're probably champing at the bit to start using them, to help keep your codebase fresh and clean. Start to write your new code to take advantage of the useful new features and think about refactoring some of your previous code to make it simpler and easier to read.
In the meantime, check out some of our other posts on iOS app development!
- iOSSwift Animation Basics
- Mobile DevelopmentWWDC 2017 Aftermath: The Most Important Announcements
- iOS SDKRealm Mobile Database for iOS
- iOSWhat's New in iOS 10