views:

235

answers:

3

I'm implementing callback routines for external static C++ library to be used in Objective-C project. Now I have trouble moving data between callback and normal routines. As you can see below my "msgStore" is defined as part of MyMessage class and can be used within class routines such as init(). However attempting same from callback routine, which is NOT part of the MyMessage class, fails.

@interface MyMessage : NSObject {
    NSMutableArray *msgStore;
}
@property (nonatomic, retain) IBOutlet NSMutableArray *msgStore;

// Callback functions declarations
void aCBack (const std::string text);

@implementation MyMessage    
@synthesize msgStore;

- (id)init
{
    if ((self = [super init])) { }

    if (msgStore == nil) {
        msgStore = [NSMutableArray arrayWithCapacity:100];        
    }
    return self;
}

void aCBack (const std::string text)
{
    NSString *msg = [[NSString alloc] initWithCString:(const char *)text.c_str() encoding:NSUTF8StringEncoding];
    [msgStore insertObject:msg atIndex:0];
}

The last code line gives error message 'msgStore' was not declared in this scope. I'm guessing it's because aCBack is a plain C function and thus does not have automatic "self" pointer?

Any ideas how to save data received in callback for use inside Obj-C class?

A: 

The simple solution is to pass a pointer to the MyMessage object as an argument of the callback function. Something like:

void aCBack( MyMessage * message, const std::string text)
{
    NSString *msg = [[NSString alloc] initWithCString:(const char *)text.c_str() encoding:NSUTF8StringEncoding];
    [message.msgStore insertObject:msg atIndex:0];
}
Alex Deem
aCBack is declared outside of my reach. I have to give aCBack function pointer to library, so that it can call me back. Thus cannot add MyMessage object as additional argument. That would make external library dependent on my implementation, which is not allowed...
JOM
Instead of using a MyMessage * you can use a void * and type-cast it as appropriate. Without something in the callback indicating which object the callback is relating to you really have no idea. Imagine the case when you have more than one MyMessage objects waiting for callbacks at the same time. When the callback comes, how do you know which one it is for?
Alex Deem
Have to check that, thanx! Btw the example is greatly simplified.. I have 5 callbacks for different purposes, but just one "object" to handle them. This object will send notification(s) when something happens and those (objects) who are interested can request more info. Yep, planning to refactor, but now just got to get it working as a demo.
JOM
+1  A: 

You cannot call msgStore from the function because it is not in the scope of the function.

There are a few ways to get to it in the function.

One is to use a singleton class. If you plan on only using one message store, then you can make that class a singleton. That means you can get the object instance of that class by calling a class method, which you can do from any scope. See also: http://stackoverflow.com/questions/145154/what-does-your-objective-c-singleton-look-like

MyMessage * myMsg = [MyMessage sharedMessage]; // this will get you a pointer to the shared instance

Another way is, if the callback function allows, you can also pass it as a void * data argument, then cast it to a MyMessage in the function. See also Alex Deem's answer.

PS. You create the array with [NSArray arrayWithCapacity:], which you might want to make [[NSArray arrayWithCapacity:] retain] or just [[NSArray alloc] initWithCapacity:], so the object won't vannish on the next autoreleasepool housekeeping round.

nash
Thanx for both tips! Singleton really seems to be solution for this problem (did some reading in between) and especial thanx for the housekeeping! However my Quick But Dirty solution was something else, no time for proper singleton... More below. But your answer is the right one!
JOM
A: 

Let's be clear: Singleton is the answer (at least one good choice). However deadline is hanging on my neck and I just got to get something more or less working today. Here's what I use, for here and now. Please note this is NOT a "singleton":

@interface MyMessage : NSObject {
    NSMutableArray *msgStore;
}
@property (nonatomic, retain) NSMutableArray *msgStore;
- (void)myStoreMessage: (NSString *) msg;
@end

// Callback C/C++ function declarations
void aCBack (const std::string text);
MyMessage *myObserver = nil;

void aCBack (const std::string text)
{
    NSString *msg = [[NSString alloc] initWithCString:(const char *)text.c_str() encoding:NSUTF8StringEncoding];
    [myObserver myStoreMessage:[NSString stringWithString:msg]];
    [msg release];
    // TODO: fix memory leak
}

@implementation MyMessage    
@synthesize msgStore;

- (id)init
{
    if ((self = [super init])) { }

    if (msgStore == nil) {
        msgStore = [[NSMutableArray arrayWithCapacity:100] retain];
    }
    myObserver = self;
    return self;
}

- (void) myStoreMessage: (NSString *)msg
{
    [self.msgStore insertObject:msg atIndex:0];
}
@end

What can I say, it seems to work. There is a memory leak with msg, which I haven't figured out, but otherwise this is what I'll be using for the demo. When there is time afterwards (yeah, sure) I'll implement a proper Singleton.

JOM
Using a global variable works too :-p Quick and dirty! The memory leak here is that you don't have a dealloc in `MyMessage`. You should deallocate msgStore. Also, since you use a global, it would be nice to set the `myObserver` to Nil in your dealloc method. Also, in the init method everything but `return self` should be between the accolades of (self = [super init]).
nash
The whole file is about 200 lines, had to cut short: dealloc is there, but added myObserver = nil; Thanx for init fix, too! The memory leak is really with msg in aCBack routine... Callback is called from different thread than MyMessage object, which might have something to do with it. Most likely I just need to define local NSAutoReleasePool within callback.
JOM