2013-10-05 112 views
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中都有相同的代码。如果有几十个呢?

方案二 - 并发与完成处理模式

,我现在在我的应用程序中使用第二个选项是完成处理模式。如果进行一些长期的网络只完成处理程序被调用,它不会阻塞主线程:

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

所以这里的问题:

  • 还有什么模式适合于提供从模型数据非阻塞方式控制器要求?
  • 根据你的经验,实践或理论知识,你会推荐什么样的选择?
+0

我不是母语,所以如果你是,请告诉我关于语法,语法或任何其他错误。 –

回答

1

简短的回答

我建议使用选项-II会因为它在你的情况下最合适的解决方案。

龙答案

首先,没有这三个解决方案是错误的,但我们只是试图找出其中最好的一个了你的情况。

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

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

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

CompletionHandler定义如下:

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

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

1

选项1因为你提到的原因而不好。

选项3很糟糕,因为如果您有两个网络请求同时进行,您可能会以不同于要求的顺序返回网络数据。根据您的数据,这可能会比正常更新您的用户界面更困难。 (例如,物品可能会出现故障。)

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

+0

感谢您的回答。顺便说一句,看起来你是一位非常有经验的开发人员,你将如何在你的实践中设计这样的课程?你会选择“完成处理程序”模式吗? –

+1

我通常使用完成处理程序,是的。 –