Say Hi to Swift 5! Part 2: Language improvements

On March 25 swift 5 had been released. It marked a major milestone in Swift language evolution, mostly by ABI stability. While this update builds blocks and fundamentals for future version it also gives us a few language improvements, some of them were very long-waited ones. Also Swift Package Manager wasn’t left untouched.

In this part we focus on language improvements that were introduced. Part 1, regarding ABI stability and SPM updates, is available here.

Language improvements

There are a few language improvements in new release. We will go through them, but if you want to try some of them in famous Xcode Playgrounds there is one created by Paul Hudson from HackingWithSwift.com.

List of improvements with links to their proposal:

Raw Strings

This proposal added way to create raw strings, with backslashes and quote marks are not escaped. It will be handy in many situations but mostly in regular expressions.Fallow

To use raw strings simply put # or ## before a string.

//SWIFT 
let sentence = #”Say Hi to “Swift 5” ”#
//
# here is string delimiter, so Swift will understand quote marks wrapping Swift phrase as quote marks literal and no as end of string. Same works for backslashes. But if we want to use string in-terpolation we should add another # like:
//SWIFT 
let version = 5
let sentence = #”Say Hi to “Swift \#(version)”"#
//
If we want to use hash char in raw string we simply put 2 # before string definition:
//SWIFT 
let sentence = ##”Say Hi to “Swift 5” #iOS “##
//

Standard result types

Long awaited proposal that brings Result type into standard library for simpler and clearer way of handling errors in async APIs.

Result type is enum with 2 cases: success and failure. Both are implemented via generic but failure must be conforming to the Error.

// SWIFT
enum NetworkError: Error {
    case badURL
} 

func fetchUnreadPostCount(from urlString: String, completionHandler: @escaping (Result<Int, NetworkError>) -> Void)  {
    guard let url = URL(string: urlString) else {
        completionHandler(.failure(.badURL))
        return
    }

    // complicated networking code here
    print("Fetching \(url.absoluteString)...")
    completionHandler(.success(5))
}

fetchUnreadPostCount(from: “https://www.blog.aspire.com”) { result in
    switch result {
    case .success(let count):
        print(“You have \(count) unread post from Aspire Blog.")
    case .failure(let error):
        print(error.localizedDescription)
    }
}

//

What happens above?

First we created our NetworkError handler with just one case: badURL. Secondly, we created a function that fetch unread count from Aspire Blog and on completion we return either number of unread or error. Then we call fetch function and based on the result we give print message. Nothing complicated. You probably done this thousands times, but with new Result we can easily get it in clear way.

What is more for Result is that it has method called get() which return success or throws error:

// SWIFT
fetchUnreadPostCount(from: “https://www.blog.aspire.com”){ result in
    If let count = try? Result.get() {
	print(“You have \(count) unread post from Aspire Blog.")
    }
}
//

Customizing String interpolation

Swift 5 revamped string interpolation system, so it is more flexible and powerful than previous. Let’s check quickly what it brings for us. For more advanced usage please check Playgrounds link at the top.

In Swift 5 we can simply decide how objects should appear in strings. While we have already build-in default behaviour for structs we don’t have it for classes or sometimes we just want to output to be more custom. So now we can easily add extension to the String.StringInterpolation with appendInterpolation() method like this:

// SWIFT 
struct User {
    var name: String
    var age: Int
}

extension String.StringInterpolation {
    mutating func appendInterpolation(_ value: User) {
        appendInterpolation("My name is \(value.name) and I'm \(value.age)")
    }
}

let user = User(name: “Aspire Guy”, age: 25) 
print(“Details: \(user)”)  
//

And the outcome will be: Details: My name is Aspire Guy and I’m 25. Easy and handy, isn’t it?

Dynamically callable types

In Swift 4.2 @dynamicMemberLookup attribute was introduced with single purpose: make Swift be easier to work along dynamic languages. @dynamicCallable is extension of that which enables type to be mark as directly callable. In order to do that we need add attribute and one or both of methods:

// SWIFT 
dynamicallyCall(withArguments:) 
dynamicallyCall(withKeywordArguments:)
//

Where both methods arguments must conform to the ExpressibleByArrayLiteral and ExpressibleByDictionaryLiteral respectively.

Handling future enum cases

This proposal brings attribute @unknown to switch default case in enumeration cases. Adding this will throw a compiler time warning ‘Switch is not exhaustive’ when new enum case is added. Consider following enum:

// SWIFT
enum NetworkError: Error {
    case badURL, badRequest
	
    var errorCode: Int { 
	switch self {
	case .badURL:
	return 403
	@unknown default :
	return 400

	}
} 
//

If now we add another enum case unauthorised, we will get warning from compiler ‘Switch is not exhaustive’.

Flatten nested try optionals

In Swift 5 we no longer get double optionals when we do try? on optionals:

//SWIFT
try? optional?.stringParameter
//

Previously snippet above would produce String?? but now it will be just String?

Checking integer multiples

Another small, readability improvement proposal, which add isMultiple(of:) method to integers. This allows us to know if number is multiplier of another. No more % operator of that 🙂

FizzBuzz likes it!

Compact map values

Compact map is a nice feature for Array with its transform, unwrap in once. Now it’s added in Dictionaries combining compactMap() and mapValues() into one compactMapValues() method.

//SWIFT 
let dict = [ “key” : 2, “key 2” : 3, “key 4” : nil] 
print(dict.compactMapValues { $0} ) 
//

Would not print “key4”

New character & unicode scalar properties

New release brings some new properties for characters and unicode scalar to easier working with them. For example isNumber property for character return Bool whether char is number or not.

//SWIFT
let word = “SWIFT5” 
var numberAmount = 0 
word.forEach { numberAmount += $0.isNumber ? 1 : 0 }
//

Same goes for unicode scalars:

//SWIFT 
let username = “Aspire Systems Poland 1111! ”
var letters = 0

username.unicodeScalars.forEach { letters += $0.properties.isAlphabetic ? 1 : 0 }

//

Lists of all of properties are available here:

Some small improvements

In Swift 4.2 Sequence customization point returned SubSequence and now it will return concrete types. Also Never now conforms to the Hashable and Equatable. And also Ranges are now Codable.

Summary

We got to the end of our tour Say Hi to Swift 5! Thanks for the ride! As we can see we got some nice features and language improvements. Which one is best? For me its either Result type or compactMapValues().

And Yours? Which one is your favourite?

Tags: