iOSネットワーキングアプリケーション(RESTクライアント)を構築するための最良のアーキテクチャアプローチ


323

私はいくつかの経験を持つiOS開発者であり、この質問は私にとって本当に興味深いものです。このトピックについてさまざまなリソースや資料をたくさん見ましたが、それでも混乱しています。iOSネットワークアプリケーションに最適なアーキテクチャは何ですか?基本的な抽象的なフレームワークであるパターンを意味します。これは、サーバーリクエストがわずかしかない小さなアプリでも、複雑なRESTクライアントでも、すべてのネットワーキングアプリケーションに適合します。Apple MVCは、すべてのiOSアプリケーションの基本的なアーキテクチャアプローチとして使用することをお勧めしますが、どちらMVCも最新のものではありません。MVVMネットワークロジックコードを配置する場所と一般的な構成方法を説明するパターン。

MVCSSfor Service)のようなものを開発する必要があり、このServiceレイヤーにすべてのAPIリクエストと他のネットワークロジックを配置します。いくつかの調査を行った後、私はこのための2つの基本的なアプローチを見つけました。ここでは、すべてが基本要求抽象クラスから継承するWebサービスへのすべてのネットワーク要求APILoginRequestクラスまたはPostCommentRequestクラスなど)ごとに個別のクラスを作成し、AbstractBaseRequestさらに、一般的なネットワークコードをカプセル化するグローバルネットワークマネージャーを作成することをお勧めしました。その他の設定(AFNetworkingカスタマイズまたはRestKit、複雑なオブジェクトのマッピングと永続性がある場合、または場合でも、チューニング標準APIによる独自のネットワーク通信実装)。しかし、このアプローチは私にとってオーバーヘッドのようです。別のアプローチはAPI、最初のアプローチと同様に、いくつかのシングルトンディスパッチャーまたはマネージャークラスを持つことです。しかし、すべてのリクエストに対してクラスを作成するのではなく、代わりにすべてのリクエストをこのマネージャークラスのインスタンスパブリックメソッドとしてカプセル化しますfetchContactsloginUserなどだから、方法、どのような最良かつ正しい方法は何ですか?私がまだ知らない他の興味深いアプローチはありますか?

そして、このようなすべてのネットワーキング関連の別のレイヤーを作成する必要がありますServiceか、またはNetworkProviderレイヤーまたは私のMVCアーキテクチャの上にあるもの、またはこのレイヤーを既存のMVCレイヤーに統合(注入)する必要がありModelます。

私は美しいアプローチが存在することを知っていますか、それともFacebookクライアントやLinkedInクライアントのようなモバイルモンスターがネットワークロジックの指数関数的に増大する複雑さにどのように対処するのですか?

私は問題に対する正確で正式な答えがないことを知っています。この質問の目的は、経験豊富なiOS開発者から最も興味深いアプローチを収集することです。提案された最善のアプローチは、承認されたものとしてマークされ、評判の賞金が授与されます。それは主に理論と研究の問題です。iOSのネットワーキングアプリケーションの基本的で抽象的な抽象的なアーキテクチャアプローチを理解したいと思います。経験豊富な開発者の方から詳しい説明をお願いします。


14
これは「買い物リスト」の質問ではありませんか?「何がベストか」というタイプの質問があまりにも非建設的な議論を引き起こしたため、私は地獄に投票して反対票を投じて終了しました。この買い物リストの質問が、賛成票と賞金に値する良い質問になる理由は何ですか?
Alvin Thompson

1
通常、ネットワークロジックはコントローラーに入り、モデルオブジェクトを変更し、デリゲートまたはオブザーバーに通知します。
2014年

1
非常に興味深い質問と回答。4年間のiOSコーディングの後、アプリにネットワークレイヤーを追加するための最も美しい方法を見つけようとしました。ネットワーク要求を管理する責任を持つべきクラスはどれですか?以下の答えは本当に適切です。ありがとう
darksider

@JoeBlowこれは真実ではありません。モバイルアプリ業界は、依然としてサーバーとクライアントの通信に非常に依存しています。
コード

回答:


327

I want to understand basic, abstract and correct architectural approach for networking applications in iOS:アプリケーションアーキテクチャを構築するための「最良の」または「最も正しい」アプローチはありません。それは非常に創造的な仕事。常に最も簡単で拡張可能なアーキテクチャを選択する必要があります。これは、プロジェクトで作業を開始する開発者やチームの他の開発者にとっては明らかですが、私は同意しますが、「良い点」と「悪い点」がある場合があります。 " 建築。

あなたは言った:collect the most interesting approaches from experienced iOS developers、私のアプローチが最も興味深く正しいとは思わないが、いくつかのプロジェクトで使用して満足している。それはあなたが上で言及したもののハイブリッドアプローチであり、また私自身の研究努力からの改善を伴います。いくつかのよく知られたパターンとイディオムを組み合わせたアプローチの構築の問題に興味があります。Fowlerのエンタープライズパターンの多くは、モバイルアプリケーションにうまく適用できると思います。これは、iOSアプリケーションアーキテクチャの作成に適用できる最も興味深いもののリストです(私の意見では):サービスレイヤー作業単位リモートファサードデータ転送オブジェクトゲートウェイレイヤースーパータイプ特殊なケースドメインモデル。モデルレイヤーは常に正しく設計し、永続性を忘れないでください(アプリのパフォーマンスを大幅に向上させることができます)。Core Dataこれに使用できます。ただし、これはORMやデータベースではなく、永続性を備えたオブジェクトグラフマネージャーであること忘れないでくださいCore Data。したがって、多くの場合Core Data、ニーズに対して重すぎる可能性があり、RealmCouchbase Liteなどの新しいソリューションを検討したり、生のSQLiteまたはLevelDBに基づいて独自の軽量オブジェクトマッピング/永続性レイヤーを構築したりできます。。また、について理解することをお勧めしますドメイン駆動設計CQRS

最初は、ネットワーク用に別のレイヤーを作成する必要があると思います。これ、太いコントローラーや重い、圧倒されたモデルは必要ないためです。私はそれらのfat model, skinny controllerことを信じていません。しかし、私skinny everythingアプローチを信じいます。なぜなら、決して太っているクラスはないからです。すべてのネットワーキングは一般にビジネスロジックとして抽象化できます。そのため、配置できる別のレイヤーが必要です。サービス層は私たちが必要とするものです:

It encapsulates the application's business logic,  controlling transactions 
and coordinating responses in the implementation of its operations.

