iOS向けモバイルバンキング:ブロックアーキテクチャをCocoa MVCに追加

iOS用のモバイルバンキングアプリケーションを作成している場合、優先事項は何ですか? そのうちの2つがあると思います。



  1. 信頼性;
  2. 変更率。


状況は非常に迅速に変更(特に新しい銀行商品を展開)できるようにする必要があるようなものです。 ただし、同時に、ヒンドゥー教のコードをすり抜けてコピー&ペーストしないでください(段落1を参照)。 これらはすべて、少なくともアイデアの面では、アプリケーションの機能が非常に大きいという事実にもかかわらず(銀行はできる限り多くのものを望んでいます)。 したがって、多くの場合、これらは数十人年のプロジェクトです。 このようなプロジェクトに参加した人は、おそらくタスクが簡単ではないことをすでに認識しており、 学校の知識はここでは役に立ちません。



どうする?



これがアーキテクチャのタスクであることはすぐに明らかです。 コードを書く前に、RBSのフロントエンドのシンプルで美しいブロックアーキテクチャを見ました。 モバイルアプリケーションでこれを見たことはありませんが、最終的には、大規模なフレームワークを追加することなく、iOS SDKのフレームワーク内で、非常に効果的でスケーラブルな同様のソリューションを作成しました。 主なポイントを共有したいと思います。



はい、これらのすべてのSDKフレームワーク、Appleの学校、 MVCパターンの機能、公式ドキュメントのクイックコードについては、深刻なアプリケーション(または少なくともHabrを読んで)を作成および保守しようとしたすべての人に知られています。 プログラミングの基本、例のあるファイル、猫のいる写真もありません。





「高度なアーキテクチャ」の最初のGoogle画像



決定の本質



操作はアトムで構成されます。

操作は、ABSで特定のアクションを実行するためのインターフェイスです。 たとえば、アカウント間の転送。



AtomはMVCの安定した「カプセル」であり、あらゆるサイズと形状の家を建てることができる一種のレンガです。 各レンガには、碑文、入力フィールド、画像というUIの本質があります。 重要な各UIブロックは、このようなMVCブリックにカプセル化されます。 たとえば、 UISegmentedControl



カプセル化されます。



これらのMVCブリックは1回作成されます。 さらに、各操作はこれらのブリックから構築されます。 レンガを置くことは1行です。 一行! 次に、値を取得します-これもまた1行です。 他のすべては、Atomのサブクラスの継承とポリモーフィズムによって決定されます。 タスクは、操作自体のコードを単純化することです。 それらは大きく異なる場合があります。 レンガは根本的に変わりません(またはまったく変わりません)。



アトムは、より複雑な要素にすることができます。 いくつかのロジックと子ViewController



カプセル化できます。 たとえば、リストからアカウントを選択するためのアトム(さらに、コンテキストからのフィルターによる)。 主なことは、すべての複雑さが原子内に残ることです。 外では、同じように使いやすいままです。





私のコンセプトはすでに建設で使用されています



