Gofor iOSのGopherフィードリンゴまたは効果的なバックエンド







約束どおり、バックエンドをGoに移行し、クライアントのビジネスロジックの量を3分の1以上削減する方法について話しました。







対象者 :小規模企業、囲and、モバイル開発者、およびこのトピックに興味を持っている、または興味を持っているすべての人。

何について :Goへの移行の理由、遭遇した困難、モバイルアプリケーションとそのバックエンドのアーキテクチャを改善するための手順とヒント。

レベル :ジュニアおよびミドル。









長い間、モバイル開発アウトソーシングのチームは、独自のバックエンド開発者がいるサードパーティのプロジェクトに取り組み、特定の製品の請負業者として行動しました。 契約には常にモバイル開発者として音楽とAPIが必要であると明記されているという事実にもかかわらず、これは必ずしも助けにはなりませんでした。













必ずしもそうではないので、最近、自分の魂を傷つけるような状況の小さなコレクションを作成しました。それを過去の記事の1つでアップロードしました。







そのため、チームにかなり強力なJava(Spring)開発者がいたため、新しい顧客ごとにしっかりと発表することにしました。 最初、彼らはそのような原則的な位置が私たちを追い払うことを恐れていました、そして、結局私たちはパンと水で裸のままでいるでしょう。 しかし、判明したように、交渉の段階で誰かがすでに私たちを気に入っていて、彼らが私たちと協力したいなら、ほとんどすべてが合意できます。 クライアントが既にチーム内に自分のスタッフを持っている場合でも、最初に使用する予定でした。 次に、マイクロサービスなどのスマートワードと、モバイルアプリケーション専用のタスクを実行するビジネスロジックを備えた別のサーバーを実行できることを学びました。 このアプローチが常に適切であるとは言いませんが、これについてはこれ以上説明しません。







行く理由



いくつかの成功したプロジェクトの後、Javaは私たちにとって重すぎました。 アプリケーションにとってすべてを可能な限り便利にするために、多くの時間がルーチンに費やされました。







一般にSpringとJavaについて悪いことを言いたくありません。それは巨大な太いスペインのガレオン船のような深刻なタスクのための素晴らしいツールです。 そして、私たちは軽量の海賊クリッパーのようなものを探していました。







機能をすばやく実装し、簡単に変更し、各状況で最適なソリューションを探すために頭を温める必要はありませんでした。 問題の典型的な解決策を長時間グーグルで調べたときにどのように起こるかを知っているので、それが最も適切であり、そのうち10個のうち5個はすでに古くなっています。 そして、変数の名前の選択に30分かかります。







Goにはそのような問題はありません。 すべての言葉から。 時々、あなたは完璧な解決策を探して座っていますが、StackOverflowはこれに答えます。「まあ、はい、forループで、何を待っていましたか?」







時間が経つにつれて、あなたはそれに慣れて、あらゆる種類のささいなことを無料でグーグルで止めますが、頭をオンにしてコードを書くだけです。







難しさは何ですか



そもそも、継承はありません。 最初は脳を運んでいました。 あなたはOOPのあなたの全体の考えを破り、 アヒルタイピングに慣れなければなりません。 簡単に言えば、アヒルのように見え、アヒルのように泳ぎ、アヒルのように鳴くなら、これはアヒルかもしれません。







実際、インターフェイスの継承のみがあります。







そして第二に、重大な不利な点-少数の既製ツール、しかし多くのバグ。 多くのことを通常の方法で行うことはできません。また、一般にクラスとして何かが欠けています。 たとえば、IoC( 依存関係の逆転 )の通常のフレームワークはありません。 経験豊かなホリネズミは、Facebookからのlibがあると言うでしょう。 でも、料理の仕方がわからないだけなのかもしれませんし、それでもその便利さは実際には多くのことが望まれています。 単純にSpringと比較することはできないため、手で多くの作業を行う必要があります。







Go全体のもう1つの小さな制限は、たとえば、次の形式のAPIを作成しないことです。







/cards/:id /cards/something
      
      





既存のhttpルーターの場合、これらは相互に排他的な要求です。 ワイルドカード変数と何かの特定のアドレスの間で混乱します。 愚かな制限ですが、あなたはそれに耐えなければなりません。 誰かが解決策を知っていれば、私は聞いてうれしいです。







