SwiftでObjective-Cスタイルの非同期APIを使用

3837 ワード

作者:Ole Begemann,原文リンク,原文日付:2017-01-19訳者:Cwift;校正:walkingway;原稿:CMB
多くのObjective-Cスタイルの非同期APIは、オペレーションが成功したときのメソッドの戻り値を表し、オペレーションが失敗したときに返されるエラー値を表す2つのオプションタイプの値をコールバック閉パケットに入力します.
一例はCore LocationフレームワークのCLGeocoderである.reverseGeocodeLocationメソッド.CLLocationオブジェクトを受け入れ、座標情報をWebサーバに送信すると、サーバは座標を読み取り可能なアドレスに解析します.ネットワーク要求が完了すると、このメソッドはコールバック閉パケットを呼び出します.パラメータはCLPlacemarkオブジェクトを格納するオプションの配列と、選択可能なErrorオブジェクトです.
class CLGeocoder {
    ...
    func reverseGeocodeLocation(_ location: CLLocation,
        completionHandler: @escaping ([CLPlacemark]?, Error?) -> Void)
    ...
}

Objective-CスタイルのAPIでは、選択可能な成功値とエラーのペアを返すモードが、このような状況を処理する際に最も実用的なスキームである.

2つの可能な結果、4つの潜在的な状態


現在のAPIの問題は、実際には、要求が成功して結果を返すか、失敗してエラーを返すかの2つの可能性しかありません.しかし、このコードは4つの異なる状態を可能にします.
  • 結果は空ではなく、エラーは空です.
  • エラーは空ではなく、結果は空です.
  • 両方とも空ではありません.
  • 両方が空です.

  • APIのドキュメントは、最後の2つの状況を明確に排除できますが、ユーザーとしては、ドキュメントが正しいことを確実にすることはできません.

    Resultによる優れた設計


    Swiftでは、このように同じAPIを設計することができます.
    class CLGeocoder {
        ...
        func reverseGeocode(location: CLLocation,
            completion: @escaping (Result) -> Void)
        ...
    }
    

    コールバック閉パケットには、Result<.../>のタイプの1つの(非選択)パラメータしか受け入れられません.Resultは、SwiftのOptionalタイプと非常に似ている列挙である.唯一の違いは、エラー値を失敗時に保存できますが、Optionalは成功時の関連値のみです.
    enum Result {
        case success(T)
        case failure(Error)
    }
    
    Resultは現在、Swift標準ライブラリのメンバーではありませんが、将来導入される可能性があります.その前に、自分で定義するのも簡単です.あるいは、現在流行しているantitypical/Resultライブラリを使用することができます.(注:このライブラリのResultは、私がここで使用しているタイプとは少し異なります.強いタイプのエラーを使用しています.つまり、2番目の汎用パラメータがエラーのタイプを表しています.)
    この架空の新しいAPIを使用すると、コンパイラは、コールバック閉パケットに渡されるパラメータが成功または失敗の2つの状態しかないことを保証できます.両方の値が存在するか、存在しないかを心配する必要はありません.

    1つのハンドル(T?,Error?)Resultに変換されたコンストラクタ


    しかし,アップルのAPIを修正することはできないので,コールバック閉パケットにおけるパラメータ固有の曖昧性には力がない.オプションの成功値とオプションのエラーを単一のResult値に変換する論理を含むことができます.コードでResultの便利なコンストラクタを定義しました.
    import Foundation // needed for NSError
    
    extension Result {
        ///                     
        ///      Result   。 
        ///          API           Result。
        init(value: T?, error: Error?) {
            switch (value, error) {
            case (let v?, _):
                //            
                self = .success(v)
            case (nil, let e?):
                self = .failure(e)
            case (nil, nil):
                let error = NSError(domain: "ResultErrorDomain", code: 1,
                    userInfo: [NSLocalizedDescriptionKey:
                        "Invalid input: value and error were both nil."])
                self = .failure(error)
            }
        }
    }
    

    両方の入力がnil(通常は発生すべきではない)の場合、カスタムエラーを作成して結果に入れます.ここではNSErrorを使用していますが、Errorプロトコルを遵守しているタイプを使用することができます.このコンストラクタを定義した後、地理エンコーダのAPIを以下のように使用します.
    let location = ...
    let geocoder = CLGeocoder()
    geocoder.reverseGeocodeLocation(location) { placemarks, error in
        //        Result
        let result = Result(value: placemarks, error: error)
        //       result    
        switch result {
        case .success(let p): ...
        case .failure(let e): ...
        }
    }
    

    追加の1行のコードを使用して、パラメータをResultタイプの値に変換します.その時から、未処理の心配はありません.
    2017年1月20日の更新:Shawn Throopは、前述したCLGeocoder拡張のコードを最適化することをお勧めします.コードはResultベースのメソッドのみを呼び出します.このメソッドは、元のAPIを内部で呼び出し、タイプの変換を担当します.
    extension CLGeocoder {
        func reverseGeocode(location: CLLocation,
            completion: @escaping (Result) -> Void) {
            reverseGeocodeLocation(location) { placemarks, error in
                completion(Result(value: placemarks, error: error))
            }
        }
    }
    

    本文はSwiftGG翻訳グループから翻訳して、すでに作者の翻訳の授権を得て、最新の文章は訪問して下さいhttp://swift.gg.