Skip to content

Commit 596b3fa

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 596b3fa

File tree

1 file changed

+90
-86
lines changed

1 file changed

+90
-86
lines changed

ReactiveObjC/NSObject+RACSelectorSignal.m

Lines changed: 90 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@
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

2425
static NSString * const RACSignalForSelectorAliasPrefix = @"rac_alias_";
25-
static NSString * const RACSignalForSelectorAliasOfOriginalPrefix = @"rac_runtime_";
2626
static NSString * const RACSubclassSuffix = @"_RACSelectorSignal";
2727
static void *RACSubclassAssociationKey = &RACSubclassAssociationKey;
28+
static void *RACInteropImplsKey = &RACInteropImplsKey;
2829

2930
static NSMutableSet *swizzledClasses() {
3031
static NSMutableSet *set;
@@ -39,8 +40,9 @@
3940

4041
@implementation NSObject (RACSelectorSignal)
4142

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

4648
// Set up a new version of -forwardInvocation:.
@@ -53,81 +55,73 @@ static void RACSwizzleForwardInvocation(Class class) {
5355
// was no existing implementation, throw an unrecognized selector
5456
// exception.
5557
id newForwardInvocation = ^(id self, NSInvocation *invocation) {
56-
BOOL forward = NO;
57-
5858
SEL originalSelector = invocation.selector;
5959
SEL aliasSelector = RACAliasForSelector(originalSelector);
60-
SEL aliasOfOriginalSelector = RACAliasOfOriginalForSelector(originalSelector);
6160

6261
RACSubject* subject = objc_getAssociatedObject(self, aliasSelector);
6362

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

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

71-
if ([baseClass instancesRespondToSelector:aliasOfOriginalSelector]) {
74+
Method method = class_getInstanceMethod(statedClass, originalSelector);
75+
const char* typeEncoding = method_getTypeEncoding(method);
76+
77+
IMP interopImpl = [[interopImpls objectForKey:[NSValue valueWithPointer:originalSelector]] pointerValue];
78+
if (interopImpl != nil) {
7279
// `self` uses a runtime subclass generated by third-party APIs, and RAC
7380
// found an existing implementation for the selector at the setup time.
7481
// Call that implementation if it is not the ObjC message forwarder.
75-
Method xchgMethod = class_getInstanceMethod(baseClass, aliasOfOriginalSelector);
76-
IMP impl = method_getImplementation(xchgMethod);
77-
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]) {
82+
IMP oldImpl = class_replaceMethod(baseClass, originalSelector, interopImpl, typeEncoding);
83+
invocation.selector = originalSelector;
84+
[invocation invoke];
85+
class_replaceMethod(baseClass, originalSelector, oldImpl, typeEncoding);
86+
return;
87+
}
88+
89+
IMP statedClassImpl = method_getImplementation(method);
90+
if (statedClassImpl != _objc_msgForward) {
8791
// The stated class has an implementation of the selector. Call that
8892
// implementation if it is not the ObjC message forwarder.
89-
Method method = class_getInstanceMethod(superclass, originalSelector);
90-
IMP impl = method_getImplementation(method);
93+
Method method = rac_getImmediateInstanceMethod(baseClass, aliasSelector);
9194

92-
if (impl != _objc_msgForward) {
93-
class_replaceMethod(baseClass, aliasSelector, impl, method_getTypeEncoding(method));
94-
invocation.selector = aliasSelector;
95-
[invocation invoke];
96-
} else {
97-
forward = YES;
95+
if (statedClassImpl != method_getImplementation(method)) {
96+
class_replaceMethod(baseClass, aliasSelector, statedClassImpl, typeEncoding);
9897
}
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;
107-
}
10898

109-
if (forward) {
110-
// Forward the invocation to the closest `forwardInvocation:` in the
111-
// inheritance hierarchy.
112-
struct objc_super target = {
113-
.super_class = superclass,
114-
.receiver = self,
115-
};
99+
invocation.selector = aliasSelector;
100+
[invocation invoke];
116101

117-
void*(*superForwardInvocation)(struct objc_super *, SEL, NSInvocation*) = (__typeof__(superForwardInvocation)) objc_msgSendSuper;
118-
superForwardInvocation(&target, forwardInvocationSEL, invocation);
102+
return;
119103
}
120104

121-
if (subject != nil) {
122-
[subject sendNext:invocation.rac_argumentsTuple];
105+
// No appropriate implementation was found. Forward the invocation to the
106+
// stated class' `forwardInvocation:` only if the selector had not been
107+
// intercepted via `signalForSelector:`.
108+
//
109+
// `subject` is usually null except for optional protocol requirements
110+
// that are implemented at runtime through RAC.
111+
if (subject == nil) {
112+
// Forward the invocation to the closest `forwardInvocation:` in the
113+
// inheritance hierarchy.
114+
Method forwardInvocationMethod = class_getInstanceMethod(statedClass, forwardInvocationSEL);
115+
void*(*superForwardInvocation)(id, SEL, NSInvocation*) = (__typeof__(superForwardInvocation)) method_getImplementation(forwardInvocationMethod);
116+
superForwardInvocation(self, forwardInvocationSEL, invocation);
123117
}
124118
};
125119

126-
class_replaceMethod(class, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@");
120+
class_replaceMethod(baseClass, forwardInvocationSEL, imp_implementationWithBlock(newForwardInvocation), "v@:@");
127121
}
128122

129-
static void RACSwizzleRespondsToSelector(Class class) {
130-
Class superclass = class_getSuperclass(class);
123+
static void RACSwizzleRespondsToSelector(Class baseClass) {
124+
Class statedClass = class_getSuperclass(baseClass);
131125
SEL respondsToSelectorSEL = @selector(respondsToSelector:);
132126

133127
// Set up a new version of -respondsToSelector: that returns YES for methods
@@ -138,24 +132,19 @@ static void RACSwizzleRespondsToSelector(Class class) {
138132
// the instance has a signal for the selector.
139133
// Otherwise, call the original -respondsToSelector:.
140134
id newRespondsToSelector = ^ BOOL (id self, SEL selector) {
141-
Method method = rac_getImmediateInstanceMethod(class, selector);
135+
Method method = rac_getImmediateInstanceMethod(baseClass, selector);
142136

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

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);
142+
Method superMethod = class_getInstanceMethod(statedClass, respondsToSelectorSEL);
143+
BOOL(*superRespondsToSelector)(id, SEL, SEL) = (__typeof__(superRespondsToSelector)) method_getImplementation(superMethod);
144+
return superRespondsToSelector(self, respondsToSelectorSEL, selector);
156145
};
157146

158-
class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), "v@::");
147+
class_replaceMethod(baseClass, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), "v@::");
159148
}
160149

161150
static void RACSwizzleGetClass(Class class, Class statedClass) {
@@ -167,32 +156,30 @@ static void RACSwizzleGetClass(Class class, Class statedClass) {
167156
class_replaceMethod(class, selector, newIMP, method_getTypeEncoding(method));
168157
}
169158

170-
static void RACSwizzleMethodSignatureForSelector(Class class) {
159+
static void RACSwizzleMethodSignatureForSelector(Class baseClass) {
160+
Class statedClass = class_getSuperclass(baseClass);
161+
SEL methodSignatureForSelectorSEL = @selector(methodSignatureForSelector:);
162+
171163
IMP newIMP = imp_implementationWithBlock(^(id self, SEL selector) {
172164
// Don't send the -class message to the receiver because we've changed
173165
// that to return the original class.
174-
Class actualClass = object_getClass(self);
175-
Method method = class_getInstanceMethod(actualClass, selector);
166+
Method method = class_getInstanceMethod(baseClass, selector);
167+
176168
if (method == NULL) {
177169
// Messages that the original class dynamically implements fall
178170
// here.
179171
//
180172
// 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);
173+
Method superMethod = class_getInstanceMethod(statedClass, methodSignatureForSelectorSEL);
174+
NSMethodSignature * (*messageSend)(id, SEL, SEL) = (__typeof__(messageSend)) method_getImplementation(superMethod);
175+
return messageSend(self, methodSignatureForSelectorSEL, selector);
187176
}
188177

189178
char const *encoding = method_getTypeEncoding(method);
190179
return [NSMethodSignature signatureWithObjCTypes:encoding];
191180
});
192181

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

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

267254
RACCheckTypeEncoding(typeEncoding);
268255

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);
256+
Method dynamicImmediateMethod = rac_getImmediateInstanceMethod(class, selector);
257+
if (dynamicImmediateMethod) {
258+
IMP dynamicImmediateImpl = method_getImplementation(dynamicImmediateMethod);
259+
if (dynamicImmediateImpl != _objc_msgForward) {
260+
@synchronized (class) {
261+
NSMutableDictionary* interopImpls = objc_getAssociatedObject(class, RACInteropImplsKey);
262+
NSValue* key = [NSValue valueWithPointer:selector];
263+
264+
if ([interopImpls objectForKey:key] == nil) {
265+
[interopImpls setObject:[NSValue valueWithPointer:dynamicImmediateImpl]
266+
forKey:key];
267+
}
268+
}
269+
}
275270
}
276271

277272
// Redefine the selector to call -forwardInvocation:.
@@ -282,11 +277,6 @@ static void RACCheckTypeEncoding(const char *typeEncoding) {
282277
}
283278
}
284279

285-
static SEL RACAliasOfOriginalForSelector(SEL originalSelector) {
286-
NSString *selectorName = NSStringFromSelector(originalSelector);
287-
return NSSelectorFromString([RACSignalForSelectorAliasOfOriginalPrefix stringByAppendingString:selectorName]);
288-
}
289-
290280
static SEL RACAliasForSelector(SEL originalSelector) {
291281
NSString *selectorName = NSStringFromSelector(originalSelector);
292282
return NSSelectorFromString([RACSignalForSelectorAliasPrefix stringByAppendingString:selectorName]);
@@ -304,6 +294,20 @@ static SEL RACAliasForSelector(SEL originalSelector) {
304294
return signature.UTF8String;
305295
}
306296

297+
static NSDictionary* RACSetupInteropImplDictionary(Class baseClass) {
298+
NSDictionary* interopImpls;
299+
300+
@synchronized (baseClass) {
301+
interopImpls = objc_getAssociatedObject(baseClass, RACInteropImplsKey);
302+
if (interopImpls == nil) {
303+
interopImpls = [[NSMutableDictionary alloc] init];
304+
objc_setAssociatedObject(baseClass, RACInteropImplsKey, interopImpls, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
305+
}
306+
}
307+
308+
return interopImpls;
309+
}
310+
307311
static Class RACSwizzleClass(NSObject *self) {
308312
Class statedClass = self.class;
309313
Class baseClass = object_getClass(self);
@@ -330,7 +334,7 @@ static Class RACSwizzleClass(NSObject *self) {
330334
// implementation may be ignorant of methods added to this class.
331335
@synchronized (swizzledClasses()) {
332336
if (![swizzledClasses() containsObject:className]) {
333-
RACSwizzleForwardInvocation(baseClass);
337+
RACSwizzleForwardInvocation(baseClass, RACSetupInteropImplDictionary(baseClass));
334338
RACSwizzleRespondsToSelector(baseClass);
335339
RACSwizzleGetClass(baseClass, statedClass);
336340
RACSwizzleGetClass(object_getClass(baseClass), statedClass);
@@ -349,7 +353,7 @@ static Class RACSwizzleClass(NSObject *self) {
349353
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
350354
if (subclass == nil) return nil;
351355

352-
RACSwizzleForwardInvocation(subclass);
356+
RACSwizzleForwardInvocation(subclass, RACSetupInteropImplDictionary(subclass));
353357
RACSwizzleRespondsToSelector(subclass);
354358

355359
RACSwizzleGetClass(subclass, statedClass);

0 commit comments

Comments
 (0)