Skip to content

Commit a98c30c

Browse files
committed
Ported a few changes from the Swift implementation.
Swift PR: ReactiveCocoa/ReactiveCocoa#3302 1. Improved performance in general method interception. 2. Reduced the overhead of computing selector aliases. 3. Reliable invocation of the next implementation of `-forwardInvocation:`, `-methodSignatureForSelector:` and `-respondsToSelector:` in the superclass hierarchy.
1 parent 71594bf commit a98c30c

File tree

1 file changed

+113
-89
lines changed

1 file changed

+113
-89
lines changed

ReactiveObjC/NSObject+RACSelectorSignal.m

Lines changed: 113 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@
1515
#import "RACSubject.h"
1616
#import "RACTuple.h"
1717
#import "NSObject+RACDescription.h"
18+
#import "EXTScope.h"
1819
#import <objc/message.h>
1920
#import <objc/runtime.h>
2021

2122
NSString * const RACSelectorSignalErrorDomain = @"RACSelectorSignalErrorDomain";
2223
const NSInteger RACSelectorSignalErrorMethodSwizzlingRace = 1;
2324

24-
static NSString * const RACSignalForSelectorAliasPrefix = @"rac_alias_";
25-
static NSString * const RACSignalForSelectorAliasOfOriginalPrefix = @"rac_runtime_";
2625
static NSString * const RACSubclassSuffix = @"_RACSelectorSignal";
26+
static const char * RACSignalForSelectorAliasPrefix = "rac_alias_";
27+
static const int RACSignalForSelectorAliasPrefixLength = 10;
2728
static void *RACSubclassAssociationKey = &RACSubclassAssociationKey;
29+
static void *RACInteropImplsKey = &RACInteropImplsKey;
2830