私たちのMVC領域でService Layerは、ドメインモデルとコントローラーの間の仲介者のようなものです。このアプローチには、MVCSと呼ばれるかなり類似したバリエーションがあり、a Storeは実際には私たちのServiceレイヤーです。Storeモデルインスタンスを販売し、ネットワーキング、キャッシングなどを処理します。サービスレイヤーにすべてのネットワーキングとビジネスロジックを記述しないでください。これも悪いデザインと考えることができます。詳細については、AnemicおよびRichドメインモデルをご覧ください。一部のサービスメソッドとビジネスロジックはモデルで処理できるため、「動作が豊富な」(動作のある)モデルになります。

私は常に2つのライブラリーAFNetworking 2.0ReactiveCocoaを幅広く使用しています。これは、ネットワークやWebサービスとやり取りする、または複雑なUIロジックを含む最新のアプリケーションに必要なものだと思います。

建築

最初にAPIClientAFHTTPSessionManagerのサブクラスである一般クラスを作成します。これは、アプリケーション内のすべてのネットワーキングの主力です。すべてのサービスクラスが実際のRESTリクエストをアプリケーションに委任します。これには、特定のアプリケーションで必要なHTTPクライアントのすべてのカスタマイズが含まれています。SSLピン留め、エラー処理、NSError詳細な失敗の理由とすべてのAPIエラーおよび接続エラーの説明を含む簡単なオブジェクトの作成(このような場合、コントローラーは正しいメッセージを表示できます)ユーザー)、要求と応答のシリアライザー、httpヘッダー、その他のネットワーク関連のものを設定します。それから私は、論理的に、より正確に、サブサービスにすべてのAPIリクエストを分割またはmicroservicesUserSerivcesCommonServicesSecurityServicesFriendsServicesなど、彼らが実装するビジネスロジックに応じて。これらのマイクロサービスはそれぞれ別のクラスです。それらは一緒にを形成しService Layerます。これらのクラスには、各APIリクエストのメソッドが含まれ、ドメインモデルを処理し、常にRACSignal解析された応答モデルとともに、またはNSError呼び出し元にを返します。

複雑なモデルのシリアル化ロジックがある場合は、そのために別のレイヤーを作成します。データマッパーのようなものですが、より一般的な例としては、JSON / XML->モデルマッパーです。キャッシュがある場合:別のレイヤー/サービスとしても作成します(ビジネスロジックとキャッシュを混在させないでください)。どうして?なぜなら、正しいキャッシングレイヤーは、独自の問題によってかなり複雑になる可能性があるためです。人々は複雑なロジックを実装して、例えば検閲者に基づく予測によるモノイドキャッシングのような、有効で予測可能なキャッシングを実現します。Carlosと呼ばれるこの美しいライブラリーを読んで、理解を深めることができます。また、Core Dataはすべてのキャッシュの問題を実際に解決し、より少ないロジックで記述できることを忘れないでください。また、NSManagedObjectContextとサーバーリクエストモデルの間にロジックがある場合は、リポジトリデータを取得してエンティティモデルにマップするロジックを、モデルに作用するビジネスロジックから分離するパターン。したがって、Core Dataベースのアーキテクチャがある場合でも、Repositoryパターンを使用することをお勧めします。リポジトリ抽象もの、のようなNSFetchRequestNSEntityDescriptionNSPredicateなどのようなプレーンな方法へのgetput

サービスレイヤーでこれらすべてのアクションを実行した後、呼び出し側(ビューコントローラー)は、ReactiveCocoaプリミティブを使用して、信号操作、チェーン、マッピングなどの複雑な非同期処理を実行したり、サブスクライブして結果をビューに表示したりできます。 。私は注射依存性注入、これらすべてのサービスクラス、私の中にAPIClient対応するに特定のサービスコール翻訳する、GETPOSTPUTDELETE、などのRESTエンドポイントに要求を。この場合APIClient、すべてのコントローラーに暗黙的に渡されます。パラメーター化してこれを明示的にすることができます。APIClientます。サービスクラスをます。これは、別のカスタマイズを使用する場合に意味があります。APIClient特定のサービスクラスの場合。ただし、何らかの理由で余分なコピーが不要な場合、またはの特定のインスタンスを1つ(カスタマイズなしで)常に使用することが確実な場合APIClient-シングルトンにしてください。ただし、しないでください。サービスクラスをシングルトンとして作成しないでください。

次に、DIを使用して各ビューコントローラーが再び必要なサービスクラスを挿入し、適切なサービスメソッドを呼び出して、UIロジックで結果を構成します。依存性注入には、BloodMagicまたはより強力なフレームワークTyphoonを使用するのが好きです。シングルトン、ゴッドAPIManagerWhateverクラス、その他の間違ったものは決して使用しません。あなたがあなたのクラスを呼ぶならばWhateverManager、これはあなたがその目的を知らないということを示しており、それは悪いデザインの選択です。シングルトンはアンチパターンでもあり、ほとんどの場合(まれなものを除く)は間違ったソリューションです。シングルトンは、次の3つの基準がすべて満たされた場合にのみ検討する必要があります。

  1. 単一インスタンスの所有権を合理的に割り当てることはできません。
  2. 遅延初期化が望ましい。
  3. それ以外の場合、グローバルアクセスは提供されません。

私たちの場合、単一のインスタンスの所有権は問題ではありません。また、god managerをサービスに分割した後、グローバルアクセスは必要ありません。これは、1つまたは複数の専用コントローラーのみが特定のサービスをUserProfile必要UserServicesとするためです(コントローラーの必要性など)。 。

私たちは常にSOLIDのS原則を尊重し、懸念の分離を使用する必要があります。そのため、特に大規模なエンタープライズアプリケーションを開発する場合は、おかしいので、すべてのサービスメソッドとネットワーク呼び出しを1つのクラスに配置しないでください。そのため、依存性注入とサービスアプローチを検討する必要があります。私はこのアプローチをモダンでポストOOと見なしています。この場合、アプリケーションを2つの部分、つまり制御ロジック(コントローラーとイベント)とパラメーターに分割します。

1種類のパラメータは、通常の「データ」パラメータです。これが、関数、操作、変更、永続化などで渡します。エンティティ、集約、コレクション、ケースクラスです。もう1つの種類は、「サービス」パラメータです。これらは、ビジネスロジックをカプセル化し、外部システムとの通信を可能にし、データアクセスを提供するクラスです。

以下に、例による私のアーキテクチャの一般的なワークフローを示します。レッツは、私たちが持っていると仮定しFriendsViewController、ユーザの友人のリストを表示し、我々は友人から削除するオプションを持っています、。私のFriendsServicesクラスに次の名前のメソッドを作成します。