たとえば、「クイックフォーム」で支払い注文を送信する操作(セキュリティに関連するすべてのポイントを隠したコードから、はい、コードの権利はDigital Technologies of the Future LLCに属します):



 class PayPPQuickOperation : Operation, AtomValueSubscriber { private let dataSource = PayPPDataSource() private var srcTitle, destBicInfo: TextAtom! private var destName, destInn, destKpp, destAccount, destBic, destDesc, amount: TextInputAtom! private var btnSend: ButtonAtom! override init() { super.init() create() initUI() } func create() { srcAccount = AccountPickerAtom(title: "  ") destName = TextInputAtom(caption: "    ") destInn = TextInputAtom(caption: " ", type: .DigitsOnly) destKpp = TextInputAtom(caption: " ", type: .DigitsOnly) destAccount = TextInputAtom(caption: " ", type: .DigitsOnly) destBic = TextInputAtom(caption: "", type: .DigitsOnly) destBicInfo = TextAtom(caption: " ,    ") destDesc = TextInputAtom(caption: " ") amount = TextInputAtom(caption: ", ₽", type: .Number) btnSend = ButtonAtom(caption: "") //      : atoms = [ srcAccount, destName, destInn, destKpp, destAccount, destBic, destBicInfo, destDesc, amount, btnSend, ] destInn!.optional = true destKpp!.optional = true btnSend.controlDelegate = self //    onButtonTap  destBic.subscribeToChanges(self) } func initUI() { destName.wideInput = true destAccount.wideInput = true destDesc.wideInput = true destBicInfo.fontSize = COMMENT_FONT_SIZE destName.capitalizeSentences = true destDesc.capitalizeSentences = true } func onAtomValueChanged(sender: OperationAtom!, commit: Bool) { if (sender == destBic) && commit { dataSource.queryBicInfo(sender.stringValue, success: { bicInfo in self.destBicInfo.caption = bicInfo?.data.name }, failure: { error in //  ,      self.destBicInfo.caption = "" }) } } func onButtonTap(sender: AnyObject?) { //   ,    sender    var hasError = false for atom in atoms { if atom.needsAttention { atom.errorView = true hasError = true } } if !hasError { var params: [String : AnyObject] = [ "operation" : "pp", "from" : srcAccount.account, "name" : destName.stringValue, "kpp" : destKpp.stringValue, "inn" : destInn.stringValue, ... "amount" : NSNumber(double: amount.doubleValue), ] self.showSignVC(params) } } }
      
      





そして、これがオペレーションコード全体です!!! 必要な原子がすべて揃っていれば、これ以上は必要ありません。 この場合、私はすでにそれらを持っていました。 そうでない場合は、新しいものを書き、ストーリーボードに描きます。 もちろん、既存のものから継承できます。



onAtomValueChanged()



は、 AtomValueSubscriber



プロトコルの実装です。 BICテキストボックスの変更をサブスクライブし、そこでBICによって銀行の名前を返すリクエストを作成します。 テキストフィールドのcommit == true



値は、UIControlEventEditingDidEnd UIControlEventEditingDidEnd



から取得されUIControlEventEditingDidEnd







showSignVC()



の最後の行は、署名操作のViewController



を表示するViewController



です。これは、同じ単純なアトム(サーバーからViewController



したセキュリティマトリックスの要素から形成)で構成される別の操作です。



Operation



クラスのコードは提供していません。 より良い解決策を見つけることができます。 (開発時間を節約するため) atoms



テーブルViewController



に供給atoms



ViewController



。 すべての「ブリック」は、セルIDによってインスタンス化されたIBテーブルセルに描画されます。 この表は、自動高さ変換およびその他の設備を提供します。 しかし、テーブルに原子を配置できるようになったことがわかりました。 iPhoneの場合は良いですが、iPadの場合はそれほど良くないかもしれません。 ロシアのアプリストアのいくつかの銀行だけがタブレットのスペースを正しく使用していますが、残りは愚かにiPhoneのUIをコピーし、左側のメニューのみを追加します。 しかし理想的には、はい、 UICollectionView



でやり直すべきUICollectionView







依存関係



ご存じのように、上記のBICの例で見たように、一部のフィールドの値に応じてフォームを入力すると、他のフィールドが変更または非表示になります。 ただし、1つのフィールドがあり、全額支払いには何倍もあります。したがって、フィールドを動的に表示/非表示するには、最小限の動きを必要とする単純なメカニズムが必要です。 これは簡単ですか? 原子は救助に来ます:)そしてNSPredicate



も。



オプション1



  func createDependencies() { // ppType -   , -    (EnumAtom) ppType.atomId = "type" ppType.subscribeToChanges(self) let taxAndCustoms = "($type == 1) || ($type == 2)" ... //    : field106.dependency = taxAndCustoms field107.dependency = taxAndCustoms field108.dependency = taxAndCustoms ...
      
      





これは支払いの例です。 同様のシステムは、さまざまなプロバイダーに有利な電子決済で機能します。何らかの選択肢(たとえば、メートルまたは任意の金額で支払う)に応じて、ユーザーは異なるフィールドセットに入力します。



ここでは 、これらの目的のために「特定のフレームワーク」を作成しましたが、どういうわけか、私は数行で十分でした。



オプション2 、述語言語がない場合、または知らない場合、依存関係を関数と見なします。たとえば、isDestInnCorrect():



  destInn.subscribeToChanges(self) destInnError.dependency = "isDestInnCorrect == NO"
      
      





プロパティの名前を変更するともっときれいになると思います。



  destInnError.showIf("isDestInnCorrect == NO")
      
      





テキストアトムdestInnError



、入力が正しくない場合に、107番目に従ってTINを正しく入力する方法をユーザーに伝えます。



残念ながら、コンパイラはこの操作がisDestInnCorrect()



メソッドを実装していることを確認しませんが、これはおそらくマクロで実行できます。



そして、基本クラスOperation



可視性を実際に設定します(Objective-Cではごめんなさい):



 - (void)recalcDependencies { for (OperationAtom *atom in atoms) { if (atom.dependency) { NSPredicate *predicate = [NSPredicate predicateWithFormat:atom.dependency]; BOOL shown = YES; NSDictionary<NSString*, id> *vars = [self allFieldsValues]; @try { shown = [predicate evaluateWithObject:self substitutionVariables:vars]; } @catch (NSException *exception) { //    } atom.shown = shown; } } }
      
      





他のチェックがどうなるか見てみましょう。 おそらく、操作は関係するべきではありません。 オプションのためにすべてが書き換えられます



 let verifications = Verifications.instance ... if let shown = predicate.evaluateWithObject(verifications, substitutionVariables: vars) { ...
      
      





もう一つ



一般に、クライアントとサーバーの相互作用におけるオブジェクトパターンに関して、 JSONModelライブラリによって提供されるJSONを操作する際の例外的な利便性に注意するしかありません。 結果のJSONオブジェクトの角括弧の代わりにドット表記を使用し、すぐにフィールドを入力します。 配列と辞書。 その結果、大きなオブジェクトのコードの可読性の大幅な向上(および信頼性の結果として)。



JSONModel



クラスを取得し、 JSONModel



からServerReply



を継承し(各回答にはフィールドの基本セットが含まれます)、 ServerReply



は特定の種類の要求に対するサーバー応答を継承します。 ライブラリの主な欠点は、Swiftにないことです(一部の言語ライフハックで機能するため)。同じ理由で、構文が奇妙です...



一例
 @class OperationData; @protocol OperationOption; #pragma mark OperationsList @interface OperationsList : ServerReply @property (readonly) NSUInteger count; @property OperationData<Optional> *data; ... @end #pragma mark OperationData @interface OperationData : JSONModel @property NSArray<OperationOption, Optional> *options; @end #pragma mark OperationOption @interface OperationOption : JSONModel @property NSString<Optional> *account; @property NSString<Optional> *currency; @property BOOL isowner; @property BOOL disabled; ...
      
      







しかし、これらはすべてSwiftコードから簡単に使用できます。 唯一のことは、誤ったサーバー応答形式が原因でアプリケーションがクラッシュしないことです。すべてのNSObject



フィールドを< Optional



>としてマークし、自分でnil



をチェックする必要があります。 JSONModel



に干渉しないようにプロパティが必要な場合は、< Ignore



>修飾子または場合によっては(readonly)



修飾子を記述し(readonly)







結論



「原子」の概念は本当に機能し、モバイルバンクの優れたスケーラブルなアーキテクチャを作成することができました。 操作コードの理解と修正は非常に簡単で、そのサポートは元のアイデアを維持しながらO(N)時間かかります。 そして、多くのプロジェクトが展開している出展者ではありません。



この実装の欠点は、表示がUITableView



を介して簡単に行われることであり、スマートフォンで動作しUITableView



。タブレットの場合、「高度な」デザインの場合、Viewと部分的にPresenterをやり直す必要があります(スマートフォンとタブレットの一般的なソリューション)。



また、大規模なプロジェクトのアーキテクチャをiOS SDKにどのように適合させますか?



All Articles