2931
static NSMutableSet *swizzledClasses() {
3032
static NSMutableSet *set;
@@ -39,8 +41,9 @@
3941

4042
@implementation NSObject (RACSelectorSignal)
4143

42-
static void RACSwizzleForwardInvocation(Class class) {
43-
Class superclass = class_getSuperclass(class);
44+
static void RACSwizzleForwardInvocation(Class baseClass, NSDictionary *interopImpls) {
45+
Class statedClass = class_getSuperclass(baseClass);
46+
4447
SEL forwardInvocationSEL = @selector(forwardInvocation:);
4548

4649
// Set up a new version of -forwardInvocation:.
@@ -53,81 +56,80 @@ static void RACSwizzleForwardInvocation(Class class) {
5356
// was no existing implementation, throw an unrecognized selector
5457
// exception.
5558
id newForwardInvocation = ^(id self, NSInvocation *invocation) {
56-
BOOL forward = NO;
57-
5859
SEL originalSelector = invocation.selector;
5960
SEL aliasSelector = RACAliasForSelector(originalSelector);
60-
SEL aliasOfOriginalSelector = RACAliasOfOriginalForSelector(originalSelector);
6161

6262
RACSubject* subject = objc_getAssociatedObject(self, aliasSelector);
6363

64-
Class baseClass = object_getClass(self);
64+
@onExit {
65+
if (subject != nil) {
66+
[subject sendNext:invocation.rac_argumentsTuple];
67+
}
68+
};
6569

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

71-
if ([baseClass instancesRespondToSelector:aliasOfOriginalSelector]) {
75+
Method method = class_getInstanceMethod(statedClass, originalSelector);
76+
const char* typeEncoding = method_getTypeEncoding(method);
77+
78+
IMP interopImpl = [[interopImpls objectForKey:[NSValue valueWithPointer:originalSelector]] pointerValue];
79+
if (interopImpl != nil) {
7280
// `self` uses a runtime subclass generated by third-party APIs, and RAC
7381
// found an existing implementation for the selector at the setup time.
7482
// Call that implementation if it is not the ObjC message forwarder.
75-
Method xchgMethod = class_getInstanceMethod(baseClass, aliasOfOriginalSelector);
76-
IMP impl = method_getImplementation(xchgMethod);
83+
IMP oldImpl = class_replaceMethod(baseClass, originalSelector, interopImpl, typeEncoding);
84+
invocation.selector = originalSelector;
85+
[invocation invoke];
86+
class_replaceMethod(baseClass, originalSelector, oldImpl, typeEncoding);
87+
return;
88+
}
89+
90+
BOOL forwardRegardless = NO;
91+
92+
if (method != nil) {
93+
IMP statedClassImpl = method_getImplementation(method);
94+
95+
if (statedClassImpl != _objc_msgForward) {
96+
// The stated class has an implementation of the selector. Call that
97+
// implementation if it is not the ObjC message forwarder.
98+
Method aliasMethod = rac_getImmediateInstanceMethod(baseClass, aliasSelector);
99+
100+
if (statedClassImpl != method_getImplementation(aliasMethod)) {
101+
class_replaceMethod(baseClass, aliasSelector, statedClassImpl, typeEncoding);
102+
}
77103

78-
if (impl != _objc_msgForward) {
79-
IMP oldImpl = class_replaceMethod(baseClass, originalSelector, impl, method_getTypeEncoding(xchgMethod));
80-
invocation.selector = originalSelector;
81-
[invocation invoke];
82-
class_replaceMethod(baseClass, originalSelector, oldImpl, method_getTypeEncoding(xchgMethod));
83-
} else {
84-
forward = YES;
85-
}
86-
} else if ([superclass instancesRespondToSelector:originalSelector]) {
87-
// The stated class has an implementation of the selector. Call that
88-
// implementation if it is not the ObjC message forwarder.
89-
Method method = class_getInstanceMethod(superclass, originalSelector);
90-
IMP impl = method_getImplementation(method);
91-
92-
if (impl != _objc_msgForward) {
93-
class_replaceMethod(baseClass, aliasSelector, impl, method_getTypeEncoding(method));
94104
invocation.selector = aliasSelector;
95105
[invocation invoke];
96-
} else {
97-
forward = YES;
106+
107+
return;
98108
}
99-
} else {
100-
// No appropriate implementation was found. Forward the invocation to the
101-
// stated class' `forwardInvocation:` only if the selector had not been
102-
// intercepted via `signalForSelector:`.
103-
//
104-
// `subject` is usually null except for optional protocol requirements
105-
// that are implemented at runtime through RAC.
106-
forward = subject == nil;
109+
110+
forwardRegardless = YES;
107111
}
108112

109-
if (forward) {
113+
// No appropriate implementation was found. Forward the invocation to the
114+
// stated class' `forwardInvocation:` only if the selector had not been
115+
// intercepted via `signalForSelector:`.
116+
//
117+
// `subject` is usually null except for optional protocol requirements
118+
// that are implemented at runtime through RAC.
119+
if (subject == nil || forwardRegardless) {
110120
// Forward the invocation to the closest `forwardInvocation:` in the
111121
// inheritance hierarchy.
112-
struct objc_super target = {
113-
.super_class = superclass,
114-
.receiver = self,
115-
};
116-
117-
void*(*superForwardInvocation)(struct objc_super *, SEL, NSInvocation*) = (__typeof__(superForwardInvocation)) objc_msgSendSuper;
118-
superForwardInvocation(&target, forwardInvocationSEL, invocation);
119-
}
120-
121-
if (subject != nil) {
122-
[subject sendNext:invocation.rac_argumentsTuple];
122+
Method forwardInvocationMethod = class_getInstanceMethod(statedClass, forwardInvocationSEL);
123+
void*(*superForwardInvocation)(id, SEL, NSInvocation*) = (__typeof__(superForwardInvocation)) method_getImplementation(forwardInvocationMethod);
124+
superForwardInvocation(self, forwardInvocationSEL, invocation);
123125
}
124126
};
125127

126-
class_replaceMethod(class, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@");
128+
class_replaceMethod(baseClass, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@");
127129
}
128130

129-
static void RACSwizzleRespondsToSelector(Class class) {
130-
Class superclass = class_getSuperclass(class);
131+
static void RACSwizzleRespondsToSelector(Class baseClass) {
132+
Class statedClass = class_getSuperclass(baseClass);
131133
SEL respondsToSelectorSEL = @selector(respondsToSelector:);
132134

133135
// Set up a new version of -respondsToSelector: that returns YES for methods
@@ -138,24 +140,19 @@ static void RACSwizzleRespondsToSelector(Class class) {
138140
// the instance has a signal for the selector.
139141
// Otherwise, call the original -respondsToSelector:.
140142
id newRespondsToSelector = ^ BOOL (id self, SEL selector) {
141-
Method method = rac_getImmediateInstanceMethod(class, selector);
143+
Method method = rac_getImmediateInstanceMethod(baseClass, selector);
142144

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

148-
struct objc_super target = {
149-
.super_class = superclass,
150-
.receiver = self,
151-
};
152-
153-
BOOL(*superRespondsToSelector)(struct objc_super *, SEL, SEL) = (__typeof__(superRespondsToSelector)) objc_msgSendSuper;
154-
155-
return superRespondsToSelector(&target, respondsToSelectorSEL, selector);
150+
Method superMethod = class_getInstanceMethod(statedClass, respondsToSelectorSEL);
151+
BOOL(*superRespondsToSelector)(id, SEL, SEL) = (__typeof__(superRespondsToSelector)) method_getImplementation(superMethod);
152+
return superRespondsToSelector(self, respondsToSelectorSEL, selector);
156153
};
157154

158-
class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), "v@::");
155+
class_replaceMethod(baseClass, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), "v@::");
159156
}
160157

161158
static void RACSwizzleGetClass(Class class, Class statedClass) {
@@ -167,32 +164,30 @@ static void RACSwizzleGetClass(Class class, Class statedClass) {
167164
class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(method));
168165
}
169166

170-
static void RACSwizzleMethodSignatureForSelector(Class class) {
167+
static void RACSwizzleMethodSignatureForSelector(Class baseClass) {
168+
Class statedClass = class_getSuperclass(baseClass);
169+
SEL methodSignatureForSelectorSEL = @selector(methodSignatureForSelector:);
170+
171171
IMP newIMP = imp_implementationWithBlock(^(id self, SEL selector) {
172172
// Don't send the -class message to the receiver because we've changed
173173
// that to return the original class.
174-
Class actualClass = object_getClass(self);
175-
Method method = class_getInstanceMethod(actualClass, selector);
174+
Method method = class_getInstanceMethod(baseClass, selector);
175+
176176
if (method == NULL) {
177177
// Messages that the original class dynamically implements fall
178178
// here.
179179
//
180180
// Call the original class' -methodSignatureForSelector:.
181-
struct objc_super target = {
182-
.super_class = class_getSuperclass(class),
183-
.receiver = self,
184-
};
185-
NSMethodSignature * (*messageSend)(struct objc_super *, SEL, SEL) = (__typeof__(messageSend))objc_msgSendSuper;
186-
return messageSend(&target, @selector(methodSignatureForSelector:), selector);
181+
Method superMethod = class_getInstanceMethod(statedClass, methodSignatureForSelectorSEL);
182+
NSMethodSignature * (*messageSend)(id, SEL, SEL) = (__typeof__(messageSend)) method_getImplementation(superMethod);
183+
return messageSend(self, methodSignatureForSelectorSEL, selector);
187184
}
188185

189186
char const *encoding = method_getTypeEncoding(method);
190187
return [NSMethodSignature signatureWithObjCTypes:encoding];
191188
});
192189

193-
SEL selector = @selector(methodSignatureForSelector:);
194-
Method methodSignatureForSelectorMethod = class_getInstanceMethod(class, selector);
195-
class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(methodSignatureForSelectorMethod));
190+
class_replaceMethod(baseClass, methodSignatureForSelectorSEL, newIMP, "@@::");
196191
}
197192

198193
// It's hard to tell which struct return types use _objc_msgForward, and
@@ -266,12 +261,20 @@ static void RACCheckTypeEncoding(const char *typeEncoding) {
266261

267262
RACCheckTypeEncoding(typeEncoding);
268263

269-
Method existingMethod = rac_getImmediateInstanceMethod(class, selector);
270-
271-
if (existingMethod) {
272-
SEL sel = RACAliasOfOriginalForSelector(selector);
273-
BOOL addedAlias __attribute__((unused)) = class_addMethod(class, sel, method_getImplementation(existingMethod), typeEncoding);
274-
NSCAssert(addedAlias, @"Existing external implementation for %@ has already been copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(sel), class);
264+
Method dynamicImmediateMethod = rac_getImmediateInstanceMethod(class, selector);
265+
if (dynamicImmediateMethod) {
266+
IMP dynamicImmediateImpl = method_getImplementation(dynamicImmediateMethod);
267+
if (dynamicImmediateImpl != _objc_msgForward) {
268+
@synchronized (class) {
269+
NSMutableDictionary* interopImpls = objc_getAssociatedObject(class, RACInteropImplsKey);
270+
NSValue* key = [NSValue valueWithPointer:selector];
271+
272+
if ([interopImpls objectForKey:key] == nil) {
273+
[interopImpls setObject:[NSValue valueWithPointer:dynamicImmediateImpl]
274+
forKey:key];
275+
}
276+
}
277+
}
275278
}
276279

277280
// Redefine the selector to call -forwardInvocation:.
@@ -282,14 +285,21 @@ static void RACCheckTypeEncoding(const char *typeEncoding) {
282285
}
283286
}
284287

285-
static SEL RACAliasOfOriginalForSelector(SEL originalSelector) {
286-
NSString *selectorName = NSStringFromSelector(originalSelector);
287-
return NSSelectorFromString([RACSignalForSelectorAliasOfOriginalPrefix stringByAppendingString:selectorName]);
288-
}
289-
290288
static SEL RACAliasForSelector(SEL originalSelector) {
291-
NSString *selectorName = NSStringFromSelector(originalSelector);
292-
return NSSelectorFromString([RACSignalForSelectorAliasPrefix stringByAppendingString:selectorName]);
289+
const char* selectorString = sel_getName(originalSelector);
290+
unsigned long length = strlen(selectorString);
291+
unsigned long prefixedLength = length + RACSignalForSelectorAliasPrefixLength;
292+
293+
char* buffer = malloc(length + RACSignalForSelectorAliasPrefixLength + 1);
294+
@onExit {
295+
free(buffer);
296+
};
297+
298+
memcpy(buffer, RACSignalForSelectorAliasPrefix, RACSignalForSelectorAliasPrefixLength);
299+
memcpy(buffer + RACSignalForSelectorAliasPrefixLength, selectorString, length);
300+
*(buffer + prefixedLength) = '\0';
301+
302+
return sel_registerName(buffer);
293303
}
294304

295305
static const char *RACSignatureForUndefinedSelector(SEL selector) {
@@ -304,6 +314,20 @@ static SEL RACAliasForSelector(SEL originalSelector) {
304314
return signature.UTF8String;
305315
}
306316

317+
static NSDictionary* RACSetupInteropImplDictionary(Class baseClass) {
318+
NSDictionary* interopImpls;
319+
320+
@synchronized (baseClass) {
321+
interopImpls = objc_getAssociatedObject(baseClass, RACInteropImplsKey);
322+
if (interopImpls == nil) {
323+
interopImpls = [[NSMutableDictionary alloc] init];
324+
objc_setAssociatedObject(baseClass, RACInteropImplsKey, interopImpls, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
325+
}
326+
}
327+
328+
return interopImpls;
329+
}
330+
307331
static Class RACSwizzleClass(NSObject *self) {
308332
Class statedClass = self.class;
309333
Class baseClass = object_getClass(self);
@@ -330,7 +354,7 @@ static Class RACSwizzleClass(NSObject *self) {
330354
// implementation may be ignorant of methods added to this class.
331355
@synchronized (swizzledClasses()) {
332356
if (![swizzledClasses() containsObject:className]) {
333-
RACSwizzleForwardInvocation(baseClass);
357+
RACSwizzleForwardInvocation(baseClass, RACSetupInteropImplDictionary(baseClass));
334358
RACSwizzleRespondsToSelector(baseClass);
335359
RACSwizzleGetClass(baseClass, statedClass);
336360
RACSwizzleGetClass(object_getClass(baseClass), statedClass);
@@ -349,7 +373,7 @@ static Class RACSwizzleClass(NSObject *self) {
349373
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
350374
if (subclass == nil) return nil;
351375

352-
RACSwizzleForwardInvocation(subclass);
376+
RACSwizzleForwardInvocation(subclass, RACSetupInteropImplDictionary(subclass));
353377
RACSwizzleRespondsToSelector(subclass);
354378

355379
RACSwizzleGetClass(subclass, statedClass);

0 commit comments

Comments
 (0)