iOS開発教程の一例使用問題詳細

5379 ワード

ガイド語
単例(Singletons)は、Cocoaのコアモードの一つである。iOSでは、UPplication、NSFileManagerなどの一例がよく見られます。それらは使いやすいですが、実際には多くの問題があります。だから、今度は自動的にdispatchを補完します。オンスコードの断片の時、このようにしたらどんな結果になるか考えてください。
何が単例ですか
「デザインモード」には一例の定義が与えられています。
単一の例モード:1つのクラスが1つのインスタンスだけであることを保証し、その全体的なアクセスポイントにアクセスすることを提供する。
単一の例モードは、共有リソースのためのクライアントクラスのユニークなインスタンスを生成するためのアクセスポイントを提供し、共有リソースにアクセスすることによって、このモードは柔軟性を提供する。
objective-cでは、以下のコードを使って一例を作成できます。

+(instancetype)sharedInstance
{
 static dispatch_once_t once;
 static id sharedInstance;
 dispatch_once(&once, ^{
 sharedInstance = [[self alloc]init];
 });
 return sharedInstance;
}
クラスが一例しかない場合には、アクセスポイントからアクセスしなければならない場合には、単一の例を使用することは、アクセスポイントの一意かつ一貫性を保証し、よく知られているため、非常に便利である。
単例における問題
グローバル状態
まず私たちは一つの共通認識を達成すべきです。「グローバル可変状態」は危険です。このようにすると、プログラムが理解しにくくなり、デバッグできなくなります。状態コードを削減する上で、対象に向けてプログラミングするべきです。
たとえば、下記のコード:

@implementation Math{
 NSUInteger _a;
 NSUInteger _b;
}

