NSInvocation in Objective-C++

UPDATE: This article was initially about some difficulties that I had calling some Objective-C++ code from C++ in a dynamic way using NSInvocation, which failed in the first place. I switched to directly calling the IMP pointer instead while passing C++ object pointers as arguments. Thanks to the comments that I received from bob, an obviously very experienced Objective-C++ developer, I could change the code and make it work also with NSInvocation.

Here is a C++ class that represents the details of a notifier/observer association called Observation:

    class Observation: public Object {
    public:

        void setObjcObserver(NSObject *theObserver) { _nsObserver = theObserver; }
        void setObjcSlot(SEL theSlot) { _nsSlot = theSlot; }
                
        void notify(bool complete);
        
        Observation() : _nsObserver(nil), _nsSlot(nil) {}
        
    private:

        Object *_notifier;
        NSObject *_nsObserver;
        SEL _nsSlot;
    };

First Attempt Using NSInvocation Failed

    void Observation::notify(bool complete) {

        NSMethodSignature *aSignature = [[_nsObserver class]
                        instanceMethodSignatureForSelector:_nsSlot];

        assert(aSignature);
        assert([_nsObserver respondsToSelector:_nsSlot]);

        NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature:aSignature];
        [anInvocation setSelector:_nsSlot];
        [anInvocation setArgument:this atIndex:2];
        [anInvocation setArgument:&complete atIndex:3];
            
        [anInvocation invokeWithTarget:_nsObserver];
    }

this code compiled fine but it crashed as soon as I tried to access members of the received Observation object in Objective-C.

IMP Based Solution

My approach to solve the issue is that I am directly calling the IMP of the method in the observer object. For this, I slightly change the C++ Observation model to cache the IMP pointer:

    class Observation: public Object {
    public:

        void setObjcObserver(NSObject *theObserver) { _nsObserver = theObserver; }
        void setObjcSlot(SEL theSlot) { _nsSlot = theSlot; }
                
        void notify(bool complete);
        
        Observation() : _nsObserver(nil), _nsSlot(nil), _nsSlotImpl(nil) {}
        
    private:

        Object *_notifier;
        NSObject *_nsObserver;
        SEL _nsSlot;

        typedef void (*slotImpl_t)(__strong id, SEL, lang::Observation *, BOOL);
        
        // used to cache the IMP of the objcSlot
        slotImpl_t _nsSlotImpl;

    };

Here, I simply add a typedef of the desired observer method signature called “slotImpl_t” and a member to cache the IMP function pointer. The cache would not be necessary but in my case the observer IMP will not change, so I cache it in order to save the look-up on frequent execution of the code. The notify() method now looks different:

    void Observation::notify(bool complete) {
        if(!_nsSlotImpl) {
            _nsSlotImpl = (slotImpl_t)[_nsObserver methodForSelector:_nsSlot];
            assert(_nsSlotImpl);
        }
        _nsSlotImpl(_nsObserver, _nsSlot, this, complete);
    }

This is working like a charm now. The address of the Observation object at the receiving end is correct as it should be, even in case of multiple-inheritance combined with virtual inheritance.

Correct Solution with NSInvocation

It turned out that in my original solution using NSInvocation I did it wrong. NSInvocation needs the address of the this pointer and not the this pointer itself. To make it worse, when simply doing

        [anInvocation setArgument:&this atIndex:2];

the compiler complains with the error:

Address expression must be an lvalue or a function designator

So, instead of &this it has to be an lvalue which essentially requires to copy the this pointer to a temporary variable, ‘temp’ in this case. Here is the complete and working code:

    void Observation::notify(bool complete) {

        NSMethodSignature *aSignature = [[_nsObserver class]
                        instanceMethodSignatureForSelector:_nsSlot];

        assert(aSignature);
        assert([_nsObserver respondsToSelector:_nsSlot]);

        NSInvocation *anInvocation = [NSInvocation invocationWithMethodSignature:aSignature];
        [anInvocation setSelector:_nsSlot];
        Observation *temp = this;
        [anInvocation setArgument:&temp atIndex:2];
        [anInvocation setArgument:&complete atIndex:3];
            
        [anInvocation invokeWithTarget:_nsObserver];
    }

Conclusion

There is expert knowlegde about Objective-C++ available on the planet. But it’s not easy to get access to it. Sometimes, it helps to blog about a problem and the solution will come and find you;) Again, bob, thank you very much

Advertisements

4 thoughts on “NSInvocation in Objective-C++

  1. Or if it is not possible to write “&this”, you can do

    Observation *temp = this;
    [anInvocation setArgument:&temp atIndex:2];

    • I’m pretty sure that I tried “&this” before but, because of the compiler error above, I was mislead to think that taking the address of the this pointer was the wrong idea. Turns out it wasn’t. Using your modification the code is working as expected. Thank you very much for your fix, bob, I will adjust the blog post accordingly in order to not blame the language nor NSInvocation but just my stupidity;)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s