Re: How does Cocoa implement delegate/callback?
Re: How does Cocoa implement delegate/callback?
- Subject: Re: How does Cocoa implement delegate/callback?
- From: Sherm Pendley <email@hidden>
- Date: Fri, 15 Nov 2002 00:06:51 -0500
On Thursday, November 14, 2002, at 12:41 PM, Bill Cheeseman wrote:
A complicating factor is that the class I'm writing covers a C
API that expects the callback to be a C function.
The naive approach would be to simply declare an Objective-C method with
the same arguments and return types, and get the address of its
implementation function (IMP) with methodForSelector:. But, that won't
work, because an IMP points to a specific type of function - one that
expects (id)self and (SEL)_cmd as its first two arguments, followed by
the rest.
Unfortunately, it's highly unlikely that the C API you're working with
can be convinced to pass self and _cmd to your callback. So, you're
probably going to have to write a C function with whatever signature the
C API expects.
I suppose I can implement the C callback as a function in the class I'm
writing and register it with the C API.
Strictly speaking, the C function is not part of the class - but you
probably knew that. I assume you're speaking of simply implementing the
C function in the same file as the Objective-C class.
BTW, there's more to this than you've mentioned. There's also the
question of finding the delegate object that will receive the message.
That's simple enough, if there's only a single instance of the owning
object, as with the shared NSApplication object, NSApp - just call that
instance's delegate: method.
It can get uglier, though, if there can be many such objects, each with
its own delegate. In your C function, you'll need to figure out which
delegate to send a message to. If you allow the user to choose the
callback selector, it gets even more interesting - each delegate could
use a different selector to receive the callback. Or, a single object
could be registered as the delegate of a number of objects, each of
which has been told to send it a different message.
But, your question was about *how* to send the message, not what message
to send or to what target it should be sent - so I'm going to assume
that you've already solved those problems.
So, let's assume that you've registered a callback to a C function
that's declared as "int doStuff(char *foo, int bar);", and in that
function you can obtain a delegate object "aDel", and a selector "aSel".
Then, when my C callback function is
triggered by the C API, I can have it send a message to my client's
callback
selector. But how?
The easy way, used by many controls that allow a delegate to be used, is
to simply hard-code the selector name, and require that the delegate
implement that selector. In your callback, you could use
respondsToSelector: to verify that the registered delegate has the
appropriate method, and then call it directly, passing along whatever
parameters were passed to your C function.
Because you don't know what class the delegate will belong to, you'll
have to declare the variable that refers to the delegate object as an
id - which will generate a compiler warning when you send a message to
it. The warning is harmless; even though the compiler can't check for
the required method at build time, you're allowing for that by using
respondsToSelector: to verify that the delegate implements it before
sending the message.
int doStuff(char *foo, int bar) {
id aDel;
SEL aSel;
NSMethodSignature *aSig;
// Initialize aDel somehow...
aSel = @selector(doStuffWithCString:andInt:)
if ([aDel respondsToSelector: aSel] {
// Get the method signature from the potential target
aSig = [aDel methodSignatureForSelector: aSel];
// Verify the number and type of arguments and return value
// Remember, args 0 and 1 are self and _cmd
if (4 == [aSig numberOfArguments] &&
@encode(int) == [aSig methodReturnType] &&
@encode(char *) == [aSig getArgumentTypeAtIndex: 2] &&
@encode(int) == [aSig getArgumentTypeAtIndex: 3])
{
return [aDel doStuffWithCString: foo andInt: bar];
} else {
// aDel has a method by the proper name, but its argument
and/or
// return type is incorrect.
return 0;
}
} else {
// Either aDel is nil, or doesn't respond
return 0;
}
}
If the warning *really* bothers you, you could declare the callback
selector as part of a formal protocol, use conformsToProtocol: to verify
it, and cast the delegate as id <ProtocolName> when sending it the
message. That gets rid of the compiler warning, but it puts an
additional burden on users of your code, who have to properly declare
their delegate class as conforming to the protocol, in addition to
implementing the needed method.
int doStuff(char *foo, int bar) {
id aDel;
// Initialize aDel somehow...
if ([aDel conformsToProtocol: @protocol(DoesStuff)] {
return [(id<DoesStuff>)aDel doStuffWithCString: foo andInt: bar];
} else {
// Either aDel is nil, or doesn't respond
return 0;
}
}
Note that when you use a protocol this way, there's no need to check the
argument and return types yourself - because they're declared as part of
a protocol, they're checked by the compiler when a class that implements
the protocol is built.
Telling the delegate to
performSelector:withObject:withObject: won't work because the callback
method I specify has too many parameters. And I can't figure out how to
get
NSInvocation to work.
Now we get to the fun stuff! :-)
Using NSInvocation is more complex, but it has the advantage of
flexibility. Not only can your users specify a delegate object, they can
also specify the message to be sent to that object.
int doStuff(char *foo, int bar) {
NSMethodSignature *aSig;
NSInvocation *anInvocation;
int returnValue;
id aDel;
SEL aSel;
// Initialize aDel to point to the potential target object, and aSel
to the selector
// ...
// First verify that aDel responds to aSel
if ([aDel respondsToSelector: aSel]) {
// Get the method signature for the requested selector from the
potential target
aSig = [aDel methodSignatureForSelector: aSel];
// Verify the number and type of arguments and return value
// Remember, args 0 and 1 are self and _cmd
if (4 == [aSig numberOfArguments] &&
@encode(int) == [aSig methodReturnType] &&
@encode(char *) == [aSig getArgumentTypeAtIndex: 2] &&
@encode(int) == [aSig getArgumentTypeAtIndex: 3])
{
// Create the invocation object
anInvocation = [NSInvocation invocationWithMethodSignature:
aSig];
// Set its target and selector
[anInvocation setTarget: aDel];
[anInvocation setSelector: aSel];
// Set the arguments to pass
[anInvocation setArgument: (void *)&foo atIndex: 2];
[anInvocation setArgument: (void *)&bar atIndex: 3];
// Send the message
[anInvocation invoke];
// Retrieve the return value
[anInvocation getReturnValue: (void *)&returnValue];
return returnValue;
} else {
// aDel is not nil, and responds to aSel, but aSel doesn't
have the correct
// arguments and/or return types
return 0;
}
} else {
// Either aDel is nil, or doesn't respond to aSel
return 0;
}
HTH!
sherm--
If you listen to a UNIX shell, can you hear the C?
_______________________________________________
cocoa-dev mailing list | email@hidden
Help/Unsubscribe/Archives:
http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored.