iOSアーキテクチャパターンに関して言えば、Model-View-Controller(MVC)デザインパターンは、アプリケーションのコードベースの寿命と保守性に優れています。クラスを簡単に再利用または置換して、クラスを相互に分離することでさまざまな要件をサポートできます。これは、オブジェクト指向プログラミング(OOP)の利点を最大化するのに役立ちます。
このiOSアプリケーションアーキテクチャはミクロレベル(アプリの個々の画面/セクション)でうまく機能しますが、アプリが成長するにつれて、複数のモデルに同様の機能を追加することに気付くかもしれません。ネットワーキングなどの場合、共通ロジックをモデルクラスからシングルトンヘルパークラスに移動することをお勧めします。このAFNetworkingiOSチュートリアルでは、マイクロレベルのMVCコンポーネントから分離され、分離されたアーキテクチャアプリケーション全体で再利用できる集中型シングルトンネットワークオブジェクトを設定する方法を説明します。
ノードjsとjavascriptの違い
Appleは、使いやすいiOS SDKでモバイルハードウェアを管理する際の複雑さの多くを抽象化するという素晴らしい仕事をしてきましたが、ネットワーキング、Bluetooth、OpenGL、マルチメディア処理などの場合、クラスを維持することを目的としているため、クラスが煩雑になることがあります。 SDKは柔軟です。ありがたいことに、金持ち iOS開発者 コミュニティは、アプリケーションの設計と構造を簡素化するために、最も一般的なユースケースを簡素化するための高レベルのフレームワークを作成しました。 iOSアプリアーキテクチャのベストプラクティスを採用している優れたプログラマーは、使用するツール、それらを使用する理由、および独自のツールとクラスを最初から作成する方がよい場合を知っています。
AFNetworkingは優れたネットワーキングの例であり、最も一般的に使用されているオープンソースフレームワークの1つであり、開発者の日常のタスクを簡素化します。これにより、RESTful APIネットワーキングが簡素化され、成功、進行、および失敗の完了ブロックを含むモジュール式の要求/応答パターンが作成されます。これにより、開発者が実装したデリゲートメソッドやカスタムのリクエスト/接続設定が不要になり、どのクラスにもすばやく含めることができます。
AFNetworkingは素晴らしいですが、そのモジュール性は断片化された方法での使用にもつながる可能性があります。一般的な非効率的な実装には、次のものが含まれます。
単一のViewControllerで同様のメソッドとプロパティを使用する複数のネットワークリクエスト
複数のViewControllerでのほぼ同一のリクエストにより、同期が外れる可能性のある分散共通変数が発生する
そのクラスに関係のないデータに対するクラス内のネットワーク要求
ビューの数が限られていて、実装するAPI呼び出しが少なく、頻繁に変更される可能性が低いアプリケーションの場合、これは大きな問題ではない可能性があります。ただし、考えが大きく、何年にもわたる更新が計画されている可能性が高くなります。後者の場合は、次の処理が必要になる可能性があります。
複数世代のアプリケーションをサポートするためのAPIバージョニング
機能を拡張するための新しいパラメータの追加または既存のパラメータへの変更
まったく新しいAPIの実装
ネットワークコードがコードベース全体に散在している場合、これは潜在的な悪夢です。うまくいけば、共通のヘッダーで静的に定義されたパラメーターの少なくとも一部がありますが、それでも、ごくわずかな変更でさえ、12のクラスに触れる可能性があります。
ネットワークシングルトンを作成して、要求、応答、およびそれらのパラメーターの処理を一元化します。
シングルトンオブジェクトは、そのクラスのリソースへのグローバルなアクセスポイントを提供します。シングルトンは、一般的なサービスやリソースを提供するクラスなど、この単一の制御ポイントが望ましい状況で使用されます。ファクトリメソッドを介してシングルトンクラスからグローバルインスタンスを取得します。 – 林檎
したがって、シングルトンは、アプリケーションの存続期間中に存在するアプリケーション内にインスタンスが1つしかないクラスです。さらに、インスタンスは1つしかないことがわかっているため、メソッドやプロパティにアクセスする必要のある他のクラスから簡単にアクセスできます。
ネットワーキングにシングルトンを使用する必要がある理由は次のとおりです。
静的に初期化されるため、作成されると、アクセスを試みるすべてのクラスで使用できるのと同じメソッドとプロパティが使用されます。奇妙な同期の問題が発生したり、クラスの間違ったインスタンスからデータを要求したりする可能性はありません。
API呼び出しを制限して、レート制限を下回るようにすることができます(たとえば、APIリクエストを1秒あたり5未満に保つ必要がある場合)。
ホスト名、ポート番号、エンドポイント、APIバージョン、デバイスタイプ、永続ID、画面サイズなどの静的プロパティを同じ場所に配置できるため、1つの変更がすべてのネットワーク要求に影響します。
共通のプロパティは、多くのネットワーク要求間で再利用できます。
シングルトンオブジェクトは、インスタンス化されるまでメモリを消費しません。これは、デバイスを持っていない場合にChromecastへの動画のキャストを処理するなど、一部のユーザーが必要としない可能性のある非常に特殊なユースケースを持つシングルトンに役立ちます。
ネットワーク要求は、ビューとコントローラーから完全に切り離すことができるため、ビューとコントローラーが破棄された後でも続行できます。
ネットワークロギングは一元化および簡素化できます。
アラートなどの失敗の一般的なイベントは、すべての要求に再利用できます。
このようなシングルトンの主な構造は、単純なトップレベルの静的プロパティの変更で複数のプロジェクトで再利用できます。
シングルトンを使用しないいくつかの理由:
それらは、単一のクラスで複数の責任を提供するために乱用される可能性があります。たとえば、ビデオ処理方法は、ネットワーク方法またはユーザー状態方法と混合できます。これはおそらく不十分な設計手法であり、コードを理解するのが困難になります。代わりに、特定の責任を持つ複数のシングルトンを作成する必要があります。
シングルトンはサブクラス化できません。
シングルトンは依存関係を隠すことができるため、モジュール性が低下します。たとえば、シングルトンが削除され、シングルトンがインポートしたインポートがクラスにない場合、将来の問題が発生する可能性があります(特に、外部ライブラリの依存関係がある場合)。
クラスは、別のクラスでは予期しない長い操作中に、シングルトンで共有プロパティを変更できます。これを適切に考慮しないと、結果が異なる場合があります。
シングルトン自体が割り当て解除されることはないため、シングルトンでのメモリリークは重大な問題になる可能性があります。
ただし、iOSアプリアーキテクチャのベストプラクティスを使用すると、これらの欠点を軽減できます。いくつかのベストプラクティスは次のとおりです。
各シングルトンは、単一の責任を処理する必要があります。
高精度が必要な場合は、複数のクラスまたはスレッドによって急速に変更されるデータを格納するためにシングルトンを使用しないでください。
シングルトンを構築して、利用可能な依存関係に基づいて機能を有効/無効にします。
C ++を学ぶのに最適な場所
大量のデータをシングルトンプロパティに保存しないでください。データはアプリケーションの存続期間中存続します(手動で管理しない限り)。
まず、前提条件として、AFNetworkingをプロジェクトに追加します。最も簡単なアプローチはCocoapodsを使用することであり、手順はその上にあります GitHubページ 。
そこにいる間に、UIAlertController+Blocks
を追加することをお勧めしますおよびMBProgressHUD
(ここでもCocoaPodsで簡単に追加できます)。これらは明らかにオプションですが、AppDelegateウィンドウのシングルトンで実装したい場合は、進行状況とアラートが大幅に簡素化されます。
一度AFNetworking
が追加されたら、NetworkManager
という新しいCocoaTouchクラスを作成することから始めます。 NSObject
のサブクラスとして。マネージャにアクセスするためのクラスメソッドを追加します。あなたのNetworkManager.h
ファイルは次のコードのようになります。
#import #import “AFNetworking.h” @interface NetworkManager : NSObject + (id)sharedManager; @end
次に、シングルトンの基本的な初期化メソッドを実装し、AFNetworkingヘッダーをインポートします。クラスの実装は次のようになります(注:これは、自動参照カウントを使用していることを前提としています)。
#import 'NetworkManager.h' @interface NetworkManager() @end @implementation NetworkManager #pragma mark - #pragma mark Constructors static NetworkManager *sharedManager = nil; + (NetworkManager*)sharedManager { static dispatch_once_t once; dispatch_once(&once, ^ { sharedManager = [[NetworkManager alloc] init]; }); return sharedManager; } - (id)init { if ((self = [super init])) { } return self; } @end
すごい!これで調理が完了し、プロパティとメソッドを追加する準備が整いました。シングルトンにアクセスする方法を理解するための簡単なテストとして、NetworkManager.h
に以下を追加しましょう。
@property NSString *appID; - (void)test;
そして、NetworkManager.m
への次の:
#define HOST @”http://www.apitesting.dev/” static const in port = 80; … @implementation NetworkManager … //Set an initial property to init: - (id)init { if ((self = [super init])) { self.appID = @”1”; } return self; } - (void)test { NSLog(@”Testing out the networking singleton for appID: %@, HOST: %@, and PORT: %d”, self.appID, HOST, port); }
次に、メインでViewController.m
ファイル(またはお持ちのもの)、インポートNetworkManager.h
そしてviewDidLoad
で追加:
[[NetworkManager sharedManager] test];
アプリを起動すると、出力に次のように表示されます。
Testing our the networking singleton for appID: 1, HOST: http://www.apitesting.dev/, and PORT: 80
わかりました。#define
、static const、@property
を混在させない可能性があります。一度にこのようになりますが、オプションを明確にするために単に表示します。 「staticconst」は型安全性のより良い宣言ですが#define
マクロを使用できるため、文字列の作成に役立ちます。その価値のために、私は#define
を使用していますこのシナリオの簡潔さのために。ポインタを使用していない限り、これらの宣言アプローチの間に実際の違いはあまりありません。
#defines
、定数、プロパティ、およびメソッドを理解したので、それらを削除して、より関連性の高い例に進むことができます。
何かにアクセスするためにユーザーがログインする必要があるアプリケーションを想像してみてください。アプリの起動時に、認証トークンを保存したかどうかを確認し、保存した場合は、APIに対してGETリクエストを実行して、トークンの有効期限が切れているかどうかを確認します。
AppDelegate.m
で、トークンのデフォルトを登録しましょう。
+ (void)initialize { NSDictionary *defaults = [NSDictionary dictionaryWithObjectsAndKeys:@'', @'token', nil]; [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; }
NetworkManagerにトークンチェックを追加し、完了ブロックを介してチェックに関するフィードバックを取得します。これらの完了ブロックは、好きなように設計できます。この例では、応答オブジェクトデータで成功を使用し、エラー応答文字列とステータスコードで失敗を使用しています。注:分析で値をインクリメントするなど、受信側にとって問題がない場合は、オプションで失敗を省略できます。
NetworkManager.h
上@interface
:
typedef void (^NetworkManagerSuccess)(id responseObject); typedef void (^NetworkManagerFailure)(NSString *failureReason, NSInteger statusCode);
@interface:
@property(非アトミック、強力)AFHTTPSessionManager * networkingManager;
- (void)tokenCheckWithSuccess:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure;
NetworkManager.m:
BASE_URLを定義します。
#define ENABLE_SSL 1 #define HOST @'http://www.apitesting.dev/' #define PROTOCOL (ENABLE_SSL ? @'https://' : @'http://') #define PORT @'80' #define BASE_URL [NSString stringWithFormat:@'%@%@:%@', PROTOCOL, HOST, PORT]
認証されたリクエストを簡単にし、エラーを解析するためのいくつかのヘルパーメソッドを追加します(この例ではJSON Webトークンを使用します)。
- (AFHTTPSessionManager*)getNetworkingManagerWithToken:(NSString*)token { if (self.networkingManager == nil) { self.networkingManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:BASE_URL]]; if (token != nil && [token length] > 0) { NSString *headerToken = [NSString stringWithFormat:@'%@ %@', @'JWT', token]; [self.networkingManager.requestSerializer setValue:headerToken forHTTPHeaderField:@'Authorization']; // Example - [networkingManager.requestSerializer setValue:@'application/json' forHTTPHeaderField:@'Content-Type']; } self.networkingManager.requestSerializer = [AFJSONRequestSerializer serializer]; self.networkingManager.responseSerializer.acceptableContentTypes = [self.networkingManager.responseSerializer.acceptableContentTypes setByAddingObjectsFromArray:@[@'text/html', @'application/json', @'text/json']]; self.networkingManager.securityPolicy = [self getSecurityPolicy]; } return self.networkingManager; } - (id)getSecurityPolicy { return [AFSecurityPolicy defaultPolicy]; /* Example - AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone]; [policy setAllowInvalidCertificates:YES]; [policy setValidatesDomainName:NO]; return policy; */ } - (NSString*)getError:(NSError*)error { if (error != nil) { NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]; NSDictionary *responseObject = [NSJSONSerialization JSONObjectWithData: errorData options:kNilOptions error:nil]; if (responseObject != nil && [responseObject isKindOfClass:[NSDictionary class]] && [responseObject objectForKey:@'message'] != nil && [[responseObject objectForKey:@'message'] length] > 0) { return [responseObject objectForKey:@'message']; } } return @'Server Error. Please try again later'; }
MBProgressHUDを追加した場合は、ここで使用できます。
#import 'MBProgressHUD.h' @interface NetworkManager() @property (nonatomic, strong) MBProgressHUD *progressHUD; @end … - (void)showProgressHUD { [self hideProgressHUD]; self.progressHUD = [MBProgressHUD showHUDAddedTo:[[UIApplication sharedApplication] delegate].window animated:YES]; [self.progressHUD removeFromSuperViewOnHide]; self.progressHUD.bezelView.color = [UIColor colorWithWhite:0.0 alpha:1.0]; self.progressHUD.contentColor = [UIColor whiteColor]; } - (void)hideProgressHUD { if (self.progressHUD != nil) { [self.progressHUD hideAnimated:YES]; [self.progressHUD removeFromSuperview]; self.progressHUD = nil; } }
そして私たちのトークンチェックリクエスト:
- (void)tokenCheckWithSuccess:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *token = [defaults objectForKey:@'token']; if (token == nil || [token length] == 0) { if (failure != nil) { failure(@'Invalid Token', -1); } return; } [self showProgressHUD]; NSMutableDictionary *params = [NSMutableDictionary dictionary]; [[self getNetworkingManagerWithToken:token] GET:@'/checktoken' parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) { [self hideProgressHUD]; if (success != nil) { success(responseObject); } } failure:^(NSURLSessionTask *operation, NSError *error) { [self hideProgressHUD]; NSString *errorMessage = [self getError:error]; if (failure != nil) { failure(errorMessage, ((NSHTTPURLResponse*)operation.response).statusCode); } }]; }
ここで、ViewController.m viewWillAppearメソッドで、このシングルトンメソッドを呼び出します。リクエストのシンプルさとViewController側の小さな実装に注目してください。
[[NetworkManager sharedManager] tokenCheckWithSuccess:^(id responseObject) { // Allow User Access and load content //[self loadContent]; } failure:^(NSString *failureReason, NSInteger statusCode) { // Logout user if logged in and deny access and show login view //[self showLoginView]; }];
それでおしまい!このスニペットは、起動時に認証を確認する必要があるすべてのアプリケーションで仮想的に使用できることに注意してください。
同様に、ログインのPOSTリクエストを処理できます:NetworkManager.h:
- (void)authenticateWithEmail:(NSString*)email password:(NSString*)password success:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure;
NetworkManager.m:
- (void)authenticateWithEmail:(NSString*)email password:(NSString*)password success:(NetworkManagerSuccess)success failure:(NetworkManagerFailure)failure { if (email != nil && [email length] > 0 && password != nil && [password length] > 0) { [self showProgressHUD]; NSMutableDictionary *params = [NSMutableDictionary dictionary]; [params setObject:email forKey:@'email']; [params setObject:password forKey:@'password']; [[self getNetworkingManagerWithToken:nil] POST:@'/authenticate' parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) { [self hideProgressHUD]; if (success != nil) { success(responseObject); } } failure:^(NSURLSessionTask *operation, NSError *error) { [self hideProgressHUD]; NSString *errorMessage = [self getError:error]; if (failure != nil) { failure(errorMessage, ((NSHTTPURLResponse*)operation.response).statusCode); } }]; } else { if (failure != nil) { failure(@'Email and Password Required', -1); } } }
ここで気を付けて、AppDelegateウィンドウのAlertController + Blocksを使用してアラートを追加するか、単に障害オブジェクトをViewControllerに送り返すことができます。さらに、ここにユーザー資格情報を保存するか、代わりにViewControllerに処理させることができます。通常、NetworkManagerと直接通信できる資格情報とアクセス許可を処理する個別のUserManagerシングルトンを実装します(個人設定)。
繰り返しになりますが、ViewController側は非常にシンプルです。
- (void)loginUser { NSString *email = @' [email protected] '; NSString *password = @'SomeSillyEasyPassword555'; [[NetworkManager sharedManager] authenticateWithEmail:email password:password success:^(id responseObject) { // Save User Credentials and show content } failure:^(NSString *failureReason, NSInteger statusCode) { // Explain to user why authentication failed }]; }
おっと! APIのバージョン管理とデバイスタイプの送信を忘れました。さらに、エンドポイントを「/ checktoken」から「/ token」に更新しました。ネットワークを一元化したため、これは非常に簡単に更新できます。コードを掘り下げる必要はありません。これらのパラメータはすべてのリクエストで使用するため、ヘルパーを作成します。
#define API_VERSION @'1.0' #define DEVICE_TYPE UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? @'tablet' : @'phone' - (NSMutableDictionary*)getBaseParams { NSMutableDictionary *baseParams = [NSMutableDictionary dictionary]; [baseParams setObject:@'version' forKey:API_VERSION]; [baseParams setObject:@'device_type' forKey:DEVICE_TYPE]; return baseParams; }
将来、これに任意の数の共通パラメーターを簡単に追加できます。次に、トークンチェックを更新し、次のようにメソッドを認証できます。
… NSMutableDictionary *params = [self getBaseParams]; [[self getNetworkingManagerWithToken:token] GET:@'/checktoken' parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) { … … NSMutableDictionary *params = [self getBaseParams]; [params setObject:email forKey:@'email']; [params setObject:password forKey:@'password']; [[self getNetworkingManagerWithToken:nil] POST:@'/authenticate' parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) {
ここで終了しますが、ご覧のとおり、共通のネットワークパラメーターとメソッドをシングルトンマネージャーに一元化したため、ViewControllerの実装が大幅に簡素化されました。将来の更新はシンプルかつ高速であり、最も重要なことは、ネットワークをユーザーエクスペリエンスから切り離すことです。次回、デザインチームがUI / UXのオーバーホールを要求したとき、私たちの仕事はすでにネットワーキング側で行われていることがわかります。
この記事では、ネットワーキングシングルトンに焦点を当てましたが、これらの同じ原則は、次のような他の多くの集中型機能にも適用できます。
iOSアプリケーションアーキテクチャにも焦点を当てましたが、これはAndroidやJavaScriptにも簡単に拡張できます。ボーナスとして、高度に定義された機能指向のコードを作成することで、アプリを新しいプラットフォームに移植する作業がはるかに迅速になります。
要約すると、上記のネットワークの例のように、主要なシングルトンメソッドを確立するために初期のプロジェクト計画に少し余分な時間を費やすことで、将来のコードをよりクリーンでシンプルに、より保守しやすくすることができます。
AFNetworkingは、iOSおよびmacOS用のオープンソースネットワーキングライブラリであり、RESTfulネットワーキングAPIを使用して開発者のタスクを簡素化し、成功、進行、および失敗の完了ブロックを含むモジュール式の要求/応答パターンを作成します。非常に活発な開発者コミュニティがあり、いくつかの最高のアプリで使用されています。
AWS認定ソリューションアーキテクトとは
シングルトンオブジェクトは、アプリケーションの存続期間中に存在するアプリケーション内のインスタンスが1つしかないクラスです。さらに、インスタンスは1つしかないことがわかっているため、メソッドやプロパティにアクセスする必要のある他のクラスから簡単にアクセスできます。