blob: b7085c0841781dba0454048ea8f697bc33d74e67 [file] [log] [blame] [edit]
//
// NSObject+RACSelectorSignal.m
// ReactiveCocoa
//
// Created by Josh Abernathy on 3/18/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "NSObject+RACSelectorSignal.h"
#import "RACEXTRuntimeExtensions.h"
#import "NSInvocation+RACTypeParsing.h"
#import "NSObject+RACDeallocating.h"
#import "RACCompoundDisposable.h"
#import "RACDisposable.h"
#import "RACObjCRuntime.h"
#import "RACSubject.h"
#import "RACTuple.h"
#import "NSObject+RACDescription.h"
#import <objc/message.h>
#import <objc/runtime.h>
NSString * const RACSelectorSignalErrorDomain = @"RACSelectorSignalErrorDomain";
const NSInteger RACSelectorSignalErrorMethodSwizzlingRace = 1;
static NSString * const RACSignalForSelectorAliasPrefix = @"rac_alias_";
static NSString * const RACSubclassSuffix = @"_RACSelectorSignal";
static NSMutableSet *swizzledClasses() {
static NSMutableSet *set;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
set = [[NSMutableSet alloc] init];
});
return set;
}
@implementation NSObject (RACSelectorSignal)
static BOOL RACForwardInvocation(id self, NSInvocation *invocation) {
SEL aliasSelector = RACAliasForSelector(invocation.selector);
RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);
Class class = object_getClass(invocation.target);
BOOL respondsToAlias = [class instancesRespondToSelector:aliasSelector];
if (respondsToAlias) {
invocation.selector = aliasSelector;
[invocation invoke];
}
if (subject == nil) return respondsToAlias;
[subject sendNext:invocation.rac_argumentsTuple];
return YES;
}
static void RACSwizzleForwardInvocation(Class class) {
SEL forwardInvocationSEL = @selector(forwardInvocation:);
Method forwardInvocationMethod = class_getInstanceMethod(class, forwardInvocationSEL);
// Preserve any existing implementation of -forwardInvocation:.
void (*originalForwardInvocation)(id, SEL, NSInvocation *) = NULL;
if (forwardInvocationMethod != NULL) {
originalForwardInvocation = (__typeof__(originalForwardInvocation))method_getImplementation(forwardInvocationMethod);
}
// Set up a new version of -forwardInvocation:.
//
// If the selector has been passed to -rac_signalForSelector:, invoke
// the aliased method, and forward the arguments to any attached signals.
//
// If the selector has not been passed to -rac_signalForSelector:,
// invoke any existing implementation of -forwardInvocation:. If there
// was no existing implementation, throw an unrecognized selector
// exception.
id newForwardInvocation = ^(id self, NSInvocation *invocation) {
BOOL matched = RACForwardInvocation(self, invocation);
if (matched) return;
if (originalForwardInvocation == NULL) {
[self doesNotRecognizeSelector:invocation.selector];
} else {
originalForwardInvocation(self, forwardInvocationSEL, invocation);
}
};
class_replaceMethod(class, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@");
}
static void RACSwizzleRespondsToSelector(Class class) {
SEL respondsToSelectorSEL = @selector(respondsToSelector:);
// Preserve existing implementation of -respondsToSelector:.
Method respondsToSelectorMethod = class_getInstanceMethod(class, respondsToSelectorSEL);
BOOL (*originalRespondsToSelector)(id, SEL, SEL) = (__typeof__(originalRespondsToSelector))method_getImplementation(respondsToSelectorMethod);
// Set up a new version of -respondsToSelector: that returns YES for methods
// 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 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) {
SEL aliasSelector = RACAliasForSelector(selector);
return objc_getAssociatedObject(self, aliasSelector) != nil;
}
return originalRespondsToSelector(self, respondsToSelectorSEL, selector);
};
class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), method_getTypeEncoding(respondsToSelectorMethod));
}
static void RACSwizzleGetClass(Class class, Class statedClass) {
SEL selector = @selector(class);
Method method = class_getInstanceMethod(class, selector);
IMP newIMP = imp_implementationWithBlock(^(id self) {
return statedClass;
});
class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(method));
}
static void RACSwizzleMethodSignatureForSelector(Class class) {
IMP newIMP = imp_implementationWithBlock(^(id self, SEL selector) {
// Don't send the -class message to the receiver because we've changed
// that to return the original class.
Class actualClass = object_getClass(self);
Method method = class_getInstanceMethod(actualClass, selector);
if (method == NULL) {
// Messages that the original class dynamically implements fall
// here.
//
// Call the original class' -methodSignatureForSelector:.
struct objc_super target = {
.super_class = class_getSuperclass(class),
.receiver = self,
};
NSMethodSignature * (*messageSend)(struct objc_super *, SEL, SEL) = (__typeof__(messageSend))objc_msgSendSuper;
return messageSend(&target, @selector(methodSignatureForSelector:), selector);
}
char const *encoding = method_getTypeEncoding(method);
return [NSMethodSignature signatureWithObjCTypes:encoding];
});
SEL selector = @selector(methodSignatureForSelector:);
Method methodSignatureForSelectorMethod = class_getInstanceMethod(class, selector);
class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(methodSignatureForSelectorMethod));
}
// It's hard to tell which struct return types use _objc_msgForward, and
// which use _objc_msgForward_stret instead, so just exclude all struct, array,
// union, complex and vector return types.
static void RACCheckTypeEncoding(const char *typeEncoding) {
#if !NS_BLOCK_ASSERTIONS
// Some types, including vector types, are not encoded. In these cases the
// signature starts with the size of the argument frame.
NSCAssert(*typeEncoding < '1' || *typeEncoding > '9', @"unknown method return type not supported in type encoding: %s", typeEncoding);
NSCAssert(strstr(typeEncoding, "(") != typeEncoding, @"union method return type not supported");
NSCAssert(strstr(typeEncoding, "{") != typeEncoding, @"struct method return type not supported");
NSCAssert(strstr(typeEncoding, "[") != typeEncoding, @"array method return type not supported");
NSCAssert(strstr(typeEncoding, @encode(_Complex float)) != typeEncoding, @"complex float method return type not supported");
NSCAssert(strstr(typeEncoding, @encode(_Complex double)) != typeEncoding, @"complex double method return type not supported");
NSCAssert(strstr(typeEncoding, @encode(_Complex long double)) != typeEncoding, @"complex long double method return type not supported");
#endif // !NS_BLOCK_ASSERTIONS
}
static RACSignal *NSObjectRACSignalForSelector(NSObject *self, SEL selector, Protocol *protocol) {
SEL aliasSelector = RACAliasForSelector(selector);
@synchronized (self) {
RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);
if (subject != nil) return subject;
Class class = RACSwizzleClass(self);
NSCAssert(class != nil, @"Could not swizzle class of %@", self);
subject = [[RACSubject subject] setNameWithFormat:@"%@ -rac_signalForSelector: %s", self.rac_description, sel_getName(selector)];
objc_setAssociatedObject(self, aliasSelector, subject, OBJC_ASSOCIATION_RETAIN);
[self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
[subject sendCompleted];
}]];
Method targetMethod = class_getInstanceMethod(class, selector);
if (targetMethod == NULL) {
const char *typeEncoding;
if (protocol == NULL) {
typeEncoding = RACSignatureForUndefinedSelector(selector);
} else {
// Look for the selector as an optional instance method.
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
if (methodDescription.name == NULL) {
// Then fall back to looking for a required instance
// method.
methodDescription = protocol_getMethodDescription(protocol, selector, YES, YES);
NSCAssert(methodDescription.name != NULL, @"Selector %@ does not exist in <%s>", NSStringFromSelector(selector), protocol_getName(protocol));
}
typeEncoding = methodDescription.types;
}
RACCheckTypeEncoding(typeEncoding);
// Define the selector to call -forwardInvocation:.
if (!class_addMethod(class, selector, _objc_msgForward, typeEncoding)) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"A race condition occurred implementing %@ on class %@", nil), NSStringFromSelector(selector), class],
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Invoke -rac_signalForSelector: again to override the implementation.", nil)
};
return [RACSignal error:[NSError errorWithDomain:RACSelectorSignalErrorDomain code:RACSelectorSignalErrorMethodSwizzlingRace userInfo:userInfo]];
}
} else if (method_getImplementation(targetMethod) != _objc_msgForward) {
// Make a method alias for the existing method implementation.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
RACCheckTypeEncoding(typeEncoding);
BOOL addedAlias __attribute__((unused)) = class_addMethod(class, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), class);
// Redefine the selector to call -forwardInvocation:.
class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));
}
return subject;
}
}
static SEL RACAliasForSelector(SEL originalSelector) {
NSString *selectorName = NSStringFromSelector(originalSelector);
return NSSelectorFromString([RACSignalForSelectorAliasPrefix stringByAppendingString:selectorName]);
}
static const char *RACSignatureForUndefinedSelector(SEL selector) {
const char *name = sel_getName(selector);
NSMutableString *signature = [NSMutableString stringWithString:@"v@:"];
while ((name = strchr(name, ':')) != NULL) {
[signature appendString:@"@"];
name++;
}
return signature.UTF8String;
}
static Class RACSwizzleClass(NSObject *self) {
Class statedClass = self.class;
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
if ([className hasSuffix:RACSubclassSuffix]) {
return baseClass;
} else if (statedClass != baseClass) {
// If the class is already lying about what it is, it's probably a KVO
// dynamic subclass or something else that we shouldn't subclass
// ourselves.
//
// Just swizzle -forwardInvocation: in-place. Since the object's class
// was almost certainly dynamically changed, we shouldn't see another of
// these classes in the hierarchy.
//
// Additionally, swizzle -respondsToSelector: because the default
// implementation may be ignorant of methods added to this class.
@synchronized (swizzledClasses()) {
if (![swizzledClasses() containsObject:className]) {
RACSwizzleForwardInvocation(baseClass);
RACSwizzleRespondsToSelector(baseClass);
RACSwizzleGetClass(baseClass, statedClass);
RACSwizzleGetClass(object_getClass(baseClass), statedClass);
RACSwizzleMethodSignatureForSelector(baseClass);
[swizzledClasses() addObject:className];
}
}
return baseClass;
}
const char *subclassName = [className stringByAppendingString:RACSubclassSuffix].UTF8String;
Class subclass = objc_getClass(subclassName);
if (subclass == nil) {
subclass = [RACObjCRuntime createClass:subclassName inheritingFromClass:baseClass];
if (subclass == nil) return nil;
RACSwizzleForwardInvocation(subclass);
RACSwizzleRespondsToSelector(subclass);
RACSwizzleGetClass(subclass, statedClass);
RACSwizzleGetClass(object_getClass(subclass), statedClass);
RACSwizzleMethodSignatureForSelector(subclass);
objc_registerClassPair(subclass);
}
object_setClass(self, subclass);
return subclass;
}
- (RACSignal *)rac_signalForSelector:(SEL)selector {
NSCParameterAssert(selector != NULL);
return NSObjectRACSignalForSelector(self, selector, NULL);
}
- (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol {
NSCParameterAssert(selector != NULL);
NSCParameterAssert(protocol != NULL);
return NSObjectRACSignalForSelector(self, selector, protocol);
}
@end