0

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

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

主要要求是:

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

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

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

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

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

//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 是代理,因此需要将每个 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

所以这里有问题:

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

2 回答 2

1

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

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

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

于 2013-10-05T19:29:49.423 回答
1

简短的回答

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

长答案

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

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

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

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

CompletionHandler定义如下:

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

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

于 2013-10-05T19:54:06.953 回答