Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ReactiveObjC.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@
7DFBED6C1CDB8DE300EE435B /* RACTestUIButton.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C3131D19EF2D9700984962 /* RACTestUIButton.m */; };
7DFBED6E1CDB918900EE435B /* UIBarButtonItem+RACCommandSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = D03764C719EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.m */; };
7DFBED6F1CDB926400EE435B /* UIBarButtonItem+RACCommandSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = D03764C619EDA41200A782A9 /* UIBarButtonItem+RACCommandSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
9AE2F1121DD8BF96008A8FDD /* NSObjectRACSelectorSignalPerformanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AE2F10E1DD8B827008A8FDD /* NSObjectRACSelectorSignalPerformanceTests.m */; };
9AE2F1131DD8BF97008A8FDD /* NSObjectRACSelectorSignalPerformanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AE2F10E1DD8B827008A8FDD /* NSObjectRACSelectorSignalPerformanceTests.m */; };
9AE2F1141DD8BF98008A8FDD /* NSObjectRACSelectorSignalPerformanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9AE2F10E1DD8B827008A8FDD /* NSObjectRACSelectorSignalPerformanceTests.m */; };
A1046B7A1BFF5661004D8045 /* EXTRuntimeExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666719EDA57100A782A9 /* EXTRuntimeExtensions.h */; settings = {ATTRIBUTES = (Private, ); }; };
A1046B7B1BFF5662004D8045 /* EXTRuntimeExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666719EDA57100A782A9 /* EXTRuntimeExtensions.h */; settings = {ATTRIBUTES = (Private, ); }; };
A1046B7C1BFF5662004D8045 /* EXTRuntimeExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = D037666719EDA57100A782A9 /* EXTRuntimeExtensions.h */; settings = {ATTRIBUTES = (Private, ); }; };
Expand Down Expand Up @@ -769,6 +772,7 @@
7A70657E1A3F88B8001E8354 /* RACKVOProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOProxy.m; sourceTree = "<group>"; };
7A7065831A3F8967001E8354 /* RACKVOProxySpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOProxySpec.m; sourceTree = "<group>"; };
7DFBED031CDB8C9500EE435B /* ReactiveObjCTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveObjCTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
9AE2F10E1DD8B827008A8FDD /* NSObjectRACSelectorSignalPerformanceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSObjectRACSelectorSignalPerformanceTests.m; sourceTree = "<group>"; };
A97451331B3A935E00F48E55 /* watchOS-Application.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "watchOS-Application.xcconfig"; sourceTree = "<group>"; };
A97451341B3A935E00F48E55 /* watchOS-Base.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "watchOS-Base.xcconfig"; sourceTree = "<group>"; };
A97451351B3A935E00F48E55 /* watchOS-Framework.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "watchOS-Framework.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1396,6 +1400,7 @@
D037667E19EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.m */,
D037667F19EDA60000A782A9 /* NSObjectRACPropertySubscribingSpec.m */,
D037668019EDA60000A782A9 /* NSObjectRACSelectorSignalSpec.m */,
9AE2F10E1DD8B827008A8FDD /* NSObjectRACSelectorSignalPerformanceTests.m */,
D037668119EDA60000A782A9 /* NSStringRACKeyPathUtilitiesSpec.m */,
D037668319EDA60000A782A9 /* NSURLConnectionRACSupportSpec.m */,
D037668419EDA60000A782A9 /* NSUserDefaultsRACSupportSpec.m */,
Expand Down Expand Up @@ -2132,6 +2137,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9AE2F1141DD8BF98008A8FDD /* NSObjectRACSelectorSignalPerformanceTests.m in Sources */,
7DFBED321CDB8DE300EE435B /* NSEnumeratorRACSequenceAdditionsSpec.m in Sources */,
7DFBED331CDB8DE300EE435B /* NSNotificationCenterRACSupportSpec.m in Sources */,
7DFBED351CDB8DE300EE435B /* NSObjectRACDeallocatingSpec.m in Sources */,
Expand Down Expand Up @@ -2339,6 +2345,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9AE2F1121DD8BF96008A8FDD /* NSObjectRACSelectorSignalPerformanceTests.m in Sources */,
D03766C719EDA60000A782A9 /* NSObjectRACPropertySubscribingExamples.m in Sources */,
D03766E319EDA60000A782A9 /* RACDelegateProxySpec.m in Sources */,
D03766F919EDA60000A782A9 /* RACSerialDisposableSpec.m in Sources */,
Expand Down Expand Up @@ -2510,6 +2517,7 @@
D03766C619EDA60000A782A9 /* NSObjectRACLiftingSpec.m in Sources */,
D0C3131F19EF2D9700984962 /* RACTestExampleScheduler.m in Sources */,
D03766D219EDA60000A782A9 /* NSURLConnectionRACSupportSpec.m in Sources */,
9AE2F1131DD8BF97008A8FDD /* NSObjectRACSelectorSignalPerformanceTests.m in Sources */,
D03766F419EDA60000A782A9 /* RACSequenceAdditionsSpec.m in Sources */,
D03766EE19EDA60000A782A9 /* RACMulticastConnectionSpec.m in Sources */,
D03766EA19EDA60000A782A9 /* RACKVOChannelSpec.m in Sources */,
Expand Down
185 changes: 129 additions & 56 deletions ReactiveObjC/NSObject+RACSelectorSignal.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,22 @@
#import "RACSubject.h"
#import "RACTuple.h"
#import "NSObject+RACDescription.h"
#import "EXTScope.h"
#import <objc/message.h>
#import <objc/runtime.h>
#import "pthread.h"