- (RACSignal *)removeFriend:(Friend * const)friend

どこFriendのモデル/ドメインオブジェクトがある(またはそれだけですることができUser、彼らは同様の属性を持っている場合、オブジェクト)。このメソッドの構文解析ボンネット下FriendNSDictionaryJSONパラメータのfriend_idnamesurnamefriend_request_idというように。この種のボイラープレートとモデルレイヤー(前後の解析、JSONでのネストされたオブジェクト階層の管理など)には、常にMantleライブラリを使用します。解析後、それを呼び出してAPIClient DELETE実際のREST要求戻るようにする方法ResponseRACSignal(呼び出し元にFriendsViewControllerユーザまたは何のために適切なメッセージを表示するための我々の場合には)。

アプリケーションが非常に大きい場合、ロジックをさらに明確に分離する必要があります。たとえば、ロジックを1つに混合またはモデル化することが常に良いとは限りません。私のアプローチを説明したとき、メソッドはレイヤーにあるべきだと言っていましたが、もっと知識を深めれば、それはに属していることに気づくでしょう。リポジトリとは何かを覚えておきましょう。Eric Evansは彼の本[DDD]でそれを正確に説明しています:RepositoryServiceremoveFriendServiceRepository

リポジトリは、特定のタイプのすべてのオブジェクトを概念セットとして表します。より複雑なクエリ機能を除いて、コレクションのように機能します。

したがって、a Repositoryは基本的に、コレクションスタイルのセマンティクス(追加、更新、削除)を使用してデータ/オブジェクトへのアクセスを提供するファサードです。:あなたが何かを持っている場合だからこそgetFriendsListgetUserGroupsremoveFriendあなたがそれを置くことができRepository、コレクションのような意味がかなりここにクリアされているので、。そして次のようなコード:

- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;

基本的なCRUD操作を超えて2つのドメインオブジェクト(FriendおよびRequest)を接続するため、これは間違いなくビジネスロジックServiceです。そのため、これをレイヤーに配置する必要があります。また、気づきたいのですが、不要な抽象化を作成しないでください。これらすべてのアプローチを賢く使用してください。あなたは抽象化でアプリケーションを圧倒するかどうので、これがします高めるその偶然の複雑さを、そして複雑さは、多くの問題を引き起こし、何よりも、ソフトウェアシステムに

「古い」Objective-Cの例について説明しますが、このアプローチは、より便利な機能と機能的な糖を備えているため、Swift言語に非常に簡単に適合させることができます。このライブラリMoyaを使用することを強くお勧めします。これにより、よりエレガントなAPIClientレイヤーを作成できます(覚えている限り、私たちの主力製品です)。これで、APIClientプロバイダーは、プロトコルに準拠し、構造化パターンマッチングを活用する拡張機能を持つ値型(列挙型)になります。Swift列挙型+パターンマッチングにより、古典的な関数型プログラミングのように代数的データ型を作成できます。当社のマイクロサービスはAPIClient、通常のObjective-Cアプローチと同様に、この改善されたプロバイダーを使用します。モデルレイヤーの代わりにObjectMapperライブラリMantleを使用できますまたは、よりエレガントで機能的なArgoライブラリを使用したい。

それで、どのようなアプリケーションにも適応できる、私の一般的なアーキテクチャアプローチについて説明しました。もちろん、さらに多くの改善点があります。関数型プログラミングを学ぶことをお勧めします。関数型プログラミングは多くのメリットを得ることができますが、あまり多くのことをしないでください。過剰な共有されたグローバルな変更可能な状態を排除し、不変のドメインモデルを作成するか、外部の副作用のない純粋な関数を作成することは、一般的には良い習慣であり、新しいSwift言語はこれを奨励します。ただし、他の開発者があなたのコードを読んでサポートし、イライラしたり怖いことがあるので、コードに重い純粋な機能パターン、カテゴリ理論のアプローチをオーバーロードすることは悪い考えであることを常に覚えておいてください。prismatic profunctorsそして、あなたの不変のモデルのようなもの。同じことReactiveCocoaRACify、コードを多用しすぎないでください。特に初心者の場合、コードが非常に速く読めなくなる可能性があるためです。目標とロジックを本当に簡素化できる場合に使用します。

だから、read a lot, mix, experiment, and try to pick up the best from different architectural approaches。それは私があなたに与えることができる最高のアドバイスです。


また、面白くてしっかりしたアプローチ。ありがとう。
MainstreamDeveloper00 14年

1
@darksider私はすでに私の答えに書いてきたように:「`シングルトンはアンチパターンであり、ほとんどの場合、(まれなものを除いて)間違ったソリューションであるので、私は、シングルトン、神APIManagerWhateverクラスまたは他の間違ったものを使用することはありません。". I don't like singletons. I have an opinion that if you decided to use singletons in your project you should have at least three criteria why you do this (I edited my answer). So I inject them (lazy of course and not each time, but 一度`)すべてのコントローラーで
Oleksandr Karaberov

14
こんにちは@alexander。GitHubにサンプルプロジェクトはありますか?非常に興味深いアプローチについて説明します。ありがとう。しかし、私はObjective-C開発の初心者です。そして、私にとっていくつかの側面を理解することは困難です。たぶん、GitHubにいくつかのテストプロジェクトをアップロードしてリンクを提供できますか?
デニス

1
@AlexanderKaraberov様こんにちは、あなたが与えたストアの説明について少し混乱しています。5つのモデルがあり、それぞれに2つのクラスがあり、1つはオブジェクトのネットワーキングと他のキャッシングを維持しているとします。ここで、ネットワークとキャッシュクラスの関数を呼び出すモデルごとに個別のStoreクラス、または各モデルのすべての機能を備えた単一のStoreクラスを用意し、コントローラーが常にデータの単一ファイルにアクセスするようにします。
隕石

1
@icodebusterこのデモプロジェクトは、ここで概説されている概念の多くを理解するのに役立ちました:github.com/darthpelo/NetworkLayerExample

31

この質問の目的に応じて、アーキテクチャアプローチについて説明します。

アーキテクチャアプローチ

私たちの一般的なiOSアプリケーションのアーキテクチャは、次のパターンに基づいています。サービスレイヤーMVVMUIデータバインディング依存性注入。および関数型リアクティブプログラミングパラダイム。

一般的な消費者向けアプリケーションを次の論理層にスライスできます。

  • アセンブリ
  • 型番
  • サービス
  • ストレージ
  • マネージャー
  • コーディネーター
  • UI
  • インフラ

