• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Re: How does Cocoa implement delegate/callback?
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

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.

References: 
 >How does Cocoa implement delegate/callback? (From: Bill Cheeseman <email@hidden>)

  • Prev by Date: Transparent NSTextField and annoying black square
  • Next by Date: Fwd: Files and arrays
  • Previous by thread: How does Cocoa implement delegate/callback?
  • Next by thread: Re: How does Cocoa implement delegate/callback?
  • Index(es):
    • Date
    • Thread