5

我正在将我的应用程序从 Syncronous 转换为 Asyncronous HTTP 请求,并且遇到了一个看起来需要对应用程序如何处理其数据进行大量修改的问题。让我试着解释一下

以前是这样的:

- Class1Class2并且Class3都是UIViewController -Helper 类 -Content 显示类的子类

他们做的事情大相径庭,但共同的特点是他们与助手类的交互。他们以多种不同的方式从用户那里收集请求的详细信息,然后最终将请求发送到帮助程序类。
当它同步完成时,助手类将返回数据。然后每个类将解释数据(XML 文件)并通过 segue 将它们传递给 Content 显示类

所以大致是这样的:

第一类:

//Get user input
SomeData *data = [helperclass makerequest];
id vcData = [data process];
[self performSegueWithIdentifier:@"segueIdentifier"];
---
- (void)prepareForSegue:(UIStoryboardSegue *)segue
{
    DestinationViewController *destination = (DestinationViewController *)segue.destinationViewController;
    destination.data = vcData;
}

内容展示类:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.data presentdata];
}

现在看起来像这样

为了解决这个问题,我首先让它与 Class1 一起工作,以便将修复程序部署到 class2 和 class3。所以 class1 和 helper 现在像这样交互

第一类:

//Get user input
SomeData *data = [helperclass makerequestWithSender:self];
id vcData = [data process];
[self performSegueWithIdentifier:@"segueIdentifier"];
---
- (void)prepareForSegue:(UIStoryboardSegue *)segue
{
    DestinationViewController *destination = (DestinationViewController *)segue.destinationViewController;
    destination.data = vcData;
} 

现在我面临的最大问题是如何将 helperclass 中的数据返回到Class1. 我设法让它工作

(void)makeRequestWithSender:(Class1*)sender
{
  [NSURLConnection sendAsynchronousRequest:...
     {
        [sender sendData:data];
     }
}

但是,当我将其推广到其他 2 个 GUI 类别时,这将构成我遇到困难的请求。我的第一个想法是设置,但在告诉我没有方法或类似方法sender:(id)的那一行失败了。[sender sendData:data]idsendData:

希望我在这里不是太含糊,你们可以提供帮助。如果需要,我将能够发布代码片段,但现在任何人都可以就如何构建此请求的代码提供更好的建议吗?

4

5 回答 5

6

您基本上想使用“观察者模式”或(可能)稍微更改的设置,因此您可以使用委托。

观察者模式

你通过NSNotificationCenterNSNotifications获得机制。您的 3 个不同的 UIViewController 子类每个都订阅特定的 NSNotification,并且您通过 NSNotificationCenter 发布通知来通知它们。

以下代码是如何在视图控制器子类中解决问题的示例:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    // subscribe to a specific notification
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doSomethingWithTheData:) name:@"MyDataChangedNotification" object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];  
    // do not forget to unsubscribe the observer, or you may experience crashes towards a deallocated observer
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
...
- (void)doSomethingWithTheData:(NSNotification *)notification {
    // you grab your data our of the notifications userinfo
    MyDataObject *myChangedData = [[notification userInfo] objectForKey:@"myChangedDataKey"];
    ...
}

在您的助手类中,数据更改后,您必须通知观察者,例如

-(void)myDataDidChangeHere {
    MyDataObject *myChangedData = ...;
    // you can add you data to the notification (to later access it in your viewcontrollers)
    [[NSNotificationCenter defaultCenter] postNotificationName:@"MyDataChangedNotification" object:nil userInfo:@{@"myChangedDataKey" : myChangedData}];
}

通过@protocol

假设您的所有 UIViewController 子类都驻留在父视图控制器中,您可以在帮助程序类中实现协议并使父视图控制器成为委托。然后父 viewcontroller 可以通过传递消息通知子 uiviewcontroller。

您的助手类声明可能如下所示(假设为 ARC):

@protocol HelperDelegate;

@interface Helper : NSObject

@property (nonatomic, weak) id<HelperDelegate> delegate;

...
@end

@protocol HelperDelegate <NSObject>

-(void)helper:(Helper *)helper dataDidChange:(MyDataObject*)data;

@end

在助手实现中,您将通过以下方式通知委托:

