Skip to main content

2 posts tagged with "version"

View All Tags

· 10 min read
Nikita Lazarev-Zubov

Swift 5.7

Swift 5.6 was released just this past March, but the language evolution is unstoppable. There are plenty of things we already know will be included in the next release, so let’s look at the most interesting developments.

Syntax Improvements

Syntax improvements aren’t necessarily very important, but they will quickly start affecting code style, so we’ll start with these.

if let Shorthand

All Swift developers are familiar with optional binding by means of if and guard statements:

let uuid: String? = "e517e38a-261d-4ca5-85f4-9136ace20683"
if let uuid = uuid {
// The UUID string is not optional here.
}

In Swift 5.7, optional binding will become even more concise:

let uuid: String? = "e517e38a-261d-4ca5-85f4-9136ace20683"
if let uuid {
// The UUID string is not optional here.
}

The same trick is also possible with the guard statement:

guard let uuid else {
// The UUID string is nil.
return
}
// The UUID string is not optional from now on.

This isn’t the most important or anticipated language addition, but it looks spectacular, and I’ll definitely make use of it.

Default Values for Generic Parameters

The compiler will finally accept method declarations like this:

func doSomethingWith<Values: Collection>(
_ values: Values = [1, 2, 3]
) { }

Yes, starting from Swift 5.7, we will be able to use default values with generic parameters.

The default value won’t limit the use of the generic parameter, and we’ll still be able to pass, for example, a set of strings if we’re not happy with the default argument.

Multi-Statement Closure Type Inference

The current version of Swift is good enough in inferring types of closures… as long as the closure has only one statement. This code is perfectly fine for Swift 5.6 and earlier versions:

func validateUUID<R>(using handler: (R) -> Bool) {
// ...
}

let uuids = [String]()
validateUUID { uuids.contains($0) }

However, if the closure has more than one statement, the compiler will complain with the error “Unable to infer type of a closure parameter in the current context”:

func requestUUID<R>(_ handler: (R) -> Result<R, Error>) { }
func notifyDuplicatedUUID<R>(_ uuidRepresentation: R) { }
func processUUID<R>(_ uuidString: R) -> Result<R, Error> { }
let uuids = [String]()
requestUUID { // The error is on this line.
if uuids.contains($0) {
notifyDuplicatedUUID($0)
}
return processUUID($0)
}

The compiler wants our help with type inference even if it could’ve managed without it:

requestUUID { (uuidString: String) in
if uuids.contains(uuidString) {
notifyDuplicatedUUID(uuidString)
}
return processUUID(uuidString)
}

Swift 5.7 will have improvements in closure type inference and will no longer ask for such help, meaning the previous code snippet will compile just fine.

New Types

Swift 5.7 will introduce a couple of new and interesting type families to the standard library. Let’s take a look at them.

Regex

Regex promises to become a new, simple, and powerful way of dealing with regular expressions. As a simple but impressive example, let’s say we need to parse text data into objects of this type:

struct Person {
let firstName: String
let secondName: String
}

Here’s a string to parse, with some unexpected whitespace characters:

let input = "  Leo   Tolstoy"

This is a Regex object, initialized from a literal that will help us deal with the string parsing (pay attention to named capturing groups):

let regex = #/\s*(?<firstName>\w+)\s*(?<secondName>\w+)\s*/#

The following code shows how we can retrieve information from the string using that Regex object:

let match = try! regex.wholeMatch(in: input)
// These are named capturing groups defined above.
let firstName = match!.firstName
let secondName = match!.secondName
let person = Person(firstName: String(firstName),
secondName: String(secondName))

Can you guess what Person the object resulted in? My jaw dropped when I saw that it’s actually Person(firstName: "Leo", secondName: "Tolstoy")! Now processing huge sheets of CSV-formatted text with pure Swift will become a piece of cake.

Clock, Instant, and Duration

These types might not look that inspiring, but they’re still important. Swift has been in need of its own clock-related abstractions to replace wrappers around C code from the Dispatch framework. And at last, it will have them.

The Clock protocol will define the concept of passing time. Two main implementations are ContiniousClock, which runs no matter what, and SuspendingClock, which doesn’t advance if the process is suspended. So, Clock determines how exactly time runs:

try await Task.sleep(until: .now + .seconds(1),
clock: .continuous)

The aforementioned .now + .seconds(1), despite its resemblance to the DispatchTime syntax, is the instance of the new type Instant. Its purpose, as you may have guessed, is to represent a moment in time.

Another use case for the Clock type is measuring the time it takes for code blocks to execute. The resolution of such a measurement is claimed to be suitable even for benchmarks:

let timeElapsed = ContinuousClock().measure { benchmarkMe() }

