What's New in Swift 5.10

Swift has a formal process for proposing and accepting changes to the language known as the Swift Evolution Process. This process encompasses all aspects of the language, including language features, standard library, compiler, and package manager.



Swift 5.10, which is included in Xcode 15.3, incorporates 5 implemented proposals, accomplishes full data isolation in the concurrency language model. This important milestone has taken years of active development over many releases.

  1. SE-0383: Deprecate @UIApplicationMain and @NSApplicationMain

@UIApplicationMain and @NSApplicationMain used to be the standard way for iOS and macOS apps respectively to declare a synthesized platform-specific entrypoint for an app. These functions have since been obsoleted by introduction of the @main attribute in Swift 5.3.

This release will deprecate these alternative entrypoint attributes in favor of @main in pre-Swift 6, and it makes their use in Swift 6 a hard error.

@UIApplicationMain // warning: '@UIApplicationMain' is deprecated in Swift 5 
                   // fixit: Change `@UIApplicationMain` to `@main` 
final class MyApplication: UIResponder, UIApplicationDelegate {
  /**/
}

Once the fixit has been applied, the result will be:

@main
final class MyApplication: UIResponder, UIApplicationDelegate {
  /**/
}
  1. SE-0404: Allow Protocols to be Nested in Non-Generic Contexts

So far protocols cannot be nested at all, and so must always be top-level types within a module. This is unfortunate, Swift 5.10 will relax this restriction so that developers can express protocols which are naturally scoped to some outer type.

For example, TableView.Delegate is naturally a delegate protocol pertaining to table-views. You should be declare it as such - nested within their TableView class:

class TableView {
  protocol Delegate: AnyObject {
    func tableView(_: TableView, didSelectRowAtIndex: Int)
  }
}

class DelegateConformer: TableView.Delegate {
  func tableView(_: TableView, didSelectRowAtIndex: Int) {
    // ...
  }
}

Protocols can also be nested within non-generic functions and closures. Admittedly, this is of somewhat limited utility, as all conformances to such protocols must also be within the same function.

  1. SE-0411: Isolated default value expressions

Default value expressions can now have the same isolation as the enclosing function or the corresponding stored property:

@MainActor
func requiresMainActor() -> Int { ... }

class C {
  @MainActor
  var x: Int = requiresMainActor()
}

@MainActor func defaultArg(value: Int = requiresMainActor()) { ... }

For isolated default values of stored properties, the implicit initialization only happens in the body of an init with the same isolation. This closes an important data-race safety hole where global-actor-isolated default values could inadvertently run synchronously from outside the actor.

  1. SE-0412: Strict concurrency for global variables

Under strict concurrency checking, every global or static variable must be either isolated to a global actor or be both immutable and of Sendable type.

var mutableGlobal = 1
// warning: var 'mutableGlobal' is not concurrency-safe because it is non-isolated global shared mutable state
// (unless it is top-level code which implicitly isolates to @MainActor)

final class NonsendableType {
  init() {}
}

struct S {
  static let immutableNonsendable = NonsendableType()
  // warning: static property 'immutableNonsendable' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor
}

The attribute nonisolated(unsafe) can be used to annotate a global variable (or any form of storage) to disable static checking of data isolation, but note that without correct implementation of a synchronization mechanism to achieve data isolation, dynamic run-time analysis from exclusivity enforcement or tools such as Thread Sanitizer could still identify failures.

nonisolated(unsafe) var global: String
  1. SE-0327: On Actors and Initialization

This proposal aims to shore up the definition of an actor, to clarify when the isolation of the data begins and ends for an actor instance, along with what can be done inside the body of an actor’s init and deinit declarations.

A non-delegating initializer of an actor or a global-actor isolated type (GAIT) is required to initialize all of the stored properties of that type.

While actors are a reference type, their delegating initializers will follow the same basic rules that exist for value types, namely:

  • If an initializer body contains a call to some self.init, then it’s a delegating initializer. No convenience keyword is required.
  • For delegating initializers, self.init must always be called on all paths, before self can be used.

The reason for this difference between actor and class types is that actors do not support inheritance, so they can shed the complexity of class initializer delegation. GAITs use the same syntactic form as ordinary classes to define delegating initializers.

The only difference between the init and the deinit is that the deinit can only access Sendable properties, whereas the init can access non-Sendable properties prior to the isolation decay.