Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

What’s New in Swift 4.2?

$
0
0

What’s New in Swift 4.2?

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!

Note: Need a refresher of the Swift 4.1 highlights? Check out our Swift 4.1 tutorial: What’s New in Swift 4.1?

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:

  1. You use random(in:) to generate random digits from ranges.
  2. randomElement() returns nil if the range is empty, so you unwrap the returned Int? with if let.
  3. You use random(in:) to generate a random Double, Float or CGFloat and random() to return a random Bool.
Generating random numbers like a pro in Swift 4.2!

Generating random numbers like a pro in Swift 4.2!

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:

  1. You use arc4random_uniform(_:) to determine the shuffling order of the playlist and return shuffledPlaylist with sorted(_:_:).
  2. You then shuffle names in place with sort(_:_:) 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!

Shuffling playlists has never been easier thanks to Swift 4.2!

Shuffling playlists has never been easier thanks to Swift 4.2!

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:

  1. You mark Person as @dynamicMemberLookup to enable dot syntax for its custom subscripts.
  2. You conform to @dynamicMemberLookup by implementing subscript(dynamicMember:) for the class.
  3. 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:

  1. You annotate Random with @dynamicMemberLookup to enable dot syntax for its subscripts.
  2. You extend the protocol and make it conform to @dynamicMemberLookup by implementing subscript(dynamicMember:). The subscript uses random(in:) to return a random digit between 0 and 9.
  3. You extend Int and make it conform to Random.
  4. 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:

  1. You conform Seasons to CaseIterable to create the array of enumeration cases.
  2. 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!

Summer is all over the place in Swift 4.2 enumerations!

Summer is all over the place in Swift 4.2 enumerations!

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:

  1. You create a reversed version of ages with reversed().
  2. You use first(where:) to determine the last teenager’s age in reversedAges, index(where:) for the last teenager’s index and index(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
  1. You make Tutorial conform to Equatable.
  2. You make Screencast generic, since website authors base their screencasts on published tutorials.
  3. You implement ==(lhs:rhs:) for screencasts since Screencast conforms to Equatable as long as Tutorial does.
  4. 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:

  1. You conform Tutorial to both Hashable and Codable.
  2. You constrain Screencast to conform to Hashable and Codable if Tutorial does.
  3. 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:

  1. You define Instrument with a certain brand.
  2. You declare Tuneable for all instruments that can tune.
  3. You override tune() in Keyboard to return keyboard standard tuning.
  4. You use where to constrain Array to conform to Tuneable as long as Element does.
  5. You add an Instrument and a Keyboard to instruments.
  6. You check if instruments implements Tuneable and tune it if the test succeeds. In this example, the array can't be cast to Tuneable because the Instrument 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.

Hashing sets and dictionaries like a pro in Swift 4.2!

Hashing sets and dictionaries like a pro in Swift 4.2!

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:

  1. You use #warning as a reminder that the functional approach for adding elements in numbers is shorter than the imperative one.
  2. You use #error to force other developers to enter their username and password 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: &copy) { pointer in print(pointer.count) }
withUnsafePointer(to: &copy) { 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:

  1. You define the point’s horizontal and vertical coordinates.
  2. You declare the circle’s center, circumference, area and radius.
  3. You use key paths to get the offsets of the circle’s stored properties.
  4. 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:

  1. You set both customDecrement and randomDecrement as internal and mark them as @usableFromInline since you use them in the inlined factorial implementation.
  2. You annotate factorial(_:) with @inlinable to make it inline. This is possible because you declared the function as public.

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.


Viewing all articles
Browse latest Browse all 4370

Trending Articles