アセンブリレイヤーは、アプリケーションのブートストラップポイントです。Dependency Injectionコンテナと、アプリケーションのオブジェクトとその依存関係の宣言が含まれています。このレイヤーには、アプリケーションの構成(URL、サードパーティのサービスキーなど)も含まれる場合があります。この目的のために、Typhoonライブラリを使用します。

モデルレイヤーには、ドメインモデルクラス、検証、マッピングが含まれます。モデルのマッピングにはMantleライブラリを使用します。これは、JSONフォーマットとNSManagedObjectモデルへのシリアル化/非シリアル化をサポートしています。モデルの検証とフォーム表現には、FXFormsおよびFXModelValidationライブラリを使用します。

サービス層は、ドメインモデルで表されるデータを送受信するために外部システムとのやり取りに使用するサービスを宣言します。したがって、通常、サーバーAPI(エンティティごと)、メッセージングサービス(PubNubなど)、ストレージサービス(Amazon S3など)と通信するためのサービスがあります。基本的に、サービスはSDK(PubNub SDKなど)によって提供されるオブジェクトをラップするか、独自の通信を実装します論理。一般的なネットワーキングには、AFNetworkingライブラリを使用します。

ストレージレイヤーの目的は、デバイス上のローカルデータストレージを整理することです。これにはCore DataまたはRealmを使用します(どちらにも長所と短所があり、使用するものの決定は具体的な仕様に基づいています)。コアデータのセットアップにはMDMCoreDataを使用しますライブラリとのクラス-ストレージ-(サービスと同様)を使用して、すべてのエンティティにローカルストレージへのアクセスを提供します。Realmの場合は、同様のストレージを使用してローカルストレージにアクセスします。

Managersレイヤーは、抽象化/ラッパーが存在する場所です。

マネージャの役割は次のようになります。

  • Credentials Managerとそのさまざまな実装(キーチェーン、NSDefaultsなど)
  • 現在のユーザーセッションを維持および提供する方法を知っている現在のセッションマネージャー
  • メディアデバイスへのアクセスを提供するキャプチャパイプライン(ビデオの録画、オーディオ、写真の撮影)
  • Bluetoothサービスと周辺機器へのアクセスを提供するBLEマネージャー
  • 地理位置情報マネージャー
  • ...

したがって、マネージャーの役​​割には、アプリケーションの動作に必要な特定の側面または関心事のロジックを実装する任意のオブジェクトを含めることができます。

シングルトンは避けようとしていますが、このレイヤーは必要な場合にシングルトンが住む場所です。

コーディネーターレイヤーは、他のレイヤー(サービス、ストレージ、モデル)のオブジェクトに依存するオブジェクトを提供して、ロジックを特定のモジュール(機能、画面、ユーザーストーリー、ユーザーエクスペリエンス)に必要な一連の作業に統合します。通常、非同期操作をチェーンし、成功と失敗のケースに対応する方法を知っています。例として、メッセージング機能と対応するMessagingCoordinatorオブジェクトを想像できます。メッセージ送信操作の処理は次のようになります。

  1. メッセージの検証(モデルレイヤー)
  2. メッセージをローカルに保存(メッセージストレージ)
  3. メッセージの添付ファイルをアップロードする(Amazon S3サービス)
  4. メッセージのステータスと添付ファイルのURLを更新し、ローカルにメッセージを保存(メッセージストレージ)
  5. メッセージをJSON形式にシリアル化(モデルレイヤー)
  6. PubNub(PubNubサービス)にメッセージを発行する
  7. メッセージのステータスと属性を更新してローカルに保存(メッセージストレージ)

上記の各ステップで、エラーが対応して処理されます。

UIレイヤーは次のサブレイヤーで構成されています。

  1. ViewModels
  2. ViewControllers
  3. ビュー

Massive View Controllersを回避するために、MVVMパターンを使用し、ViewModelsでのUIプレゼンテーションに必要なロジックを実装します。ViewModelは通常、依存関係としてコーディネーターとマネージャーを持っています。ViewControllersといくつかの種類のビュー(テーブルビューセルなど)が使用するViewModels。ViewControllersとViewModelsの間の接着剤は、データバインディングとコマンドパターンです。その接着剤を使用可能にするために、ReactiveCocoaを使用しますライブラリます。

また、ReactiveCocoaとそのRACSignal概念をインターフェイスとして使用し、すべてのコーディネーター、サービス、ストレージメソッドの値型を返します。これにより、操作をチェーンして、並列または連続で実行したり、ReactiveCocoaが提供する他の多くの便利な機能を使用したりできます。

宣言的な方法でUI動作を実装しようとします。データバインディングと自動レイアウトは、この目標を達成するのに大いに役立ちます。

インフラストラクチャ層には、アプリケーションの作業に必要なすべてのヘルパー、拡張機能、ユーティリティが含まれています。


このアプローチは、私たちと、私たちが通常作成する種類のアプリに適しています。ただし、これは具体的なチームの目的に合わせて変更または変更する必要がある主観的なアプローチであることを理解しておく必要があります。

これがあなたに役立つことを願っています!

また、iOS開発プロセスについての詳細は、このブログの記事「サービスとしてのiOS開発」をご覧ください。


数か月前にこのアーキテクチャが好きになり始めました。共有してくれたAlexに感謝します。近いうちにRxSwiftで試してみたい!
インガハム

18

すべてのiOSアプリが異なるため、ここで検討するアプローチはさまざまだと思いますが、通常は次のようにし
ます。すべてのAPIリクエスト(通常はAPICommunicator)を処理する中央マネージャー(シングルトン)クラスを作成し、すべてのインスタンスメソッドはAPI呼び出しです。そして、1つの中心的な(非公開)メソッドがあります。

-(RACSignal *)sendGetToServerToSubPath:(NSString *)path withParameters:(NSDictionary *)params;

記録のために、私は2つの主要なライブラリ/フレームワーク、ReactiveCocoaとAFNetworkingを使用しています。ReactiveCocoaは非同期ネットワーキング応答を完全に処理します(sendNext:、sendError:など)。
このメソッドは、APIを呼び出して結果を取得し、RACを介して(AFNetworkingが返すNSArrayのような)「未加工」形式で送信します。
次に、getStuffList:上記のメソッドを呼び出したようなメソッドがそのシグナルをサブスクライブし、生データをオブジェクト(Motisのようなもの)に解析して、オブジェクトを1つずつ呼び出し元に送信します(getStuffList:同様のメソッドも、コントローラーがサブスクライブできるシグナルを返します)。
サブスクライブしたコントローラーは、subscribeNext:のブロックによってオブジェクトを受け取り、それらを処理します。

