• What’s new in Swift 5.0
  • By Paul Hudson
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: iWeslie
  • Proofreader: DevMcryYu, Swants

Swift 5.0 is the next major release of Swift, along with ABI stability, and several key new features, including raw Strings, future enumeration cases, Result types, checking integer multiples, and more.

  • Try it out for yourself: I created an Xcode Playground to show off the new Swift 5.0 features, with some examples you can use.

standardResulttype

  • YouTube Video Explanation

Se-0235 introduces an entirely new Result type to the standard library, which makes it easier and clearer to handle errors in complex code, such as asynchronous apis.

Swift’s Result type is implemented as an enumeration, which includes success and failure. Both use generics, so you can specify any type for them. However, Failure must follow Swift’s Error protocol.

To further demonstrate Result, we can write a network request function that calculates how many unread messages the user has. In this sample code, we will have only one possible error, which is that the requested string is not a valid URL:

enum NetworkError: Error {
    case badURL
}
Copy the code

The FETCH function takes a URL string as its first argument and a Completion closure as its second argument. The Completion closure itself will accept a Result, where success will store an integer and failure will be some kind of NetworkError. We’re not actually connecting to the server here, but using the Completion closure allows us to simulate asynchronous code.

The code is as follows:

import Foundation

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

    // Omit complex network requests here
    print("Fetching \(url.absoluteString)...")
    completionHandler(.success(5))}Copy the code

To call this function, we need to check the value in Result to see if our request succeeded or failed, as follows:

