Recently I had the same issue you were having, sort of. And I came
up with
a solution I really liked.
When I was playing with Distributed Objects, I fell in love with the
abstract simplicity. However, it blocks and that's bad. It's even
worse when
the server stops responding, because you could potentially have a
60 second
timeout before the single method will return. It's a potential
disaster.
So, I wrote an elegant compromise. Code is still written inline, no
callbacks or delegate messages needed. But, it requires Blocks (and
thus
10.6) to work.
Essentially, I wrote some code on top of AsyncSocket (which is a
brilliant
framework by the way) that allows me to wrap up ObjC messages as
NSData,
send it across the server, and unpack it on the other side. The
other side
then responds to the ObjC message as if it was called right inside
the
application. (All this is thanks to NSInvocation's ability to
introspect an
ObjC message, by the way).
The problem came when I had to return values. As long as the return
value
was void, this worked like a charm. But once I wanted to return an
array of
strings or a number, I had to define a method in the sender's
protocol to
receive such information. This is akin to your "thousands of delegate
messages" you would have to implement, as you stated.
So, using Blocks and NSInvocation and AsyncSocket, I ended up
writing code
that allows me to write code like this:
// protocol.h
@protocol ServerProtocol
- (NSNumber*) calculatePiAndKillTime:(NSNumber*)shouldKillTime;
@end
// client.m
- (void) someMethod {
id <ServerProtocol> server;
NSNumber *sure = [NSNumber numberWithBool:YES];
[[server calculatePiAndKillTime: sure]
returnedValue:^(id value) {
// this will be called later on at some point
NSLog(@"pi = %@", value);
}]
}
// server.m
- (NSNumber*) calculatePiAndKillTime:(NSNumber*)shouldKillTime {
if ([shouldKillTime boolValue])
// synchronously watch some film
[self goWatchTheNewStarTrekFilmFrom2009];
return [NSNumber numberWithFloat: 3.14];
}
All methods sent to a destination's proxy are sent asynchronously.
And, as
you can see, the return value of the method -
calculatePiAndKillTime: is not
actually an NSNumber, but rather a proxy that waits for a response
from the
destination. When the destination responds to the source with a
return
value, the method -returnedValue: is called with the value.
But that's only half of the coolness.
The other half is that methods can simply return the value they
want right
inside the method, no hacks necessary or anything by the
programmer. In this
case, we just use this line of code: return [NSNumber
numberWithFloat:
3.14]; and then the NSNumber object is packaged up and sent back to
the
source through the proxy, all automagically.
The main downfall of this is that every argument and return value
must be
an ObjC type, no scalars or structs or anything else will work with
this
system. (Mike Ash explains pretty well on this blog why trying to
support
those things can lead to some unfixable trickiness, which I just
wanted to
avoid altogether.)
If you can't support 10.6, then, this won't work. But hopefully you
can
soon ;)
Good luck.
-Steven
On Wed, Jan 20, 2010 at 11:39 AM, Carter R. Harrison <
carterharri...@mac.com> wrote:
I need some folks experienced with cocoa and socket programming to
weigh
in for me on some design problems I've been having. I'm designing
an
application that acts as a client in a client-server model. The
client
communicates with the server over the network by issuing a request
and then
receiving a response. Requests can only be issued one at a time,
meaning
that a request cannot be sent until a response from any
outstanding request
is first received. My application works in such a way that the it
could
request a handle to an object on the server and then use that
handle in
subsequent requests to retrieve additional information about the
object. I
see two ways of modeling the application - I've tried both and I'm
not
particularly happy with either.
The first is to send a request, and then have the socket block
until a
response is received. This benefit to this model is that it is so
much
easier to write the higher level application code. The issue with
this
model is that over a slow network connection it can take a
considerable
amount of time for the response to come back from the server and
while that
is happening my CPU usage is through the roof b/c the thread is
blocking.
The second way is to send a request and then let the NSInputStream
call a
delegate method when the response data is available. The response
data is
then pushed up through my protocol stack and finally up to the
higher level
application code. The benefit to this method is that CPU usage is
minimal
due to the fact that I'm no longer blocking, but the downside is
that the
higher level application code is so much more difficult to write
because I
have to write about a thousand methods to act as a callback for
each request
in a series of requests.
I've provided an example of how I see each working below. My first
question is, is there other ways to design an application around
this
client-server model that I'm not thinking about? My 2nd question
is, if
there aren't other ways, how can I adapt either method that I have
outlined
to make it work a little bit better?
As an example let's say the server knows about the following
objects:
1. VendingMachine
- An object that represents a vending machine.
- A vending machine contains Soft Drink objects.
2. SoftDrink
- Has the following properties: drink name, price, number of
calories.
If I use the blocking model, I could write my code like this. The
code is
simple to write but I'm forced to wait for the server to respond
with
information on pretty much every line of code. If the vending
machine had
enough soft drinks it could take a long time to iterate over each
one and
have the server respond with the drink's name of each drink.
-(void)printDrinkNames
{
VendingMachine *machine = [server fetchVendingMachine];
NSArray *softDrinks = [machine getSoftDrinks];
for (int i = 0 ; i < softDrinks.count ; i++)
{
NSString *drinkName = [[softDrinks objectAtIndex:i]
name];
NSLog(@"Found a drink named %@", drinkName);
}
}
Likewise if I do the non-blocking approach I would have to have a
method
that gets called for each step in the process (see below). This
model
drives me crazy b/c the higher level application code is long, has
tons of
methods, and is just difficult to read and maintain. The example
I have
provided is simple enough to get the point across, but in reality
some of
the processes I'm trying to drive are much more complex and
require numerous
callback methods to pull off.
-(void)printDrinkNames
{
[server fetchVendingMachineWithCallBackObject:self
selector:@selector(didFetchVendingMachine:)
}
-(void)didFetchVendingMachine:(VendingMachine *)machine
{
[machine fetchSoftDrinksWithCallBackObject:self
selector:@selector
(didFetchSoftDrinks:)];
}
-(void)didFetchSoftDrinks:(NSArray *)drinks
{
for (int i = 0 ; i < drinks.count ; i++)
{
SoftDrink *drink = [drinks objectAtIndex:i];
[drink fetchNameWithCallBackObject:self
selector:@selector
(didFetchDrinkName:)]
}
}
-(void)didFetchDrinkName:(NSString *)name
{
NSLog(@"Drink name is %@", name);
}
_______________________________________________
Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/steven.degutis%40gmail.com
This email sent to steven.degu...@gmail.com
--
Steven Degutis
http://www.thoughtfultree.com/
http://www.degutis.org/