また、冬眠や多かれ少なかれ適切な類似体はありません。 はい、多くのORMがありますが、それらのすべては今のところかなり弱いです。 Goの開発中に見た中で最高のものはgormです。 その主な利点は、ベースから構造への応答の最も便利なマッピングです。 疑わしい動作のデバッグに長時間を費やしたくない場合は、ベアSQLでクエリを記述する必要があります。







PS このlibを使用するプロセスで発生した回避策を個別に共有したいと思います。 通常の構造ではなくgormを使用して変数に挿入した後にidを書き込む必要がある場合は、次の松葉杖が役立ちます。 リクエストレベルで、id以外に戻る結果の名前を変更します。







 ... returning id as value
      
      





変数への後続のスキャンで:







 ... Row().Scan(&variable)
      
      





事実は、idフィールドがオブジェクトの特定のフィールドとしてgormによって認識されることです。 そして、それを解き放つには、リクエストレベルで別の名前に変更する必要があります。







長所、またはなぜGoで書くのか



エントリーのしきい値から始めたいと思います。それは最小限です。 同じ春がどのようなガラガラになったのかを覚えておくと、Goはそれと比較して、ジュニアクラスで教えることができるので、とても簡単です。







そして、この単純さは、言語だけでなく、それが持つ環境にもあります。 gradleとmavenで長いマナを読む必要はありません。すべてが少なくとも1回だけ開始されるように長い設定を書く必要はありません。 ここでは、いくつかのチームがすべてを管理し、価値のあるコレクターとプロファイラーはすでに言語の一部であり、開始するために詳細な調査を必要としません。

彼らが言うように:習得しやすく、習得しにくい。 これは私が個人的に常に現代の技術に欠けていたものです。人々のために作られたものではないようです。







これから開発スピードが続きます。 言語は1つの目的のために作られました:













本質的に、それはビジネスのバックエンド言語です。 それは高速で、シンプルであり、複雑な問題を理解可能な方法で解決することができます。 複雑なタスクと理解度に関しては、Goにはgoroutinkiやチャンネルなどのクールなものがあるため、これは議論のための別のトピックです。 これは、自分の足で撃つ機会が最小限の、最も便利なマルチスレッドです。







建築



Web



Webフレームワークとして、 Ginを選択しました。 まだRevelがありますが、私たちにはあまりにも狭く、そのパラダイムを揺るぎなく口述しているように見えました。 柔軟性を持たせるために、もう少し自由な手をお勧めします。







Ginは、便利なAPIと不必要な複雑さの欠如に魅了されました。 彼の入場閾値は非常に低いです。 とても多くのことで、研修生は1日で文字通りそれを理解しました。 単に混乱する場所はありません。 一目で必要なすべての機能。







もちろん、彼は問題がないわけではありません。 キャッシュなどのいくつかの決定は、サードパーティによって行われます。 また、githubを介したインポートの使用に慣れている場合、インポートの競合があり、gopkgを介してインポートを行った場合、またはその逆も同様です。 その結果、2つのプラグインは単純に相互排他的になります。

誰かがこの問題の解決策を知っているなら、コメントに書いてください。







依存関係マネージャー



長い間書きませんが、これは間違いなくグライドだとすぐに言います。 gradleまたはmavenを使用したことがある場合は、特定のファイル内の依存関係宣言のパラダイムに精通しているので、必要に応じてそれらを後で使用できます。 したがって、GlideはハムスターGradleであり、紛争解決やその他の利点があります。







ちなみに、テスト中に問題が発生した場合、各フォルダーをテストしてベンダーフォルダーにテストクロールすると、問題は簡単に解決されます。







 go test $(glide novendor)
      
      





このオプションは、ベンダーフォルダーをテストから除外します。 glide.yamlおよびglide.lockファイルをリポジトリ自体に入れるだけで十分です。







これはモバイル開発には一切役立ちませんが、知っているだけです)







ORMとレルム



これは、バックエンドからクライアントへのデータの転送と保存に関する膨大なセクションになります。 Goから始めて、モバイルプラットフォームにシームレスに移行します。







そして、Realmとは何ですか、なぜCoreData / Arrays / SQLiteよりも優れているのですか?

Realmに遭遇したことがなく、それが何であるかを理解していない場合、スポイラーを正しく開きました。







レルムは、アプリケーション全体でのデータ同期の作業を容易にするモバイルデータベースです。 オブジェクトがまだどこにも保存されていない場合でも、常にコンテキストで作業する必要があるCoreDataのような問題はありません。 一貫性を維持する方が簡単です。