The type of the resulting timeElapsed is not TimeInterval, it’s an instance of another new type called Duration. The type is Comparable, has all necessary arithmetic operations defined, and, in general, is ready to humbly serve.

Concurrency

Of course, the next Swift release cannot do without additions to the new concurrency model that was introduced a couple of updates back. This time, we’ll have all sorts of groovy little door prizes.

Concurrency on the Top Level

Currently, such calls are not permitted on the top level (for instance, in the root of the main.swift file):

await doAsyncWork()

With Swift 5.7, it will become perfectly legal.

Concurrent Function Execution Fixes

Another improvement in the concurrent world of Swift is that non-isolated asynchronous functions will now always run in the global concurrent pool instead of in an isolated context, from where they’re called. Consider this code snippet:

func doHeavyComputations() async {
// ...
}

actor Computer {
func compute() async {
await doHeavyComputations()
}
}

Prior to Swift 5.7, doHeavyComputations might start running on the Computer actor, blocking it unnecessarily. Starting with Swift 5.7, it will always execute concurrent functions in their own context. Although the improvement is mostly invisible, it’s rather important for code performance.

Unavailability from a Concurrent Environment

We’ll be able to mark functions as not available in an asynchronous context. For example, the code below won’t compile, resulting in the error “Global function 'doSomethingSensitive' is unavailable from asynchronous contexts; Use the asynchronous counterpart instead”:

@available(*, noasync, message: "Use the asynchronous counterpart instead")
func doSomethingSensitive() {
// ...
}
func soAsynchronousThings() async {
doSomethingSensitive() // The error is here.
}

Since we often write code that is just not meant to be used in multi-threaded environments, this new annotation will help avoid concurrency-related problems like race conditions in such code areas.

distributed actor

Swift 5.7 will introduce a new type of actors—distributed actors, as opposed to regular, local ones. At first, the keyword distributed will just enable more checks on the call site: Non-distributed members will only be available from within the scope of the actor, and distributed members will become implicitly async and throwing.

Unfortunately, at the moment of this writing, the current development snapshot of Swift 5.7 and the public beta version of Xcode 14 crashed on compiling distributed members of actors.

According to the proposal, non-distributed members of distributed actors can only be called from the actor’s isolated context. From the outside, only distributed members can be called. They will be also implicitly asynchronous and throwing in order to expose potential I/O and network operations that might be lurking inside. And finally, all those involved in distributed members’ types (i.e., types of arguments and returned values) must conform to Codable.

Here’s an example of a distributed actor:

distributed actor Person {
var name = "Leo Tolstoy"
distributed func changeName(_ name: String) {
self.name = name
}
}

Calls to the name property from outside the Person type won’t compile, whereas calls to changeName(_: String) will compile only when marked with try and await. These measures are aimed to increase the thread safety of distributed systems.

Opaque Types

Opaque types, introduced ages ago along with the SwiftUI, hide information about real underlying types and allow you to describe types in terms of the protocols they implement. Swift 5.7 will add a few important things to the syntax of opaque types.

First of all, they will be allowed as function arguments. This, in particular, will allow our SwiftUI compositions to be more concise:

func viewWrapping(_ wrapee: some View) -> some View {
// ...
}

Currently, you can only define the function above using a more clumsy, generics-based construction:

func viewWrapping<V: View>(_ wrapee: V) -> some View {
// ...
}

Opaque types will be also allowed as return values inside structural types:

func viewsWrapping(_ wrapee: some View) -> [some View] {
// ...
}

Existential Types

Existential types, just introduced in the last release, will expectedly evolve as well, moving Swift farther from generics-based code. As an example, consider the following snippet that uses good ol’ protocols with associated types (a.k.a. PATs):

protocol ServiceSupplier {
associatedtype SuppliedService
var service: SuppliedService { get }
}
protocol BackendService { }
struct ServiceCoordinator<Supplier: ServiceSupplier>
where Supplier.SuppliedService == BackendService {
let supplier: Supplier
}

After the next language release, we’ll be able to add one or more so-called primary associated types to protocols using a syntax similar to the generic clause:

protocol ServiceSupplier<SuppliedService> {
associatedtype SuppliedService
var service: SuppliedService { get }
}

And why would you want to do this? Because it enables us to use ServiceSupplier as an existential type, instead of a generic constraint:

struct ServiceCoordinator {
let supplier: any ServiceSupplier<BackendService>
}

Primary associated types are going to be added to many types in the standard library (most notably, to the Collection protocol), which will allow us to use them as existential types as well:

let numbers: any Collection<Int> = [1, 2, 3]

Low-Level Additions

And finally, all lovers of messing with raw pointers and other low-level stuff will have a bunch of additions to the standard library to play with. For example, currently the language doesn’t provide a way of loading data from arbitrary, unaligned sources like binary files. Swift 5.7 will bring this method to deal with the problem:

