LoginSignup
40
26

More than 1 year has passed since last update.

Swift5.5の変更点まとめ

Last updated at Posted at 2021-06-16

はじめに

これは、Swift5.5で変更が入った機能についてまとめもの。
変更点はCHANGELOG を元に作成した。

※ 2021/06/16現在時点のため、まだ変更が入る可能性があります

Swift5.5のアップデート一覧

  • SE-0296 Async/await
  • SE-0297 Concurrency Interoperability with Objective-C: Objective-Cとの並行処理の相互運用性
  • SE-0298 Async/Await: Sequences
  • SE-0300 Continuations for interfacing async tasks with synchronous code: 非同期タスクと同期コードを連動させるための改善
  • SE-0310 Effectful Read-only Properties: 副作用のある読み取り専用のプロパティ
  • SE-0311 Task Local Values
  • SE-0316 Global actors
  • SE-0306 Actors
  • SE-0313 Improved control over actor isolation: アクターのアイソレーションの管理を改善
  • SE-0293 Extend Property Wrappers to Function and Closure Parameters: プロパティ・ラッパーを関数やクロージャのパラメータに拡張
  • SE-0299 Extending Static Member Lookup in Generic Contexts: Generic ContextsにおけるStatic Member Lookupの拡張

SE-0296 Async/await

async/await を使った非同期プログラミングがネイティブにサポートされた。非同期関数は async を使って定義できる。

func loadWebResource(_ path: String) async throws -> Resource { ... }
func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image
func dewarpAndCleanupImage(_ i : Image) async -> Image

非同期関数の呼び出しはサスペンドする可能性がある。つまり、実行中のスレッドで実行されず、スケジューリングされて後で再実行する。非同期関数を呼び出す場合は、throws 関数の呼び出しに try を使うことと同様に、await が必要。

func processImageData() async throws -> Image {
  let dataResource  = try await loadWebResource("dataprofile.txt")
  let imageResource = try await loadWebResource("imagedata.dat")
  let imageTmp      = try await decodeImage(dataResource, imageResource)
  let imageResult   = await dewarpAndCleanupImage(imageTmp)
  return imageResult
}

lazy キーワードがローカルスコープでも機能するようになり以下のように書けるようになった。

func test(useIt: Bool) {
  lazy var result = getPotentiallyExpensiveResult()
  if useIt {
    doIt(result)
  }
}

SE-0297 Concurrency Interoperability with Objective-C

完了ハンドラブロックを介して非同期に結果を返す Objective-C メソッドは、結果を直接返す(またはスローする)非同期メソッドに変換される。
例えば以下の CloudKitObjective-C メソッド

- (void)fetchShareParticipantWithUserRecordID:(CKRecordID *)userRecordID
    completionHandler:(void (^)(CKShareParticipant * _Nullable, NSError * _Nullable))completionHandler;

は、CKShareParticipant のインスタンスを返す非同期の throws メソッドに変換される。

func fetchShareParticipant(
    withUserRecordID userRecordID: CKRecord.ID
) async throws -> CKShare.Participant

Swiftの呼び出し側は、await でこの非同期メソッドを呼び出すことができる。

guard let participant = try? await container.fetchShareParticipant(withUserRecordID: user) else {
    return nil
}

SE-0298 Async/Await: Sequences

for ループは、非同期コードの非同期シーケンスをトラバースするために使用できる。

for try await line in myFile.lines() {
  // Do something with each line
}

非同期 for ループは非同期シーケンスを使用します。これはプロトコル AsyncSequence とそれに対応する AsyncIterator で定義される。

SE-0300 Continuations for interfacing async tasks with synchronous code

withUnsafeContinuation および withUnsafeThrowingContinuation 関数を使用して、非同期関数を中断できるようになった。これらの関数はクロージャを受け取り、現在の非同期タスクを中断して、現在のタスクの値を持つクロージャを実行する。プログラムは将来のある時点でその値を使用してタスクを再開し、再開されたタスクの withUnsafeContinuation 呼び出しの結果の値またはエラーを渡す必要がある。

func operation() async -> OperationResult {
  return await withUnsafeContinuation { continuation in
    beginOperation(completion: { result in
      continuation.resume(returning: result)
    }) 
  }
}