エンティティを作成し、通常のオブジェクトとして操作し、フロー間で転送し、好きなようにジャグリングするだけで十分です。







彼女はあなたのために多くの操作を行いますが、もちろん、いくつかの間違いもあります。一般的に、大文字と小文字を区別しない検索はありません。など。







これらの問題に耐える価値があり、価値があると考えました。







自分自身を繰り返さないために、 Gormをormとして使用し、いくつかの推奨事項を示します。









おそらく、これはすべての技術に当てはまります。ここでは、私は少しスカニタニルですが、それでもです。 繰り返しますが、リコールは傷つきませんが、重要です。







次に、モバイルアプリケーションについて説明します。 主なタスクは、クエリで返されるフィールドがクライアント上の対応するフィールドと同じ名前になるようにすることです。 これは、いわゆるタグを使用して簡単に実現できます。











jsonタグの名前が正しいことを確認してください。 そして、例のように、彼は除外フラグを設定しておくことが望ましいです。 これにより、空のフィールドで応答が乱雑になるのを防ぎます。







なぜ行くの?

正しい質問をすることができます。同じ名前を任意の言語で作成できる場合、Goはそれと何をする必要がありますか? そして、あなたは正しいでしょうが、Goの利点の1つは、リフレクションと構造を使用した最も簡単な書式設定です。 多くの言語にはリフレクションがありますが、Goを使用するのが最も簡単な方法です。







ところで、答えから空の構造を非表示にする必要がある場合、最良の方法は、構造のMarshalJSONメソッドをオーバーロードすることです。







 // ,     Pharmacy   Object func (r Object) MarshalJSON() ([]byte, error) { type Alias Object var pharmacy *Pharmacy = nil //  id != 0,   .   -  nil if r.Pharmacy.ID != 0 { pharmacy = &r.Pharmacy } return json.Marshal(&struct { Pharmacy *Pharmacy `json:"pharmacy,omitempty"` Alias }{ Pharmacy: pharmacy, Alias: (Alias)(r), }) }
      
      





多くは気にせず、値の代わりに構造体にポインターをすぐに書き込みますが、これはGoの方法ではありません。 Goは一般に、必要のないポインターを好まない。 これにより、コードを最適化し、その可能性を最大限に活用することができなくなります。







フィールド名に加えて、そのタイプにも注意してください。 数字は数字でなければならず、行は行(thanks、cap)でなければなりません。 日付に関しては、RFC3339を使用するのが最も便利です。 サーバーでは、オーバーロードを介して日付をフォーマットすることもできます。







 func (c *Comment) MarshalJSON() ([]byte, error) { type Alias Comment return json.Marshal(&struct { CreatedAt string `json:"createdAt"` *Alias }{ CreatedAt: c.CreatedAt.Format(time.RFC3339), Alias: (*Alias)(c), }) }
      
      





クライアントでは、これは次のパターンを使用して日付をフォーマットすることにより行われます。







 "yyyy-MM-dd'T'HH:mm:ssZ"
      
      





RFC3339のもう1つの利点は、Swaggerのデフォルトの日付形式として機能することです。 また、この方法でフォーマットされた日付自体は、特にPOSIX時間と比較した場合、人間にとって非常に読みやすいものです。







