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 SE-0200
- Standard result types SE-0235
- Customizing String interpolation SE-0228
- Dynamically callable types SE-0216
- Handling future enum cases SE-0192
- Flatten nested try optionals SE-0230
- Checking integer multiples SE-0225
- Compact map values SE-0218
- New character properties SE-0221
- New unicode scalar properties SE-0211
- Removing subsequences SE-0234
- Conforming Never to Hashable and Equatable SE-0215
- Making Codable Ranges SE-0239
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?