はじめに
これは、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
メソッドは、結果を直接返す(またはスローする)非同期メソッドに変換される。
例えば以下の CloudKit
の Objective-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つまたは両方を記述することで、そのget
を async
または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
のように、メンバーへのアクセスには await
や try
を使う必要がある
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
}