Sample Code

Updating an App to Use Swift Concurrency アプリをSwift並行性を使うように更新する

Improve your app’s performance by refactoring your code to take advantage of asynchronous functions in Swift. Swiftでの並行性関数を利用するようにあなたのコードをリファクタリングすることであなたのアプリの性能を改良してください。
Download

Overview 概要

Swift concurrency provides a standard set of language tools and techniques for concurrent programming. However, you may already have an existing project built with concurrency that uses other frameworks and techniques. You don’t have to convert all of your code all at once; instead, you can use specific refactoring techniques to convert your code one piece at time. Swift並行性は、標準的な一揃いの言語ツールと技法を並行性プログラミングのために提供します。しかしながら、あなたは既に、他のフレームワークと技法を使う並行性でビルドされた既存のプロジェクトを持っているかもしれません。あなたは、あなたのコードの全てを一斉に変換する必要はありません;あなたは特定のリファクタリング技法を使ってあなたのコードを一度に一断片ずつ変換できます。

This sample provides two separate versions of the Coffee Tracker app: この見本は、2つの別のバージョンのCoffee Trackerアプリを提供します:

  • The original version uses completion handlers to query the HealthKit SDK and to respond to CLKComplicationDataSource calls, and dispatch queues to isolate concurrent access to memory. For more information on the original version, see Creating and Updating a Complication’s Timeline. オリジナルバージョンは、完了ハンドラを、HealthKit SDKへの問い合わせのためそしてCLKComplicationDataSource呼出しへの応答のために、そしてディスパッチキューを、メモリへの並行アクセスを隔離するために使います。オリジナルバージョンに関するさらなる情報として、Creating and Updating a Complication’s Timelineを見てください。

  • The updated version uses Swift’s concurrency features to provide clearer code with better error checking at compile time. It replaces the completion handlers with async functions, and uses actors to guarantee safe access to data. 更新されたバージョンは、Swiftの並行性機能を使って、明快なコードをより良いコンパイル時でのエラーチェックとともに提供します。それは、完了ハンドラをasync関数で置き換えます、そしてアクターを使ってデータへの安全なアクセスを保証します。

Watch the session to see the process step by step, and then compare the two projects to see the differences. この過程を段階的に理解するためにセッションを視聴してください、そしてそれから2つのプロジェクトを比べてその違いを見てください。

Configure the Sample Code Project 見本コードプロジェクトを構成設定する

To add the complication to an active watch face, start by building and running the sample code project in the simulator, and follow these steps: コンプリケーションをアクティブなウォッチフェイスに加えるには、見本コードプロジェクトをシミュレータにおいてビルドおよび実行することによって始めます、そしてこれらの段階に従ってください:

  1. Click the Digital Crown to exit the app and return to the watch face. Digital Crownをクリックして、アプリを出て、ウォッチフェイスに戻ってください。

  2. Using the trackpad, firmly press the watch face to put the face in edit mode, then tap Customize. トラックパッドを使って、しっかりウォッチフェイスを押して、フェイスを編集モードに入れて、それからCustomizeをタップしてください。

  3. Swipe left until the configuration screen highlights the complications. Select the complication to modify. 構成画面がコンプリケーションをハイライト表示するまで、左にスワイプしてください。修正するためにコンプリケーションを選択します。

  4. Scroll to the Coffee Tracker complication, and then click the Digital Crown again to save your changes. Coffee Trackerコンプリケーションまでスクロールしてください、そしてそれからDigital Crownを再びクリックしてあなたの変更を保存してください。

  5. Tap the Coffee Tracker complication to go back to the app. Coffee Trackerコンプリケーションをタップしてアプリに戻ってください。

For more information on setting up watch faces, see Change the watch face on your Apple Watch. ウォッチフェイスを設定することに関するさらなる情報として、Change the watch face on your Apple Watchを見てください。