fetchUnreadCount1(from: "https://www.hackingwithswift.com") { result in
    switch result {
    case .success(let count) :print("\ [count)Unread messages.)
    case .failure(let error):
        print(error.localizedDescription)
    }
}
Copy the code

There are three more things you should know before you start using Result in your own code.

First, Result has a get() method that returns success if it exists and throws an error otherwise. This allows you to convert Result to a regular function call that throws an error, like this:

fetchUnreadCount1(from: "https://www.hackingwithswift.com") { result in
    if let count = try? result.get() {
        print("\ [count)Unread messages.)}}Copy the code

Second, Result also has an initializer that accepts a thrown error closure: if the closure returns a success value, in the case of success, otherwise the thrown error is passed to Failure.

For example:

let result = Result { try String(contentsOfFile: someFile) }
Copy the code

Third, you can use the generic Error protocol rather than enumerations of specific errors that you create. In fact, Swift Evolution suggests that “most uses of Result are expected to use swift. Error as an Error type parameter.”

So you use Result

instead of Result

. This means you lose the security of the type of error you can throw, but you can throw a variety of error enumerations, depending on your code style.
,>
,>

Raw string

  • YouTube Video Explanation

Se-0200 adds the ability to create raw strings, where backslashes and hashes are used as punctuation marks rather than escape characters or string terminators. This makes many usages easier, especially regular expressions.

To use the raw string, place one or more # before the string, as follows:

Let rain fall mainly on the Spanish. #Copy the code

The # at the beginning and end of the string becomes part of the string separator, so Swift understands that the separate quotation marks around “rain” and “Spain” should be treated as punctuation marks, not terminators.

Raw strings also allow you to use backslashes:

let keypaths = #"Swift keyPath such as \ person.name contains uninvoked references to attributes."#
Copy the code

This treats the backslash as a literal character in a string rather than an escape character. Not meaning string interpolation works differently:

let answer = 42
let dontpanic = #"The ultimate answer to life, the universe and everything is # answer."#
Copy the code

Notice how I call string interpolation using \#(answer). Normally \(answer) will be interpreted as characters in the answer string, so you have to add extra # when you want to do reference string interpolation in the original string.

An interesting feature of Swift’s original string is the use of hashsigns at the beginning and end, because you generally don’t use more than one hashsign at a time. It’s hard to provide a good example here, as it really should be very rare, but consider this string: My dog barked “woof “# Gooddog. Because there is no space before the pound sign, Swift sees “# “and immediately treats it as a string terminator. In this case, we need to change the delimiter from #” to ##”, as follows:

let str = ##"My dog barked."wang"# Good dog"# #Copy the code

Note that the number at the end must match the number at the beginning.

Raw strings are fully compatible with Swift’s multi-line string system by simply starting with #””” “and ending with “””#, as shown below:

The answer to life, the universe, and all sentient beings is. """#Copy the code

Being able to eliminate the heavy use of backslashes in regular expressions is enough to prove useful. For example, writing a simple regular expression to query the critical path, such as \ person.name, would look something like this:

let regex1 = "\\\\[A-Z]+[A-Za-z]+\\.[a-z]+"
Copy the code

Thanks to the raw string, we can write the same thing with half the backslash:

let regex2 = #"\\[A-Z]+[A-Za-z]+\.[a-z]+"#
Copy the code

We still need some backslashes, because regular expressions also use them.

Customizing string interpolation

The SE-0228 dramatically improves Swift’s string interpolation system, making it more efficient, flexible, and creating entirely new capabilities that were previously impossible.

In its most basic form, the new string interpolation system lets you control how objects appear in strings. Swift has the default behavior of a debug-friendly structure, which prints the structure name followed by all of its properties. But if you don’t have this behavior with classes, or if you want to format the output for the user, you can use the new string interpolation system.

For example, if we have a structure like this:

struct User {
    var name: String
    var age: Int
}
Copy the code

If we want to add a special String interpolation for it, so that we can neatly printed user information, we will use a new appendInterpolation () method as a String. The StringInterpolation add an extension. Swift already has several built-in and User interpolation types, in which case User is needed to determine which method to call.

In this case, we’ll add an implementation that puts the user’s name and age into a string and then calls one of the built-in appendInterpolation() methods to add it to our string, as follows:

extension String.StringInterpolation {
    mutating func appendInterpolation(_ value: User) {
        appendInterpolation("My name is\(value.name).\(value.age)At the age of")}}Copy the code

Now we can create a user and print out their data:

let user = User(name: "Guybrush Threepwood", age: 33)
print("User Information:\(user)")
Copy the code

This will print the User information: my name is Guybrush Threepwood, 33 years old, and using custom string interpolation it will print the User information: User(name: “Guybrush Threepwood”, age: 33). Of course, this functionality is no different than just implementing the CustomStringConvertible protocol, so let’s move on to more advanced usage.

Your custom interpolation method can use as many parameters, labeled and unlabeled, as needed. For example, we can use various styles to add interpolation to print numbers, like this:

extension String.StringInterpolation {
    mutating func appendInterpolation(_ number: Int, style: NumberFormatter.Style) {
        let formatter = NumberFormatter()
        formatter.numberStyle = style

        if let result = formatter.string(from: number as NSNumber) {
            appendLiteral(result)
        }
    }
}
Copy the code

The NumberFormatter class has many styles, including monetary ($489.00), ordinal (first, twelfth), and spoken (five, forty-three). Therefore, we can create a random number and spell it as the following string:

let number = Int.random(in: 0.100)
let lucky = "This week's lucky number is\(number, style: .spellOut)."
print(lucky)
Copy the code

You can call appendLiteral() as many times as you want, or not at all if you need to. For example, we can add a string interpolation to repeat a string multiple times, like this:

extension String.StringInterpolation {
    mutating func appendInterpolation(repeat str: String, _ count: Int) {
        for _ in 0 ..< count {
            appendLiteral(str)
        }
    }
}

print("Baby shark \ [repeat: "doo ", 6)")
Copy the code

Since these are just general methods, you can use the full capabilities of Swift. For example, we might add an interpolation that concatenates an array of strings together, but if the array is empty, execute a closure that returns a string:

extension String.StringInterpolation {
    mutating func appendInterpolation(_ values: [String], empty defaultValue: @autoclosure (a) -> String) {
        if values.count= =0 {
            appendLiteral(defaultValue())
        } else {
            appendLiteral(values.joined(separator: ","))}}}let names = ["Harry"."Ron"."Hermione"]
print("Name of student:\(names, empty: "empty ").")
Copy the code

Using @Autoclosure means that we can use simple values or call complex functions as defaults, but nothing will be done unless values.count is zero.

By combining the use of ExpressibleByStringLiteral and ExpressibleByStringInterpolation terms, we can now use string interpolation to create the type, If we add CustomStringConvertible, we can even print these types as strings if we want.

In order for it to work, we need to meet certain criteria:

  • The type we create should followExpressibleByStringLiteral.ExpressibleByStringInterpolationCustomStringConvertible. Follow the last protocol only if you want to customize the way you print types.
  • In your typeinternalIt needs to be a name calledStringInterpolationAnd followStringInterpolationProtocolThe nested structure of.
  • A nested structure needs to have an initializer that takes two integers and tells us roughly how much data to expect.
  • It needs to implement one moreappendLiteral()Method, as well as one or moreappendInterpolation()Methods.
  • Your main type needs to have two initializers that allow it to be created from string literals and string interpolation.

We can put all this into a sample type that can construct HTML from a variety of common elements. The ‘scratchpad’ in the nested StringInterpolation structure will be a string: we append it to the end of the string every time we add new text or interpolation. To give you an idea of exactly what’s going on, I’ve added some print() to the various append methods to print.

Here’s the code:

struct HTMLComponent: ExpressibleByStringLiteral.ExpressibleByStringInterpolation.CustomStringConvertible {
    struct StringInterpolation: StringInterpolationProtocol {
        // Start with an empty string
        var output = ""

        // Allocate enough space to hold double text
        init(literalCapacity: Int, interpolationCount: Int) {
            output.reserveCapacity(literalCapacity * 2)}// A piece of hard-coded text, just add it
        mutating func appendLiteral(_ literal: String) {
            print("Additional"\(literal)'")
            output.append(literal)
        }

        // Twitter username to add as link
        mutating func appendInterpolation(twitter: String) {
            print("Additional"\(twitter)'")
            output.append("<a href=\"https://twitter/\(twitter)\ "> @\(twitter)</a>")}// Email address, added using mailto
        mutating func appendInterpolation(email: String) {
            print("Additional"\(email)'")
            output.append("<a href=\"mailto:\(email)\ ">\(email)</a>")}}// Complete text for the entire component
    let description: String

    Create an instance from a literal string
    init(stringLiteral value: String) {
        description = value
    }

    // Create an instance from the interpolated string
    init(stringInterpolation: StringInterpolation) {
        description = stringInterpolation.output
    }
}
Copy the code

We can now create and use an instance of HTMLComponent using string interpolation, as follows:

let text: HTMLComponent = "You should follow me on Twitter\(twitter: "twostraws")Or you can send me an E-mail\(email: "[email protected]")."
print(text)
Copy the code

Thanks to the scattered print() inside, you can see exactly what the string interpolation function does: Append ‘you should follow me on Twitter’, ‘append’ twostraws’, ‘append’, or you can email me ‘, ‘append’ [email protected] ‘, and finally ‘append’. ‘”, each part triggers a method call and is added to our string.

DynamicCallable type

Se-0216 adds a new @dynamicCallable annotation to Swift, which allows a type to be called directly. It’s syntactic sugar, not compiler magic of any kind, and it puts the following code:

let result = random(numberOfZeroes: 3)
Copy the code

To:

let result = random.dynamicallyCall(withKeywordArguments: ["numberOfZeroes": 3])
Copy the code

The dynamic lookup member (@dynamicMemberLookup) was mentioned in a previous article about dynamic features in Swift. DynamicCallable is a natural extension of @DynamicMemberLookup that makes Swift code easier to work with dynamic languages like Python and JavaScript.

To add this functionality to your own type, you need to add the @dynamicCallable annotation and one or two of these methods:

func dynamicallyCall(withArguments args: [Int]) -> Double

func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double
Copy the code

The first is used to call types with no argument labels (such as a(b, c)), and the second is used when you supply labels (such as a(b: cat, c: dog)).

DynamicCallable is very flexible about the types of data its methods accept and return, allowing you to benefit from all of Swift’s type security, as well as some advanced usage. Therefore, for the first method (no label) parameters, you can use the following ExpressibleByArrayLiteral anything, such as an array, array slicing and collection, for the second method (using the tag) parameters, You can use the following ExpressibleByDictionaryLiteral anything. Such as dictionaries and key-value pairs.

In addition to accepting various inputs, you can provide multiple overloads for various outputs, possibly returning a string, integer, and so on. As long as Swift can roll out which one to use, you can mix and match everything you want.

Let’s look at an example. First, this is a RandomNumberGenerator structure that generates a number between 0 and some maximum value based on incoming input:

struct RandomNumberGenerator {
    func generate(numberOfZeroes: Int) -> Double {
        let maximum = pow(10.Double(numberOfZeroes))
        return Double.random(in: 0. maximum) } }Copy the code

To switch it to @dynamicCallable, we’ll write something like this:

@dynamicCallable
struct RandomNumberGenerator {
    func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double {
        let numberOfZeroes = Double(args.first? .value ??0)
        let maximum = pow(10, numberOfZeroes)
        return Double.random(in: 0. maximum) } }Copy the code

You can call this method with any number of arguments or even no arguments, so we’re careful to read the first value and make sure there’s a reasonable default value with a nil check.

We can now create an instance of RandomNumberGenerator and call it like a function:

let random = RandomNumberGenerator(a)let result = random(numberOfZeroes: 0)
Copy the code

If you’ve ever used dynamicallyCall(withArguments:), or both, because you can make them both single types, you can write the following code:

@dynamicCallable
struct RandomNumberGenerator {
    func dynamicallyCall(withArguments args: [Int]) -> Double {
        let numberOfZeroes = Double(args[0])
        let maximum = pow(10, numberOfZeroes)
        return Double.random(in: 0. maximum) } }let random = RandomNumberGenerator(a)let result = random(0)
Copy the code

There are some important rules to note when using @dynamicCallable:

  • You can apply it to structures, enumerations, classes, and protocols.
  • If you achieve itwithKeywordArguments:And it didn’t happenwithArguments:, your type can still be called without argument labels, you just need to get the empty string for the key.
  • ifwithKeywordArguments:withArguments:Is marked as throw, and calls to that type will also be thrown.
  • You can’t leave@dynamicCallableAdd to Extension. Only add to the body of the class.
  • You can still add other methods and attributes to your type and use them as usual.

Perhaps more importantly, method resolutions are not supported, which means we have to call the type directly (such as random(numberOfZeroes: 5)) instead of calling a specific method on the type (such as random.generate(numberOfZeroes: 5)). There has been some discussion of using method signatures to add the latter, for example:

func dynamicallyCallMethod(named: String, withKeywordArguments: KeyValuePairs<String, Int>)
Copy the code

If that is possible in a future Swift release, it could open up some very interesting possibilities for test mock.

At the same time @DynamicCallable is unlikely to be wildly popular, but it’s very important for the few people who want to interact with Python, JavaScript, and other languages.

Future-oriented enumeration cases

Se-0192 increases the distinction between fixed enumerations and enumerations that may be changed.

One security feature of Swift is that it requires all switch statements to be exhaustive, and they must cover all cases. While this works fine from a security perspective, adding new cases in the future can cause compatibility issues: the system framework may send different things you didn’t provide, or the code you rely on May add new cases and cause your build to break because your Switch is no longer exhaustive.

Using the @Unknown annotation, we can now distinguish between two slightly different scenarios: “This default should run for all other cases because I don’t want to handle them individually” and “I want to handle all cases individually, but if there are any future problems, use this instead of reporting an error.”

Here is an example of an enumeration:

enum PasswordError: Error {
    case short
    case obvious
    case simple
}
Copy the code

We can use switch to write code to handle each case:

func showOld(error: PasswordError) {
    switch error {
    case .short:
        print("Your password was too short.")
    case .obvious:
        print("Your password was too obvious.")
    default:
        print("Your password was too simple.")}}Copy the code

It uses two cases for short and weak passwords, but the third case is handled in default.

Now if we add a new case named old to enum in the future, our default case will be called automatically for previously used passwords, even if its message makes no sense.

Swift can’t warn us about this code because it’s syntactically sound, so it’s easy to miss the error. Fortunately, the new @Unknown annotation fixes it perfectly, works only in the default case, and is designed to run in the future when new cases appear.

Such as:

func showNew(error: PasswordError) {
    switch error {
    case .short:
        print("Your password was too short.")
    case .obvious:
        print("Your password was too obvious.")
    @unknown default:
        print("Your password wasn't suitable.")}}Copy the code

This code will now generate a warning because the switch block is no longer exhaustive and Swift expects us to handle each case explicitly. It’s really just a warning, which makes this property useful: if a framework adds a new case in the future, you’ll get a warning, but it won’t let your code compile unsuccessfully.

try? Nesting optional flattening

SE – 0230 modified the try? Works in such a way that the nested options are flattened out as a regular option. This makes it work in the same way that alternative chains and conditional type conversions (iflets) worked, both of which flattened optionality in earlier versions of Swift.

Here is an example of the change:

struct User {
    var id: Int

    init? (id:Int) {
        if id < 1 {
            return nil
        }

        self.id = id
    }

    func getMessages(a) throws -> String {
        // A complex piece of code
        return "No messages"}}let user = User(id: 1)
let messages = try? user? .getMessages()Copy the code

The User structure has an initializer available because we want to ensure that developers create users with valid ids. The getMessages() method theoretically contains some complicated code to get a list of all the user’s messages, so it is marked throws. I’ve asked it to return a fixed string, so the code compiles.

The key is the last line: use the optional chain because the user is optional, and because getMessages() can throw an error, it uses a try? The throw method is converted to optional, so we end up with a nested optional. In Swift 4.2 and earlier, this makes messages a String?? , an optional optional string, but in Swift 5.0 and later try? If for a type that is already optional, they don’t wrap the value as an optional type, so messages will just be a String? .

This new behavior matches the existing behavior of optional chains and conditional type conversions (IF lets). That is, you can use the optional chain a dozen times in one line of code if you need to, but you won’t end up with that many nested optional chains. Similarly, if you use as? With the optional chain, you still only have one level of optionality, which is usually what you want.

Integer Integer multiple introspection

  • YouTube Video Explanation

Se-0225 adds the isMultiple(of:) method to integers to allow us to check whether one number is a multiple of another in a clearer way than using the remainder % operation.

Such as:

let rowNumber = 4

if rowNumber.isMultiple(of: 2) {
    print("Even")}else {
    print("Odd")}Copy the code

Yes, we could do the same thing with if rowNumber % 2 == 0, but you have to admit that it doesn’t look clear. Using isMultiple(of:) means that it can be listed in Xcode’s code completion, which helps you find.

Use compactMapValues() to convert and unpack dictionary values

  • YouTube Video Explanation

Se-0218 added a new compactMapValues() method to the dictionary, which converts the compactMap() function in the array to the values I need, unpacks the results, and then discards any nil, Use with the mapValues() method in the dictionary to keep the key intact and convert only the values.

For example, here’s a dictionary of race data, and the number of seconds they completed. One of them was not completed and marked “DNF” (not completed) :

let times = [
    "Hudson": "38"."Clarke": "42"."Robinson": "35"."Hartis": "DNF"
]
Copy the code

We can use compactMapValues() to create a new dictionary with integer names and times to delete a DNF person:

let finishers1 = times.compactMapValues { Int($0)}Copy the code

Or you can pass the Int initializer directly to compactMapValues() as follows:

let finishers2 = times.compactMapValues(Int.init)
Copy the code

You can also use compactMapValues() to expand options and discard nil values without performing any type conversions, as follows:

let people = [
    "Paul": 38."Sophie": 8."Charlotte": 5."William": nil
]

let knownAges = people.compactMapValues { $0 }
Copy the code

Removed feature: Computes matches in sequences

  • YouTube Video Explanation

This Swift 5.0 feature was withdrawn in the beta because it caused performance issues with the type checker. Hopefully it will be back in Swift 5.1, or with a new name to avoid problems.

Se-0220 introduces a new count(where:) method that executes the equivalent of filter() and counts in a pass. This saves the creation of new arrays that are immediately discarded and provides a clear and concise solution to common problems.

This example creates an array of test results and counts the number of numbers greater than or equal to 85:

let scores = [100.80.85]
let passCount = scores.count{$0> =85 }
Copy the code

This counts how many names in the array begin with “Terry” :

let pythons = ["Eric Idle"."Graham Chapman"."John Cleese"."Michael Palin"."Terry Gilliam"."Terry Jones"]
let terryCount = pythons.count{$0.hasPrefix("Terry")}Copy the code

This method is available to all types that follow Sequence, so you can use it on collections and dictionaries as well.

What’s next?

Swift 5.0 is the latest version of Swift, but previous versions also include many features. You can read the following article:

  • What’s new in Swift 4.2?
  • What’s new in Swift 4.1?
  • What’s new in Swift 4.0?

But there’s more. Apple has announced the Swift 5.1 release process on Swift.org, which includes module stability and other improvements. At the time of writing, 5.1 has very few add-ons, but it looks like we’ll see it released around WWDC.

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.