在类/异步请求/ iOS之间传递数据

时间:2013-07-12 15:22:54

标签: iphone ios objective-c ipad oop

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

以前是这样的:

- Class1Class2Class3都是UIViewController的子类  -Helper类   - 内容显示类

他们做的事情大不相同,但共同的特点是他们与助手班级的互动。他们以多种不同的方式从用户收集请求的详细信息,然后最终向帮助者类发送请求 当它同步完成时,助手类将返回数据。然后,每个类将解释数据(XML文件)并通过segue

将它们传递给Content显示类

大致如此:

的Class1:

//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现在像这样进行交互

的Class1:

//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]行失败,告诉我id没有方法sendData:或类似方法。

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

5 个答案:

答案 0 :(得分:6)

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

观察者模式

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

以下代码是您如何在viewcontroller子类中解决问题的示例:

- (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子类都驻留在父视图控制器中,您可以在助手类中实现协议,并使父视图控制器成为委托。然后父视图控制器可以通过传递消息来通知子视图控制器。

您的帮助程序类声明可能如下所示(假设为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)。如果不清楚,请询问。

答案 1 :(得分:1)

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

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

接下来,当一个类需要执行为另一个类提供的代码时,我首先想一想使用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想要的位置都在新的vc上。重试更为明显,因为它在尝试获取数据时总是如此。对于取消,“响应”取消的方式是回到旧的vc,vc转换回来并不难看,这是用户刚要求的。

答案 2 :(得分: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();
}

希望这有帮助!

答案 3 :(得分:0)

我不确定我过去所做的事情是否与您的问题相关,但我所做的是使用单一方法创建一个具有delegate协议的下载类:{{ 1}}。

任何需要获取异步数据的类,都会创建此下载类的实例,并将自己设置为-(void)downloadFinished:(id) data。我从delegatedownloadFinished:致电connection:didFailWithError:。然后,在委托中实现该方法时,我会检查数据的类是connectionDidFinishLoading:还是NSData,然后评估该数据是否适用于该类。

答案 4 :(得分:0)

我不确定我是否正确理解了您的问题,但如果它有点:

  1. 异步启动任务A.
  2. 当任务A成功完成时,获取其结果并启动任务B,其输入结果为A。
  3. 当任务B成功完成时,获取其结果并启动其输入为结果B的任务C.
  4. ...
  5. 成功完成后,请高兴,否则打印错误。
  6. 代码示例如下所示:

    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 - 我是作者;)