【问题标题】:Preferable design pattern for model with background networking具有背景网络的模型的首选设计模式
【发布时间】:2018-03-07 13:56:40
【问题描述】:

我正在开发一个模型类,它提供从网络解析的数据。当然,我希望我的应用程序具有响应性,因此网络应该在单独的线程/队列上完成。

这就引出了一个问题:我应该如何设计我的班级的@interface

主要要求是:

  • 它应该将数据从模型传递到视图控制器:) ;
  • 不应阻塞主 (UI) 线程;
  • 它应该易于其他开发人员理解和遵循。

根据我从 WWDC2012 视频中了解到的“在 iOS 上构建并发用户界面”Apple 建议将并发代码移动到使用模型的类本身。

假设我们有 Posts 类 (Posts.h/.m),它应该为 ViewController 提供 NSArray* 格式的最新帖子。

选项一——并发在类用户中

类本身不是并发的,但是用户是:

//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 - 与完成处理程序模式的并发

我目前在我的应用程序中使用的第二个选项是完成处理程序模式。由于完成处理程序仅在 执行了一些长时间的联网之后才被调用,因此它不会阻塞主线程:

//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 相当复杂,方法名称很长并且(在我看来)被混淆了。

选项三——委托模式

我看到的另一个选项是委托模式。困扰我的是,可能只有一个ViewController是delegate,因此需要在viewWillAppear中将每个VC都设置为delegate,这很容易忘记。

//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

以下是问题:

  • 还有哪些模式可以满足以非阻塞方式将数据从模型传递到控制器的要求?
  • 根据您的经验、实践或理论知识,您会推荐哪个选项?

【问题讨论】:

  • 我不是母语人士,如果你是,请告诉我语法、句法或任何其他错误。

标签: ios objective-c design-patterns


【解决方案1】:

简答

我建议使用 option-II,因为它是最适合您情况的解决方案。

长答案

首先,这三种解决方案都没有错,但我们只是想为您的情况找出其中最好的一种。

• option-I 的问题在于它总是 同步 并且会在您需要它是 异步 时阻塞调用线程,因此,您会发现你自己总是从后台线程调用它,这意味着会有很多重复的代码,这会使维护变得更加困难(如果你需要方法有时是异步的并且大部分时间是同步的,这个选项可能是一个很好的解决方案)。

• 选项-II 通过提供一种在数据准备好时通知调用者线程的方法来解决问题,与它所提供的易用性和灵活性相比,添加的参数并不是真正的缺点。如果你认为在某些情况下你真的不需要添加的参数,你可以简单地制作另一个没有该参数的方法的同步版本:

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

CompletionHandler 定义如下:

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

• 第三个选项对于您的问题并不是一个很好的解决方案。委托应该用于传递关于类本身的事件,而不是传递对先前调用的方法的响应。另外,请注意,您只能有一个委托,这意味着您不能同时从两个不同的控制器调用此类方法。

【讨论】:

    【解决方案2】:

    由于您提到的原因,选项 1 不好。

    选项 3 不好,因为如果您同时有 2 个网络请求,您可能会以不同于请求的顺序返回网络数据。这可能会使正确更新 UI 变得比必要更困难,具体取决于您的数据。 (例如,项目可能出现乱序。)

    选项 2 似乎很理想。它具有选项 3 的优点,而且您还可以获得 Objective-C 块的范围捕获优势。您可能想查看网络库AFNetworking,它大致遵循此模式。另一个考虑因素是,您应该将数据网络和序列化与数据持久性/处理放在一个单独的类中。例如,一个类应该下载数据,将其转换为Post 对象的数组,并将其发送到回调块。该回调块可以是一个视图控制器,也可以是一个将数据缓存到磁盘的单独类(例如,使用 NSCoder 或 Core Data)。这种方法将使您的代码尽可能灵活。

    【讨论】:

    • 感谢您的回答。顺便说一句,您似乎是一位经验丰富的开发人员,您将如何在实践中设计这样一个类?你会选择“完成处理程序”模式吗?
    • 我通常使用完成处理程序,是的。
    猜你喜欢
    • 1970-01-01
    • 2010-10-14
    • 2016-04-18
    • 2014-07-11
    • 1970-01-01
    • 1970-01-01
    • 2011-08-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多