私はさまざまなアプリでさまざまな方法を試しましたが、これはすべての中で最もうまくいきましたので、最近いくつかのアプリでこれを使用しています。これは、小規模プロジェクトと大規模プロジェクトの両方に適合し、何かを変更する必要がある場合に拡張および維持するのが簡単です。
これがお役に立てば幸いです。私のアプローチについて他の人の意見を聞きたいです。


2
答えてくれてありがとう+1。良いアプローチ。私は質問を残します。他の開発者からの別のアプローチがあるかもしれません。
MainstreamDeveloper00 14年

1
このアプローチのバリエーションが好きです。APIとの通信メカニズムを処理する中央APIマネージャーを使用します。ただし、モデルオブジェクトで公開されているすべての機能を作成しようとしています。モデルのようなメソッドを提供します+ (void)getAllUsersWithSuccess:(void(^)(NSArray*))success failure:(void(^)(NSError*))failure;し、- (void)postWithSuccess:(void(^)(instancetype))success failure:(void(^)(NSError*))failure;どの必要な準備を行い、その後、APIマネージャに介してコール。
jsadler 2014年

1
このアプローチは簡単ですが、APIの数が増えるにつれて、シングルトンAPIマネージャーを維持することが難しくなります。また、新しく追加されたすべてのAPIは、このAPIがどのモジュールに属しているかに関係なく、マネージャーに関連付けられます。github.com/kevin0571/STNetTaskQueueを使用して、APIリクエストを管理してみてください。
ケビン

なぜ私のソリューションから可能な限り遠く、はるかに複雑なライブラリを宣伝しているのかという点を除けば、前述のように、小規模および大規模なプロジェクトの両方でこのアプローチを試してみましたが、私はそれを正確に使用しています私がこの答えを書いてからずっと同じです。巧妙な命名規則があれば、維持するのは難しくありません。
Rickye

8

私の状況では、通常、ResKitライブラリを使用してネットワーク層を設定しています。使いやすい構文解析を提供します。これにより、さまざまな応答やものに対するマッピングをセットアップする手間が軽減されます。

マッピングを自動的にセットアップするコードを追加するだけです。私はモデルの基本クラスを定義します(いくつかのメソッドが実装されているかどうかをチェックするコードが多く、モデル自体のコードが少ないため、プロトコルではありません)。

MappableEntry.h

@interface MappableEntity : NSObject

+ (NSArray*)pathPatterns;
+ (NSArray*)keyPathes;
+ (NSArray*)fieldsArrayForMapping;
+ (NSDictionary*)fieldsDictionaryForMapping;
+ (NSArray*)relationships;

@end

MappableEntry.m

@implementation MappableEntity

+(NSArray*)pathPatterns {
    return @[];
}

+(NSArray*)keyPathes {
    return nil;
}

+(NSArray*)fieldsArrayForMapping {
    return @[];
}

+(NSDictionary*)fieldsDictionaryForMapping {
    return @{};
}

+(NSArray*)relationships {
    return @[];
}

@end

リレーションシップは、ネストされたオブジェクトを表すオブジェクトです。

RelationshipObject.h

@interface RelationshipObject : NSObject

@property (nonatomic,copy) NSString* source;
@property (nonatomic,copy) NSString* destination;
@property (nonatomic) Class mappingClass;

+(RelationshipObject*)relationshipWithKey:(NSString*)key andMappingClass:(Class)mappingClass;
+(RelationshipObject*)relationshipWithSource:(NSString*)source destination:(NSString*)destination andMappingClass:(Class)mappingClass;

@end

RelationshipObject.m

@implementation RelationshipObject

+(RelationshipObject*)relationshipWithKey:(NSString*)key andMappingClass:(Class)mappingClass {
    RelationshipObject* object = [[RelationshipObject alloc] init];
    object.source = key;
    object.destination = key;
    object.mappingClass = mappingClass;
    return object;
}

+(RelationshipObject*)relationshipWithSource:(NSString*)source destination:(NSString*)destination andMappingClass:(Class)mappingClass {
    RelationshipObject* object = [[RelationshipObject alloc] init];
    object.source = source;
    object.destination = destination;
    object.mappingClass = mappingClass;
    return object;
}

@end

次に、RestKitのマッピングを次のように設定します。

ObjectMappingInitializer.h

@interface ObjectMappingInitializer : NSObject

+(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager;

@end

ObjectMappingInitializer.m

@interface ObjectMappingInitializer (Private)

+ (NSArray*)mappableClasses;

@end

@implementation ObjectMappingInitializer

+(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager {

    NSMutableDictionary *mappingObjects = [NSMutableDictionary dictionary];

    // Creating mappings for classes
    for (Class mappableClass in [self mappableClasses]) {
        RKObjectMapping *newMapping = [RKObjectMapping mappingForClass:mappableClass];
        [newMapping addAttributeMappingsFromArray:[mappableClass fieldsArrayForMapping]];
        [newMapping addAttributeMappingsFromDictionary:[mappableClass fieldsDictionaryForMapping]];
        [mappingObjects setObject:newMapping forKey:[mappableClass description]];
    }

    // Creating relations for mappings
    for (Class mappableClass in [self mappableClasses]) {
        RKObjectMapping *mapping = [mappingObjects objectForKey:[mappableClass description]];
        for (RelationshipObject *relation in [mappableClass relationships]) {
            [mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:relation.source toKeyPath:relation.destination withMapping:[mappingObjects objectForKey:[relation.mappingClass description]]]];
        }
    }

    // Creating response descriptors with mappings
    for (Class mappableClass in [self mappableClasses]) {
        for (NSString* pathPattern in [mappableClass pathPatterns]) {
            if ([mappableClass keyPathes]) {
                for (NSString* keyPath in [mappableClass keyPathes]) {
                    [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[mappingObjects objectForKey:[mappableClass description]] method:RKRequestMethodAny pathPattern:pathPattern keyPath:keyPath statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
                }
            } else {
                [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[mappingObjects objectForKey:[mappableClass description]] method:RKRequestMethodAny pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
            }
        }
    }

    // Error Mapping
    RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[Error class]];
    [errorMapping addAttributeMappingsFromArray:[Error fieldsArrayForMapping]];
    for (NSString *pathPattern in Error.pathPatterns) {
        [[RKObjectManager sharedManager] addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:errorMapping method:RKRequestMethodAny pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)]];
    }
}

@end