let data = dataSource.withUnsafeBytes {
$0.loadUnaligned(fromByteOffset: 128, as: YourType.self)
}

Swift 5.7 will also allow us to compare pointers using simple operators like &lt;= without type conversions.

The family of UnsafeRawPointer types will obtain methods to get pointers to the previous or next alignment boundaries using methods like this:

let next = current.alignedUp(for: UInt8.self)

It may not look as exciting as playing with new concurrency possibilities, but undoubtedly, this will have its own important areas of application.

Conclusion

All the above is most likely not everything that will be included in the next Swift release, but it’s definitely the bulk of it. Just as expected, the concurrency model keeps evolving, as well as existential types, which means they are here to stay.

I’m personally glad to see more improvements for using opaque types, and, of course, I can’t wait to start using the shorthand syntax of optional binding.

In the meantime, to improve the code quality of the software you’re developing, check out Shipbook. It gives you the power to remotely gather, search, and analyze your user logs and exceptions in the cloud, on a per-user & session basis.

· 8 min read
Nikita Lazarev-Zubov

What’s New in Swift 5.6

Introduction

Since 2014, the year Swift was born and included in Xcode, the language has matured and become an integral part of software development on Apple platforms. The days when Swift language syntax would change with every major release are gone, but Swift still can surprise. Judge for yourself: Synthesized conformance to Equatable was introduced in Swift 4.1, built-in randomization functionality in Swift 4.2, the Result type in Swift 5, support for Windows in Swift 5.3, and so on.

Built-in concurrency—released just last September with Swift 5.5—became an exciting game changer. And now, the next language release, 5.6, is out. In this post, we’ll take a peek at what this latest version delivers.

Concurrency Evolution

After introducing so many thrilling concurrency-related features in Swift 5.5, it‘s not a surprise to see more of this in 5.6.

Incremental Migration to Concurrency

Swift 6 is expected to be packed with breaking concurrency changes, but until that happens, we now have a chance to migrate to the new concurrency model incrementally. If you want to migrate your libraries to built-in concurrency, you may want to first start annotating your closures with@Sendable. This annotation marks values as safe to be used in Swift's modern concurrency environment, or as Swift language developers put it, the values are “data race safe.” Passing a “non-sendable” value to concurrent code generates a warning (given the “-warn-concurrency” compiler option is enabled):

    func runConcurrently(_ task: @Sendable () -> Void) { }

class OldStyleClass {
var message = "Sent"
func callFromOldStyleCode() {
runConcurrently {
message = "Received" // "​​Capture of 'self' with non-sendable type 'OldStyleClass' in a `@Sendable` closure"
}
}
}

If you annotate runConcurrently(_:) with @preconcurrency, the warning will only be generated in a new concurrency environment; this is because the "@preconcurrency" annotation disables the warning for code that doesn't use modern Swift concurrency features:

    @preconcurrency func runConcurrently(_ task: @Sendable () -> Void) { }

class OldStyleClass {
var message = "Sent"
func callFromOldStyleCode() {
runConcurrently {
message = "Received" // No warnings.
}
}
}

class NewStyleClass {
var message = "Sent"
func callFromNewStyleCode() async {
runConcurrently {
message = "Received" // The same warning.
}
}
}

Above, the method implementation inside the NewStyleClass has the async keyword. This means it takes advantage of the modern Swift concurrency, whereas the method inside the OldStyleClass doesn’t and is thus affected by the warning.

You can also use @preconcurrency to annotate imports of old modules to suppress such warnings when passing a module’s types to concurrency environments.

Actors Isolation Warnings

Global actors instantiated as default values of instance-member properties are now outlawed and generate a warning:

    @MainActor struct Dependency { }

class Client {
@MainActor let member = Dependency() // Expression requiring global actor 'MainActor' cannot appear in default-value expression of property 'member'; this is an error in Swift 6
}

As you can see, the warning will become an error in Swift 6, so it’s better to prepare your code now. To get rid of the warning, just move the initialization to the init block:

    @MainActor struct Dependency { }

class Client {
let member: Dependency
@MainActor init() {
member = Dependency()
}
}

Syntax Additions

any Keyword

Swift 5.6 introduces a new keyword for using protocols as types in the form of any:

    protocol Employee { }
func fire(_ employee: any Employee) { }

You’re still allowed to omit it, but this is likely to change in the future. The new keyword creates a distinction between protocol conformance constraints and so-called existential types:

    func fire<Someone: Employee>(_ employee: Someone) { }
func fire(_ employee: any Employee) { }