...
if ([self.delegate respondsToSelector:@selector(helper:dataDidChange:)]) {
    [self.delegate helper:self dataDidChange:myChangedDataObject];
}
...

您的父视图控制器需要成为助手类的委托并实现其协议;粗略的草图,在声明中

@interface ParentViewController : UIViewController <HelperDelegate>

并用于简短版本的实施

// you alloc init your helper and assign the delegate to self, also of course implement the delegate method

-(void)helper:(Helper *)helper dataDidChange:(MyDataObject*)data {
    [self.myCustomChildViewController doSomethingWithTheNewData:data];
}

除了..

您可能会问自己更喜欢哪种方法。两者都是可行的,主要区别在于,通过观察者模式,您可以“一次”通知更多对象,而协议只能有一个委托,并且如果需要,必须转发消息。有很多关于利弊的讨论。我建议您在下定决心后阅读它们(抱歉,没有足够的声誉来发布两个以上的链接,所以请在 stackoverflow 上搜索)。如果有不清楚的地方,请询问。

于 2013-07-12T16:11:13.570 回答
1

这里有一些合理的想法。详细说明/添加我的意见:

首先,哪个对象应该告诉下载者(HelperClass)开始下载?我的做法是在将呈现数据的视图控制器中执行此操作。所以我通常在 segue 之后启动网络请求(就像在viewWillAppear:提供的 vc 中一样),而不是之前。

接下来,当一个类需要执行为另一个类提供的代码时,我首先考虑使用block. 很多时候(并非总是)块比委托、通知、KVO 等更有意义并提供更易读的代码。例如,我认为 NSURLConnection 完成比委托更适合块。(Apple 有点同意,已经介绍了+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler)。

所以我的应用程序模式是这样的:

// Class1.m

// when user has completed providing input
...
// don't do any request yet.  just start a segue
[self performSegueWithIdentifier:@"ToContentDisplayClass" sender:self];
...

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

    // don't do a request yet, just marshall the data needed for the request
    // and send it to the vc who actually cares about the request/result

    if ([segue.identifier isEqualToString:@"ToContentDisplayClass"]) {

        NSArray *userInput = // collect user input in a collection or custom object
        ContentDisplayClass *vc = segue.destinationViewController;
        vc.dataNeededForRequest = userInput;
    }
    ...

然后在 ContentDisplayClass.m

// this is the class that will present the result, let it make the request

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    HelperClass *helper = [[HelperClass alloc]
                              initWithDataNeededForRequest:self.dataNeededForRequest];

    // helper class forms a request using the data provided from the original vc,
    // then...

   [helper sendRequestWithCompletion:^(NSURLResponse *response, NSData *data, NSError *error) {
        if (!error) {
            // interpret data, update view
            self.label.text = // string we pulled out of data
        } else {
            // present an AlertView? dismiss this vc?
        }
    }];

这取决于HelperClass实现的block形式NSURLConnection

// HelperClass.m

- (id)initWithDataNeededForRequest:(id)dataNeededForRequest {
   // standard init pattern, set properties from the param
}

- (void)sendRequestWithCompletion:(void (^)(NSURLResponse *, NSData *, NSError *))completion {

    NSURLRequest *request = ...
    // the stuff we need to formulate the request has been setup in init
    // use NSURLConnection block method

    [NSURLConnection sendAsynchronousRequest:request
        queue:[NSOperationQueue mainQueue]
        completionHandler:completion];
}

编辑 - 在开始网络请求之前进行 VC 转换有几个基本原理:

1)围绕成功案例构建标准行为:除非应用程序是关于测试网络连接,否则成功案例是请求有效。

2)应用程序的主要原则是响应,在用户操作后立即做一些明智的事情。因此,当用户执行某些操作来发起请求时,立即进行 vc 转换是好的。(取而代之的是什么?一个微调器?)。新呈现的 UI 甚至可以通过在运行时为用户提供一些新的东西来减少请求的感知延迟。

3)当请求失败时,应用程序应该做什么?如果应用程序并不真的需要请求有用,那么什么都不做是一个不错的选择,所以你会想要在新的 vc 上。更典型地,该请求是继续进行所必需的。UI 也应该“响应”请求失败。典型的行为是呈现一个提供某种形式的“重试”或“取消”的警报。对于任何一种选择,UI 想要的位置都在新的 vc 上。重试更加明显,因为它总是在尝试获取数据时出现。对于取消,“响应”取消的方式是回到旧的 vc,vc 转换回来并不难看,这是用户刚刚要求的。