NSString * const RACSelectorSignalErrorDomain = @"RACSelectorSignalErrorDomain";
const NSInteger RACSelectorSignalErrorMethodSwizzlingRace = 1;

static NSString * const RACSignalForSelectorAliasPrefix = @"rac_alias_";
static NSString * const RACSubclassSuffix = @"_RACSelectorSignal";
static const char * RACSignalForSelectorAliasPrefix = "rac_alias_";
static const int RACSignalForSelectorAliasPrefixLength = 10;
static const char * RACSignalForSelectorInteropAliasPrefix = "rac_interop_";
static const int RACSignalForSelectorInteropAliasPrefixLength = 12;
static void *RACSubclassAssociationKey = &RACSubclassAssociationKey;
static void *RACInteropImplsKey = &RACInteropImplsKey;
static void *RACInteropImplsLockKey = &RACInteropImplsLockKey;

static NSMutableSet *swizzledClasses() {
static NSMutableSet *set;
Expand All @@ -38,32 +45,10 @@

@implementation NSObject (RACSelectorSignal)

static BOOL RACForwardInvocation(id self, NSInvocation *invocation) {
SEL aliasSelector = RACAliasForSelector(invocation.selector);
RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);
static void RACSwizzleForwardInvocation(Class baseClass) {
Class statedClass = class_getSuperclass(baseClass);

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:.
//
Expand All @@ -75,26 +60,83 @@ static void RACSwizzleForwardInvocation(Class class) {
// was no existing implementation, throw an unrecognized selector
// exception.
id newForwardInvocation = ^(id self, NSInvocation *invocation) {
BOOL matched = RACForwardInvocation(self, invocation);
if (matched) return;
SEL originalSelector = invocation.selector;
SEL aliasSelector = RACAliasForSelector(originalSelector);
SEL interopAliasSelector = RACInteropAliasForSelector(originalSelector);

if (originalForwardInvocation == NULL) {
[self doesNotRecognizeSelector:invocation.selector];
} else {
originalForwardInvocation(self, forwardInvocationSEL, invocation);
RACSubject* subject = objc_getAssociatedObject(self, aliasSelector);

@onExit {
if (subject != nil) {
[subject sendNext:invocation.rac_argumentsTuple];
}
};

// RAC exchanges implementations at runtime so as to invoke the desired
// version without using fragile dependencies like libffi. This means
// all instances that had been applied `signalForSelector:` are
// non-threadsafe as any mutable instances.

Method method = class_getInstanceMethod(statedClass, originalSelector);
const char* typeEncoding = method_getTypeEncoding(method);

if (class_respondsToSelector(baseClass, interopAliasSelector)) {
// `self` uses a runtime subclass generated by third-party APIs, and RAC
// found an existing implementation for the selector at the setup time.
// Call that implementation if it is not the ObjC message forwarder.
IMP interopImpl = class_getMethodImplementation(baseClass, interopAliasSelector);
IMP oldImpl = class_replaceMethod(baseClass, originalSelector, interopImpl, typeEncoding);
invocation.selector = originalSelector;
[invocation invoke];
class_replaceMethod(baseClass, originalSelector, oldImpl, typeEncoding);
return;
}

BOOL forwardRegardless = NO;

if (method != nil) {
IMP statedClassImpl = method_getImplementation(method);

if (statedClassImpl != _objc_msgForward) {
// The stated class has an implementation of the selector. Call that
// implementation if it is not the ObjC message forwarder.
Method aliasMethod = rac_getImmediateInstanceMethod(baseClass, aliasSelector);

if (statedClassImpl != method_getImplementation(aliasMethod)) {
class_replaceMethod(baseClass, aliasSelector, statedClassImpl, typeEncoding);
}

invocation.selector = aliasSelector;
[invocation invoke];

return;
}

forwardRegardless = YES;
}

// No appropriate implementation was found. Forward the invocation to the
// stated class' `forwardInvocation:` only if the selector had not been
// intercepted via `signalForSelector:`.
//
// `subject` is usually null except for optional protocol requirements
// that are implemented at runtime through RAC.
if (subject == nil || forwardRegardless) {
// Forward the invocation to the closest `forwardInvocation:` in the
// inheritance hierarchy.
Method forwardInvocationMethod = class_getInstanceMethod(statedClass, forwardInvocationSEL);
void*(*superForwardInvocation)(id, SEL, NSInvocation*) = (__typeof__(superForwardInvocation)) method_getImplementation(forwardInvocationMethod);
superForwardInvocation(self, forwardInvocationSEL, invocation);
}
};

class_replaceMethod(class, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@");
class_replaceMethod(baseClass, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@");
}

static void RACSwizzleRespondsToSelector(Class class) {
static void RACSwizzleRespondsToSelector(Class baseClass) {
Class statedClass = class_getSuperclass(baseClass);
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:.
//
Expand All @@ -103,17 +145,19 @@ static void RACSwizzleRespondsToSelector(Class class) {
// the instance has a signal for the selector.
// Otherwise, call the original -respondsToSelector:.
id newRespondsToSelector = ^ BOOL (id self, SEL selector) {
Method method = rac_getImmediateInstanceMethod(class, selector);
Method method = rac_getImmediateInstanceMethod(baseClass, selector);

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

return originalRespondsToSelector(self, respondsToSelectorSEL, selector);
Method superMethod = class_getInstanceMethod(statedClass, respondsToSelectorSEL);
BOOL(*superRespondsToSelector)(id, SEL, SEL) = (__typeof__(superRespondsToSelector)) method_getImplementation(superMethod);
return superRespondsToSelector(self, respondsToSelectorSEL, selector);
};

class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), method_getTypeEncoding(respondsToSelectorMethod));
class_replaceMethod(baseClass, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), "v@::");
}

static void RACSwizzleGetClass(Class class, Class statedClass) {
Expand All @@ -125,32 +169,30 @@ static void RACSwizzleGetClass(Class class, Class statedClass) {
class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(method));
}

static void RACSwizzleMethodSignatureForSelector(Class class) {
static void RACSwizzleMethodSignatureForSelector(Class baseClass) {
Class statedClass = class_getSuperclass(baseClass);
SEL methodSignatureForSelectorSEL = @selector(methodSignatureForSelector:);

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);
Method method = class_getInstanceMethod(baseClass, 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);
Method superMethod = class_getInstanceMethod(statedClass, methodSignatureForSelectorSEL);
NSMethodSignature * (*messageSend)(id, SEL, SEL) = (__typeof__(messageSend)) method_getImplementation(superMethod);
return messageSend(self, methodSignatureForSelectorSEL, 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));
class_replaceMethod(baseClass, methodSignatureForSelectorSEL, newIMP, "@@::");
}

// It's hard to tell which struct return types use _objc_msgForward, and
Expand All @@ -173,6 +215,7 @@ static void RACCheckTypeEncoding(const char *typeEncoding) {

static RACSignal *NSObjectRACSignalForSelector(NSObject *self, SEL selector, Protocol *protocol) {
SEL aliasSelector = RACAliasForSelector(selector);
SEL interopAliasSelector = RACInteropAliasForSelector(selector);

@synchronized (self) {
RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);
Expand Down Expand Up @@ -224,8 +267,18 @@ static void RACCheckTypeEncoding(const char *typeEncoding) {

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);
@synchronized (class) {
if (!class_respondsToSelector(class, interopAliasSelector)) {
Method dynamicImmediateMethod = rac_getImmediateInstanceMethod(class, selector);
if (dynamicImmediateMethod) {
IMP dynamicImmediateImpl = method_getImplementation(dynamicImmediateMethod);
if (dynamicImmediateImpl != _objc_msgForward) {
BOOL success __attribute__((unused)) = class_addMethod(class, interopAliasSelector, dynamicImmediateImpl, typeEncoding);
NSCAssert(success, @"Unexpected race condition: `%@` has already been added, and subsequent attempts should have been ignored.", NSStringFromSelector(interopAliasSelector));
}
}
}
}

// Redefine the selector to call -forwardInvocation:.
class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));
Expand All @@ -236,8 +289,28 @@ static void RACCheckTypeEncoding(const char *typeEncoding) {
}

static SEL RACAliasForSelector(SEL originalSelector) {
NSString *selectorName = NSStringFromSelector(originalSelector);
return NSSelectorFromString([RACSignalForSelectorAliasPrefix stringByAppendingString:selectorName]);
return _RACAliasForSelector(originalSelector, RACSignalForSelectorAliasPrefix, RACSignalForSelectorAliasPrefixLength);
}

static SEL RACInteropAliasForSelector(SEL originalSelector) {
return _RACAliasForSelector(originalSelector, RACSignalForSelectorInteropAliasPrefix, RACSignalForSelectorInteropAliasPrefixLength);
}

static SEL _RACAliasForSelector(SEL originalSelector, const char* prefix, int prefixLength) {
const char* selectorString = sel_getName(originalSelector);
unsigned long length = strlen(selectorString);
unsigned long finalLength = length + prefixLength;

char* buffer = malloc(finalLength + 1);
@onExit {
free(buffer);
};

memcpy(buffer, prefix, prefixLength);
memcpy(buffer + prefixLength, selectorString, length);
*(buffer + finalLength) = '\0';

return sel_registerName(buffer);
}

static const char *RACSignatureForUndefinedSelector(SEL selector) {
Expand Down
Loading