-(NSUInteger)computeSum
{
 return _a + _b;
}
このコードは計算したいです。aと_Bは加算して返します。しかし、このコードには多くの問題があります。
  • computteSumメソッドには、_aと_bパラメータとして。interfaceを検索してどの変数制御方法の出力を知るよりも、implemenationを検索して、より隠蔽に見えるが、エラーが発生しやすいことを表している。
  • 修正準備ができたら_aと_bの値をcomputeSumメソッドに呼び出させる場合、プログラマは、それらの値が他の2つの値を含むコードの正確さに影響しないことを明確にしなければならないが、マルチスレッドの場合には、このような判断を行うことは特に困難である。
  • 以下のコードを比較します。
    
    +(NSUInteger)computeSumOf:(NSUInteger)a plus:(NSUInteger)b
    {
     return a + b;
    }
    このコードの中で、aとbの従属は非常に明確であり、例の状態を変えてこの方法を呼び出す必要はなく、またこの方法の副作用を心配する必要はない。
    この例は単例と何の関係がありますか?実は、羊の皮をかぶった全体の状態です。一例は、どこでも使用されてもよく、従属を明確に宣言する必要はない。プログラム中のどのモジュールも簡単に[MySingleton sharedInstance]を呼び出すことができ、この単一の例のアクセスポイントを取得すると、これは任意の例と単一の例が相互作用すると発生する副作用がプログラム中のランダムなコードに影響を与える可能性があることを意味します。
    
    @interface MySingleton : NSObject
    
    +(instancetype)sharedInstance;
    
    -(NSUInteger)badMutableState;
    -(void)setBadMutableState:(NSUInteger)badMutableState;
    
    @end
    
    @implementation ConsumerA
    
    -(void)someMethod
    {
     if([[MySingleton sharedInstance] badMutableState]){
     //do something...
     }
    }
    
    @end
    
    @implementation ConsumerB
    
    -(void)someOtherMethod
    {
     [[MySingleton sharedInstance] setBadMutableState:0];
    }
    上のコードでは、ConsmeramanとCommerBはプログラムの中で完全に独立した二つのモジュールですが、CommerBの方法はCommerraの動作に影響を及ぼします。この状態の変化は、単例で過去を伝えました。
    このコードは、一例のグローバル性と状態性のために、CommerraとCommermer Bという無関係に見える二つのモジュール間に暗黙的に結合されている。
    オブジェクトライフサイクル
    もう一つの例の主な問題はそれらのライフサイクルです。
    例えば、アプリで友達リストを見せる機能を実現する必要があると仮定します。各友達は自分の顔写真を持っています。同時に、このアプリがこれらの友達の顔写真をダウンロードしてキャッシュしてほしいです。この時、前に単例の知識を勉強して、私達は下記のコードを書くかもしれません。
    
    @interface MyAppCache : NSObject
    
    +(instancetype)sharedCMyAppCache;
    
    -(void)cacheProfileImage:(NSData *)imageData forUserId:(NSString *)userID;
    -(NSData *)cachedProfileImageForUserId:(NSString *)userId;
    
    @end
    このコードは全く問題がないように見えます。運行もいいので、いつかアプリを開発し続けます。突然、ユーザーデータがグローバル単一の例に保存されていることを発見しました。ユーザーが登録する時、これらのデータを消去したいです。新しいユーザーが登録すると、新しいMyAppCacheを作成します。
    しかし問題は一例では、一例の定義は「一度作成して永久保存」の例です。実際には上の問題を解決する多くの方法があります。私たちはユーザーがログインした時にこの単例を破棄することができるかもしれません。
    
    static MyAppCache *myAppCache;
    
    +(instancetype)sharedMyAppCache
    {
     if(!myAppCache)
     {
     myAppCache = [[self alloc] init];
     }
     return myAppCache;
    }
    
    +(void)tearDown
    {
     myAppCache = nil;
    }
    上のコードは一例のパターンを歪めていますが、役割を果たします。
    実はこの方法を使ってこの問題を解決することができますが、価格が大きすぎます。一番重要な点は私たちがディアストを放棄したことです。Oneceは、メソッド呼び出し時のスレッドの安全を保証しており、現在はすべての呼び出しコードが同じ変数になります。MyAppcacheコードを使って実行する手順を明確にする必要があります。ユーザがログインしているときにたまたまバックグラウンドからこの方法を呼び出して画像を保存します。
    一方、この方法を実行するには、tearDownを確保する必要があります。この方法は、バックグラウンドタスクがまだ実行されていないときに呼び出すことはできません。あるいは、tearDownメソッドを実行するとバックグラウンドタスクはキャンセルされます。他の新しいMyAppcacheが作成され、古いデータを保存します。
    しかし、一例では明確なownerがないため、一つの例を破棄するのは非常に難しいです。
    だから、「MyAppcacheを一例にしないでください」と思うかもしれません。実は問題は一つの対象のライフサイクルはプロジェクトの初期にはうまく確定できないかもしれません。もし一つの対象のライフサイクルがプログラム全体のライフサイクルに合致すると仮定すれば、これは大幅にコードの開拓性を制限します。
    したがって、上記のすべては、「一例ではグローバル状態だけを維持し、この状態のライフサイクルはプログラムのライフサイクルと一致する」という観点を明らかにするためです。プログラム中に既に存在している単一の例については、批判的な検閲が必要である。
    テストに不利です
    この部分の原文については前の章に述べましたが、ソフトウェア開発においてテストは非常に重要な一環であると思いますので、このブロックの内容を単独で章を開いて、個人的な見解を入れてください。
    単一の例はずっとappのライフサイクルの中で生存していますから、テストを実行する時もずっと生存しています。これは一つのテストで他のテストに影響を与えるかもしれません。これはユニットテスト中のタブーです。
    したがって、ユニットテストを行う際には、一例を有効に破棄し、一例スレッドの安全性を維持する必要があります。上記では、
    「ただし、一例では明確なownerがないため(自分のライフサイクルを単独で管理しているため)、一つのケースを廃棄するのは非常に難しいです」
    両者は矛盾しているようですが、そうではないです。単例を簡略化して選択してもいいです。様々な例を持っているよりは、「本当の」一例のServiceRegistryだけを持っています。他の「潜在的」な例をServiceRegistryに引用してもいいです。このように他のシングルは一つのWnerがあります。ユニットテストを行う時に、単例を破壊することができます。ユニットテストの独立性を保証しました。
    一方、ServiceRegistryの存在により、他の「単一例」は一例ではなくなり、TDDの時には以前のmockが困難となった一例がよりシンプルなmockになる。
    結論
    グローバル可変状態は良くないということは知っていますが、一例を使う時、私たちはそれを私たちの嫌いなグローバル可変状態に変えてしまいます。
    オブジェクト指向プログラミングでは、可能な限り可変状態の作用域を減らす必要がありますが、単一の例ではこの考えとは逆方向に走ります。次の例を使う時は、この変数が本当に単一の例に値するかどうか考えてみてください。
    翻訳、修正はObj.ioからです。
    リンク:Avoiding Singleton Abuse
    締め括りをつける
    以上はこの文章の全部の内容です。本文の内容は皆さんの学習や仕事に対して一定の参考学習価値を持ってほしいです。ありがとうございます。