@implementation ObjectMappingInitializer (Private)

+ (NSArray*)mappableClasses {
    return @[
        [FruiosPaginationResults class],
        [FruioItem class],
        [Pagination class],
        [ContactInfo class],
        [Credentials class],
        [User class]
    ];
}

@end

MappableEntry実装の例:

User.h

@interface User : MappableEntity

@property (nonatomic) long userId;
@property (nonatomic, copy) NSString *username;
@property (nonatomic, copy) NSString *email;
@property (nonatomic, copy) NSString *password;
@property (nonatomic, copy) NSString *token;

- (instancetype)initWithUsername:(NSString*)username email:(NSString*)email password:(NSString*)password;

- (NSDictionary*)registrationData;

@end

User.m

@implementation User

- (instancetype)initWithUsername:(NSString*)username email:(NSString*)email password:(NSString*)password {
    if (self = [super init]) {
        self.username = username;
        self.email = email;
        self.password = password;
    }
    return self;
}

- (NSDictionary*)registrationData {
    return @{
        @"username": self.username,
        @"email": self.email,
        @"password": self.password
    };
}

+ (NSArray*)pathPatterns {
    return @[
        [NSString stringWithFormat:@"/api/%@/users/register", APIVersionString],
        [NSString stringWithFormat:@"/api/%@/users/login", APIVersionString]
    ];
}

+ (NSArray*)fieldsArrayForMapping {
    return @[ @"username", @"email", @"password", @"token" ];
}

+ (NSDictionary*)fieldsDictionaryForMapping {
    return @{ @"id": @"userId" };
}

@end

リクエストのラッピングについて:

すべてのAPIRequestクラスの行の長さを減らすために、ブロック定義を含むヘッダーファイルがあります。

APICallbacks.h

typedef void(^SuccessCallback)();
typedef void(^SuccessCallbackWithObjects)(NSArray *objects);
typedef void(^ErrorCallback)(NSError *error);
typedef void(^ProgressBlock)(float progress);

そして私が使用している私のAPIRequestクラスの例:

LoginAPI.h

@interface LoginAPI : NSObject

- (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError;

@end

LoginAPI.m

@implementation LoginAPI

- (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError {
    [[RKObjectManager sharedManager] postObject:nil path:[NSString stringWithFormat:@"/api/%@/users/login", APIVersionString] parameters:[credentials credentialsData] success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        onSuccess(mappingResult.array);
    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
        onError(error);
    }];
}

@end

そして、コードで行う必要があるのは、APIオブジェクトを初期化し、必要なときに呼び出すだけです。

SomeViewController.m

@implementation SomeViewController {
    LoginAPI *_loginAPI;
    // ...
}

- (void)viewDidLoad {
    [super viewDidLoad];

    _loginAPI = [[LoginAPI alloc] init];
    // ...
}

// ...

- (IBAction)signIn:(id)sender {
    [_loginAPI loginWithCredentials:_credentials onSuccess:^(NSArray *objects) {
        // Success Block
    } onError:^(NSError *error) {
        // Error Block
    }];
}

// ...

@end

私のコードは完璧ではありませんが、一度設定して別のプロジェクトに使用するのは簡単です。それが誰にとっても興味深い場合は、時間をかけてGitHubとCocoaPodsのどこかでユニバーサルソリューションを作成できます。


7

私の考えでは、すべてのソフトウェアアーキテクチャはニーズによって推進されます。これが学習または個人的な目的である場合は、主要な目標を決定し、それがアーキテクチャを推進するようにします。これが採用の仕事であれば、ビジネスニーズが最も重要です。秘訣は、光沢のあるものが本当のニーズからあなたをそらさないようにすることです。これを行うのは難しいと思います。このビジネスには常に新しい光沢のあるものが登場しており、それらの多くは役に立たないものですが、常にそれを前もって伝えることはできません。必要に焦点を合わせ、できれば悪い選択を放棄することをいとわない。

たとえば、最近、ローカルビジネス向けの写真共有アプリの簡単なプロトタイプを作成しました。ビジネスニーズは迅速で不潔なことをすることだったので、アーキテクチャはカメラをポップアップするiOSコードと、画像をS3ストアにアップロードしてSimpleDBドメインに書き込んだ送信ボタンに接続されたネットワークコードになりました。コードは簡単でコストは最小限で、クライアントはREST呼び出しを使用してWeb経由でアクセスできるスケーラブルな写真コレクションを持っています。安くてばかげている、アプリには多くの欠陥があり、UIが時々ロックされますが、プロトタイプに多くのことをするのは無駄であり、スタッフにデプロイして、パフォーマンスやスケーラビリティなしに数千のテスト画像を簡単に生成できます懸念。気の利いたアーキテクチャですが、ニーズとコストに完全に適合します。

別のプロジェクトには、ネットワークが利用可能なときにバックグラウンドで会社のシステムと同期するローカルの安全なデータベースの実装が含まれていました。RestKitを使用するバックグラウンドシンクロナイザーを作成しました。必要なものがすべてそろっていたためです。しかし、RestKitが特異なJSONを処理するために独自のJSONからCoreDataへの変換を作成することですべてをより迅速に行うことができるほど多くのカスタムコードを記述する必要がありました。しかし、顧客はこのアプリを社内に持ち込みたいと思っていて、RestKitは他のプラットフォームで使用していたフレームワークに似ていると感じました。それが良い決定であるかどうか私は待っている。

繰り返しますが、私にとっての問題は、ニーズに焦点を当て、それによってアーキテクチャを決定させることです。私はサードパーティのパッケージを使用しないように地獄のように努力します。彼らがアプリをしばらくの間フィールドに置いた後にのみ表示されるコストをもたらすからです。クラス階層はほとんど効果がないので、作成しないようにしています。完全に適合しないパッケージを採用する代わりに、妥当な期間内に何かを書くことができれば、それを行います。私のコードはデバッグ用に適切に構造化され、適切にコメントされていますが、サードパーティのパッケージはめったにそうではありません。そうは言っても、AFネットワーキングは無視するにはあまりにも有用であり、適切に構造化され、よくコメントされ、維持されていると私は思います。RestKitは多くの一般的なケースをカバーしますが、私がそれを使用するとき、私は戦いに巻き込まれたように感じます、そして、私が遭遇するほとんどのデータソースは、カスタムコードで最もよく処理される癖と問題でいっぱいです。最後のいくつかのアプリでは、組み込みのJSONコンバーターを使用して、いくつかのユーティリティメソッドを記述しています。

私が常に使用するパターンの1つは、ネットワークコールをメインスレッドから切り離すことです。最後に行った4〜5個のアプリは、dispatch_source_createを使用してバックグラウンドタイマータスクを設定します。このタスクは、頻繁に起動し、必要に応じてネットワークタスクを実行します。スレッドセーフティの作業をいくつか行い、UI変更コードがメインスレッドに送信されるようにする必要があります。また、ユーザーの負担や遅延を感じさせないように、オンボーディング/初期化を行うのにも役立ちます。これまでのところ、これはかなりうまく機能しています。これらの事柄を調べることをお勧めします。

最後に、私たちがより働き、OSが進化するにつれて、より良いソリューションを開発する傾向があると思います。他の人が必須であると主張するパターンやデザインに従わなければならないという私の信念を乗り越えるのに何年もかかりました。私がそれが地元の宗教の一部である状況で働いている場合、私は部門の最高のエンジニアリングプラクティスを意味し、私は手紙の慣習に従います、それは彼らが私に支払っているものです。しかし、古いデザインやパターンに従うことが最適なソリューションであることはめったにありません。私は常にビジネスニーズのプリズムを通してソリューションを検討し、それに一致するようにアーキテクチャを構築して、物事をできるだけシンプルに保つようにしています。そこに十分ではないように感じても、すべてが正しく機能する場合、私は正しい軌道に乗っています。


4

私はここから得たアプローチを使用します:https : //github.com/Constantine-Fry/Foursquare-API-v2。私はそのライブラリをSwiftで書き直したので、コードのこれらの部分からアーキテクチャアプローチを見ることができます。

typealias OpertaionCallback = (success: Bool, result: AnyObject?) -> ()

class Foursquare{
    var authorizationCallback: OperationCallback?
    var operationQueue: NSOperationQueue
    var callbackQueue: dispatch_queue_t?

    init(){
        operationQueue = NSOperationQueue()
        operationQueue.maxConcurrentOperationCount = 7;
        callbackQueue = dispatch_get_main_queue();
    }

    func checkIn(venueID: String, shout: String, callback: OperationCallback) -> NSOperation {
        let parameters: Dictionary <String, String> = [
            "venueId":venueID,
            "shout":shout,
            "broadcast":"public"]
        return self.sendRequest("checkins/add", parameters: parameters, httpMethod: "POST", callback: callback)
    }

    func sendRequest(path: String, parameters: Dictionary <String, String>, httpMethod: String, callback:OperationCallback) -> NSOperation{
        let url = self.constructURL(path, parameters: parameters)
        var request = NSMutableURLRequest(URL: url)
        request.HTTPMethod = httpMethod
        let operation = Operation(request: request, callbackBlock: callback, callbackQueue: self.callbackQueue!)
        self.operationQueue.addOperation(operation)
        return operation
    }

    func constructURL(path: String, parameters: Dictionary <String, String>) -> NSURL {
        var parametersString = kFSBaseURL+path
        var firstItem = true
        for key in parameters.keys {
            let string = parameters[key]
            let mark = (firstItem ? "?" : "&")
            parametersString += "\(mark)\(key)=\(string)"
            firstItem = false
        }
    return NSURL(string: parametersString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding))
    }
}

