Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix to -rac_signalForSelector: properly implement -respondsToSelector: for optional method from a protocol #926

Merged
Merged
Expand Up @@ -99,12 +99,16 @@ static void RACSwizzleRespondsToSelector(Class class) {
// added by -rac_signalForSelector:.
//
// If the selector has a method defined on the receiver's actual class, and
// if that method's implementation is _objc_msgForward, then return YES.
// if that method's implementation is _objc_msgForward, then returns whether
// the instance has a signal for the selector.
// Otherwise, call the original -respondsToSelector:.
id newRespondsToSelector = ^ BOOL (id self, SEL selector) {
Method method = rac_getImmediateInstanceMethod(object_getClass(self), selector);

if (method != NULL && method_getImplementation(method) == _objc_msgForward) return YES;
if (method != NULL && method_getImplementation(method) == _objc_msgForward) {
SEL aliasSelector = RACAliasForSelector(selector);
return objc_getAssociatedObject(self, aliasSelector) != nil;
}

return originalRespondsToSelector(self, respondsToSelectorSEL, selector);
};
Expand Down Expand Up @@ -248,6 +252,7 @@ static Class RACSwizzleClass(NSObject *self) {
if (subclass == nil) return nil;

RACSwizzleForwardInvocation(subclass);
RACSwizzleRespondsToSelector(subclass);
objc_registerClassPair(subclass);
}

Expand Down
Expand Up @@ -190,6 +190,29 @@ - (id)objectValue;

expect([object respondsToSelector:selector]).to.beTruthy();
});

it(@"should properly implement -respondsToSelector: for optional method from a protocol", ^{
// Selector for the targeted optional method from a protocol.
SEL selector = @selector(optionalProtocolMethodWithObjectValue:);

RACTestObject *object1 = [[RACTestObject alloc] init];

// Method implementation of the selector is added to its swizzled class.
[object1 rac_signalForSelector:selector fromProtocol:@protocol(RACTestProtocol)];

expect([object1 respondsToSelector:selector]).to.beTruthy();

RACTestObject *object2 = [[RACTestObject alloc] init];

// Call -rac_signalForSelector: to swizzle this instance's class,
// method implementations of -respondsToSelector: and
// -forwardInvocation:.
[object2 rac_signalForSelector:@selector(lifeIsGood:)];

// This instance should not respond to the selector because of not
// calling -rac_signalForSelector: with the selector.
expect([object2 respondsToSelector:selector]).to.beFalsy();
});

it(@"should send non-object arguments", ^{
RACTestObject *object = [[RACTestObject alloc] init];
Expand Down
9 changes: 8 additions & 1 deletion ReactiveCocoaFramework/ReactiveCocoaTests/RACTestObject.h
Expand Up @@ -13,7 +13,14 @@ typedef struct {
double doubleField;
} RACTestStruct;

@interface RACTestObject : NSObject
@protocol RACTestProtocol <NSObject>

@optional
- (void)optionalProtocolMethodWithObjectValue:(id)objectValue;

@end

@interface RACTestObject : NSObject <RACTestProtocol>

@property (nonatomic, strong) id objectValue;
@property (nonatomic, strong) id secondObjectValue;
Expand Down