| // |
| // 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 |