I am relatively new to Objective C, and have an architecture question regarding creating custom delegates around existing delegates. It’s probably best described in pseudocode. Apologies for the length, I couldn’t determine how to make it more succinct.
Overall desired architecture summary
I have a class in some library that makes a call to a web service, which takes a delegate for callbacks when the web service returns asynchronously. This low-level library is wrapped in a facade (a singleton) to shield clients from knowledge of it, and the facade methods can take a delegate that effectively wraps the low-level library’s delegates, so clients can assign themselves as delegates when the facade (itself a delegate) gets data back from web service calls).
In other words, client instance A calls the facade [f makeCallWithDelegate:self], and the facade hides its internal workings so A only has to implement the facade’s delegate’s protocol.
Pseudocode
The low level class:
// The low-level class that calls a web service
// LowLevelClass.h
@protocol LowLevelClassDelegate
- (void)success:(LowLevelClass*) c;
@end
@interface LowLevelClass
- (void)makeAsynchronousCallWithDelegate:(id <LowLevelClassDelegate>) d;
@end
// LowLevelClass.m omitted
The facade wrapping the low-level class. The issue is noted in the makeCallWithDelegate selector, and in the success delegate callback:
// partial Facade.h
@protocol FacadeDelegate
- (void)success;
@end
@interface Facade <LowLevelClassDelegate> // the facade handles the delegate calls of the LowLevelClass
...
- (void)makeCallWithDelegate:(id <FacadeDelegate>) d;
...
@end
// Facade.m (pseudocode)
@implementation Facade
- (void)makeCallWithDelegate:(id <FacadeDelegate>) the_delegate {
LowLevelClass * llc = ... // get instance
[llc makeAsynchronousCallWithDelegate:self]; // delegate to self, catch events
// Issue: have to somehow pass the_delegate to the "success" method below,
// or have it available
}
// LowLevelClassDelegate implementation, hands result to the FacadeDelegate
- (void)success:(LowLevelClass*) c {
// Issue: need the_delegate to get down here somehow, or be reachable.
[the_delegate success]
}
@end
The client:
// Client.m fragment, client implements the FacadeDelegate protocol
...
- (void)makeFacadeGetData {
[facade makeCallWithDelegate:self];
}
- (void)success {
NSLog(@"Hooray");
}
...
Recap
The Client instance calls the facade and passes itself as a delegate. The facade in turn calls the low level class instance, passing the facade itself as a delegate. The facade hears the response from the low level class instance, and returns a nicer result to the Client instance. The problem I am working around is how to ensure that the correct facade delegate is getting used when wrapping the low-level delegate’s response.
Reiterating the above, with instances: Client instance A calls the facade [f makeCallWithDelegate:self]. Client instance B also calls the facade [f makeCallWithDelegate:self]. I need to be sure that client A is used as the delegate for client A’s call, and client B is used as the delegate for client B’s call.
Notes impacting the design
The LowLevelClass can’t be modified. If it could, I would perhaps pass in the delegate to call as a “userdata” field, or something … I’m not a fan of that approach anyway, because that pollutes a lower-layer class with knowledge of higher-layer classes.
The facade is a singleton, for a few reasons. Perhaps this could be changed, and I could create multiple facade instances.
Possible Solutions
A. Now, if I could guarantee that the facade was going to be used by a single client at a time, I could just store the_delegate in a Facade member variable, and I could call it in the Facade success: selector; however, I can’t guarantee that. Several different client instances can call the facade, and I need to be sure that each client’s call’s delegate is the client itself, and not some other client (using the instance example above, A needs to handle A’s call, and B needs to handle B’s).
B. I could make the facade a non-singleton class, and just store the_delegate in a member variable. Maybe that’s the best solution … something feels wrong about it though.
C. I was thinking that perhaps I could assign a unique key to each call to makeCallWithDelegate, and the facade singleton instance would store a dictionary of the call keys and the delegate passed in for that call. (Note: The only key that makes sense to me is the actual value of the LowLevelClass pointer — after all, if I generate a random string ID, I’m just deferring the problem — but this somehow feels shaky.) The facade’s “success” method would have access to the dictionary, and so would call the correct delegate:
// Facade.m (pseudocode)
@implementation Facade
- (void)makeCallWithDelegate:(id <FacadeDelegate>) the_delegate
{
LowLevelClass * llc = ... // get instance
NSString* uniqueKey = makeUniqueKeyFromPointerValue(llc);
[self.delegateDictionary setObject:the_delegate forKey:uniqueKey];
[llc makeAsynchronousCallWithDelegate:self]; // delegate to self, catch events
}
// LowLevelClassDelegate implementation, hands result to the FacadeDelegate
- (void)success:(LowLevelClass*) c
{
NSString* uniqueKey = makeUniqueKeyFromPointerValue(c);
id<FacadeDelegate> del = [self.delegateDictionary objectForKey:uniqueKey];
[del success]
}
@end
I hope that the above is sufficiently detailed and clear enough. After all that, I feel confused myself (again, new to objective C).
If you’ve made it this far, I thank you (and congratulate you). I’d like to hear any suggestions as to what would be a good design for this problem.
Thanks very much for your time.
I believe @lnafziger’s correct above, and B is the best answer (change the facade into a regular class, non-singleton, which also moves the design away from singleton issues). I’ll wait to see if any other suggestions arise before accepting this as the answer.