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

About these ads