tags:

views:

439

answers:

2

I'm designing a delegate method that is called when the remote server needs input from the delegate. The delegate at that point is responsible for filling in data to be sent to the server and also telling the server if there is an error at that point.

The delegate method I have right now is:

-(void)server:(MBServer*)server
 willSendData:(NSMutableString*)data
        error:(NSError**)error;

At this point, the delegate can provide data for the server to send by modifying the NSMutableString that is passed in. Additionally, it can create an NSError to be sent back by setting the error variable to the new NSError instance.

The problem I have with this is that the delegate method might be called from another thread via an NSInvocation. The delegate can create a new NSError object, but it will be autoreleased in the current thread before the other thread receives the response.

In a single-threaded app, this wouldn't be a problem because the delegator could just retain the response straight away but I can't do that in a multi-threaded design (again because the current event loop will autorelease it). The delegate MUST retain the error so the delegator can release it.

This is for a public api and I don't like the idea of requiring via documentation that implementers know they have to retain the error object. It seems too unsafe.

// You *must* retain the returned error before
// passing it back or you will crash

-(void)server:(MBServer*)server
 willSendData:(NSMutableString*)data
        error:(NSError**)error;

Other ideas I have thought of were to send an NSError as the return value (seems somewhat non-standard as most methods return a bool and take an NSError double-pointer). It also just looks weird.

-(NSError *)server:(MBServer*)server
      willSendData:(NSMutableString*)data;

I've also thought of passing a mutable dictionary and asking the delegate to fill in an error. The dictionary would then retain the error, safely getting it back across threads.

// Send your NSError response in the dictionary with the key
// MBServerErrorResponseKey

-(void)server:(MBServer*)server
 willSendData:(NSMutableString*)data
     response:(NSMutableDictionary*)response;

Has anyone dealt with this design problem and if so what was your solution?

Thank you.

A: 

One thing you could do is try something like

-(void)server:(MBServer*)server
 willSendData:(NSMutableString*)data
        shouldStop:(BOOL*)stopFlag;
-(NSError*)getLastErrorForServer:(MBServer*)server;

This would allow the delegate to signal the server that something went wrong by setting the stopFlag and then the server can call the getLastErrorForServer: method to get what actually went wrong. It might also make more sense to make the server:WillSendData: method return a BOOL instead of passing in a pointer.

Daniel
+3  A: 

How about this: instead of calling the delegate method directly, call a "trampoline" method via NSInvocation. The trampoline (which you would implement) would just call the delegate method, and retain the error before returning. Putting it another way: the trampoline (which would probably be a method on your server object) would just wrap the call to the delegate with the passed arguments, then retain the error and return.

Jesse Rusak
That's a great idea! And that allows me to use an NSString and not just a mutable one.So I'd create:@implementation MBServer-(void)MB_threadSafeServerWillSendData:(NSString**)string error:(NSError**){ [delegate server:self willSendData:string error:error]; [*string retain]; [*error retain];}@endand call that in a separate thread?
Michael Bishop
Yeah, that sounds about right to me. I think it might be clearer, though, if the method returns the NSString, (or even an NSData) though.
Jesse Rusak
Actually, one more thing: you probably want to pass the delegate to that trampoline as well - otherwise, if it changes between the request-to-invoke and the invocation, you'll get weird behavior. (Even weirder if it's changing during the trampoline's execution!) So perhaps:-(NSData*)MB_threadSafeDataToSendToDelegate:(id)localDelegate error:(NSError**) { *error = nil; NSData *data = [localDelegate dataToSendForServer:self error:error]; [*error retain]; return data; }
Jesse Rusak