steipete / Aspects

Delightful, simple library for aspect oriented programming in Objective-C and Swift.
https://twitter.com/steipete
MIT License
8.4k stars 1.26k forks source link

An `doesNotRecognizeSelector:` exception raises, if the superclass of an Aspects-tracking class swizzles its `forwardInvocation:` SEL to custom IMP #87

Closed coppercash closed 8 years ago

coppercash commented 8 years ago

I am using a lib named JSPatch and Apects at the same time. It swizzles methods to _objc_msgForward and (SEL)forwardInvocation: to custom IMP just as Aspects does.

My Case

The method map<SEL, IMP> of the superclass, in which the method foo is swizzled by JSPatch:

SuperClass {
    forwardInvocation: JPForwardInvocation,
    foo: _objc_msgForward,
}

The method map<SEL, IMP> of the subclass, in which the method bar is swizzled by Aspects:

SubClass: SuperClass {
    forwardInvocation: __ASPECTS_ARE_BEING_CALLED__,
    bar: _objc_msgForward,
}

Pseudo-callstack

6 aSubClass.doesNotRecognizeSelector
5 __ASPECTS_ARE_BEING_CALLED__
4 SubClass.forwardInvocation
3 _objc_msgForward
2 SuperClass.foo
1 [aSubClass foo];
  1. An instance of SubClass calles method foo
  2. SubClass dosen't implement method foo, so the implementation of SuperClass is used
  3. The implementation of SuperClass.foo is redirected to _objc_msgForward by JSPatch
  4. Because Method forwardInvocation is added onto Subclass by Aspects, it is called
  5. SubClass.forwardInvocation points to __ASPECTS_ARE_BEING_CALLED__
  6. __ASPECTS_ARE_BEING_CALLED__ tries to find method named aspects__foo in the inheritance, which apparently is impossible. Thus, it throws the exception.

    Solution

Alter

        if ([self respondsToSelector:originalForwardInvocationSEL]) {
            ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
        }else {
            [self doesNotRecognizeSelector:invocation.selector];
        }

to

        if ([self respondsToSelector:originalForwardInvocationSEL]) {
            ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
        }else {
            struct objc_super superObjc = {
                self,
                self.superclass,
            };
            ((void( *)(struct objc_super *, SEL, NSInvocation *))objc_msgSendSuper)(&superObjc, @selector(forwardInvocation:), invocation);
        }

Aspects breaks the inheritance of method forwardInvocation:, my solution is to add it back. On the other hand, I am worrying how it will affect other cases. However, it will crashes anyway, why not give it a chance to see if the superclass is capable to take over.

steipete commented 8 years ago

Aspects does some heavy tricks and is incompatible with other libraries that do the same, such as JSPatch or ReactiveCocoa. The only way to solve this is to completely change the way it works, e.g. in my 1.5 branch that requires assembly and trampolines.

I'm just using it for tests and do not recommend shipping it in apps.

coppercash commented 8 years ago

But I believe many people regard Aspects as the only AOP solution for ObjC just as I do. It is too bad to heard the denial from its own auther.

steipete commented 8 years ago

Sorry to break the news :)