class Operation: NSOperation {
    var callbackBlock: OpertaionCallback
    var request: NSURLRequest
    var callbackQueue: dispatch_queue_t

    init(request: NSURLRequest, callbackBlock: OpertaionCallback, callbackQueue: dispatch_queue_t) {
        self.request = request
        self.callbackBlock = callbackBlock
        self.callbackQueue = callbackQueue
    }

    override func main() {
        var error: NSError?
        var result: AnyObject?
        var response: NSURLResponse?

        var recievedData: NSData? = NSURLConnection.sendSynchronousRequest(self.request, returningResponse: &response, error: &error)

        if self.cancelled {return}

        if recievedData{
            result = NSJSONSerialization.JSONObjectWithData(recievedData, options: nil, error: &error)
            if result != nil {
                if result!.isKindOfClass(NSClassFromString("NSError")){
                    error = result as? NSError
            }
        }

        if self.cancelled {return}

        dispatch_async(self.callbackQueue, {
            if (error) {
                self.callbackBlock(success: false, result: error!);
            } else {
                self.callbackBlock(success: true, result: result!);
            }
            })
    }

    override var concurrent:Bool {get {return true}}
}

基本的に、NSURLRequestを作成し、JSON応答を解析して、結果を含むコールバックブロックをキューに追加するNSOperationサブクラスがあります。メインAPIクラスはNSURLRequestを構築し、そのNSOperationサブクラスを初期化して、それをキューに追加します。


3

状況に応じていくつかのアプローチを使用します。ほとんどの場合、AFNetworkingは、ヘッダーを設定し、マルチパートデータをアップロードし、GET、POST、PUT、DELETEを使用できるという点で最も単純で最も堅牢なアプローチです。UIKitには、たとえば、 URL。呼び出しが多い複雑なアプリでは、これを、次のような便利なメソッドに抽象化することがあります。

-(void)makeRequestToUrl:(NSURL *)url withParameters:(NSDictionary *)parameters success:(void (^)(id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;

AFNetworkingがすでに別のコードベースにある可能性があるため、フレームワークまたは他のライブラリコンポーネントを作成している場合など、AFNetworkingが適切でないいくつかの状況があります。この状況では、単一の呼び出しを行う場合はインラインでNSMutableURLRequestを使用するか、要求/応答クラスに抽象化します。


私にとってこれが最善で最も明確な答えです、乾杯。「それはとても簡単です」。@martin、個人的には、常にNSMutableURLRequestを使用します。AFNetworkingを使用する本当の理由はありますか?
Fattie、2014年

AFNetworkingは本当に便利です。私にとって成功と失敗のブロックは、コードの管理を容易にするため、価値があると言えます。時々それは完全なやり過ぎだと私は同意します。
マーティン

そのおかげで、ブロックの見事なポイント。これの具体的な性質はすべてSwiftで変わると思います。
Fattie、2014年

2

アプリケーションを設計するときは、シングルトンを避けます。彼らは多くの人にとって典型的な行き先ですが、他の場所でよりエレガントな解決策を見つけることができると思います。通常、私が行うことは、CoreDataでエンティティをビルドし、RESTコードをNSManagedObjectカテゴリに配置することです。たとえば、新しいユーザーを作成してPOSTしたい場合は、次のようにします。

User* newUser = [User createInManagedObjectContext:managedObjectContext];
[newUser postOnSuccess:^(...) { ... } onFailure:^(...) { ... }];

オブジェクトマッピングにはRESTKitを使用し、起動時に初期化します。すべての通話をシングルトン経由でルーティングするのは時間の無駄であり、不要な定型文をたくさん追加します。

NSManagedObject + Extensions.m:

+ (instancetype)createInContext:(NSManagedObjectContext*)context
{
    NSAssert(context.persistentStoreCoordinator.managedObjectModel.entitiesByName[[self entityName]] != nil, @"Entity with name %@ not found in model. Is your class name the same as your entity name?", [self entityName]);
    return [NSEntityDescription insertNewObjectForEntityForName:[self entityName] inManagedObjectContext:context];
}

NSManagedObject + Networking.mで:

- (void)getOnSuccess:(RESTSuccess)onSuccess onFailure:(RESTFailure)onFailure blockInput:(BOOL)blockInput
{
    [[RKObjectManager sharedManager] getObject:self path:nil parameters:nil success:onSuccess failure:onFailure];
    [self handleInputBlocking:blockInput];
}

カテゴリを通じて共通の基本クラスの機能を拡張できるのに、なぜ追加のヘルパークラスを追加するのですか?

私のソリューションの詳細情報に興味がある場合は、お知らせください。共有させていただきます。


3
ブログ投稿でこのアプローチについてさらに詳しく読むことに間違いなく興味があります。
Danyal Aytekin


0

純粋にクラスの設計の観点からすると、通常は次のようになります。

  • あなたのビューコントローラは、一の以上のビューを制御します
  • データモデルクラス -実際に扱うエンティティの数とそれらの関係に依存します。

    たとえば、4つの異なる表現(リスト、チャート、グラフなど)で表示されるアイテムの配列がある場合、アイテムのリスト用に1つのデータモデルクラスがあり、アイテム用にもう1つあります。アイテムクラスリストは、4つのビューコントローラー(タブバーコントローラーまたはナビゲーションコントローラーのすべての子)によって共有されます。

    データモデルクラスは、データの表示だけでなく、JSON / XML / CSV(またはその他の)エクスポートメソッドを介して独自のシリアル化形式を公開できるシリアル化にも役立ちます。

  • REST APIエンドポイントに直接マップするAPIリクエストビルダークラスも必要であることを理解することが重要です。ユーザーをログインさせるAPIがあるとしましょう。LoginAPIビルダークラスがログインAPIのPOST JSONペイロードを作成します。別の例では、カタログアイテムのリストAPIのAPIリクエストビルダークラスは、対応するAPIのGETクエリ文字列を作成し、REST GETクエリを起動します。

    これらのAPIリクエストビルダークラスは通常、ビューコントローラーからデータを受け取り、UIの更新やその他の操作のために同じデータをビューコントローラーに返します。次に、View Controllerは、Data Modelオブジェクトをそのデータで更新する方法を決定します。

  • 最後に、RESTクライアントの中心 - アプリが行うあらゆる種類のAPIリクエストに気づかないAPIデータフェッチャークラス。このクラスはシングルトンである可能性が高くなりますが、他の人が指摘したように、シングルトンである必要はありません。

    リンクは単なる典型的な実装であり、セッションやCookieなどのシナリオを考慮に入れていないことに注意してください。ただし、サードパーティのフレームワークを使用しなくても十分です。


0

この質問にはすでに多くの優れた広範な回答がありますが、他の誰も持っていないので、私はそれを言及しなければならないように感じます。

SwiftのAlamofire。https://github.com/Alamofire/Alamofire

AFNetworkingと同じ人が作成しましたが、Swiftを念頭に置いてより直接的に設計されています。


0

今のところ、中規模プロジェクトではMVVMアーキテクチャを使用し、大規模プロジェクトではVIPERアーキテクチャ使用 して、

  • プロトコル指向プログラミング
  • ソフトウェア設計パターン
  • 販売原則
  • 一般的なプログラミング
  • 繰り返さないでください(DRY)

iOSネットワーキングアプリケーション(RESTクライアント)を構築するためのアーキテクチャアプローチ

クリーンで読みやすいコードを分離することで、重複を回避できます。

import Foundation
enum DataResponseError: Error {
    case network
    case decoding

    var reason: String {
        switch self {
        case .network:
            return "An error occurred while fetching data"
        case .decoding:
            return "An error occurred while decoding data"
        }
    }
}

extension HTTPURLResponse {
    var hasSuccessStatusCode: Bool {
        return 200...299 ~= statusCode
    }
}

enum Result<T, U: Error> {
    case success(T)
    case failure(U)
}

依存関係の逆転

 protocol NHDataProvider {
        func fetchRemote<Model: Codable>(_ val: Model.Type, url: URL, completion: @escaping (Result<Codable, DataResponseError>) -> Void)
    }

主な担当:

  final class NHClientHTTPNetworking : NHDataProvider {

        let session: URLSession

        init(session: URLSession = URLSession.shared) {
            self.session = session
        }

        func fetchRemote<Model: Codable>(_ val: Model.Type, url: URL,
                             completion: @escaping (Result<Codable, DataResponseError>) -> Void) {
            let urlRequest = URLRequest(url: url)
            session.dataTask(with: urlRequest, completionHandler: { data, response, error in
                guard
                    let httpResponse = response as? HTTPURLResponse,
                    httpResponse.hasSuccessStatusCode,
                    let data = data
                    else {
                        completion(Result.failure(DataResponseError.network))
                        return
                }
                guard let decodedResponse = try? JSONDecoder().decode(Model.self, from: data) else {
                    completion(Result.failure(DataResponseError.decoding))
                    return
                }
                completion(Result.success(decodedResponse))
            }).resume()
        }
    }

ここに、GitHub MVVMアーキテクチャとREST API Swiftプロジェクトがあります。


0

モバイルソフトウェアエンジニアリングで最も広く使用されているのは、Clean Architecture + MVVMおよびReduxパターンです。

クリーンアーキテクチャ+ MVVMは、ドメイン、プレゼンテーション、データレイヤーの3つのレイヤーで構成されています。プレゼンテーション層とデータリポジトリ層がドメイン層に依存する場合:

Presentation Layer -> Domain Layer <- Data Repositories Layer

プレゼンテーションレイヤーは、ViewModelとViews(MVVM)で構成されています。

Presentation Layer (MVVM) = ViewModels + Views
Domain Layer = Entities + Use Cases + Repositories Interfaces
Data Repositories Layer = Repositories Implementations + API (Network) + Persistence DB

この記事では、Clean Architecture + MVVM https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3の詳細な説明があり ます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.