When using delegates, need better way to do sequen

2020-07-23 05:28发布

问题:

I have a class WebServiceCaller that uses NSURLConnection to make asynchronous calls to a web service. The class provides a delegate property and when the web service call is done, it calls a method webServiceDoneWithXXX on the delegate.

There are several web service methods that can be called, two of which are say GetSummary and GetList.

The classes that use WebServiceCaller initially need both the summary and list so they are written like this:

-(void)getAllData {
    [webServiceCaller getSummary];
}
-(void)webServiceDoneWithGetSummary {
    [webServiceCaller getList];
}
-(void)webServiceDoneWithGetList {
    ...
}

This works but there are at least two problems:

  1. The calls are split across delegate methods so it's hard to see the sequence at a glance but more important it's hard to control or modify the sequence.
  2. Sometimes I want to call just GetSummary and not also GetList so I would then have to use an ugly class-level state variable that tells webServiceDoneWithGetSummary whether to call GetList or not.

Assume that GetList cannot be done until GetSummary completes and returns some data which is used as input to GetList.

Is there a better way to handle this and still get asynchronous calls?

Update based on Matt Long's answer:

Using notifications instead of a delegate, it looks like I can solve problem #2 by setting a different selector depending on whether I want the full sequence (GetSummary+GetList) or just GetSummary. Both observers would still use the same notification name when calling GetSummary. I would have to write two separate methods to handle GetSummaryDone instead of using a single delegate method (where I would have needed some class-level variable to tell whether to then call GetList).

-(void)getAllData {
    [[NSNotificationCenter defaultCenter] addObserver:self
             selector:@selector(getSummaryDoneAndCallGetList:) 
                 name:kGetSummaryDidFinish object:nil];
    [webServiceCaller getSummary];
}
-(void)getSummaryDoneAndCallGetList {
    [NSNotificationCenter removeObserver]
    //process summary data

    [[NSNotificationCenter defaultCenter] addObserver:self
             selector:@selector(getListDone:) 
                 name:kGetListDidFinish object:nil];
    [webServiceCaller getList];
}
-(void)getListDone {
    [NSNotificationCenter removeObserver]
    //process list data 
}


-(void)getJustSummaryData {
    [[NSNotificationCenter defaultCenter] addObserver:self
             selector:@selector(getJustSummaryDone:)     //different selector but
                 name:kGetSummaryDidFinish object:nil];  //same notification name
    [webServiceCaller getSummary];
}
-(void)getJustSummaryDone {
    [NSNotificationCenter removeObserver]
    //process summary data
}

I haven't actually tried this yet. It seems better than having state variables and if-then statements but you have to write more methods. I still don't see a solution for problem 1.

回答1:

It's true that obtaining the results of your web services calls are (and should be) asynchronous, however, you want your calls to happen in a sequence. One way would be to wait for the first one to finish before calling the second and post a notification when the first is done. So, in -connectionDidFinishLoading of your first request (in your webServiceCaller, I'm assuming), post a notification back to your controller telling it that the first request finished successfully. Something like:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [connection release];
    // receivedData is an NSMutableData object we've been appending
    // to in the -didReceiveData delegate. We'll pass it in the
    // notification so we can hand the data off to the next request.
    [[NSNotificationCenter defaultCenter] 
           postNotificationName:kGetSummaryDidFinish object:receivedData];
}

Then, back in your controller, register for that notification:

- (void)viewDidLoad;
{
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self
             selector:@selector(getSummaryDidFinish:) 
                 name:kGetSummaryDidFinish object:nil];
}

- (void) getSummaryDidFinish:(NSNotification*)notification;
{
    // If you needed the downloaded data, we passed it in
    NSData *data = [notification object];

    // Decide here if you want to call getList or not.
    if (someConditionOfDataObjectIsTrue)
        [webServiceCaller getList];
}

If you have a lot of calls that wait on each other like this, it can get pretty confusing and hard to maintain, so there may be a design pattern you should consider (not that one comes to mind at the moment). However, this method has worked pretty well for me. Notifications help it to make more sense as you can call all of your requests from your controller when you receive a notification that meets some criteria.

Hope that makes sense.