型名は、メタタイプをパラメータの引数としては使用できなくなった。

struct MyValue {
}

struct MyStruct {
  subscript(a: MyValue.Type) -> Int { get { ... } }
}

func test(obj: MyStruct) {
  let _ = obj[MyValue]
}

MyValueがメタタイプを参照するために明示的な.selfを必要とするため、引数としてMyValueを引数で受け入れることは見落だった。正しい構文は obj[MyValue.self] を使用することになる。

SE-0310 Effectful Read-only Properties

読み取り専用のプロパティ、get{の間にこれらのキーワードの1つまたは両方を記述することで、そのgetasync またはthrows に定義できるようになった。これによりメンバは、値を生成する過程で非同期呼び出しやエラーのスローを行うことができるようになった。

class BankAccount: FinancialAccount {
  var manager: AccountManager?

  var lastTransaction: Transaction {
    get async throws {
      guard manager != nil else { throw BankError.notInYourFavor }
      return await manager!.getLastTransaction()
    }
  }

  subscript(_ day: Date) -> [Transaction] {
    get async {
      return await manager?.getTransactions(onDay: day) ?? []
    }
  }
}

protocol FinancialAccount {
  associatedtype T
  var lastTransaction: T { get async throws }
  subscript(_ day: Date) -> [T] { get async }
}

上記の lastTransaction のように、メンバーへのアクセスには awaittry を使う必要がある

extension BankAccount {
  func meetsTransactionLimit(_ limit: Amount) async -> Bool {
    return try! await self.lastTransaction.amount < limit
    //                    ^~~~~~~~~~~~~~~~ this access is async & throws
  }                
}


func hadWithdrawlOn(_ day: Date, from acct: BankAccount) async -> Bool {
  return await !acct[day].allSatisfy { $0.amount >= Amount.zero }
  //            ^~~~~~~~~ this access is async
}

SE-0311 Task Local Values

タスクローカル値は、新しい @TaskLocal プロパティラッパーを使用して定義できます。このような値は、バインディングが行われたタスクや、子タスク、タスク・コンテキストから作成された非構造化タスクによって暗黙的に伝達されます。

struct TraceID { 
  @TaskLocal
  static var current: TraceID? 
}

func printTraceID() {
  if let traceID = TraceID.current {
    print("\(traceID)")
  } else {
    print("nil")
  }
}

func run() async { 
  printTraceID()    // prints: nil
  TraceID.$current.withValue("1234-5678") { 
    printTraceID()  // prints: 1234-5678
    inner()         // prints: 1234-5678
  }
  printTraceID()    // prints: nil
}

func inner() {
  // タスクローカル値がバインドされているコンテキストから呼び出された場合、
  // それを表示します(そうでない場合は 'nil' を表示します)。
  printTraceID()
}

SE-0316 Global actors

型を global actor として定義することができます。global actor は、actor isolation の概念を単一のアクター・タイプの外に拡張し、ステートや関数が多くの異なるタイプ、関数、モジュールに分散していても、グローバル・ステート(およびそれにアクセスする関数)が actor isolation の恩恵を受けられるようにします。global actor は、同時実行プログラムでグローバル変数を安全に扱うことを可能にし、「メイン・スレッド」や「UIスレッド」でのみ実行しなければならないコードなど、その他のグローバルなプログラム制約をモデル化します。新しい global actor は、globalActor 属性で定義できます

@globalActor
struct DatabaseActor {
  actor ActorType { }

  static let shared: ActorType = ActorType()
}

global actor 型は、さまざまな宣言のカスタム属性として使用することができます。これにより、これらの宣言は、global actor の共有インスタンスによって記述された Actor 上でのみアクセスされるようになります。例えば、以下のようになります。

@DatabaseActor func queryDB(query: Query) throws -> QueryResult

func runQuery(queryString: String) async throws -> QueryResult {
  let query = try Query(parsing: queryString)
  return try await queryDB(query: query) // 'await' なぜなら、これは暗黙のうちにDatabaseActor.sharedに切り替わるからです。
}

並行ライブラリでは、実行のメインスレッドを表す MainActor というグローバルアクターを定義しています。UI の更新など、メイン スレッドで実行する必要があるコードには、このアクターを使用する必要があります。

SE-0306 Actors

Swift 5.5には、アクターのサポートが含まれています。アクターは、同時アクセスから保護するためにそのインスタンスデータを分離する新しい種類の型。外部からのアクターへのアクセスは非同期でなければならない。

actor Counter {
  var value = 0

  func increment() {
    value = value + 1
  }
}

func useCounter(counter: Counter) async {
  print(await counter.value) // 非同期でなければならない
  await counter.increment()  // 非同期でなければならない
}

rethrows 関数の呼び出しが throw できるかどうかの判断は、Optional 型のデフォルト引数を考慮するようになった。

Swift 5.4では、そのようなデフォルトの引数は rethrows のチェックで完全に無視されていた。

func foo(_: (() throws -> ())? = nil) rethrows {}
foo()  // no 'try' needed

しかしこれでは、foo() の呼び出しがスローされる可能性があり、呼び出しサイトが try されていないにもかかわらず、以下の例がエラーにならないことを意味する。

func foo(_: (() throws -> ())? = { throw myError }) rethrows {}
foo()  // 'try' *should* be required here

新しい動作としては、最初の例はデフォルトの引数が構文的に nil と書かれていて、スローしないことがわかっているのでエラーにならない。2つ目の例は、デフォルトの引数が can throw であるため、try がないという理由で正しくエラーになる。

SE-0313 Improved control over actor isolation

通常は actor-isolated となっているアクター内部の宣言は、nonisolated キーワードを使って明示的に non-isolated とすることができる。
non-isolated は、同期的に呼び出すことができる。

actor Account: Hashable {
  let idNumber: Int
  var balance: Double

  nonisolated func hash(into hasher: inout Hasher) { // OK、nonisolatedでも同期要件を満たす
    hasher.combine(idNumber) // OK、外側の定数を参照できる
    hasher.combine(balance) // Error、外側の変数は参照できない
  }
}

func test() {
  var hasher = Hasher()
  Account().hash(into: &hasher) // await が必要ない
}

アクター型のパラメーターは、isolated として宣言することができる。つまり、そのコードが実行時にアクターを表す。isolated パラメータは、引数におけるアクターのメソッドの actor-isolate セマンティクスを任意の引数に拡張します。

actor MyActor {
  func f() { }
}

func g(actor: isolated MyActor) {
  actor.f()   // Actor上で実行される
}

func h(actor: MyActor) async {
  g(actor: actor)        // エラー, async で呼び出す必要がある
  await g(actor: actor)  // OK, g を呼び出す前にアクターに切り替わる
}

SE-0293 Extend Property Wrappers to Function and Closure Parameters

関数やクロージャのパラメータにプロパティラッパーを適用できるようになりました。

@propertyWrapper
struct Wrapper<Value> {
  var wrappedValue: Value

  var projectedValue: Self { return self }

  init(wrappedValue: Value) { ... }

  init(projectedValue: Self) { ... }
}

func test(@Wrapper value: Int) {
  print(value)
  print($value)
  print(_value)
}

test(value: 10)

let projection = Wrapper(wrappedValue: 10)
test($value: projection)

コール呼び出し側では、ラップ値または予測値を渡すことができ、Wrapper はそれぞれ init(wrappedValue:) または init(projectedValue:) を使用して初期化されます。

SE-0299 Extending Static Member Lookup in Generic Contexts

Self が完全な具象型に制約されているプロトコル拡張の静的メンバーにアクセスするために、ジェネリクスのコンテキストで型省略を使用することが可能になりました。

public protocol ToggleStyle { ... }

public struct DefaultToggleStyle: ToggleStyle { ... }

extension ToggleStyle where Self == DefaultToggleStyle {
  public static var `default`: Self { .init() }
}

struct Toggle {
  func applyToggle<T: ToggleStyle>(_ style: T) { ... }
}

Toggle(...).applyToggle(.default)

Self への参照が、値の型としてのプロトコルや、プロトコル型の値に対するプロトコル・メンバの使用を妨げない場合は、[Self][Key : Self] への参照についても同様に可能になりました。


protocol Copyable {
  func copy() -> Self
  func copy(count: Int) -> [Self]
}

func test(c: Copyable) {
  let copy: Copyable = c.copy() // OK
  let copies: [Copyable] = c.copy(count: 5) // also OK
}
40
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
26