0

Web から解析されたデータを提供するモデル クラスを開発しています。もちろん、アプリケーションをレスポンシブにしたいので、ネットワーキングは別のスレッド/キューで行う必要があります。

これにより、クラスの @interface をどのように設計すればよいでしょうか?という疑問が生じます。

主な要件は次のとおりです。

  • モデルからビューコントローラーにデータを配信する必要があります:);
  • メイン (UI) スレッドをブロックするべきではありません。
  • 理解しやすく、他の開発者がフォローしやすいものにする必要があります。

WWDC2012 のビデオ「Building Concurrent User Interfaces on iOS」から学んだことから、Apple はコンカレント コードをモデルを使用するクラス自体に移動することを推奨しています。

ViewController に NSArray* 形式の最新の投稿を提供する Posts クラス (Posts.h/.m) があるとします。

オプション I -- 並行性はクラス ユーザーにあります

クラス自体は並行ではありませんが、ユーザーは次のとおりです。

//Posts.h:
@interface Posts : NSObject

- (NSArray*)getPostsForUser:(NSString*)user;

@end


//ViewController.m
@implementation ViewController

- (void)someFunctionThatUpdatesUI
{
    //<...>
    NSOperationQueue *q = [[NSOperationQueue alloc] init];
    [q addOperationWithBlock:^{
        NSArray *currentPosts = [Posts shared] getPostsForUser:@"mike";
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    //UI should be updated only on main thread
            [self updateUIWithPosts:currentPosts]; 
        }];
    }];
    //<...>
}

このアプローチの主な欠点は、すべての ViewController でほぼ同じコードを繰り返す必要があることです。それらが数十個ある場合はどうなりますか?

オプション II -- 完了ハンドラー パターンによる並行性

現在アプリで使用している 2 番目のオプションは、完了ハンドラー パターンです。完了ハンドラーは、長いネットワークが実行された後にのみ呼び出されるため、メイン スレッドをブロックしません。

//Posts.h:
@interface Posts : NSObject

- (NSError*)getPostsForUser:(NSString*)user 
    withCompletionHandler:(void(^)(NSArray*))handler;

@end

@implementation Posts

- (NSError*)getPostsForUser:(NSString*)user 
    withCompletionHandler:(void(^)(NSArray*))handler
{
    //<...>
    dispatch_async(dipatch_get_global_queue(0, 0), ^{
        //Do some may-be-long networking stuff here, 
        //parse, etc and put it into NSArray *result
        dispatch_async(dipatch_get_main_queue(), ^{
            handler(result);
        });
    });
    //<...>
}

//ViewController.m
- (void)someFunctionThatUpdatesUI
{
    //<...>
    [Posts shared] getPostsForUser:@"mike" 
         withCompletionHandler:^(NSArray* posts){
        //updateUI with posts
    }];
}

私の観点からは、この方法は良いのですが、@interface はかなり複雑で、メソッド名は長く、(私の観点からは)難読化されています。

オプション III -- 委任パターン

私が目にするもう 1 つのオプションは、デリゲート パターンです。気になるのは、ViewController が 1 つしかデリゲートにならない可能性があることです。そのため、すべての VC を viewWillAppear でデリゲートとして設定する必要が生じますが、これは忘れがちです。

//Posts.h:
@protocol PostUpdate<NSObject>

- (void)updateUIWithPosts:(NSArray*)posts FromUser:(NSString*)user;

@end

@interface Posts

- (NSError*)updatePostsFromUser:(NSString*)user;
@property(nonatomic, weak) id<PostUpdate> delegate;

@end

//ViewController.m:
@implementation ViewController<PostUpdate>

- (void)viewWillAppear
{
    //<...>
    [Posts shared].delegate = self;
}

- (IBAction)getLatestsPostButtonPressed:(id)sender
{
    [[Posts shared] updatePostsFromUser:@"mike"];
}

// protocol PostUpdate
- (void)updateUIWithPosts:(NSArray*)posts FromUser:(NSString*)user
{
    //updating UI with posts
}

@end

だからここに質問があります:

  • Model から Controller にノンブロッキングでデータを配信するための要件に適合するパターンは他にあるでしょうか?
  • あなたの経験、実践、または理論的知識に基づいて、どのオプションをお勧めしますか?
4

2 に答える 2

1

簡潔な答え

あなたの場合に最も適したソリューションであるため、オプション II を使用することをお勧めします。

長い答え

まず第一に、3 つの解決策はどれも間違っていませんが、あなたのケースに最適な解決策を見つけようとしているだけです。

• option-I の問題は、それが常に同期的であり、非同期にする必要がある間、呼び出し元のスレッドをブロックすることです。したがって、常にバックグラウンド スレッドから呼び出していることに気付くでしょう。つまり、多くの繰り返しが発生します。メンテナンスを困難にするコード (このオプションは、メソッドを時々非同期にし、ほとんどの場合同期にする必要がある場合に適したソリューションです)。

• オプション II は、データの準備ができたときに呼び出し元スレッドに通知する方法を提供することで問題を解決します。追加されたパラメーターは、使いやすさと柔軟性に比べて実際には不利ではありません。また、場合によっては、追加されたパラメーターが実際には必要ないと思われる場合は、そのパラメーターを使用しない別の同期バージョンのメソッドを単純に作成できます。

- (NSArray *)fetchPostsForUser:(NSString*)user; /// Synchronous
- (void)fetchPostsForUser:(NSString*)user       /// Asynchronous
               completion:(CompletionHandler)completion;

CompletionHandlerは次のように定義されます。

typedef void (^CompletionHandler)(NSArray *result, NSError *error);

• 3 番目のオプションは、問題に対する適切な解決策ではありません。デリゲートは、以前に呼び出されたメソッドへの応答を配信するためではなく、クラス自体に関するイベントを配信するために使用する必要があります。また、デリゲートは 1 つしか持てないことに注意してください。これは、2 つの異なるコントローラーからそのようなメソッドを同時に呼び出すことができないことを意味します。

于 2013-10-05T19:54:06.953 に答える
1

オプション1は、あなたが言及した理由で悪いです。

2 つのネットワーク リクエストが同時に進行している場合、ネットワーク データがリクエストされた順序とは異なる順序で返される可能性があるため、オプション 3 は不適切です。これにより、データによっては、UI を適切に更新することが必要以上に難しくなる場合があります。(たとえば、項目が順不同で表示される場合があります。)

オプション 2 が理想的です。オプション 3 の利点に加えて、Objective-C ブロックのスコープ キャプチャの利点が得られます。おおよそこのパターンに従っているネットワーク ライブラリAFNetworkingを確認することをお勧めします。追加の考慮事項の 1 つは、データのネットワーク化とシリアル化を、データの永続性/処理とは別のクラスに保持する必要があることです。たとえば、あるクラスはデータをダウンロードし、それをPostオブジェクトの配列に変換して、コールバック ブロックに送信する必要があります。そのコールバック ブロックは、View Controller である場合もあれば、データをディスクにキャッシュする別のクラスである場合もあります (たとえば、NSCoder や Core Data を使用します)。このアプローチにより、コードを可能な限り柔軟に保つことができます。

于 2013-10-05T19:29:49.423 に答える