After configuring and running the Coffee Tracker app, you can test the background updates. Make sure the Coffee Tracker complication appears on the active watch face. Then build and run the app in Simulator, and follow these steps: Coffee Trackerアプリを構成設定および実行した後、あなたはバックグラウンド更新をテストできます。Coffee Trackerコンプリケーションがアクティブなウォッチフェイス上に現れることを確かめてください。それからアプリをシミュレータ上でビルドおよび実行してください、そしてこれらの手順に従ってください:

  1. Add one or more drinks using the app’s main view. 1つ以上の飲み物を、アプリのメインビューを使って加えてください。

  2. Click the Digital Crown to send the app to the background. Digital Crownをクリックして、アプリをバックグラウンドに送ってください。

  3. Open Settings, and scroll down to Health > Health Data > Nutrition > Caffeine to see all of the drinks you added to the app. Settingsを開いて、そして Health > Health Data > Nutrition > Caffeine へと下にスクロールして、あなたがアプリに加えた飲み物の全てを見てください。

  4. Click Delete Caffeine Data to clear all of the caffeine samples from HealthKit. Delete Caffeine Data をクリックして、カフェイン見本の全てを HealthKit から消去してください。

  5. Navigate back to the watch face. ウォッチフェイスに戻ってください。

Coffee Tracker updates the complication within 15 minutes; however, the update may be delayed based on the system’s current state. Coffee Tracker は、コンプリケーションを 15 分以内に更新します;しかしながら、更新はシステムの現在の状態に依存して遅れるかもしれません。

Convert Completion Handlers to Use Asynchronous Methods 完了ハンドラを変換して非同期メソッドを使うようにする

The HealthKitController type contains several calls to the HealthKit SDK. In SDKs that support Swift concurrency, frameworks add async-await versions of most functions that previously took completion handlers. You can remove completion handlers by updating these calls to use the async-await versions. You suspend the store.save() operation by adding the await keyword. Execution resumes after the await completes. An async function can also be a throwing function, which you call by prepending try await to the function call. Wrap the call in a do-catch statement instead of using an Error? type as a parameter to the completion handler. HealthKitController型は、HealthKit SDKへのいくつかの呼び出しを含みます。Swift並行性をサポートするSDKにおいて、フレームワークそれらは以前は完了ハンドラをとっていた大部分の関数のasync-awaitバージョンを加えます。あなたは完了ハンドラそれらを取り除くことがそれらの呼び出しをasync-await版を使うよう更新することで可能です。あなたはstore.save()操作を、awaitキーワードの追加によって一時停止します。遂行は、awaitが完了した後に再開します。async関数はまた、あるスロー関数であることが可能です、それはあなたがtry awaitをその関数呼び出しの前に付けることによって呼び出すものです。呼び出しをdo-catch文の中に包んでください、Error?型をパラメータとして完了ハンドラに使う代わりに。


// Save the sample to the HealthKit store.
do {
    try await store.save(caffeineSample)
    self.logger.debug("\(mgCaffeine) mg Drink saved to HealthKit")
} catch {
    self.logger.error("Unable to save \(caffeineSample) to the HealthKit store: \(error.localizedDescription)")
}

In some cases an SDK call requires using a completion handler. For example, a call to init(type:predicate:anchor:limit:resultsHandler:) takes a completion handler, but the call that needs to await is the call to execute(_:). いくつかの場合においてSDKは完了ハンドラを使うことを要求します。例えば、init(type:predicate:anchor:limit:resultsHandler:)への呼び出しは完了ハンドラをとります、しかしawaitする必要がある呼び出しは、execute(_:)への呼び出しです。

To await the results of a completion handler in these cases, add a continuation: それらの場合に完了ハンドラの結果をawaitするには、continuationを加えてください:


private func queryHealthKit() async throws -> ([HKSample]?, [HKDeletedObject]?, HKQueryAnchor?) {
    return try await withCheckedThrowingContinuation { continuation in
        // Create a predicate that only returns samples created within the last 24 hours.
        let endDate = Date()
        let startDate = endDate.addingTimeInterval(-24.0 * 60.0 * 60.0)
        let datePredicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [.strictStartDate, .strictEndDate])
        
        // Create the query.
        let query = HKAnchoredObjectQuery(
            type: caffeineType,
            predicate: datePredicate,
            anchor: anchor,
            limit: HKObjectQueryNoLimit) { (_, samples, deletedSamples, newAnchor, error) in
            
            // When the query ends, check for errors.
            if let error = error {
                continuation.resume(throwing: error)
            } else {
                continuation.resume(returning: (samples, deletedSamples, newAnchor))
            }
            
        }
        store.execute(query)
    }
}

To protect the stored properties on the controller when accessed asynchronously, change HealthKitController from a class type to an actor: 格納プロパティをコントローラ上で非同期にアクセスされた時に保護するには、HealthKitControllerclass型からactorへと変えてください:


actor HealthKitController {

Calls to async functions from synchronous functions are made by creating new asynchronous tasks, which can use await to wait for completion: async関数への非同期関数からの呼び出しは、新しい非同期タスクを作成することによって作られます、それらは awaitを使うことで完了に対して待機できます:


// Handle background refresh tasks.
case let backgroundTask as WKApplicationRefreshBackgroundTask:
    
    async {
        // Check for updates from HealthKit.
        let model = CoffeeData.shared
        
        let success = await model.healthKitController.loadNewDataFromHealthKit()
            
        if success {
            // Schedule the next background update.
            scheduleBackgroundRefreshTasks()
            self.logger.debug("Background Task Completed Successfully!")
        }
        
        // Mark the task as ended, and request an updated snapshot, if necessary.
        backgroundTask.setTaskCompletedWithSnapshot(success)
    }

Put the Coffee Data Class on the Main Actor Coffee Data Classをメインアクター上に置く

The CoffeeData class implements ObservableObject and has an @Published property to feed the SwiftUI views. To ensure that all updates to this property are made on the main thread, place the type on the main actor: CoffeeDataクラスは、ObservableObjectを実装して、そしてSwiftUIビューに供給する@Publishedプロパティを持ちます。このプロパティへの全ての更新がメインスレッド上でなされることを確実にするために、その型をメインアクター上に置いてください:


@MainActor
class CoffeeData: ObservableObject {

Two methods that perform synchronous IO — the load and save methods — are factored out into a separate CoffeeDataStore actor, which performs these activities away from the main thread. The model type on the main actor must use await to call methods on the CoffeeDataStore actor, which allows other work to run on the main thread during the synchronous IO operations. 同期的なIOを実行する2つのメソッド — loadsaveメソッド — それらは、ある隔てられたCoffeeDataStoreアクターへと抽出されます、それはこれらの活動をメインスレッドから離して実行します。メインアクター上のmodel型は、awaitを使ってメソッドそれらをCoffeeDataStoreアクター上で呼び出さなければなりません、それは他の作業が同期IO操作の間にメインスレッド上で動作するのを可能にします。

The two types communicate by passing an array of Drink values, which is a value type because Drink is a structure. Loading returns an array of drinks, and saving takes an array of drinks as an argument. 2つの型は、Drink値それらからなるある配列を渡すことによって通信します、それは値型です、なぜならDrinkが構造体であるからです。ロードでは、ドリンクそれらからなるある配列を返します、そしてセーブ(保存)ではドリンクそれらからなるある配列を引数としてとります。

To perform all methods asynchronously, replace the currentDrinks property’s didSet operation with private(set) and add a new async method named drinksUpdated. Move the code from the setter into the new method. Call the drinksUpdated after any code that sets the currentDrinks property, using an await call. 全てのメソッドを非同期に実行するには、currentDrinksプロパティのもつdidSet演算をprivate(set)で置き換えます、そして新しいasyncメソッドをdrinksUpdatedという名前で加えてください。コードをそのセッターから新しいメソッドに移動してください。drinksUpdatedを、currentDrinksプロパティを設定するあらゆるコードの後に呼び出してください、await呼出を使って。

Update the drinksUpdated() method to call the CoffeeDataStore actor using an await call. The CoffeeDataStore actor saves the data on a background thread. drinksUpdated()メソッドを更新して、CoffeeDataStoreアクターをawait呼出を使って呼び出すようにください。CoffeeDataStoreアクターは、データをあるバックグラウンドスレッド上で保存します。

Calls to the CoffeeData object from SwiftUI views don’t require any use of await as these views are also on the main actor due to their use of @EnvironmentObject. CoffeeDataオブジェクトへのSwiftUIビューそれらからの呼び出しは、awaitのどのような利用も必要としません、それらのビューがまたメインアクター上で@EnvironmentObjectのそれらの使用によってするのと同じく。

Replace Delegates and Completion Handlers with Async Methods 委任と完了ハンドラをasyncメソッドで置き換える

Several methods on the CLKComplicationDataSource protocol used to configure the app’s timeline take completion handlers, which you can replace with their async equivalents: アプリのもつタイムラインを構成設定するために使われるCLKComplicationDataSourceプロトコル上のいくつかのメソッドは、完了ハンドラをとります、それはあなたがそれらのasync相当物と置き換え可能です:


// Define whether the complication is visible when the watch is unlocked.
func privacyBehavior(for complication: CLKComplication) async -> CLKComplicationPrivacyBehavior {
    // This is potentially sensitive data. Hide it on the lock screen.
    .hideOnLockScreen
}