Question

This must be so obvious to some of you, but I cannot find an example of this:

I need for a boost::signals2 signal to connect a slot callback that is a C++ class member function or functor, so I can make model callbacks into Objective-C/C++ Controller code.

That callback needs to store the Class and Selector of an instance of an Objective-C/C++ Method that can be called inside the C++ callback function. (I'm assuming there's no possible way to actually provide a direct callback function address of an Objective-C/C++ method). I've assumed I need to create an instance of the C++ class / functor to CONTAIN the information for calling the Objective-C/C++ method.

I'm also not sure if I can separate out Class and SEL (selector) and store them inside the instance of the C++ class for callback without passing them as void*. Once the C++ callback is called by signal(), I expect I can convert them to a usable (callable) form with class_getInstanceMethod and method_getImplementation.

Additionally, I'll probably want to send at least one parameter with an arbitrary struct ("EventInfo") to the slot from the signal, that can provide information about the nature of the signal.

Can anyone please shine some light on the darkness?

Was it helpful?

Solution

It took me a long time, but I finally figured this out. THere may be easier ways to do this, but I found I needed to create a C++ class in a .mm file that acts as a bridge between boost::signals2 signal and an Objective-C callback function:

In CPPToCocoaModelMessageCallbacks.h:

/* ------------------------------------------------------------------------
    class CPPToCocoaModelMessageCallback - 
--------------------------------------------------------------------------- */
class CPPToCocoaModelMessageCallback
{
public:
    CPPToCocoaModelMessageCallback( PMD_Signal_Messenger<PrefEvent> *theSignalClass, 
                                    int         whichPrefIdxToObserve, 
                                    id          pObjCClass, 
                                    SEL         pObjCMethod);

    ~CPPToCocoaModelMessageCallback();

    void    CallBackMessage(PrefEvent* pPrefEvent);


private:

    id          fpObjCClass;
    SEL         fpObjCMethod;

    ls_index    fWhichPrefIdxToObserve;

    boost::signals2::connection fTheConnection;

};  // CPPToCocoaModelMessageCallback

In CPPToCocoaModelMessageCallbacks.mm

/* ------------------------------------------------------------------------
    CPPToCocoaModelMessageCallback - CONSTRUCTOR

    whichPrefIdxToObserve - the preference idx to observe

    Pass the id and selector of the Objective-C/C++ object & method to be
    called.
--------------------------------------------------------------------------- */
CPPToCocoaModelMessageCallback::CPPToCocoaModelMessageCallback(PMD_Signal_Messenger<PrefEvent> *theSignalClass, int whichPrefIdxToObserve, id pObjCClass, SEL pObjCMethod) 
        :   fpObjCClass (pObjCClass),
            fpObjCMethod (pObjCMethod),
            fWhichPrefIdxToObserve (whichPrefIdxToObserve)
{
    fTheConnection = theSignalClass->ObserveSignal(&CPPToCocoaModelMessageCallback::CallBackMessage, this);
}

/* ------------------------------------------------------------------------
    ~CPPToCocoaModelMessageCallback - DESTRUCTOR

    Pass the id and selector of the Objective-C/C++ object & method to be
    called.
--------------------------------------------------------------------------- */
CPPToCocoaModelMessageCallback::~CPPToCocoaModelMessageCallback()
{
    fTheConnection.disconnect();
}


/* ------------------------------------------------------------------------
    CPPToCocoaModelMessageCallback::CallBackMessage - 

    Handles single and range-type preference change events.
--------------------------------------------------------------------------- */
void CPPToCocoaModelMessageCallback::CallBackMessage(PrefEvent* pPrefEvent)
{
    // Only make the callback if this event is the preference we're observing

    if (pPrefEvent->GetChangedPrefIdx() == fWhichPrefIdxToObserve) {
        [fpObjCClass performSelector:fpObjCMethod];
    }
}

///////////////////////////////////////////////////////////////////////////////

In your controller.mm:

// set up messaging from Model.  The message callback functions must be destructed in dealloc.  
// I've done this in awakeFromNib but it could be elsewhere

- (void)awakeFromNib {

    PMD_Signal_Messenger<MyEventKind>* theModelClass = GetMyModelClassPointer();

    displayMenuPrefChangedCallBack = new CPPToCocoaModelMessageCallback(theModelClass, kAppPrefDictionaryDisplayShortDef, self, @selector(displayMenuChanged));
}


/* ------------------------------------------------------------------------
    displayMenuChanged - this gets called when the model fires a signal
        (via CPPToCocoaModelMessageCallback::CallBackMessage())

--------------------------------------------------------------------------- */
- (void) displayMenuChanged
{
    NSLog(@"displayMenuChanged\n");

    // DO SOMETHING TO RESPOND TO THE SIGNAL (in this case I'm reloading an NSWebView):

    [self reloadWebViewText];
}

//////////////////////////////////////////////////////////////////////////////

Class to combine with model class for signaling observers:

PMD_Signal_Messenger.h:

/* ------------------------------------------------------------------------
    class PMD_Signal_Messenger<MyEventKind> -

        This class is designed to be multiple inherited with various
        Model classes.
--------------------------------------------------------------------------- */
template <class MyEventKind>
class PMD_Signal_Messenger {
public:

    PMD_Signal_Messenger() { }
    ~PMD_Signal_Messenger() { }

        template<typename Fn, typename Obj>
            boost::signals2::connection ObserveSignal(Fn callback, Obj &object) {
                return fSignalObservers.connect(boost::bind(callback, object, _1));
            }

protected:
    boost::signals2::signal<void (MyEventKind*)> fSignalObservers;  // all observers of my preference changes

private:
    PMD_Signal_Messenger(const PMD_Signal_Messenger& thePMD_Signal_Messenger)   { assert(false); }  // prevent copy constructor
};

In the .cpp MODEL file where you want to signal a model change:

// construct theEvent (your own struct) and fire the signal with your event structure that gets passed to CPPToCocoaModelMessageCallback::CallBackMessage()

MyEventKind theEvent(someUsefulParams);

fSignalObservers(&theEvent);

OTHER TIPS

You could use this solution : https://github.com/godexsoft/objc_callback

#pragma once
#ifndef _OBJC_CALLBACK_H_
#define _OBJC_CALLBACK_H_

template<typename Signature> class objc_callback;

template<typename R, typename... Ts>
class objc_callback<R(Ts...)>                                                              
{                              
public:                                                    
    typedef R (*func)(id, SEL, Ts...);                                              

    objc_callback(SEL sel, id obj)                  
    : sel_(sel)                                     
    , obj_(obj)                                     
    , fun_((func)[obj methodForSelector:sel])       
    {
    }     

    inline R operator ()(Ts... vs)
    {                                                   
        return fun_(obj_, sel_, vs...);      
    }                                                   
private:                                                
    SEL sel_;                                           
    id obj_;                                            
    func fun_;                                          
};      

#endif // _OBJC_CALLBACK_H_
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top