Good news: Swift 4.2 is now available in Xcode 10 beta! This release updates important Swift 4.1 features and improves the language in preparation for ABI stability.
This tutorial covers the most significant changes in Swift 4.2. It requires Xcode 10, so make sure you download and install the latest beta of Xcode before getting started.
Getting Started
Swift 4.2 is source compatible with Swift 4.1, but isn’t binary compatible with any other releases. Apple designed Swift 4.2 to be an intermediate step towards achieving ABI stability in Swift 5, which should enable binary compatibility between applications and libraries compiled with different Swift versions. The ABI features receive plenty of time for feedback from the community before integration into the final ABI.
This tutorial’s sections contain Swift Evolution proposal numbers such as [SE-0001]. You can explore the details of each change by clicking the linked tag of each proposal.
You’ll get the most out of this tutorial if you try out the changes in a playground. Start Xcode 10 and go to File ▸ New ▸ Playground. Select iOS for the platform and Blank for the template. Name it whatever you like and save it anywhere you wish. You’re now ready to get started!
Language Improvements
There are quite a few language features in this release such as random number generators, dynamic member lookup and more.
Generating Random Numbers
Swift 4.1 imports C APIs to generate random numbers, as in the snippet below:
let digit = Int(arc4random_uniform(10))
arc4random_uniform(_:)
returned a random digit between 0 and 9. It required you to import Foundation
, and didn’t work on Linux. On the other hand, all Linux-based approaches introduced modulo bias, which meant that certain numbers were generated more often than others.
Swift 4.2 solves these problems by adding a random API to the standard library [SE-0202]:
// 1
let digit = Int.random(in: 0..<10)
// 2
if let anotherDigit = (0..<10).randomElement() {
print(anotherDigit)
} else {
print("Empty range.")
}
// 3
let double = Double.random(in: 0..<1)
let float = Float.random(in: 0..<1)
let cgFloat = CGFloat.random(in: 0..<1)
let bool = Bool.random()
Here’s what this does:
- You use
random(in:)
to generate random digits from ranges. randomElement()
returnsnil
if the range is empty, so you unwrap the returnedInt?
withif let
.- You use
random(in:)
to generate a randomDouble
,Float
orCGFloat
andrandom()
to return a randomBool
.
Swift 4.1 also used C functions for generating random values from arrays:
let playlist = ["Nothing Else Matters", "Stairway to Heaven", "I Want to Break Free", "Yesterday"]
let index = Int(arc4random_uniform(UInt32(playlist.count)))
let song = playlist[index]
Swift 4.1 used arc4random_uniform(_:)
to generate a valid index
from playlist
and return the corresponding song
. This solution required you to cast between Int
and UInt32
and also had all the previously mentioned issues.
Swift 4.2 takes a more straightforward approach:
if let song = playlist.randomElement() {
print(song)
} else {
print("Empty playlist.")
}
randomElement()
returns nil
if playlist
is empty, so you unwrap the returned String?
.
Swift 4.1 didn’t contain any collection shuffling algorithms, so you had to use a roundabout way to achieve the intended result:
// 1
let shuffledPlaylist = playlist.sorted{ _, _ in arc4random_uniform(2) == 0 }
// 2
var names = ["Cosmin", "Oana", "Sclip", "Nori"]
names.sort { _, _ in arc4random_uniform(2) == 0 }
Here’s what you’re doing in this code:
- You use
arc4random_uniform(_:)
to determine the shuffling order of theplaylist
and returnshuffledPlaylist
withsorted(_:_:)
. - You then shuffle
names
in place withsort(_:_:)
using the previous technique.
Swift 4.2 provides more efficient, and arguably more elegant, shuffling algorithms:
let shuffledPlaylist = playlist.shuffled()
names.shuffle()
In 4.2, you simply use shuffled()
to create a shuffled playlist and shuffle names
on the spot with shuffle()
. Boom!
Dynamic Member Lookup
Swift 4.1 used the following square brackets syntax for custom subscript calls:
class Person {
let name: String
let age: Int
private let details: [String: String]
init(name: String, age: Int, details: [String: String]) {
self.name = name
self.age = age
self.details = details
}
subscript(key: String) -> String {
switch key {
case "info":
return "\(name) is \(age) years old."
default:
return details[key] ?? ""
}
}
}
let details = ["title": "Author", "instrument": "Guitar"]
let me = Person(name: "Cosmin", age: 32, details: details)
me["info"] // "Cosmin is 32 years old."
me["title"] // "Author"
The subscript in this case returns contents from a private data store or a custom message based on the person’s name and age.
Swift 4.2 uses dynamic member lookup to provide dot syntax for subscripts instead in [SE-0195]:
// 1
@dynamicMemberLookup
class Person {
let name: String
let age: Int
private let details: [String: String]
init(name: String, age: Int, details: [String: String]) {
self.name = name
self.age = age
self.details = details
}
// 2
subscript(dynamicMember key: String) -> String {
switch key {
case "info":
return "\(name) is \(age) years old."
default:
return details[key] ?? ""
}
}
}
// 3
me.info // "Cosmin is 32 years old."
me.title // "Author"
Taking it comment-by-comment:
- You mark
Person
as@dynamicMemberLookup
to enable dot syntax for its custom subscripts. - You conform to
@dynamicMemberLookup
by implementingsubscript(dynamicMember:)
for the class. - You call the previously implemented subscript using dot syntax.
The compiler evaluates the subscript call dynamically at runtime, which lets you to write type-safe code much like you would in scripting languages like Python or Ruby.
Dynamic member lookup doesn’t mess up your class properties:
me.name // "Cosmin"
me.age // 32
You use dot syntax to call name
and age
instead of the subscript in this case.
Further, derived classes inherit dynamic member lookup from their base ones:
@dynamicMemberLookup
class Vehicle {
let brand: String
let year: Int
init(brand: String, year: Int) {
self.brand = brand
self.year = year
}
subscript(dynamicMember key: String) -> String {
return "\(brand) made in \(year)."
}
}
class Car: Vehicle {}
let car = Car(brand: "BMW", year: 2018)
car.info // "BMW made in 2018."
You can use dot syntax to call the car’s subscript, since any Car
is a Vehicle
and Vehicle
implements @dynamicMemberLookup
.
You can add dynamic member lookup to existing types with protocol extensions:
// 1
@dynamicMemberLookup
protocol Random {}
// 2
extension Random {
subscript(dynamicMember key: String) -> Int {
return Int.random(in: 0..<10)
}
}
// 3
extension Int: Random {}
// 4
let number = 10
let randomDigit = String(number.digit)
let noRandomDigit = String(number).filter { String($0) != randomDigit }
Here’s the play-by-play:
- You annotate
Random
with@dynamicMemberLookup
to enable dot syntax for its subscripts. - You extend the protocol and make it conform to
@dynamicMemberLookup
by implementingsubscript(dynamicMember:)
. The subscript usesrandom(in:)
to return a random digit between 0 and 9. - You extend
Int
and make it conform toRandom
. - You use dot syntax to generate a random digit and filter it out from
number
.
Enumeration Cases Collections
Swift 4.1 didn’t provide access to collections of enumeration cases by default. This left you with rather inelegant solutions like the following:
enum Seasons: String {
case spring = "Spring", summer = "Summer", autumn = "Autumn", winter = "Winter"
}
enum SeasonType {
case equinox
case solstice
}
let seasons = [Seasons.spring, .summer, .autumn, .winter]
for (index, season) in seasons.enumerated() {
let seasonType = index % 2 == 0 ? SeasonType.equinox : .solstice
print("\(season.rawValue) \(seasonType).")
}
Here, you add Seasons
cases to seasons
and loop through the array to get each season name and type. But Swift 4.2 can do you one better!
Swift 4.2 adds enumeration cases arrays to enumerations [SE-0194]:
// 1
enum Seasons: String, CaseIterable {
case spring = "Spring", summer = "Summer", autumn = "Autumn", winter = "Winter"
}
enum SeasonType {
case equinox
case solstice
}
// 2
for (index, season) in Seasons.allCases.enumerated() {
let seasonType = index % 2 == 0 ? SeasonType.equinox : .solstice
print("\(season.rawValue) \(seasonType).")
}
Here’s how you can accomplish the same thing in Swift 4.2:
- You conform
Seasons
toCaseIterable
to create the array of enumeration cases. - You loop through
allCases
and print each season name and type.
You have the option of only adding certain cases to the enumeration cases array:
enum Months: CaseIterable {
case january, february, march, april, may, june, july, august, september, october, november, december
static var allCases: [Months] {
return [.june, .july, .august]
}
}
Here you add only the summer months to allCases
since they are the sunniest ones of the year!
You should add all available
cases manually to the array if the enumeration contains unavailable
ones:
enum Days: CaseIterable {
case monday, tuesday, wednesday, thursday, friday
@available(*, unavailable)
case saturday, sunday
static var allCases: [Days] {
return [.monday, .tuesday, .wednesday, .thursday, .friday]
}
}
You add only weekdays to allCases
because you mark both .saturday
and .sunday
as unavailable
on any version of any platform.
You can also add cases with associated values to the enumeration cases array:
enum BlogPost: CaseIterable {
case article
case tutorial(updated: Bool)
static var allCases: [BlogPost] {
return [.article, .tutorial(updated: true), .tutorial(updated: false)]
}
}
In this example, you add all types of blog posts on the website to allCases
: articles, new tutorials and updated ones.
New Sequence Methods
Swift 4.1 defined Sequence
methods that determined either the first index of a certain element, or the first element which satisfied a certain condition:
let ages = ["ten", "twelve", "thirteen", "nineteen", "eighteen", "seventeen", "fourteen", "eighteen",
"fifteen", "sixteen", "eleven"]
if let firstTeen = ages.first(where: { $0.hasSuffix("teen") }),
let firstIndex = ages.index(where: { $0.hasSuffix("teen") }),
let firstMajorIndex = ages.index(of: "eighteen") {
print("Teenager number \(firstIndex + 1) is \(firstTeen) years old.")
print("Teenager number \(firstMajorIndex + 1) isn't a minor anymore.")
} else {
print("No teenagers around here.")
}
The Swift 4.1 way of doing things is to use first(where:)
to find the first teenager’s age in ages
, index(where:)
for the first teenager’s index and index(of:)
for the index of the first teenager who is 18.
Swift 4.2 renames some of these methods for consistency [SE-0204]:
if let firstTeen = ages.first(where: { $0.hasSuffix("teen") }),
let firstIndex = ages.firstIndex(where: { $0.hasSuffix("teen") }),
let firstMajorIndex = ages.firstIndex(of: "eighteen") {
print("Teenager number \(firstIndex + 1) is \(firstTeen) years old.")
print("Teenager number \(firstMajorIndex + 1) isn't a minor anymore.")
} else {
print("No teenagers around here.")
}
index(where:)
becomes firstIndex(where:)
, and index(of:)
becomes firstIndex(of:)
to remain consistent with first(where:)
.
Swift 4.1 also didn’t define any Collection
methods for finding either the last index of a certain element or the last element which matched a given predicate. Here’s how you’d handle this in 4.1:
// 1
let reversedAges = ages.reversed()
// 2
if let lastTeen = reversedAges.first(where: { $0.hasSuffix("teen") }),
let lastIndex = reversedAges.index(where: { $0.hasSuffix("teen") })?.base,
let lastMajorIndex = reversedAges.index(of: "eighteen")?.base {
print("Teenager number \(lastIndex) is \(lastTeen) years old.")
print("Teenager number \(lastMajorIndex) isn't a minor anymore.")
} else {
print("No teenagers around here.")
}
Looking at this in sections:
- You create a reversed version of
ages
withreversed()
. - You use
first(where:)
to determine the last teenager’s age inreversedAges
,index(where:)
for the last teenager’s index andindex(of:)
for the index of the last teenager who is 18.
Swift 4.2 adds the corresponding Sequence
methods which collapses the above down to:
if let lastTeen = ages.last(where: { $0.hasSuffix("teen") }),
let lastIndex = ages.lastIndex(where: { $0.hasSuffix("teen") }),
let lastMajorIndex = ages.lastIndex(of: "eighteen") {
print("Teenager number \(lastIndex + 1) is \(lastTeen) years old.")
print("Teenager number \(lastMajorIndex + 1) isn't a minor anymore.")
} else {
print("No teenagers around here.")
}
You can simply use last(where:)
, lastIndex(where:)
and lastIndex(of:)
to find the previous element and specific indices in ages
.
Testing Sequence Elements
A fairly simple routine absent from Swift 4.1 is a way to check whether all elements in a Sequence
satisfied a certain condition. You could always craft your own approach, though, such as here where you have to determine whether all elements are even:
let values = [10, 8, 12, 20]
let allEven = !values.contains { $0 % 2 == 1 }
Kludgey, isn’t it? Swift 4.2 adds this missing method to Sequence
[SE-0207]:
let allEven = values.allSatisfy { $0 % 2 == 0 }
Much better! This simplifies your code and improves its readability.
Conditional Conformance Updates
Swift 4.2 adds several conditional conformance improvements to extensions and the standard library [SE-0143].
Conditional conformance in extensions
Swift 4.1 couldn’t synthesize conditional conformance to Equatable
in extensions. Take the following Swift 4.1 snippet as an example:
// 1
struct Tutorial : Equatable {
let title: String
let author: String
}
// 2
struct Screencast<Tutorial> {
let author: String
let tutorial: Tutorial
}
// 3
extension Screencast: Equatable where Tutorial: Equatable {
static func ==(lhs: Screencast, rhs: Screencast) -> Bool {
return lhs.author == rhs.author && lhs.tutorial == rhs.tutorial
}
}
// 4
let swift41Tutorial = Tutorial(title: "What's New in Swift 4.1?", author: "Cosmin Pupăză")
let swift42Tutorial = Tutorial(title: "What's New In Swift 4.2?", author: "Cosmin Pupăză")
let swift41Screencast = Screencast(author: "Jessy Catterwaul", tutorial: swift41Tutorial)
let swift42Screencast = Screencast(author: "Jessy Catterwaul", tutorial: swift42Tutorial)
let sameScreencast = swift41Screencast == swift42Screencast
- You make
Tutorial
conform toEquatable
. - You make
Screencast
generic, since website authors base their screencasts on published tutorials. - You implement
==(lhs:rhs:)
for screencasts sinceScreencast
conforms toEquatable
as long asTutorial
does. - You compare screencasts directly because of the conditional conformance you declared.
Swift 4.2 adds a default implementation for Equatable
conditional conformance to an extension:
extension Screencast: Equatable where Tutorial: Equatable {}
This feature applies to Hashable
and Codable
conformances in extensions as well:
// 1
struct Tutorial: Hashable, Codable {
let title: String
let author: String
}
struct Screencast<Tutorial> {
let author: String
let tutorial: Tutorial
}
// 2
extension Screencast: Hashable where Tutorial: Hashable {}
extension Screencast: Codable where Tutorial: Codable {}
// 3
let screencastsSet: Set = [swift41Screencast, swift42Screencast]
let screencastsDictionary = [swift41Screencast: "Swift 4.1", swift42Screencast: "Swift 4.2"]
let screencasts = [swift41Screencast, swift42Screencast]
let encoder = JSONEncoder()
do {
try encoder.encode(screencasts)
} catch {
print("\(error)")
}
In this block:
- You conform
Tutorial
to bothHashable
andCodable
. - You constrain
Screencast
to conform toHashable
andCodable
ifTutorial
does. - You add screencasts to sets and dictionaries and encode them.
Conditional conformance runtime queries
Swift 4.2 implements dynamic queries of conditional conformances. You can see this in action in the following code:
// 1
class Instrument {
let brand: String
init(brand: String = "") {
self.brand = brand
}
}
// 2
protocol Tuneable {
func tune()
}
// 3
class Keyboard: Instrument, Tuneable {
func tune() {
print("\(brand) keyboard tuning.")
}
}
// 4
extension Array: Tuneable where Element: Tuneable {
func tune() {
forEach { $0.tune() }
}
}
// 5
let instrument = Instrument()
let keyboard = Keyboard(brand: "Roland")
let instruments = [instrument, keyboard]
// 6
if let keyboards = instruments as? Tuneable {
keyboards.tune()
} else {
print("Can't tune instrument.")
}
Here’s what’s going on above:
- You define
Instrument
with a certain brand. - You declare
Tuneable
for all instruments that can tune. - You override
tune()
inKeyboard
to return keyboard standard tuning. - You use
where
to constrainArray
to conform toTuneable
as long asElement
does. - You add an
Instrument
and aKeyboard
toinstruments
. - You check if
instruments
implementsTuneable
and tune it if the test succeeds. In this example, the array can't be cast toTuneable
because theInstrument
type isn't tuneable. If you created an array of two keyboards, the test would pass and the keyboards would be tuned.
Hashable conditional conformance improvements in the standard library
Optionals, arrays, dictionaries and ranges are Hashable
in Swift 4.2 when their elements are Hashable
as well:
struct Chord: Hashable {
let name: String
let description: String?
let notes: [String]
let signature: [String: [String]?]
let frequency: CountableClosedRange<Int>
}
let cMajor = Chord(name: "C", description: "C major", notes: ["C", "E", "G"],
signature: ["sharp": nil, "flat": nil], frequency: 432...446)
let aMinor = Chord(name: "Am", description: "A minor", notes: ["A", "C", "E"],
signature: ["sharp": nil, "flat": nil], frequency: 440...446)
let chords: Set = [cMajor, aMinor]
let versions = [cMajor: "major", aMinor: "minor"]
You add cMajor
and aMinor
to chords
and versions
. This wasn’t possible prior to 4.2 because String?
, [String]
, [String: [String]?]
and CountableClosedRange<Int>
weren’t Hashable
.
Hashable Improvements
Take the following example in Swift 4.1 which implements custom hash functions for a class:
class Country: Hashable {
let name: String
let capital: String
init(name: String, capital: String) {
self.name = name
self.capital = capital
}
static func ==(lhs: Country, rhs: Country) -> Bool {
return lhs.name == rhs.name && lhs.capital == rhs.capital
}
var hashValue: Int {
return name.hashValue ^ capital.hashValue &* 16777619
}
}
let france = Country(name: "France", capital: "Paris")
let germany = Country(name: "Germany", capital: "Berlin")
let countries: Set = [france, germany]
let countryGreetings = [france: "Bonjour", germany: "Guten Tag"]
You can add countries to sets and dictionaries here since they are Hashable
. But the hashValue
implementation is hard to understand and isn’t efficient enough for untrusted source values.
Swift 4.2 fixes this by defining universal hash functions [SE-0206]:
class Country: Hashable {
let name: String
let capital: String
init(name: String, capital: String) {
self.name = name
self.capital = capital
}
static func ==(lhs: Country, rhs: Country) -> Bool {
return lhs.name == rhs.name && lhs.capital == rhs.capital
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
hasher.combine(capital)
}
}
Here, you’ve replaced hashValue
with hash(into:)
in Country
. The function uses combine()
to feed the class properties into hasher
. It’s easy to implement, and it improves performance over all previous versions.
Removing Elements From Collections
You’ll often want to remove all occurrences of a particular element from a Collection
. Here’s a way to do it in Swift 4.1 with filter(_:)
:
var greetings = ["Hello", "Hi", "Goodbye", "Bye"]
greetings = greetings.filter { $0.count <= 3 }
You filter greetings
to return only the short greetings. This doesn’t affect the original array, so you have to make the assignment back to greetings
.
Swift 4.2 adds removeAll(_:)
in [SE-0197]:
greetings.removeAll { $0.count > 3 }
This performs the removal in place. Again, you have simplified code and improved efficiency.
Toggling Boolean States
Toggling Booleans! Who hasn’t done something like this in Swift 4.1:
extension Bool {
mutating func toggle() {
self = !self
}
}
var isOn = true
isOn.toggle()
Swift 4.2 adds toggle()
to Bool
under [SE-0199].
New Compiler Directives
Swift 4.2 defines compiler directives that signal issues in your code [SE-0196]:
// 1
#warning("There are shorter implementations out there.")
let numbers = [1, 2, 3, 4, 5]
var sum = 0
for number in numbers {
sum += number
}
print(sum)
// 2
#error("Please fill in your credentials.")
let username = ""
let password = ""
switch (username.filter { $0 != " " }, password.filter { $0 != " " }) {
case ("", ""):
print("Invalid username and password.")
case ("", _):
print("Invalid username.")
case (_, ""):
print("Invalid password.")
case (_, _):
print("Logged in succesfully.")
}
Here’s how this works:
- You use
#warning
as a reminder that the functional approach for adding elements innumbers
is shorter than the imperative one. - You use
#error
to force other developers to enter theirusername
andpassword
before logging in.
New Pointer Functions
withUnsafeBytes(of:_:)
and withUnsafePointer(to:_:)
only worked for mutable variables in Swift 4.1:
let value = 10
var copy = value
withUnsafeBytes(of: ©) { pointer in print(pointer.count) }
withUnsafePointer(to: ©) { pointer in print(pointer.hashValue) }
You had to create a copy
of value
to make both functions work. Swift 4.2 overloads these functions for constants, so you no longer need to save their values [SE-0205]:
withUnsafeBytes(of: value) { pointer in print(pointer.count) }
withUnsafePointer(to: value) { pointer in print(pointer.hashValue) }
Memory Layout Updates
Swift 4.2 uses key paths to query the memory layout of stored properties [SE-0210]. Here's how it works:
// 1
struct Point {
var x, y: Double
}
// 2
struct Circle {
var center: Point
var radius: Double
var circumference: Double {
return 2 * .pi * radius
}
var area: Double {
return .pi * radius * radius
}
}
// 3
if let xOffset = MemoryLayout.offset(of: \Circle.center.x),
let yOffset = MemoryLayout.offset(of: \Circle.center.y),
let radiusOffset = MemoryLayout.offset(of: \Circle.radius) {
print("\(xOffset) \(yOffset) \(radiusOffset)")
} else {
print("Nil offset values.")
}
// 4
if let circumferenceOffset = MemoryLayout.offset(of: \Circle.circumference),
let areaOffset = MemoryLayout.offset(of: \Circle.area) {
print("\(circumferenceOffset) \(areaOffset)")
} else {
print("Nil offset values.")
}
Going over this step-by-step:
- You define the point’s horizontal and vertical coordinates.
- You declare the circle’s
center
,circumference
,area
andradius
. - You use key paths to get the offsets of the circle’s stored properties.
- You return
nil
for the offsets of the circle’s computed properties since they aren’t stored inline.
Inline Functions in Modules
In Swift 4.1, you couldn’t declare inline functions in your own modules. Go to View ▸ Navigators ▸ Show Project Navigator, right-click Sources and select New File. Rename the file FactorialKit.swift and replace its contents with the following block of code:
public class CustomFactorial {
private let customDecrement: Bool
public init(_ customDecrement: Bool = false) {
self.customDecrement = customDecrement
}
private var randomDecrement: Int {
return arc4random_uniform(2) == 0 ? 2 : 3
}
public func factorial(_ n: Int) -> Int {
guard n > 1 else {
return 1
}
let decrement = customDecrement ? randomDecrement : 1
return n * factorial(n - decrement)
}
}
You’ve created a custom version of the factorial implementation. Switch back to the playground and add this code at the bottom:
let standard = CustomFactorial()
standard.factorial(5)
let custom = CustomFactorial(true)
custom.factorial(5)
Here, you’re generating both the default factorial and a random one. Cross-module functions are more efficient when inlined in Swift 4.2 [SE-0193], so go back to FactorialKit.swift and replace CustomFactorial
with the following:
public class CustomFactorial {
@usableFromInline let customDecrement: Bool
public init(_ customDecrement: Bool = false) {
self.customDecrement = customDecrement
}
@usableFromInline var randomDecrement: Int {
return Bool.random() ? 2 : 3
}
@inlinable public func factorial(_ n: Int) -> Int {
guard n > 1 else {
return 1
}
let decrement = customDecrement ? randomDecrement : 1
return n * factorial(n - decrement)
}
}
Here’s what this does:
- You set both
customDecrement
andrandomDecrement
asinternal
and mark them as@usableFromInline
since you use them in the inlined factorial implementation. - You annotate
factorial(_:)
with@inlinable
to make it inline. This is possible because you declared the function aspublic
.
Miscellaneous Bits and Pieces
There are a few other changes in Swift 4.2 you should know about:
Swift Package Manager Updates
Swift 4.2 adds a few improvements to the Swift Package Manager:
Defining Swift language versions for packages
Swift 4.1 defined swiftLanguageVersions
in Package.swift as [Int]
, so you could declare only major releases for your packages:
let package = Package(name: "Package", swiftLanguageVersions: [4])
Swift 4.2 lets you define minor versions as well with SwiftVersion
cases [SE-0209]:
let package = Package(name: "Package", swiftLanguageVersions: [.v4_2])
You can also use .version(_:)
to declare future releases:
let package = Package(name: "Package", swiftLanguageVersions: [.version("5")])
Declaring local dependencies for packages
In Swift 4.1, you declared dependencies for your packages using repository links. This added overhead if you had interconnected packages, so Swift 4.2 uses local paths in this case instead [SE-0201].
Adding system library targets to packages
System-module packages required separate repositories in Swift 4.1. This made the package manager harder to use, so Swift 4.2 replaces them with system library targets [SE-0208].
Removing Implicitly Unwrapped Optionals
In Swift 4.1, you could use implicitly unwrapped optionals in nested types:
let favoriteNumbers: [Int!] = [10, nil, 7, nil]
let favoriteSongs: [String: [String]!] = ["Cosmin": ["Nothing Else Matters", "Stairway to Heaven"],
"Oana": nil]
let credentials: (usermame: String!, password: String!) = ("Cosmin", nil)
Swift 4.2 removes them from arrays, dictionaries and tuples [SE-0054]:
let favoriteNumbers: [Int?] = [10, nil, 7, nil]
let favoriteSongs: [String: [String]?] = ["Cosmin": ["Nothing Else Matters", "Stairway to Heaven"],
"Oana": nil]
let credentials: (usermame: String?, password: String?) = ("Cosmin", nil)
Where to Go From Here?
You can download the final playground using the Download Materials link at either the top or bottom of this tutorial.
Swift 4.2 improves upon many Swift 4.1 features and prepares the language for ABI stability in Swift 5, coming in early 2019.
You can read more about the changes in this version either on the official Swift CHANGELOG or the Swift standard library diffs.
You can also check out the Swift Evolution proposals to see what changes are coming in Swift 5. Here you can give feedback for current proposals under review and even pitch a proposal yourself!
What do you like or dislike about Swift 4.2 so far? Let us know in the forum discussion below!
The post What’s New in Swift 4.2? appeared first on Ray Wenderlich.