The difference between the two versions doesn’t seem to be a big deal now–even seasoned Swift programmers might find it subtle. Conceptually, the first method takes an argument of any type that conforms to the Employee protocol, while the second one takes an argument of Employee as a type itself. For the time being, both versions will result in the same behavior, but the keyword is here to stay, so it’s a good idea to start getting used to it.

Type Placeholders

Type annotations for local variables and properties can contain underscore placeholders to take advantage of type inference:

    let int: _ = 1
struct IntContainer {
let int: _ = 1
}

In the above code snippet, it’s not particularly useful since those types are inferred even without placeholders. However, it might be useful for partial inference. For example, the variable in the code extract below would have the type of [String : Int]:

    let dict = ["1" : 1,  "2" : 2]

If we want it to be [String : Double], we can help the compiler with the second part of the type and still leave the first part for the compiler to infer:

    let dict: [_ : Double] = ["1" : 1, "2" : 2] // [String : Double]

Another interesting use case is making an inferred type optional. Just imagine how useful this could be in situations where you have to explicitly specify a very long type only to let the compiler know that it should be optional. Swift 5.6 lets you go with a shorter version:

    let int: _? = SomeVeryLongTypeName() // SomeVeryLongTypeName?

Unfortunately, placeholders don’t work with function parameters and return values. But they do produce useful hints in the latter case and suggest fixes:

    // Type placeholder may not appear in function return type
// Replace the placeholder with the inferred type 'Int' (Fix)
func returnInt() -> _ {
anInt
}

If you try to use a type placeholder with a function parameter, you’ll be shown another funny hint:

alt_text

Figure 1: Attempt to use a function parameter with a type placeholder

Standard Library Additions

CodingKeyRepresentable Protocol

Swift lets you encode dictionaries with String and Int keys into JSON objects of key-value pairs:

    import Foundation

let dict = ["key1" : "value1", "key2" : "value2"]
let data = try! JSONEncoder().encode(dict)

print(String(data: data, encoding: .utf8)!)
// {"key1":"value1","key2":"value2"}

But if you try to use any other type for keys, you’ll end up with an array of alternating keys and values instead of key-value pairs—not very convenient for sending to your backend:

    struct CustomKey: Hashable, Encodable {
let key: String
}

let dict = [CustomKey(key: "key1") : "value1",
CustomKey(key: "key2") : "value2"]
let data = try! JSONEncoder().encode(dict)

print(String(data: data, encoding: .utf8)!)
// [{"key":"key1"},"value1",{"key":"key2"},"value2"]

Now, in Swift 5.6, the CodingKeyRepresentable protocol fixes this issue. What it expects from you is to essentially implement a way to represent your custom key as a String and, optionally, an Int. Of course, this is just a wrapper for existing limitations, but the protocol at least provides a unified way of doing it instead of forcing you to “reinvent the wheel” with your own solution:

    struct CustomCodingKey: CodingKey {


let stringValue: String
let intValue: Int? = nil


init(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
nil
}


}

struct CustomKey: Hashable, Codable, CodingKeyRepresentable {


let id: String
var codingKey: CodingKey { CustomCodingKey(stringValue: id) }


init(id: String) {
self.id = id
}
init?<Key: CodingKey>(codingKey: Key) {
self.init(id: codingKey.stringValue)
}


}

let dict = [CustomKey(id: "key1") : "value1", CustomKey(id: "key2") : "value2"]
let data = try! JSONEncoder().encode(dict)

print(String(data: data, encoding: .utf8)!)

This prints “["key1":"value1","key2":"value2"]”–much better for outside-Swift environments.

Compiler Additions

#unavailable Keyword

The #available keyword now has an inverted counterpart–#unavailable.

First of all, this is useful in situations when you would need to write something extra for older platforms. For example, starting from iOS 13, you might have moved a lot of application initialization stuff to UISceneDelegate and executed a lot of code in UIApplicationDelegate only conditionally, to ensure backward compatibility with previous iOS versions:

    if #available(iOS 13.0, *) {
// Do nothing. The case is covered in UISceneDelegate.
} else {
// ... (execute pre-iOS 13 code).
}

Swift 5.6 allows you to make such code look a little bit nicer:

    if #unavailable(iOS 13.0) {
// ... (execute pre-iOS 13 code).
}

In the #unavailable case, you of course don’t need a wildcard as the second argument–another small advantage to making your code a bit more concise.

Conclusion

Although Swift 5.6 doesn’t seem to bring us a pile of new exciting possibilities like Swift 5.5 did, it’s another important step towards Swift 6. After introducing a built-in concurrency with the previous language release, it’s not surprising that the authors continue building a path for us to slowly start using this feature in real projects.

Another important thing about this new release is the existential “any” keyword. If I had to bet, this keyword is going to be mandatory in one of the upcoming major releases. Whether or not this actually happens, Swift users should keep up with what’s going on in the field.