views:

403

answers:

1

Hi,

I'm relatively new to Cocoa/ObjC. Could someone please help me change my code to use asynchronous network calls? Currently it looks like this (fictional example):

// Networker.m
-(AttackResult*)attack:(Charactor*)target {
    // prepare attack information to be sent to server
    ServerData *data = ...;
    id resultData = [self sendToServer:data];
    // extract and return the result of the attack as an AttackResult
}

-(MoveResult*)moveTo:(NSPoint*)point {
    // prepare move information to be sent to server
    ServerData *data = ...;
    id resultData = [self sendToServer:data];
    // extract and return the result of the movement as a MoveResult
}


-(ServerData*)sendToServer:(ServerData*)data {
    // json encoding, etc
    [NSURLConnection sendSynchronousRequest:request ...]; // (A)
    // json decoding
    // extract and return result of the action or request
}

Notice that for each action (attack, move, etc), the Networker class has logic to convert to and from ServerData. It is unacceptable to expect the other classes in my code to deal with this ServerData.

I need to make line A an asynchronous call. It seems that the correct way to do this is to use [NSURLConnection connectionWithRequest:...delegate:...] implementing a callback to do the post-processing. This is the only way I can think to do it:

//Networker.m
-(void)attack:(Charactor*)target delegate:(id)delegate {
    // prepare attack information to be sent to server
    ServerData *data = ...;
    self._currRequestType = @"ATTACK";
    self._currRequestDelegate = delegate;
    [self sendToServer:data];
    // extract and return the result of the attack
}

-(void)moveTo:(NSPoint*)point delegate:(id)delegate {
    // prepare move information to be sent to server
    ServerData *data = ...;
    self._currRequestType = @"MOVE";
    self._currRequestDelegate = delegate;
    [self sendToServer:data];
    // extract and return the result of the movement
}


-(void)sendToServer:(ServerData*)data {
    // json encoding, etc
    [NSURLConnection connectionWithRequest:...delegate:self] // (A)
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    //json decoding, etc
    switch( self._currRequestType ) {
        case @"ATTACK": {...} // extract and return the result of the attack in a callback
        case @"MOVE": {...} // extract and return the result of the move in a callback
    }
}

However, this is very ugly and not thread safe. What is the proper way to do this?

Thanks,

+1  A: 

One option is to have one object instance per command/request; as an added bonus you can then use subclasses so that the handling of the type is polymorphic, rather than based on a big switch statement. Make a base command object with an initWithDelegate: method (then have specialized inits in the subclasses corresponding to commands that need arguments) and methods for the basic sending/receiving plumbing. Each subclass can implement a handleResponse: or similar that's called from your baseclass connectionDidFinishLoading:.

If you want to hide that from the clients of this service, your attack:, moveTo:, etc. methods can hide the instantiation of these objects, so the clients would be interacting with the same API.

smorgan
That's a good idea, but it seems like a lot of extra code.
brainfsck
If you give it a try, you'll likely find that it's not as much extra code as you think--all you would need to do is spread your existing code over a few classes. But yes, sometimes good designs take a little more code. Understandability and maintainability should be much more important considerations than the raw number of lines.
smorgan
It was a pain to implement, but at least now I can do things like keep track of the current request and stop/retry it.
brainfsck