What's new in Swift 4, 4.1 and 4.2

I continue my journey exploring what has been introduced in Swift 4, 4.1 and 4.2.
Strings
Strings are now seen as array of characters, so it's possible to loop them and invoke the usual methods usually available for collections:
let galaxy = "Milky Way 🐮"
for char in galaxy {
print(char)
}
galaxy.count // 11
galaxy.isEmpty // false
galaxy.dropFirst() // "ilky Way 🐮"
String(galaxy.reversed()) // "🐮 yaW ykliM"
// Filter out any none ASCII characters
galaxy.filter { char in
let isASCII = char.unicodeScalars.reduce(true, { $0 && $1.isASCII })
return isASCII
} // "Milky Way "
Also new is the StringProtocol to offer the same functionality of Strings also to Substrings:
// Grab a subsequence of String
let endIndex = galaxy.index(galaxy.startIndex, offsetBy: 3)
var milkSubstring = galaxy[galaxy.startIndex...endIndex] // "Milk"
type(of: milkSubstring) // Substring.Type
// Concatenate a String onto a Substring
milkSubstring += "🥛" // "Milk🥛"
// Create a String from a Substring
let milkString = String(milkSubstring) // "Milk🥛"
Dictionary and Set
It's possible to instantiate a Dictionary from an array of Keys and an array of Value:
let nearestStarNames = ["Proxima Centauri", "Alpha Centauri A", "Alpha Centauri B", "Barnard's Star", "Wolf 359"]
let nearestStarDistances = [4.24, 4.37, 4.37, 5.96, 7.78]
// Dictionary from sequence of keys-values
let starDistanceDict = Dictionary(uniqueKeysWithValues: zip(nearestStarNames, nearestStarDistances))
// ["Alpha Centauri A": 4.37, "Barnard\'s Star": 5.96, "Wolf 359": 7.78, "Proxima Centauri": 4.24, "Alpha Centauri B": 4.37]
It's also possible to automatically handle duplicates during instantiation:
// Random vote of people's favorite stars
let favoriteStarVotes = ["Alpha Centauri A", "Wolf 359", "Alpha Centauri A", "Barnard's Star"]
// Merging keys with closure for conflicts
let mergedKeysAndValues = Dictionary(zip(favoriteStarVotes, repeatElement(1, count: favoriteStarVotes.count)), uniquingKeysWith: +) // ["Barnard's Star": 1, "Alpha Centauri A": 2, "Wolf 359": 1]
Dictionaries and Sets support now filtering:
// Filtering results into dictionary rather than array of tuples
let closeStars = starDistanceDict.filter { $0.value < 5.0 }
closeStars // Dictionary: ["Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Alpha Centauri B": 4.37]
Dictionaries gain also mapping:
// Mapping values directly resulting in a dictionary
let mappedCloseStars = closeStars.mapValues { "\($0)" }
mappedCloseStars // ["Proxima Centauri": "4.24", "Alpha Centauri A": "4.37", "Alpha Centauri B": "4.37"]
During initialization, it's possible to pass a default value, to be returned instead of nil when a value is not found:
// Subscript with a default value
let siriusDistance = mappedCloseStars["Wolf 359", default: "unknown"] // "unknown"
// Subscript with a default value used for mutating
var starWordsCount: [String: Int] = [:]
for starName in nearestStarNames {
let numWords = starName.split(separator: " ").count
starWordsCount[starName, default: 0] += numWords // Amazing
}
starWordsCount // ["Wolf 359": 2, "Alpha Centauri B": 3, "Proxima Centauri": 2, "Alpha Centauri A": 3, "Barnard's Star": 2]
Dictionary instantiation from Sequences plus grouping:
// Grouping sequences by computed key
let starsByFirstLetter = Dictionary(grouping: nearestStarNames) { $0.first! }
starsByFirstLetter
// ["B": ["Barnard's Star"], "A": ["Alpha Centauri A", "Alpha Centauri B"], "W": ["Wolf 359"], "P": ["Proxima Centauri"]]
Sequences and Dictionaries can reserve capacity in advance:
// Improved Set/Dictionary capacity reservation
starWordsCount.reserveCapacity(20) // reserves at _least_ 20 elements of capacity
Private Access Modifier
Now the private access modifier works inside a type plus its extensions in the same file.
For example, in the following code, the extension can use the private variable, because the type and its protocol extension are defined in the same file:
struct SpaceCraft {
private let warpCode: String
init(warpCode: String) {
self.warpCode = warpCode
}
}
extension SpaceCraft {
func goToWarpSpeed(warpCode: String) {
if warpCode == self.warpCode { // Error in Swift 3 unless warpCode is fileprivate
print("Do it Scotty!")
}
}
}
let enterprise = SpaceCraft(warpCode: "KirkIsCool")
//enterprise.warpCode // error: 'warpCode' is inaccessible due to 'private' protection level
enterprise.goToWarpSpeed(warpCode: "KirkIsCool") // "Do it Scotty!"
API Additions
Codable
To serialize and deserialize to Json, Swift introduces the Codable protocol to serialize and deserialize (and Encodable and Decodable if you need only one.).
How to implement Codable:
struct CuriosityLog: Codable {
enum Discovery: String, Codable {
case rock, water, martian
}
var sol: Int
var discoveries: [Discovery]
}
// Create a log entry for Mars sol 42
let logSol42 = CuriosityLog(sol: 42, discoveries: [.rock, .rock, .rock, .rock])
How to serialize:
let jsonEncoder = JSONEncoder() // One currently available encoder
// Encode the data
let jsonData = try jsonEncoder.encode(logSol42)
// Create a String from the data
let jsonString = String(data: jsonData, encoding: .utf8) // "{"sol":42,"discoveries":["rock","rock","rock","rock"]}"
How to deserialize:
let jsonDecoder = JSONDecoder() // Pair decoder to JSONEncoder
// Attempt to decode the data to a CuriosityLog object
let decodedLog = try jsonDecoder.decode(CuriosityLog.self, from: jsonData)
decodedLog.sol // 42
decodedLog.discoveries // [rock, rock, rock, rock]
Key Paths
Swift 4 introduces the possibility to reference properties of objects:
struct Lightsaber {
enum Color {
case blue, green, red
}
let color: Color
}
class ForceUser {
var name: String
var lightsaber: Lightsaber
var master: ForceUser?
init(name: String, lightsaber: Lightsaber, master: ForceUser? = nil) {
self.name = name
self.lightsaber = lightsaber
self.master = master
}
}
let sidious = ForceUser(name: "Darth Sidious", lightsaber: Lightsaber(color: .red))
let obiwan = ForceUser(name: "Obi-Wan Kenobi", lightsaber: Lightsaber(color: .blue))
let anakin = ForceUser(name: "Anakin Skywalker", lightsaber: Lightsaber(color: .blue), master: obiwan)
So now you can create a key-path with a backslash, followed by type.property:
// Create reference to the ForceUser.name key path
let nameKeyPath = \ForceUser.name
// Access the value from key path on instance
let obiwanName = obiwan[keyPath: nameKeyPath] // "Obi-Wan Kenobi"
A more complex example with sub-objects:
// Use keypath directly inline and to drill down to sub objects
let anakinSaberColor = anakin[keyPath: \ForceUser.lightsaber.color] // blue
// Access a property on the object returned by key path
let masterKeyPath = \ForceUser.master
let anakinMasterName = anakin[keyPath: masterKeyPath]?.name // "Obi-Wan Kenobi"
// Change Anakin to the dark side using key path as a setter
anakin[keyPath: masterKeyPath] = sidious
anakin.master?.name // Darth Sidious
// Note: not currently working, but works in some situations
// Append a key path to an existing path
//let masterNameKeyPath = masterKeyPath.appending(path: \ForceUser.name)
//anakin[keyPath: masterKeyPath] // "Darth Sidious"
Multi-line strings
You can define multi-line strings with 3 double-quotes:
let star = "⭐️"
let introString = """
A long string,
spanning multiple lines,
with also an interpolated value: \(star)
"""
One-Sided Ranges
It's possible now to define ranges without one of the two boundaries:
// Collection Subscript
var planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
let outsideAsteroidBelt = planets[4...] // Before: planets[4..<planets.endIndex]
let firstThree = planets[..<4] // Before: planets[planets.startIndex..<4]
Generic Subscripts
Subscripts can now be generic:
struct GenericDictionary<Key: Hashable, Value> {
private var data: [Key: Value]
init(data: [Key: Value]) {
self.data = data
}
subscript<T>(key: Key) -> T? {
return data[key] as? T
}
}
So you can use the generic subscript without casting:
// Dictionary of type: [String: Any]
var earthData = GenericDictionary(data: ["name": "Earth", "population": 7500000000, "moons": 1])
// Automatically infers return type without "as? String"
let name: String? = earthData["name"]
// Automatically infers return type without "as? Int"
let population: Int? = earthData["population"]
Miscellanuos
Here other minor changes:
- The MutableCollection has a new swapAt method;
- Types associated to protocols can now be constrained with where;
- A type can now conform to a class and to multiple protocols
Language improvements
Conditional conformance
Now it's possible to compare collections of Equatables even if they are optional:
// Array of Int?
let firstArray = [1, nil, 2, nil, 3, nil]
let secondArray = [1, nil, 2, nil, 3, nil]
let sameArray = firstArray == secondArray
// Dictionary with Int? values
let firstDictionary = ["Cosmin": 10, "George": nil]
let secondDictionary = ["Cosmin": 10, "George": nil]
let sameDictionary = firstDictionary == secondDictionary
// Comparing Int?? (Optional of Optional)
let firstOptional = firstDictionary["Cosmin"]
let secondOptional = secondDictionary["Cosmin"]
let sameOptional = firstOptional == secondOptional
Convert between Camel Case and Snake Case During JSON Encoding
When converting to JSON, you can specify that you want snake case:
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.outputFormatting = .prettyPrinted
When converting back, you can specify that you want camel casing:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
Equatable and Hashable Protocols Conformance
Swift now provides default implementations of Equatable and Hashable protocols, as long as all type properties are both Equatable and Hashable:
struct Country: Hashable {
let name: String
let capital: String
}
Platform Settings and Build Configuration Updates
Check if framework is available
Before it was needed to check the OS containing the framework:
#if os(iOS) || os(tvOS)
import UIKit
print("UIKit is available on this platform.")
#else
print("UIKit is not available on this platform.")
#endif
Now it's possible to check directly the framework:
#if os(iOS) || os(tvOS)
import UIKit
print("UIKit is available on this platform.")
#else
print("UIKit is not available on this platform.")
#endif
Check if simulator mode
Before it was needed to check for the architecture and operative system:
#if (arch(i386) || arch(x86_64)) && (os(iOS) || os(tvOS) || os(watchOS))
print("Testing in the simulator.")
#else
print("Testing on the device.")
#endif
Now it's possible to check directly for the target environment:
#if targetEnvironment(simulator)
print("Testing in the simulator.")
#endif
Language improvements
Generation of Random Numbers
Before, it was needed to use arc4random_uniform:
let digit = Int(arc4random_uniform(10))
Now, the numerical types have the random method:
// 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()
It's also possible to get a random element from an array:
let playlist = ["Nothing Else Matters", "Stairway to Heaven", "I Want to Break Free", "Yesterday"]
if let song = playlist.randomElement() {
print(song)
} else {
print("Empty playlist.")
}
And it's also possible to shuffle an array:
let shuffledPlaylist = playlist.shuffled()
names.shuffle()
Dynamic members
It's possible to define dynamic types:
// 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
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"
Enumeration Cases Collections
To get all cases of an enum in a collection, make it to conform to CaseIterable:
// 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).")
}
It's also possible to return only some cases to the resulting collection:
enum Months: CaseIterable {
case january, february, march, april, may, june, july, august, september, october, november, december
static var allCases: [Months] {
return [.june, .july, .august]
}
}
Testing Sequence Elements
There is a new helper method to check if all elements of a collection satisfy a condition:
let values = [10, 8, 12, 20]
let allEven = values.allSatisfy { $0 % 2 == 0 }
Removing Elements From Collection
You can remove all elements satisfying a condition from a collection:
var greetings = ["Hello", "Hi", "Goodbye", "Bye"]
greetings.removeAll { $0.count > 3 }
Toggling Boolean State
Now it's easy to toggle a boolean:
var isOn = true
isOn.toggle()
New compiler directives
There are the new directives #warning and \error.