于 2013-07-12T16:28:07.423 回答
0

我不是 100% 清楚您现在如何处理数据,但是要将您的数据更改为异步调用,我会使用块。例如,您当前的同步代码如下:

//Get user input
data = [helperclass makerequest]
sendData = [data process]

会变成这样:

//Get user input
data = [helperclass makerequestWithSuccess:^{
    sendData = [data process]
}];

使用成功块将允许您等待处理数据,直到makerequest完成。

您的新makerequest功能现在看起来像这样:

-(void)makerequestWithSuccess:(void (^)(void))success{
     // Put your makerequest code here
     // After your makerequest is completed successfully, call:
     success();
}

希望这可以帮助!

于 2013-07-12T15:39:42.280 回答
0

我不确定我过去所做的是否与您的问题相关,但我所做的是创建一个下载类,该类具有delegate具有单一方法的协议:-(void)downloadFinished:(id) data.

任何需要获取异步数据的类,都会创建此下载类的实例,并将自身设置为delegate. 我downloadFinished:connection:didFailWithError:和呼叫connectionDidFinishLoading:。然后,在委托中实现该方法时,我检查数据的类是否为NSDataor NSError,并评估该数据是否适合该类。

于 2013-07-12T15:48:41.977 回答
0

我不确定我是否正确理解了您的问题,但如果是这样的话:

  1. 异步启动任务 A。
  2. 当任务 A 成功完成后,获取其结果并启动任务 B,其输入为结果 A。
  3. 当任务 B 成功完成后,获取其结果并启动任务 C,其输入为结果 B。
  4. ...
  5. 成功完成后,高兴,否则打印错误。

代码示例如下所示:

typedef (void)(^completion_block_t)(id result);

-(void) asyncTaskA:(completion_block_t)completionHandler;
-(void) asyncTaskBWithInput:(id)input completion:(completion_block_t)completionHandler;    
-(void) asyncTaskCWithInput:(id)input completion:(completion_block_t)completionHandler;
-(void) asyncSomethingWithCompletion:(completion_block_t)completionHandler;

-(void) asyncSomethingWithCompletion:(completion_block_t)completionHandler
{
    [self asyncTaskA:^(id resultA){
        if (![resultA isKindOfClass:[NSError class]]) {
            [self asyncTaskBWithInput:resultA completion:^(id resultB){
                if (![resultB isKindOfClass:[NSError class]]) {
                    [self asyncTaskCWithInput:resultB completion:^(id resultC) {
                        completionHandler(resultC);                         
                    }];
                }
                else {
                    completionHandler(resultB); // error;
                }
            }];
        }
        else {
            completionHandler(resultA); // error
        }    
    }];
}

你像这样使用它:

[self asyncSomethingWithCompletion:^(id result){
    if ([result isKindOfClass:[NSError class]]) {
        NSLog(@"ERROR: %@", error);
    }
    else {
        // success!
        self.myData = result;
    }
}];

“延续”和错误处理使这有点令人困惑(而 Objective-C 语法并没有真正增加可读性)。


另一个第三方库支持的例子:

同样的逻辑可以写成这样:

-(Promise*) asyncTaskA;
-(Promise*) asyncTaskBWithInput;    
-(Promise*) asyncTaskCWithInput;
-(Promise*) asyncSomething;

- (Promise*) asyncSomething 
{
    return [self asyncTaskA]
    .then(id^(id result) {
       return [self asyncTaskBWithInput:result];
    }, nil)
    .then(id^(id result) {
       return [self asyncTaskCWithInput:result];
    }, nil);
}

它的用法如下:

[self asyncSomething]
.then(^(id result) {
    self.myData = result;
    return nil;
},  
^id(NSError* error) {
    NSLog(@"ERROR: %@", error);
    return nil;
});

如果你更喜欢后者,可以在 GitHub 上找到“Promise”框架:RXPromise - 我是作者;)

于 2013-07-12T16:14:38.967 回答