iOSの関数型リアクティブプログラミングを使用して設計した経験を共有したいと思います。 RAC 、 RxSwift 、 Interstellarなど、選択したツールに依存しません。 これは、MacOS用に開発する場合にも適用されます。
特定の時点で、Swift + RAC4を使用して作成します。これらは現時点でのメインツールです。 ただし、この記事ではRAC4の用語と機能は使用しません。
たぶんあなたはリアクティブプログラミングを放棄して無駄になっていて、それを使い始める時ですか?
そもそも、試薬についてしか聞いておらず、良くも聞いていない人々の間の神話について簡単に説明します。
神話1-リアクティブプログラミングのしきい値が高すぎます。
最初の数分から利用可能なすべての機能を使用する必要があると言う人はいません。 概念と基本的な基本演算子を理解する必要があります(以下では、最低限必要な4つの演算子について記述し、さまざまな種類の問題を解決するときに残りの部分を説明します)。
数か月や数年を費やす必要はなく、数日/数週間で十分です(利用可能なバックグラウンドによって異なります)。経験豊富なチームがいる場合、参加ははるかに速くなります。
神話2-試薬はUIレイヤーでのみ使用される
試薬をビジネスロジックで使用すると便利です。以下では、これから得られるものを示します。
神話3-リアクティブコードは、読み取りと解析が非常に困難です。
それはすべて、書かれたコードのレベルに依存します。 システムを適切に分離することにより、コードの理解が向上します。
さらに、多くのカルベックを使用するよりも複雑ではありません。
そして、ほとんどの場合、読めないコードを書くことができます。
試薬コンセプト
リアクティブコードを記述するプロセスは、子供向けのゲームのトリクルに似ています。 水の経路を作成してから、水を開始します。 私たちの場合の水は計算です。 ネットワーク要求、データベースからのデータの受信、座標およびその他多くのことの取得。 この場合の水の経路は信号です。
したがって、信号は特定の計算を含む主要な構成要素です。 次に、特定の操作を信号に対して実行できます。 信号に操作を適用すると、以前の構成を含む新しい信号が取得されます。
信号に操作を適用し、他の信号と組み合わせて、計算用のデータストリーム(データフロー)を作成します。 このスレッドはすべて、シグナルをサブスクライブした時点で実行を開始します。これは、遅延計算に似ています。 これにより、実行の開始と後続のアクションをより細かく制御できます。 コードは論理部分に分割され(読みやすさが向上します)、文字通り「オンザフライ」で新しい「メソッド」を作成する機会が得られ、システムでのコードの再利用が増加します。 高階関数のようですね。
データフローを初めて構成するために最低限必要な操作は、 map 、 filter 、 flatMap 、およびcomposeLatestです。
最後に、小さな機能であるデータフローはdata + errorsです 。これにより、2方向の一連のアクションを記述することができます。
これは最低限必要な理論です。
試薬およびモジュラーアーキテクチャ
SOAを例として取り上げますが、もちろんこれはあなたをまったく制限しません。
使用されるスキームは他のものとは異なる場合もあれば、同じ場合もあります。普遍的なふりをしていないため、標準のふりをしていません。 ほとんどのタスクでは、このソリューションを順守し、データ処理をサービスの背後に隠します(これはネットワーク要求である必要はありません)。
輸送
したがって、これが反応性の最初の候補です。 したがって、この場所について詳しく説明します。
まず、この問題の典型的な解決策を見てみましょう。
2つのカルベックを使用する
typealias EmptyClosure = () -> () func getReguestJSON(urlPath: String, parameters: [String : AnyObject]?, success: EmptyClosure, failed: EmptyClosure) -> NSURLSessionTask
最初のコールバックを使用する
typealias Response = (data: NSData?, code: Int) typealias Result = (response: Response, failed: NSError?) func getReguestJSON(urlPath: String, parameters: [String : AnyObject]?, result: Result) -> NSURLSessionTask
どちらのソリューションにも長所と短所があります。 タスクのフレームワーク内の両方のソリューションを検討してください。ネットワーク要求の継続中にローダーを表示します。
最初のケースでは、アクションをアクションの成功と失敗に明確に分けますが、アクションの完了を報告するコードを複製します。
2番目のケースでは、コードを複製する必要はありませんが、すべてを1つのヒープにミックスします。
また、ネットワーク要求をキャンセルする機能も必要であり、トランスポートの操作をカプセル化する必要があります。
ほとんどの場合、この場合、コードはおよそ
このように:
protocol Disposable { func dispose() } typealias Response = (data: NSData?, code: Int) typealias Result = (response: Response, failed: NSError?) func getReguestJSON(urlPath: String, parameters: [String : AnyObject]?, result: Result) -> Disposable? ... ... ... typealias EmptyClosure = () -> () func getReguestJSON(urlPath: String, parameters: [String : AnyObject]?, success: EmptyClosure, failed: EmptyClosure) -> Disposable?
では、信号を使用したソリューションを見てみましょう
func getRequestJSON(urlPath: String, parameters: [String : String]) -> SignalProducer<Response, NSError> { return SignalProducer<Response, NSError> { observer, disposable in let task = ... { observer.sendNext(data: data, code: code) observer.sendCompleted() //or observer.sendFailed(error) } disposable.addDisposable { task.cancel() } } }
以前、私は意図的に重要な点を1つ見逃していました-シグナルを作成するとき、シグナルをサブスクライブするときの処理だけでなく、シグナルがキャンセルされたときの処理も記述します。
シグナルをサブスクライブすると、Disposableクラスのインスタンスが返されます(上記では説明していませんが、それ以上)。これにより、シグナルをキャンセルできます。
コード例
let disposable = getRequestJSON(url, parameters: parameters) // .startWithNext { data, code in ... ... ... } // startWithNext disposable.dispose() //
これで、着信側は、リクエストの実行を簡単に遅延させ、結果を他のリクエストと組み合わせ、シグナルイベントにいくつかのアクションを書き込み(リクエストを完了するための上記の例から)、データ受信時の処理とエラー発生時の処理を行うことができます。
しかし、そのようなコードを示す前に、次のような概念についてお話したいと思います
副作用
この概念に出会わなかったとしても、100%はそれを観察しました(または、偶然ここに目を向けました)。
簡単な言葉で言えば、これは私たちのコンピューティングの流れが環境に依存し、変化するときです。
信号を別のコードとして記述し、再利用の可能性を高めます。
しかし、副作用が時々必要であり、それについてひどいものは何もありません。 リアクティブプログラミングで副作用を使用する方法を図で見てみましょう。
とても簡単ですね。 シグナルの実行をウェッジし、特定のアクションを実行します。 実際、特定のシグナルイベントに対してアクションを実行します。 しかし、同時に、信号はクリーンで再利用の準備ができたままになります。
たとえば、以前に作成されたタスクから:「信号の開始時にローダーを表示し、終了時に削除します。」
解析
典型的な状況を思い出してください-サーバーからのデータが正しい形式または間違った形式で送られてきました。 ソリューションオプション:
1)カルベキ「データ+エラー」
2)NSError +&を使用したAppleのアプローチ
3)トライキャッチ
そして、試薬は私たちに何を与えることができますか?
サーバーからの応答を解析し、特定のイベント(次/失敗)で結果を提供するシグナルを作成しましょう。
信号を使用すると、コードの作業をより明確に確認し、作業をネットワーク要求の信号と組み合わせることができます。 しかし、それだけの価値はありますか?
例
class ArticleSerializer { func deserializeArticles(data: NSData?, code: Int) -> SignalProducer<[Article], NSError> { return SignalProducer<[Article], NSError> { observer, _ in ... ... ... } }
サービス
ネットワーク要求を組み合わせて解析し、解析結果をDAOに保存する機能を追加します。
コード例
class ArticleService { ... ... ... func downloadArticles() -> SignalProducer<[Article], NSError> { let url = resources.articlesPath let request = transport.getRequestJSON(url, parameters: nil) .flatMap(.Latest, transform: serializer.deserializeArticles) .on(next: dao.save) return request }
ネストなし、すべてが非常にシンプルで読みやすい。 一般的に非常に一貫したコードではありませんか? そして、シグナルが異なるスレッドで実行されたとしても、それは単純なままです。 ところで、combinateLatestの使用を検討してください。
並列クエリ同期
userService.downloadRelationshipd() // .combineLatestWith(inviteService.downloadInvitation()) // + .observeOn(UIScheduler()) // ( ) .startWithNext { users, invitations in // }
上記のコードは、シグナルにサブスクライブして起動するまで実行されないことに注意してください。 実際、アクションのみを示しています。
そして今、サービスはさらに透明になっています。 ビジネスロジックの一部(他のサービスを含む)を接続し、これらの接続のデータフローを返すだけです。 また、受信した信号を使用している人は、非常に迅速にイベントへの反応を追加したり、他の信号と組み合わせることができます。
そしてまた...
しかし、信号に対する多くの操作がなければ、これはそれほど面白くないでしょう。
遅延の設定、結果の繰り返し、さまざまな組み合わせシステム、特定のストリーム、畳み込み、ジェネレーターへの結果の受信の設定...このリストを見るだけです。
多くの人にとって最も「ジュース」であるUIとバインディングの操作について話さないのはなぜですか?
これは別の記事のトピックであり、非常に多くの記事があるので、リンクをいくつか挙げて終了します
ReactiveCocoaでより良い世界を
ReactiveCocoa。 並行性。 マルチスレッド
それだけです。 無駄な結論の代わりに、最後のドラフトからいくつかの実用的な結論を残します。
1)許可の取得は、シグナルとして非常に優れていることが証明されています。
2)CLLocationManagerは信号に対して完全に動作しました。 特にポイントの蓄積と編集。
3)写真の選択、SMSの送信、電子メールの送信などのアクションの信号を使用することも便利でした。