一方、クライアントでは(iOSの例ですが、Androidでも同様です)、すべてのフィールドとクラスの関係の名前が完全に一致するため、1つの汎用メソッドを使用して保存を実行できます。







 func save(dictionary: [String : AnyObject]) -> Promise<Void>{ return Promise {fulfill, reject in let realm = Realm.instance //       ,       . //          . try! realm.write { realm.create(T.self, value: dictionary, update: true) } fulfill() } }
      
      





配列の場合も状況は似ていますが、保存は既にループ内で実行される必要があります。 多くの場合、経験のない開発者は間違いを犯し、書き込みブロック全体をループでラップします。







 array.forEach { object in try! realm.write { realm.create(T.self, value: object, update: true) } }
      
      





これは、すべてを一括して保存するのではなく、各オブジェクトの新しいトランザクションを開く方法であるため、根本的に間違っています。 また、更新の通知を接続している場合、すべてがさらに楽しくなります。 トランザクションを次のレベルに引き上げて、次のようにする方がより正確です。







 try! realm.write { array.forEach { object in realm.create(T.self, value: object, update: true) } }
      
      





ご覧のとおり、マッピングを担当する中間層は完全に落ちています。 データが十分に準備できたら、追加の処理を行わずにすぐにデータベースに取り込むことができます。 また、バックエンドが優れていればいるほど、この余分な処理は少なくなります。 理想的には、日付をオブジェクトに変換するだけです。 それ以外はすべて事前に行う必要があります。







ちなみに、トピックから少し離れています。 クライアントに永続的なデータベースを用意する必要がない場合、これはレルムを放棄する理由にはなりません。 RAM内で厳密に作業を行うことができ、コンテンツをオンデマンドでリセットできます。







iOSおよびAndroidのリンク。







このアプローチにより、上記のリアクティブデータベースとマッピングのすべての利点を活用できます。







また、詳細に特に注意を払っている人のために付け加えたいと思います。ここでは、Goが唯一の正しいソリューションであり、モバイル開発の万能薬であるという声明はありません。 誰もが独自の方法でこの問題を解決できます。 このパスを選択しました。







Project Goの構造



これで、Go開発者向けのコードが多くなります。 あなたがモバイル開発者であれば、次のセクションに自由にスクロールできます。







あなたが囲developer開発者である場合、今私たちは最も興味深いことになります。 典型的なアプリケーションのバックエンドを作成していると仮定します。RESTAPI、いくつかのビジネスロジック、モデル、データベースロジック、ユーティリティ、移行スクリプト、リソースの設定を含むレイヤーがあります。 どういうわけか、クラスとパパのためにプロジェクトでこれらすべてを調整し、 SOLIDの原則を守り、できればそれに夢中にならないようにする必要があります。







これまでのところ、私たちはそれを抽象的に投げており、深く潜り込むのではなく、全体的な構造が理解されるようにしています。 興味深い場合は、これを本格的な別の資料に充てます。 それでも、Goと連携したモバイルアプリケーションについて話しているところです。







私の声明で独断的であるふりをしないようにすぐに予約してください。誰もが自分のプロジェクトに合うように自由に仕事をすることができます。







構造のスクリーンショットから始めましょう。









(Intellij Ideaのハムスターはどれくらいかわいいでしょう?触るたびに)







非展開ディレクトリには、Goファイルまたはリソースファイルが一度に含まれます。 簡単に言えば、すべてが開かれて最大限の没入感が得られます。







この記事では、API、サービス、データベースの操作、およびそれらが相互にどのように依存しているかについて、ビジネスロジックの責任についてのみ説明します。 親愛なる皆さんがこのトピックに興味を示したら、1つの記事には情報が多すぎるので、残りを書きます。







だから、順番に:







Web







リクエストの処理を担当するものはすべて、Webに保存されます:バインダー、フィルター、コントローラー-すべてのはんだ付けはapi.goで行われます。 そのような結合の例:







 regions := r.Group("/regions") regions.GET("/list", Cache.Gin, rc.List) regions.GET("/list/active", Cache.Gin, regionController.ListActive) regions.GET("", binders.Coordinates, regionController.RegionByCoord)
      
      





コントローラーの初期化と依存関係の注入がそこで行われます。 実際、 api.goファイル全体は、ルーターが形成および開始されるRunメソッドと、すべての依存関係とそのグループを持つコントローラーを作成するための補助メソッドのヒープで構成されています。







Web。バインダー







バインダはバインダフォルダにあり、クエリからパラメータを解析し、便利な形式に変換し、さらに作業するためにコンテキストにドロップします。







このパッケージのメソッドの例。 クエリからパラメーターを受け取り、boolに変換してコンテキストに入れます。







 func OpenNow(c *gin.Context) { openNow, _ := strconv.ParseBool(c.Query(BindingOpenNow)) c.Set(BindingOpenNow, openNow) }
      
      





エラー処理のない最も簡単なオプション。 わかりやすくするために。







Web.Controllers







通常、コントローラーのレベルでは、ほとんどの間違いを犯します。余分なロジックをプッシュし、インターフェイスと分離を忘れてから、一般に関数型プログラミングにスライドします。 一般的に、Goでは、コントローラーはiOSと同じ病気に苦しんでいます。コントローラーは常に過負荷になっています。 したがって、実行するタスクをすぐに決定します。









典型的なコントローラーの例を見てみましょう。







インポートを省略した場合、クラスはコントローラーインターフェイスで始まります。 はい、はい、常に1つの実装しかない場合でも、SOLIDという単語の文字「D」を確認します。 これにより、テストが非常に簡単になり、コントローラー自体をモックに置き換えることができます。







 type Order interface { PlaceOrder(c *gin.Context) AroundWithPrices(c *gin.Context) }
      
      





次に、コントローラー構造自体とそのコンストラクターがあります。これは、 api.goでコントローラーを作成するときに呼び出す依存関係を考慮します。







 //   ,      type order struct { service services.Order } func NewOrder(service services.Order) Order { return &order { service: service, } }
      
      





そして最後に、リクエストを処理するメソッド。 レイヤーをバインダーで正常に渡したので、すべてのパラメーターがあることが保証され、パニック攻撃を恐れることなくMustGetを使用してパラメーターを取得できます。







 func (o order)PlaceOrder(c *gin.Context) { m := c.MustGet(BindingOrder).(*model.Order) o.service.PlaceOrder(m) c.IndentedJSON(http.StatusCreated, gin.H { "identifier": m.ID, }) }
      
      





オプションのパラメーターを使用した同じストーリーですが、バインダーレベルでのみ、ゼロ値を設定する価値があります。コントローラーでチェックインし、デフォルト値を置き換えるか、単に無視します。







サービス







サービスの状況はほとんど同じです。また、サービスはインターフェース、構造、コンストラクターで始まり、その後に一連のメソッドが続きます。 1つの詳細に焦点を当てたいと思います-これがデータベースを操作する原則です。







サービスのコンストラクタは、それが動作する一連のリポジトリとトランザクションファクトリを考慮する必要があります。







 func NewOrder(repo repositories.Order, txFactory TransactionFactory) Order { return &order { repo: repo, txFactory: txFactory } }
      
      





トランザクションファクトリはトランザクションを生成する単なるクラスであり、複雑なものはありません。







 type TransactionFactory interface { BeginNewTransaction() Transaction }
      
      





gormの完全な工場コード
 type TransactionFactory interface { BeginNewTransaction() Transaction } type transactionFactory struct { db *gorm.DB } func NewTransactionFactory(db *gorm.DB) TransactionFactory { return &transactionFactory{db: db} } func (t transactionFactory)BeginNewTransaction() Transaction { tx := new(transaction) tx.db = t.db tx.Begin() return tx }
      
      





しかし、トランザクション自体で停止する価値があります。 そもそも、それは何についてですか。 トランザクションは実装と同じインターフェースであり、トランザクションの開始、完了、ロールバック、および以下のエンジンレベルの実装へのアクセスのためのメソッドが含まれています。







 type Transaction interface { Begin() Commit() Rollback() DataSource() interface{} }
      
      





gormの完全なトランザクションコード
 type Transaction interface { Begin() Commit() Rollback() DataSource() interface{} } type transaction struct { Transaction db *gorm.DB tx *gorm.DB } func (t *transaction)Begin() { t.tx = t.db.Begin() } func (t *transaction)Commit() { t.tx.Commit() } func (t *transaction)Rollback() { t.tx.Rollback() } func (t *transaction)DataSource() interface{} { return t.tx }
      
      





begincommitrollbackですべてをクリアする必要がある場合、Datasourceは低レベルの実装にアクセスするための単なる松葉杖です。Goのデータベースでの作業は、トランザクションが変更された設定を持つデータベースへのアクセサーの単なるコピーになるように設計されているためです 後でリポジトリで作業するときに必要になります。







実際、これはサービスメソッドでトランザクションを操作する例です。







 func (o order)PlaceOrder(m *model.Order) { tx := o.txFactory.BeginNewTransaction() defer tx.Commit() o.repo.Insert(tx, m) }
      
      





トランザクションを開始し、データベースにアクセスし、必要に応じてコミットまたはロールバックしました。







もちろん、トランザクションの利点はいくつかの操作で特に明らかになりますが、例のように1つしかなくても、これはそれほど悪くはなりません。







専門家に

分離レベルを制御できないことを知っています。

まだ浅瀬を見つけたら-コメントに書いてください。







後輩への追加のアドバイスとして、できるだけ早く取引を開始すべきだと言いたいです。 すべてのデータを準備して、開始とコミットの間に最小限のロジックと計算ができるようにします。







トランザクションが開かれ、人々が喫煙して、たとえばGoogleにリクエストを送信することが起こります。 そして、彼らはなぜそれがすべてデッドロックで起こったのだろうと思います。







興味深い事実

現代の多くのデータベースでは、 デッドロックはできるだけ単純に、タイムアウトによって定義されています。 負荷が大きい場合、ブロックのためのリソースのスキャンは高価です。 したがって、代わりに通常のタイムアウトがよく使用されます。 たとえば、 mysqlで 。 この機能がわからない場合は、最も素晴らしい時間の楽しいデバッグを自分で行うことができます。


リポジトリ







同じこと:インターフェース、構造、コンストラクターは、原則として既にパラメーターなしです。

サービスコードで呼び出した挿入操作の例を示します。







 func (order)Insert(tx Transaction, m *model.Order) { db := tx.DataSource().(*gorm.DB) query := "insert into orders (shop_id) values (?) returning id" db.Raw(query, m.Shop.ID).Scan(m) }
      
      





トランザクションから低レベルのアクセス修飾子を受け取り、リクエストをコンパイルして完了しました。 できた







これはすべて、アーキテクチャを台無しにしないために十分なはずです。 少なくとも速すぎる。 ご質問や異議がある場合は、コメントを書いてください、私は議論して喜んでいるでしょう。







アプリ



さて、ゴーファーはいいですが、今どのようにクライアントと仕事をしていますか?







Goの場合と同様に、スタックから始めましょう。 一般的に、私たちはほぼすべての場所で試薬を積極的に使用していますが、ここでは、すぐに誰かの精神を傷つけないように、より穏やかなバージョンのアーキテクチャについて説明します。







スタック



ネットワーク層







SwiftプロジェクトのAlamofireおよびObjective-CのAFNetworking







ところで、AlamofireがAFNetworkingであることを知っていましたか? AFNetworkingライセンスを見ればわかるように、 AFプレフィックスはAlamofireを意味します。







短絡







callback- / . , . , .







. iOS: PromiseKit . — , , , success/failure , always, , / . , .







, — . flow , . , , , :







 func details(id: Int) -> Promise<Void> { return getDetails(id) .then(execute: parse) .then(execute: save) }
      
      





getDetails, :







 func getDetails(id: Int) -> Promise<DataResponse<Any>> { return Promise { fulfill, reject in Alamofire.request(NetworkRouter.drugDetails(id: id)).responseJSON { fulfill($0) } } }
      
      





, . , . , . , .







 func parseAsDictionary(response: DataResponse<Any>) -> Promise<[String:AnyObject]> { return Promise {fulfill, reject in switch response.result { case .success(let value): let json = value as! [String : AnyObject] guard response.response!.statusCode < 400 else { let error = Error(dictionary: json) reject(error) return } fulfill(json) break case .failure(let nserror): let error = Error(error: nserror as NSError) reject(error) break } } } //     ,   func save(items: [[String : AnyObject]]) -> Promise<Int> { return Promise {fulfill, reject in let realm = Realm.instance try! realm.write { items.forEach { item in //       generic realm.create(Item.self, value: item, update: true) } } fulfill(items.count) } }
      
      





, MVC, :







 _ = service.details().then {[weak self] array -> Void in // Success. Do w/e you like. }
      
      





データベース







, ORM Go-side, , . - , . , datasource . , .







fine-grained notifications , .







extra-
 class ViewController: UITableViewController { var notificationToken: NotificationToken? = nil override func viewDidLoad() { super.viewDidLoad() let realm = try! Realm() let results = realm.objects(Person.self).filter("age > 5") // Observe Results Notifications notificationToken = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in guard let tableView = self?.tableView else { return } switch changes { case .initial: // Results are now populated and can be accessed without blocking the UI tableView.reloadData() break case .update(_, let deletions, let insertions, let modifications): // Query results have changed, so apply them to the UITableView tableView.beginUpdates() tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }), with: .automatic) tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}), with: .automatic) tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }), with: .automatic) tableView.endUpdates() break case .error(let error): // An error occurred while opening the Realm file on the background worker thread fatalError("\(error)") break } } } deinit { notificationToken?.stop() } }
      
      







, ApiManager.swift . , , — extension ApiManager, .

singleton, . , , .







SOA (service oriented architecture). Rambler , , , .







. — . , . , viewDidLoad. , , . , , , , , .







:













, . , 200-300 . , , .







, : , . , .







おわりに



まとめると。 Realm- mobile-side , . , -, . , iOS Android, — !







, . , , - .







. .







, . , , , , , , ? MVP MVVM , .







, , : “, ?” : “, .”

.







, , . , , .







PS . ? , , . , , , , .








All Articles