From afd00ec3dc0b3569028b47815765304d53e7e5ae Mon Sep 17 00:00:00 2001 From: Anders Date: Sat, 12 Nov 2016 22:40:58 +0100 Subject: [PATCH 01/14] Selector Signal Interoperability. --- ReactiveObjC/NSObject+RACSelectorSignal.m | 64 +++++----- .../NSObjectRACSelectorSignalSpec.m | 109 +++++++++++++++++- 2 files changed, 139 insertions(+), 34 deletions(-) diff --git a/ReactiveObjC/NSObject+RACSelectorSignal.m b/ReactiveObjC/NSObject+RACSelectorSignal.m index 63e232003..2d06f11b2 100644 --- a/ReactiveObjC/NSObject+RACSelectorSignal.m +++ b/ReactiveObjC/NSObject+RACSelectorSignal.m @@ -38,32 +38,9 @@ @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) { + Class superclass = class_getSuperclass(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:. // @@ -75,13 +52,37 @@ 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(invocation.selector); + RACSubject* subject = objc_getAssociatedObject(self, aliasSelector); + + BOOL responseToSelector = [superclass instancesRespondToSelector:originalSelector]; + BOOL forward = NO; + if (responseToSelector) { + Method method = class_getInstanceMethod(superclass, invocation.selector); + IMP impl = method_getImplementation(method); + + if (impl != _objc_msgForward) { + class_replaceMethod(object_getClass(self), aliasSelector, impl, method_getTypeEncoding(method)); + invocation.selector = aliasSelector; + [invocation invoke]; + } else { + forward = YES; + } + } + + if (forward || (!responseToSelector && subject == nil)) { + struct objc_super target = { + .super_class = superclass, + .receiver = self, + }; + + void*(*superForwardInvocation)(struct objc_super *, SEL, NSInvocation*) = (__typeof__(superForwardInvocation)) objc_msgSendSuper; + superForwardInvocation(&target, forwardInvocationSEL, invocation); + } - if (originalForwardInvocation == NULL) { - [self doesNotRecognizeSelector:invocation.selector]; - } else { - originalForwardInvocation(self, forwardInvocationSEL, invocation); + if (subject != nil) { + [subject sendNext:invocation.rac_argumentsTuple]; } }; @@ -224,9 +225,6 @@ 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); - // Redefine the selector to call -forwardInvocation:. class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod)); } diff --git a/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m b/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m index 7c4afbaa0..956222ce0 100644 --- a/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m +++ b/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m @@ -9,9 +9,13 @@ @import Quick; @import Nimble; +#import + #import "RACTestObject.h" #import "RACSubclassObject.h" +#import + #import "NSObject+RACDeallocating.h" #import "NSObject+RACPropertySubscribing.h" #import "NSObject+RACSelectorSignal.h" @@ -193,7 +197,7 @@ - (id)objectValue; expect(@([object respondsToSelector:selector])).to(beTruthy()); }); - + qck_it(@"should properly implement -respondsToSelector: when called on signalForSelector'd receiver that has subsequently been KVO'd", ^{ RACTestObject *object = [[RACTestObject alloc] init]; @@ -306,6 +310,109 @@ - (id)objectValue; }); }); +qck_describe(@"interoperability", ^{ + __block BOOL invoked; + __block RACTestObject * object; + __block Class originalClass; + + qck_beforeEach(^{ + invoked = NO; + object = [[RACTestObject alloc] init]; + originalClass = RACTestObject.class; + }); + + qck_it(@"should invoke the swizzled `forwardInvocation:` on an instance isa-swizzled by both RAC and KVO.", ^{ + [[RACObserve(object, objectValue) publish] connect]; + [object rac_signalForSelector:@selector(lifeIsGood:)]; + + SEL swizzledSelector = @selector(lifeIsGood:); + + // Redirect `swizzledSelector` to the forwarding machinery. + Method method = class_getInstanceMethod(originalClass, swizzledSelector); + const char *typeDescription = (char *)method_getTypeEncoding(method); + IMP originalImp = class_replaceMethod(originalClass, swizzledSelector, _objc_msgForward, typeDescription); + + @onExit { + class_replaceMethod(originalClass, swizzledSelector, originalImp, typeDescription); + }; + + // Swizzle `forwardInvocation:` to intercept `swizzledSelector`. + id patchForwardInvocationBlock = ^(id self, NSInvocation *invocation) { + if (invocation.selector == swizzledSelector) { + expect(@(invoked)).to(beFalsy()); + invoked = YES; + } + }; + + IMP newForwardInvocation = imp_implementationWithBlock(patchForwardInvocationBlock); + IMP oldForwardInvocation = class_replaceMethod(originalClass, @selector(forwardInvocation:), newForwardInvocation, "v@:@"); + + @onExit { + class_replaceMethod(originalClass, @selector(forwardInvocation:), oldForwardInvocation, "v@:@"); + }; + + [object lifeIsGood:nil]; + expect(@(invoked)).to(beTruthy()); + }); + + qck_it(@"should invoke the swizzled `forwardInvocation:` on an instance isa-swizzled by RAC.", ^{ + [object rac_signalForSelector:@selector(lifeIsGood:)]; + + SEL swizzledSelector = @selector(lifeIsGood:); + + // Redirect `swizzledSelector` to the forwarding machinery. + Method method = class_getInstanceMethod(originalClass, swizzledSelector); + const char *typeEncoding = (char *)method_getTypeEncoding(method); + IMP originalImp = class_replaceMethod(originalClass, swizzledSelector, _objc_msgForward, typeEncoding); + + @onExit { + class_replaceMethod(originalClass, swizzledSelector, originalImp, typeEncoding); + }; + + // Swizzle `forwardInvocation:` to intercept `swizzledSelector`. + id patchForwardInvocationBlock = ^(id self, NSInvocation *invocation) { + if (invocation.selector == swizzledSelector) { + expect(@(invoked)).to(beFalsy()); + invoked = YES; + } + }; + + IMP newForwardInvocation = imp_implementationWithBlock(patchForwardInvocationBlock); + IMP oldForwardInvocation = class_replaceMethod(originalClass, @selector(forwardInvocation:), newForwardInvocation, "v@:@"); + + @onExit { + class_replaceMethod(originalClass, @selector(forwardInvocation:), oldForwardInvocation, "v@:@"); + }; + + [object lifeIsGood:nil]; + expect(@(invoked)).to(beTruthy()); + }); + + qck_it(@"should invoke the swizzled selector on an instance isa-swizzled by RAC.", ^{ + [object rac_signalForSelector:@selector(lifeIsGood:)]; + + SEL swizzledSelector = @selector(lifeIsGood:); + + Method method = class_getInstanceMethod(originalClass, swizzledSelector); + const char *typeEncoding = (char *)method_getTypeEncoding(method); + + id methodSwizzlingBlock = ^(id self) { + expect(@(invoked)).to(beFalsy()); + invoked = YES; + }; + + IMP newImplementation = imp_implementationWithBlock(methodSwizzlingBlock); + IMP oldImplementation = class_replaceMethod(originalClass, swizzledSelector, newImplementation, typeEncoding); + + @onExit { + class_replaceMethod(originalClass, swizzledSelector, oldImplementation, typeEncoding); + }; + + [object lifeIsGood:nil]; + expect(@(invoked)).to(beTruthy()); + }); +}); + qck_it(@"should swizzle an NSObject method", ^{ NSObject *object = [[NSObject alloc] init]; From 39bf8b92a20babeb3b6a01cb4385750b7e3cf408 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 13 Nov 2016 03:18:04 +0100 Subject: [PATCH 02/14] Improved KVO-involving test cases. --- .../NSObjectRACSelectorSignalSpec.m | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m b/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m index 956222ce0..4cee74037 100644 --- a/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m +++ b/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m @@ -141,7 +141,11 @@ - (id)objectValue; qck_it(@"should send arguments for invocation and invoke the original method on previously KVO'd receiver", ^{ RACTestObject *object = [[RACTestObject alloc] init]; - [[RACObserve(object, objectValue) publish] connect]; + __block id latestValue; + [[[RACObserve(object, objectValue) publish] autoconnect] subscribeNext:^(id objectValue) { + latestValue = objectValue; + }]; + expect(latestValue).to(beNil()); __block id key; __block id value; @@ -156,6 +160,8 @@ - (id)objectValue; expect(object.objectValue).to(equal(@YES)); expect(object.secondObjectValue).to(equal(@"Winner")); + expect(latestValue).to(equal(@YES)); + expect(value).to(equal(@YES)); expect(key).to(equal(@"Winner")); }); @@ -170,7 +176,11 @@ - (id)objectValue; key = x.second; }]; - [[RACObserve(object, objectValue) publish] connect]; + __block id latestValue; + [[[RACObserve(object, objectValue) publish] autoconnect] subscribeNext:^(id objectValue) { + latestValue = objectValue; + }]; + expect(latestValue).to(beNil()); [object setObjectValue:@YES andSecondObjectValue:@"Winner"]; @@ -178,6 +188,8 @@ - (id)objectValue; expect(object.objectValue).to(equal(@YES)); expect(object.secondObjectValue).to(equal(@"Winner")); + expect(latestValue).to(equal(@YES)); + expect(value).to(equal(@YES)); expect(key).to(equal(@"Winner")); }); @@ -411,6 +423,30 @@ - (id)objectValue; [object lifeIsGood:nil]; expect(@(invoked)).to(beTruthy()); }); + + qck_it(@"should invoke the swizzled setter on an instance isa-swizzled by RAC.", ^{ + [object rac_signalForSelector:@selector(setObjectValue:)]; + + SEL swizzledSelector = @selector(lifeIsGood:); + + Method method = class_getInstanceMethod(originalClass, swizzledSelector); + const char *typeEncoding = (char *)method_getTypeEncoding(method); + + id methodSwizzlingBlock = ^(id self) { + expect(@(invoked)).to(beFalsy()); + invoked = YES; + }; + + IMP newImplementation = imp_implementationWithBlock(methodSwizzlingBlock); + IMP oldImplementation = class_replaceMethod(originalClass, swizzledSelector, newImplementation, typeEncoding); + + @onExit { + class_replaceMethod(originalClass, swizzledSelector, oldImplementation, typeEncoding); + }; + + [object lifeIsGood:nil]; + expect(@(invoked)).to(beTruthy()); + }); }); qck_it(@"should swizzle an NSObject method", ^{ From 9ae4776d16ba88be4343d53c057c2a02609018f4 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 13 Nov 2016 03:33:35 +0100 Subject: [PATCH 03/14] Brought `respondsToSelector` swizzling into line with others. --- ReactiveObjC/NSObject+RACSelectorSignal.m | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ReactiveObjC/NSObject+RACSelectorSignal.m b/ReactiveObjC/NSObject+RACSelectorSignal.m index 2d06f11b2..a8bae426a 100644 --- a/ReactiveObjC/NSObject+RACSelectorSignal.m +++ b/ReactiveObjC/NSObject+RACSelectorSignal.m @@ -90,12 +90,9 @@ static void RACSwizzleForwardInvocation(Class class) { } static void RACSwizzleRespondsToSelector(Class class) { + Class superclass = class_getSuperclass(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:. // @@ -111,10 +108,17 @@ static void RACSwizzleRespondsToSelector(Class class) { if (objc_getAssociatedObject(self, aliasSelector) != nil) return YES; } - return originalRespondsToSelector(self, respondsToSelectorSEL, selector); + struct objc_super target = { + .super_class = superclass, + .receiver = self, + }; + + BOOL(*superRespondsToSelector)(struct objc_super *, SEL, SEL) = (__typeof__(superRespondsToSelector)) objc_msgSendSuper; + + return superRespondsToSelector(&target, respondsToSelectorSEL, selector); }; - class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), method_getTypeEncoding(respondsToSelectorMethod)); + class_replaceMethod(class, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), "v@::"); } static void RACSwizzleGetClass(Class class, Class statedClass) { From 1901739fcf77ff415a6faed972fa62b156f6a044 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 13 Nov 2016 06:37:31 +0100 Subject: [PATCH 04/14] Fixed a few KVO interoperability issues. --- ReactiveObjC/NSObject+RACSelectorSignal.m | 40 ++++++++++++++---- .../NSObjectRACSelectorSignalSpec.m | 42 +++++++++++++++++++ ReactiveObjCTests/RACTestObject.m | 4 ++ 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/ReactiveObjC/NSObject+RACSelectorSignal.m b/ReactiveObjC/NSObject+RACSelectorSignal.m index a8bae426a..3ded4c74f 100644 --- a/ReactiveObjC/NSObject+RACSelectorSignal.m +++ b/ReactiveObjC/NSObject+RACSelectorSignal.m @@ -22,6 +22,7 @@ const NSInteger RACSelectorSignalErrorMethodSwizzlingRace = 1; static NSString * const RACSignalForSelectorAliasPrefix = @"rac_alias_"; +static NSString * const RACSignalForSelectorAliasOfOriginalPrefix = @"rac_runtime_"; static NSString * const RACSubclassSuffix = @"_RACSelectorSignal"; static void *RACSubclassAssociationKey = &RACSubclassAssociationKey; @@ -52,26 +53,38 @@ static void RACSwizzleForwardInvocation(Class class) { // was no existing implementation, throw an unrecognized selector // exception. id newForwardInvocation = ^(id self, NSInvocation *invocation) { + BOOL forward = NO; + SEL originalSelector = invocation.selector; - SEL aliasSelector = RACAliasForSelector(invocation.selector); + SEL aliasSelector = RACAliasForSelector(originalSelector); + SEL aliasOfOriginalSelector = RACAliasOfOriginalForSelector(originalSelector); + RACSubject* subject = objc_getAssociatedObject(self, aliasSelector); - BOOL responseToSelector = [superclass instancesRespondToSelector:originalSelector]; - BOOL forward = NO; - if (responseToSelector) { - Method method = class_getInstanceMethod(superclass, invocation.selector); + Class baseClass = object_getClass(self); + + if ([baseClass instancesRespondToSelector:aliasOfOriginalSelector]) { + Method xchgMethod = class_getInstanceMethod(baseClass, aliasOfOriginalSelector); + IMP oldImpl = class_replaceMethod(baseClass, originalSelector, method_getImplementation(xchgMethod), method_getTypeEncoding(xchgMethod)); + invocation.selector = originalSelector; + [invocation invoke]; + class_replaceMethod(baseClass, originalSelector, oldImpl, method_getTypeEncoding(xchgMethod)); + } else if ([superclass instancesRespondToSelector:originalSelector]) { + Method method = class_getInstanceMethod(superclass, originalSelector); IMP impl = method_getImplementation(method); if (impl != _objc_msgForward) { - class_replaceMethod(object_getClass(self), aliasSelector, impl, method_getTypeEncoding(method)); + class_replaceMethod(baseClass, aliasSelector, impl, method_getTypeEncoding(method)); invocation.selector = aliasSelector; [invocation invoke]; } else { forward = YES; } + } else { + forward = subject == nil; } - if (forward || (!responseToSelector && subject == nil)) { + if (forward) { struct objc_super target = { .super_class = superclass, .receiver = self, @@ -229,6 +242,14 @@ static void RACCheckTypeEncoding(const char *typeEncoding) { RACCheckTypeEncoding(typeEncoding); + Method existingMethod = rac_getImmediateInstanceMethod(class, selector); + + if (existingMethod) { + SEL sel = RACAliasOfOriginalForSelector(selector); + BOOL addedAlias __attribute__((unused)) = class_addMethod(class, sel, method_getImplementation(existingMethod), typeEncoding); + NSCAssert(addedAlias, @"Existing external implementation for %@ has already been copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(sel), class); + } + // Redefine the selector to call -forwardInvocation:. class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod)); } @@ -237,6 +258,11 @@ static void RACCheckTypeEncoding(const char *typeEncoding) { } } +static SEL RACAliasOfOriginalForSelector(SEL originalSelector) { + NSString *selectorName = NSStringFromSelector(originalSelector); + return NSSelectorFromString([RACSignalForSelectorAliasOfOriginalPrefix stringByAppendingString:selectorName]); +} + static SEL RACAliasForSelector(SEL originalSelector) { NSString *selectorName = NSStringFromSelector(originalSelector); return NSSelectorFromString([RACSignalForSelectorAliasPrefix stringByAppendingString:selectorName]); diff --git a/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m b/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m index 4cee74037..f5072fe36 100644 --- a/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m +++ b/ReactiveObjCTests/NSObjectRACSelectorSignalSpec.m @@ -166,6 +166,48 @@ - (id)objectValue; expect(key).to(equal(@"Winner")); }); + qck_it(@"should send arguments for invocation and invoke the a KVO-swizzled then RAC-swizzled setter", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block id latestValue; + [[[RACObserve(object, objectValue) publish] autoconnect] subscribeNext:^(id objectValue) { + latestValue = objectValue; + }]; + expect(latestValue).to(beNil()); + + __block id value; + [[object rac_signalForSelector:@selector(setObjectValue:)] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; + + [object setObjectValue:@YES]; + + expect(object.objectValue).to(equal(@YES)); + expect(latestValue).to(equal(@YES)); + expect(value).to(equal(@YES)); + }); + + qck_it(@"should send arguments for invocation and invoke the a RAC-swizzled then KVO-swizzled setter", ^{ + RACTestObject *object = [[RACTestObject alloc] init]; + + __block id value; + [[object rac_signalForSelector:@selector(setObjectValue:)] subscribeNext:^(RACTuple *x) { + value = x.first; + }]; + + __block id latestValue; + [[[RACObserve(object, objectValue) publish] autoconnect] subscribeNext:^(id objectValue) { + latestValue = objectValue; + }]; + expect(latestValue).to(beNil()); + + [object setObjectValue:@YES]; + + expect(object.objectValue).to(equal(@YES)); + expect(latestValue).to(equal(@YES)); + expect(value).to(equal(@YES)); + }); + qck_it(@"should send arguments for invocation and invoke the original method when receiver is subsequently KVO'd", ^{ RACTestObject *object = [[RACTestObject alloc] init]; diff --git a/ReactiveObjCTests/RACTestObject.m b/ReactiveObjCTests/RACTestObject.m index 66aac010a..32298abec 100644 --- a/ReactiveObjCTests/RACTestObject.m +++ b/ReactiveObjCTests/RACTestObject.m @@ -15,6 +15,10 @@ - (void)dealloc { free((void *)_constCharPointerValue); } +- (void)setObjectValue:(id)objectValue { + _objectValue = objectValue; +} + - (void)setNilValueForKey:(NSString *)key { if (!self.catchSetNilValueForKey) [super setNilValueForKey:key]; } From d983255ec1ab2e2e811b2999b10d8b53700023e7 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 13 Nov 2016 16:33:32 +0100 Subject: [PATCH 05/14] Added stress tests for the new `signalForSelector:` impl. --- ReactiveObjC.xcodeproj/project.pbxproj | 8 ++ ...SObjectRACSelectorSignalPerformanceTests.m | 75 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 ReactiveObjCTests/NSObjectRACSelectorSignalPerformanceTests.m diff --git a/ReactiveObjC.xcodeproj/project.pbxproj b/ReactiveObjC.xcodeproj/project.pbxproj index f9b4a1af8..a58990a73 100644 --- a/ReactiveObjC.xcodeproj/project.pbxproj +++ b/ReactiveObjC.xcodeproj/project.pbxproj @@ -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, ); }; }; @@ -769,6 +772,7 @@ 7A70657E1A3F88B8001E8354 /* RACKVOProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOProxy.m; sourceTree = ""; }; 7A7065831A3F8967001E8354 /* RACKVOProxySpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACKVOProxySpec.m; sourceTree = ""; }; 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 = ""; }; A97451331B3A935E00F48E55 /* watchOS-Application.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "watchOS-Application.xcconfig"; sourceTree = ""; }; A97451341B3A935E00F48E55 /* watchOS-Base.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "watchOS-Base.xcconfig"; sourceTree = ""; }; A97451351B3A935E00F48E55 /* watchOS-Framework.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "watchOS-Framework.xcconfig"; sourceTree = ""; }; @@ -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 */, @@ -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 */, @@ -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 */, @@ -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 */, diff --git a/ReactiveObjCTests/NSObjectRACSelectorSignalPerformanceTests.m b/ReactiveObjCTests/NSObjectRACSelectorSignalPerformanceTests.m new file mode 100644 index 000000000..6bf639684 --- /dev/null +++ b/ReactiveObjCTests/NSObjectRACSelectorSignalPerformanceTests.m @@ -0,0 +1,75 @@ +// +// NSObjectRACSelectorSignalPerformanceTests.m +// ReactiveObjC +// +// Created by Anders Ha on 13/11/2016. +// Copyright © 2016 GitHub. All rights reserved. +// + +@import Nimble; + +#import +#import "RACTestObject.h" +#import "NSObject+RACSelectorSignal.h" +#import "RACSignal.h" +#import "NSObject+RACPropertySubscribing.h" + +@interface NSObjectRACSelectorSignalPerformanceTests : XCTestCase + +@end + +@implementation NSObjectRACSelectorSignalPerformanceTests + +- (void)testKVORACStressTest { + RACTestObject* object = [[RACTestObject alloc] init]; + + [self measureBlock:^{ + __block unsigned int racCount = 0, kvoCount = 0; + + [RACObserve(object, objectValue) subscribeNext:^(id value) { + kvoCount += 1; + }]; + + [[object rac_signalForSelector:@selector(setObjectValue:)] subscribeNext:^(id value) { + racCount += 1; + }]; + + unsigned int iterations = 2500; + + for (unsigned int i = 0; i < iterations; i++) { + [object setObjectValue:@(i)]; + } + + expect(object.objectValue).to(equal(@(iterations - 1))); + expect(@(racCount)).to(equal(@(iterations))); + expect(@(kvoCount)).to(equal(@(iterations + 1))); + }]; +} + +- (void)testRACKVOStressTest { + RACTestObject* object = [[RACTestObject alloc] init]; + + [self measureBlock:^{ + __block unsigned int racCount = 0, kvoCount = 0; + + [[object rac_signalForSelector:@selector(setObjectValue:)] subscribeNext:^(id value) { + racCount += 1; + }]; + + [RACObserve(object, objectValue) subscribeNext:^(id value) { + kvoCount += 1; + }]; + + unsigned int iterations = 2500; + + for (unsigned int i = 0; i < iterations; i++) { + [object setObjectValue:@(i)]; + } + + expect(object.objectValue).to(equal(@(iterations - 1))); + expect(@(racCount)).to(equal(@(iterations))); + expect(@(kvoCount)).to(equal(@(iterations + 1))); + }]; +} + +@end From fe1bdb6d50eb804cc2e2a012a20fb143adcb4594 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 13 Nov 2016 16:58:54 +0100 Subject: [PATCH 06/14] Documented the `forwardInvocation:` swizzling. --- ReactiveObjC/NSObject+RACSelectorSignal.m | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/ReactiveObjC/NSObject+RACSelectorSignal.m b/ReactiveObjC/NSObject+RACSelectorSignal.m index 3ded4c74f..ef2acc0f0 100644 --- a/ReactiveObjC/NSObject+RACSelectorSignal.m +++ b/ReactiveObjC/NSObject+RACSelectorSignal.m @@ -63,13 +63,25 @@ static void RACSwizzleForwardInvocation(Class class) { Class baseClass = object_getClass(self); + // 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. + if ([baseClass instancesRespondToSelector:aliasOfOriginalSelector]) { + // `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. Method xchgMethod = class_getInstanceMethod(baseClass, aliasOfOriginalSelector); - IMP oldImpl = class_replaceMethod(baseClass, originalSelector, method_getImplementation(xchgMethod), method_getTypeEncoding(xchgMethod)); + IMP impl = method_getImplementation(xchgMethod); + + IMP oldImpl = class_replaceMethod(baseClass, originalSelector, impl, method_getTypeEncoding(xchgMethod)); invocation.selector = originalSelector; [invocation invoke]; class_replaceMethod(baseClass, originalSelector, oldImpl, method_getTypeEncoding(xchgMethod)); } else if ([superclass instancesRespondToSelector:originalSelector]) { + // The stated class has an implementation of the selector. Call that + // implementation if it is not the ObjC message forwarder. Method method = class_getInstanceMethod(superclass, originalSelector); IMP impl = method_getImplementation(method); @@ -81,10 +93,18 @@ static void RACSwizzleForwardInvocation(Class class) { forward = YES; } } else { + // 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. forward = subject == nil; } if (forward) { + // Forward the invocation to the closest `forwardInvocation:` in the + // inheritance hierarchy. struct objc_super target = { .super_class = superclass, .receiver = self, From 632ecadd1815f04e8b8e52958acec4ee3e9954d8 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 13 Nov 2016 17:00:15 +0100 Subject: [PATCH 07/14] Guard against a potential infinite feedback loop. --- ReactiveObjC/NSObject+RACSelectorSignal.m | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ReactiveObjC/NSObject+RACSelectorSignal.m b/ReactiveObjC/NSObject+RACSelectorSignal.m index ef2acc0f0..20d4bec32 100644 --- a/ReactiveObjC/NSObject+RACSelectorSignal.m +++ b/ReactiveObjC/NSObject+RACSelectorSignal.m @@ -71,14 +71,18 @@ static void RACSwizzleForwardInvocation(Class class) { if ([baseClass instancesRespondToSelector:aliasOfOriginalSelector]) { // `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. + // Call that implementation if it is not the ObjC message forwarder. Method xchgMethod = class_getInstanceMethod(baseClass, aliasOfOriginalSelector); IMP impl = method_getImplementation(xchgMethod); - IMP oldImpl = class_replaceMethod(baseClass, originalSelector, impl, method_getTypeEncoding(xchgMethod)); - invocation.selector = originalSelector; - [invocation invoke]; - class_replaceMethod(baseClass, originalSelector, oldImpl, method_getTypeEncoding(xchgMethod)); + if (impl != _objc_msgForward) { + IMP oldImpl = class_replaceMethod(baseClass, originalSelector, impl, method_getTypeEncoding(xchgMethod)); + invocation.selector = originalSelector; + [invocation invoke]; + class_replaceMethod(baseClass, originalSelector, oldImpl, method_getTypeEncoding(xchgMethod)); + } else { + forward = YES; + } } else if ([superclass instancesRespondToSelector:originalSelector]) { // The stated class has an implementation of the selector. Call that // implementation if it is not the ObjC message forwarder. From 84aa0a44bb480773587b57558671b5ef9003ef5a Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 13 Nov 2016 19:55:17 +0100 Subject: [PATCH 08/14] Update podspec CI test config. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2257ca06b..30074d745 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,7 +57,8 @@ matrix: - JOB=CARTHAGE-watchOS - script: - gem install cocoapods --pre - - pod repo update + - pod --version + - pod repo update --silent - pod lib lint ReactiveObjC.podspec env: - JOB=PODSPEC From a98c30c25c9bd83225cfcec8f75005c22cbd8408 Mon Sep 17 00:00:00 2001 From: Anders Date: Sat, 19 Nov 2016 21:20:07 +0100 Subject: [PATCH 09/14] Ported a few changes from the Swift implementation. Swift PR: https://github.com/ReactiveCocoa/ReactiveCocoa/pull/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. --- ReactiveObjC/NSObject+RACSelectorSignal.m | 202 ++++++++++++---------- 1 file changed, 113 insertions(+), 89 deletions(-) diff --git a/ReactiveObjC/NSObject+RACSelectorSignal.m b/ReactiveObjC/NSObject+RACSelectorSignal.m index 20d4bec32..8c95a218c 100644 --- a/ReactiveObjC/NSObject+RACSelectorSignal.m +++ b/ReactiveObjC/NSObject+RACSelectorSignal.m @@ -15,16 +15,18 @@ #import "RACSubject.h" #import "RACTuple.h" #import "NSObject+RACDescription.h" +#import "EXTScope.h" #import #import NSString * const RACSelectorSignalErrorDomain = @"RACSelectorSignalErrorDomain"; const NSInteger RACSelectorSignalErrorMethodSwizzlingRace = 1; -static NSString * const RACSignalForSelectorAliasPrefix = @"rac_alias_"; -static NSString * const RACSignalForSelectorAliasOfOriginalPrefix = @"rac_runtime_"; static NSString * const RACSubclassSuffix = @"_RACSelectorSignal"; +static const char * RACSignalForSelectorAliasPrefix = "rac_alias_"; +static const int RACSignalForSelectorAliasPrefixLength = 10; static void *RACSubclassAssociationKey = &RACSubclassAssociationKey; +static void *RACInteropImplsKey = &RACInteropImplsKey; static NSMutableSet *swizzledClasses() { static NSMutableSet *set; @@ -39,8 +41,9 @@ @implementation NSObject (RACSelectorSignal) -static void RACSwizzleForwardInvocation(Class class) { - Class superclass = class_getSuperclass(class); +static void RACSwizzleForwardInvocation(Class baseClass, NSDictionary *interopImpls) { + Class statedClass = class_getSuperclass(baseClass); + SEL forwardInvocationSEL = @selector(forwardInvocation:); // Set up a new version of -forwardInvocation:. @@ -53,81 +56,80 @@ static void RACSwizzleForwardInvocation(Class class) { // was no existing implementation, throw an unrecognized selector // exception. id newForwardInvocation = ^(id self, NSInvocation *invocation) { - BOOL forward = NO; - SEL originalSelector = invocation.selector; SEL aliasSelector = RACAliasForSelector(originalSelector); - SEL aliasOfOriginalSelector = RACAliasOfOriginalForSelector(originalSelector); RACSubject* subject = objc_getAssociatedObject(self, aliasSelector); - Class baseClass = object_getClass(self); + @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. - if ([baseClass instancesRespondToSelector:aliasOfOriginalSelector]) { + Method method = class_getInstanceMethod(statedClass, originalSelector); + const char* typeEncoding = method_getTypeEncoding(method); + + IMP interopImpl = [[interopImpls objectForKey:[NSValue valueWithPointer:originalSelector]] pointerValue]; + if (interopImpl != nil) { // `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. - Method xchgMethod = class_getInstanceMethod(baseClass, aliasOfOriginalSelector); - IMP impl = method_getImplementation(xchgMethod); + 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); + } - if (impl != _objc_msgForward) { - IMP oldImpl = class_replaceMethod(baseClass, originalSelector, impl, method_getTypeEncoding(xchgMethod)); - invocation.selector = originalSelector; - [invocation invoke]; - class_replaceMethod(baseClass, originalSelector, oldImpl, method_getTypeEncoding(xchgMethod)); - } else { - forward = YES; - } - } else if ([superclass instancesRespondToSelector:originalSelector]) { - // The stated class has an implementation of the selector. Call that - // implementation if it is not the ObjC message forwarder. - Method method = class_getInstanceMethod(superclass, originalSelector); - IMP impl = method_getImplementation(method); - - if (impl != _objc_msgForward) { - class_replaceMethod(baseClass, aliasSelector, impl, method_getTypeEncoding(method)); invocation.selector = aliasSelector; [invocation invoke]; - } else { - forward = YES; + + return; } - } else { - // 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. - forward = subject == nil; + + forwardRegardless = YES; } - if (forward) { + // 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. - struct objc_super target = { - .super_class = superclass, - .receiver = self, - }; - - void*(*superForwardInvocation)(struct objc_super *, SEL, NSInvocation*) = (__typeof__(superForwardInvocation)) objc_msgSendSuper; - superForwardInvocation(&target, forwardInvocationSEL, invocation); - } - - if (subject != nil) { - [subject sendNext:invocation.rac_argumentsTuple]; + 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) { - Class superclass = class_getSuperclass(class); +static void RACSwizzleRespondsToSelector(Class baseClass) { + Class statedClass = class_getSuperclass(baseClass); SEL respondsToSelectorSEL = @selector(respondsToSelector:); // Set up a new version of -respondsToSelector: that returns YES for methods @@ -138,24 +140,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; } - struct objc_super target = { - .super_class = superclass, - .receiver = self, - }; - - BOOL(*superRespondsToSelector)(struct objc_super *, SEL, SEL) = (__typeof__(superRespondsToSelector)) objc_msgSendSuper; - - return superRespondsToSelector(&target, 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), "v@::"); + class_replaceMethod(baseClass, respondsToSelectorSEL, imp_implementationWithBlock(newRespondsToSelector), "v@::"); } static void RACSwizzleGetClass(Class class, Class statedClass) { @@ -167,32 +164,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 @@ -266,12 +261,20 @@ static void RACCheckTypeEncoding(const char *typeEncoding) { RACCheckTypeEncoding(typeEncoding); - Method existingMethod = rac_getImmediateInstanceMethod(class, selector); - - if (existingMethod) { - SEL sel = RACAliasOfOriginalForSelector(selector); - BOOL addedAlias __attribute__((unused)) = class_addMethod(class, sel, method_getImplementation(existingMethod), typeEncoding); - NSCAssert(addedAlias, @"Existing external implementation for %@ has already been copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(sel), class); + Method dynamicImmediateMethod = rac_getImmediateInstanceMethod(class, selector); + if (dynamicImmediateMethod) { + IMP dynamicImmediateImpl = method_getImplementation(dynamicImmediateMethod); + if (dynamicImmediateImpl != _objc_msgForward) { + @synchronized (class) { + NSMutableDictionary* interopImpls = objc_getAssociatedObject(class, RACInteropImplsKey); + NSValue* key = [NSValue valueWithPointer:selector]; + + if ([interopImpls objectForKey:key] == nil) { + [interopImpls setObject:[NSValue valueWithPointer:dynamicImmediateImpl] + forKey:key]; + } + } + } } // Redefine the selector to call -forwardInvocation:. @@ -282,14 +285,21 @@ static void RACCheckTypeEncoding(const char *typeEncoding) { } } -static SEL RACAliasOfOriginalForSelector(SEL originalSelector) { - NSString *selectorName = NSStringFromSelector(originalSelector); - return NSSelectorFromString([RACSignalForSelectorAliasOfOriginalPrefix stringByAppendingString:selectorName]); -} - static SEL RACAliasForSelector(SEL originalSelector) { - NSString *selectorName = NSStringFromSelector(originalSelector); - return NSSelectorFromString([RACSignalForSelectorAliasPrefix stringByAppendingString:selectorName]); + const char* selectorString = sel_getName(originalSelector); + unsigned long length = strlen(selectorString); + unsigned long prefixedLength = length + RACSignalForSelectorAliasPrefixLength; + + char* buffer = malloc(length + RACSignalForSelectorAliasPrefixLength + 1); + @onExit { + free(buffer); + }; + + memcpy(buffer, RACSignalForSelectorAliasPrefix, RACSignalForSelectorAliasPrefixLength); + memcpy(buffer + RACSignalForSelectorAliasPrefixLength, selectorString, length); + *(buffer + prefixedLength) = '\0'; + + return sel_registerName(buffer); } static const char *RACSignatureForUndefinedSelector(SEL selector) { @@ -304,6 +314,20 @@ static SEL RACAliasForSelector(SEL originalSelector) { return signature.UTF8String; } +static NSDictionary* RACSetupInteropImplDictionary(Class baseClass) { + NSDictionary* interopImpls; + + @synchronized (baseClass) { + interopImpls = objc_getAssociatedObject(baseClass, RACInteropImplsKey); + if (interopImpls == nil) { + interopImpls = [[NSMutableDictionary alloc] init]; + objc_setAssociatedObject(baseClass, RACInteropImplsKey, interopImpls, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + } + + return interopImpls; +} + static Class RACSwizzleClass(NSObject *self) { Class statedClass = self.class; Class baseClass = object_getClass(self); @@ -330,7 +354,7 @@ static Class RACSwizzleClass(NSObject *self) { // implementation may be ignorant of methods added to this class. @synchronized (swizzledClasses()) { if (![swizzledClasses() containsObject:className]) { - RACSwizzleForwardInvocation(baseClass); + RACSwizzleForwardInvocation(baseClass, RACSetupInteropImplDictionary(baseClass)); RACSwizzleRespondsToSelector(baseClass); RACSwizzleGetClass(baseClass, statedClass); RACSwizzleGetClass(object_getClass(baseClass), statedClass); @@ -349,7 +373,7 @@ static Class RACSwizzleClass(NSObject *self) { subclass = objc_allocateClassPair(baseClass, subclassName, 0); if (subclass == nil) return nil; - RACSwizzleForwardInvocation(subclass); + RACSwizzleForwardInvocation(subclass, RACSetupInteropImplDictionary(subclass)); RACSwizzleRespondsToSelector(subclass); RACSwizzleGetClass(subclass, statedClass); From 83f16d52da42184c6f02c2c4492ceab1639f77bc Mon Sep 17 00:00:00 2001 From: Anders Date: Sat, 19 Nov 2016 23:58:16 +0100 Subject: [PATCH 10/14] Fixed a thread safety issue in the method interception logic. --- ReactiveObjC/NSObject+RACSelectorSignal.m | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/ReactiveObjC/NSObject+RACSelectorSignal.m b/ReactiveObjC/NSObject+RACSelectorSignal.m index 8c95a218c..340c291e2 100644 --- a/ReactiveObjC/NSObject+RACSelectorSignal.m +++ b/ReactiveObjC/NSObject+RACSelectorSignal.m @@ -41,7 +41,7 @@ @implementation NSObject (RACSelectorSignal) -static void RACSwizzleForwardInvocation(Class baseClass, NSDictionary *interopImpls) { +static void RACSwizzleForwardInvocation(Class baseClass) { Class statedClass = class_getSuperclass(baseClass); SEL forwardInvocationSEL = @selector(forwardInvocation:); @@ -75,6 +75,7 @@ static void RACSwizzleForwardInvocation(Class baseClass, NSDictionary *interopIm Method method = class_getInstanceMethod(statedClass, originalSelector); const char* typeEncoding = method_getTypeEncoding(method); + NSDictionary* interopImpls = objc_getAssociatedObject(baseClass, RACInteropImplsKey); IMP interopImpl = [[interopImpls objectForKey:[NSValue valueWithPointer:originalSelector]] pointerValue]; if (interopImpl != nil) { // `self` uses a runtime subclass generated by third-party APIs, and RAC @@ -266,12 +267,18 @@ static void RACCheckTypeEncoding(const char *typeEncoding) { IMP dynamicImmediateImpl = method_getImplementation(dynamicImmediateMethod); if (dynamicImmediateImpl != _objc_msgForward) { @synchronized (class) { + // Mutations to `interopImpls` must be made on a a copy and + // subsequently assigned back, so as to avoid partially updated + // state from being visible to the concurrent readers. + NSMutableDictionary* interopImpls = objc_getAssociatedObject(class, RACInteropImplsKey); NSValue* key = [NSValue valueWithPointer:selector]; if ([interopImpls objectForKey:key] == nil) { - [interopImpls setObject:[NSValue valueWithPointer:dynamicImmediateImpl] + NSMutableDictionary* newInteropImpls = [interopImpls mutableCopy]; + [newInteropImpls setObject:[NSValue valueWithPointer:dynamicImmediateImpl] forKey:key]; + objc_setAssociatedObject(class, RACInteropImplsKey, newInteropImpls, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } } @@ -354,7 +361,8 @@ static Class RACSwizzleClass(NSObject *self) { // implementation may be ignorant of methods added to this class. @synchronized (swizzledClasses()) { if (![swizzledClasses() containsObject:className]) { - RACSwizzleForwardInvocation(baseClass, RACSetupInteropImplDictionary(baseClass)); + RACSetupInteropImplDictionary(baseClass); + RACSwizzleForwardInvocation(baseClass); RACSwizzleRespondsToSelector(baseClass); RACSwizzleGetClass(baseClass, statedClass); RACSwizzleGetClass(object_getClass(baseClass), statedClass); @@ -373,7 +381,8 @@ static Class RACSwizzleClass(NSObject *self) { subclass = objc_allocateClassPair(baseClass, subclassName, 0); if (subclass == nil) return nil; - RACSwizzleForwardInvocation(subclass, RACSetupInteropImplDictionary(subclass)); + RACSetupInteropImplDictionary(subclass); + RACSwizzleForwardInvocation(subclass); RACSwizzleRespondsToSelector(subclass); RACSwizzleGetClass(subclass, statedClass); From 04c5f76885bc943358e295ae2e7e457453c92a81 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 20 Nov 2016 01:31:11 +0100 Subject: [PATCH 11/14] Revert "Fixed a thread safety issue in the method interception logic." This reverts commit 83f16d52da42184c6f02c2c4492ceab1639f77bc. --- ReactiveObjC/NSObject+RACSelectorSignal.m | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/ReactiveObjC/NSObject+RACSelectorSignal.m b/ReactiveObjC/NSObject+RACSelectorSignal.m index 340c291e2..8c95a218c 100644 --- a/ReactiveObjC/NSObject+RACSelectorSignal.m +++ b/ReactiveObjC/NSObject+RACSelectorSignal.m @@ -41,7 +41,7 @@ @implementation NSObject (RACSelectorSignal) -static void RACSwizzleForwardInvocation(Class baseClass) { +static void RACSwizzleForwardInvocation(Class baseClass, NSDictionary *interopImpls) { Class statedClass = class_getSuperclass(baseClass); SEL forwardInvocationSEL = @selector(forwardInvocation:); @@ -75,7 +75,6 @@ static void RACSwizzleForwardInvocation(Class baseClass) { Method method = class_getInstanceMethod(statedClass, originalSelector); const char* typeEncoding = method_getTypeEncoding(method); - NSDictionary* interopImpls = objc_getAssociatedObject(baseClass, RACInteropImplsKey); IMP interopImpl = [[interopImpls objectForKey:[NSValue valueWithPointer:originalSelector]] pointerValue]; if (interopImpl != nil) { // `self` uses a runtime subclass generated by third-party APIs, and RAC @@ -267,18 +266,12 @@ static void RACCheckTypeEncoding(const char *typeEncoding) { IMP dynamicImmediateImpl = method_getImplementation(dynamicImmediateMethod); if (dynamicImmediateImpl != _objc_msgForward) { @synchronized (class) { - // Mutations to `interopImpls` must be made on a a copy and - // subsequently assigned back, so as to avoid partially updated - // state from being visible to the concurrent readers. - NSMutableDictionary* interopImpls = objc_getAssociatedObject(class, RACInteropImplsKey); NSValue* key = [NSValue valueWithPointer:selector]; if ([interopImpls objectForKey:key] == nil) { - NSMutableDictionary* newInteropImpls = [interopImpls mutableCopy]; - [newInteropImpls setObject:[NSValue valueWithPointer:dynamicImmediateImpl] + [interopImpls setObject:[NSValue valueWithPointer:dynamicImmediateImpl] forKey:key]; - objc_setAssociatedObject(class, RACInteropImplsKey, newInteropImpls, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } } @@ -361,8 +354,7 @@ static Class RACSwizzleClass(NSObject *self) { // implementation may be ignorant of methods added to this class. @synchronized (swizzledClasses()) { if (![swizzledClasses() containsObject:className]) { - RACSetupInteropImplDictionary(baseClass); - RACSwizzleForwardInvocation(baseClass); + RACSwizzleForwardInvocation(baseClass, RACSetupInteropImplDictionary(baseClass)); RACSwizzleRespondsToSelector(baseClass); RACSwizzleGetClass(baseClass, statedClass); RACSwizzleGetClass(object_getClass(baseClass), statedClass); @@ -381,8 +373,7 @@ static Class RACSwizzleClass(NSObject *self) { subclass = objc_allocateClassPair(baseClass, subclassName, 0); if (subclass == nil) return nil; - RACSetupInteropImplDictionary(subclass); - RACSwizzleForwardInvocation(subclass); + RACSwizzleForwardInvocation(subclass, RACSetupInteropImplDictionary(subclass)); RACSwizzleRespondsToSelector(subclass); RACSwizzleGetClass(subclass, statedClass); From 96bd2b7aae752f3e750e2884d89d7b32158dfb9c Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 20 Nov 2016 01:52:59 +0100 Subject: [PATCH 12/14] Explicit locking for the interop-implementation table. --- ReactiveObjC/NSObject+RACSelectorSignal.m | 37 ++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/ReactiveObjC/NSObject+RACSelectorSignal.m b/ReactiveObjC/NSObject+RACSelectorSignal.m index 8c95a218c..b0b659704 100644 --- a/ReactiveObjC/NSObject+RACSelectorSignal.m +++ b/ReactiveObjC/NSObject+RACSelectorSignal.m @@ -18,6 +18,7 @@ #import "EXTScope.h" #import #import +#import "pthread.h" NSString * const RACSelectorSignalErrorDomain = @"RACSelectorSignalErrorDomain"; const NSInteger RACSelectorSignalErrorMethodSwizzlingRace = 1; @@ -27,6 +28,7 @@ static const int RACSignalForSelectorAliasPrefixLength = 10; static void *RACSubclassAssociationKey = &RACSubclassAssociationKey; static void *RACInteropImplsKey = &RACInteropImplsKey; +static void *RACInteropImplsLockKey = &RACInteropImplsLockKey; static NSMutableSet *swizzledClasses() { static NSMutableSet *set; @@ -41,7 +43,10 @@ @implementation NSObject (RACSelectorSignal) -static void RACSwizzleForwardInvocation(Class baseClass, NSDictionary *interopImpls) { +static void RACSwizzleForwardInvocation(Class baseClass, RACTuple *interopImplsInfo) { + NSDictionary* interopImpls = [interopImplsInfo first]; + pthread_mutex_t* mutex = [[interopImplsInfo second] pointerValue]; + Class statedClass = class_getSuperclass(baseClass); SEL forwardInvocationSEL = @selector(forwardInvocation:); @@ -75,7 +80,10 @@ static void RACSwizzleForwardInvocation(Class baseClass, NSDictionary *interopIm Method method = class_getInstanceMethod(statedClass, originalSelector); const char* typeEncoding = method_getTypeEncoding(method); + pthread_mutex_lock(mutex); IMP interopImpl = [[interopImpls objectForKey:[NSValue valueWithPointer:originalSelector]] pointerValue]; + pthread_mutex_unlock(mutex); + if (interopImpl != nil) { // `self` uses a runtime subclass generated by third-party APIs, and RAC // found an existing implementation for the selector at the setup time. @@ -266,12 +274,20 @@ static void RACCheckTypeEncoding(const char *typeEncoding) { IMP dynamicImmediateImpl = method_getImplementation(dynamicImmediateMethod); if (dynamicImmediateImpl != _objc_msgForward) { @synchronized (class) { + pthread_mutex_t* mutex = [objc_getAssociatedObject(class, RACInteropImplsLockKey) pointerValue]; NSMutableDictionary* interopImpls = objc_getAssociatedObject(class, RACInteropImplsKey); + +#if !NS_BLOCK_ASSERTIONS + NSCAssert(mutex != nil && interopImpls != nil, @"The interop implementation dictionary and its lock should have been initialized at the time the runtime subclass is swizzled."); +#endif + NSValue* key = [NSValue valueWithPointer:selector]; if ([interopImpls objectForKey:key] == nil) { + pthread_mutex_lock(mutex); [interopImpls setObject:[NSValue valueWithPointer:dynamicImmediateImpl] forKey:key]; + pthread_mutex_unlock(mutex); } } } @@ -314,18 +330,25 @@ static SEL RACAliasForSelector(SEL originalSelector) { return signature.UTF8String; } -static NSDictionary* RACSetupInteropImplDictionary(Class baseClass) { +static RACTuple* RACSetupInteropImplDictionary(Class baseClass) { NSDictionary* interopImpls; + pthread_mutex_t* mutex = malloc(sizeof(pthread_mutex_t)); + pthread_mutex_init(mutex, NULL); + @synchronized (baseClass) { interopImpls = objc_getAssociatedObject(baseClass, RACInteropImplsKey); - if (interopImpls == nil) { - interopImpls = [[NSMutableDictionary alloc] init]; - objc_setAssociatedObject(baseClass, RACInteropImplsKey, interopImpls, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } + +#if !NS_BLOCK_ASSERTIONS + NSCAssert(interopImpls == nil, @"The interop implementation dictionary should be set only once for every runtime subclass."); +#endif + + interopImpls = [[NSMutableDictionary alloc] init]; + objc_setAssociatedObject(baseClass, RACInteropImplsKey, interopImpls, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(baseClass, RACInteropImplsLockKey, [NSValue valueWithPointer:mutex], OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - return interopImpls; + return RACTuplePack(interopImpls, [NSValue valueWithPointer:mutex]); } static Class RACSwizzleClass(NSObject *self) { From 306a0daa24c59decd32534a1687f39dfbe90cced Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 20 Nov 2016 03:19:03 +0100 Subject: [PATCH 13/14] Abolished the interop-implementation table. --- ReactiveObjC/NSObject+RACSelectorSignal.m | 86 ++++++++--------------- 1 file changed, 30 insertions(+), 56 deletions(-) diff --git a/ReactiveObjC/NSObject+RACSelectorSignal.m b/ReactiveObjC/NSObject+RACSelectorSignal.m index b0b659704..ed0b8eb7e 100644 --- a/ReactiveObjC/NSObject+RACSelectorSignal.m +++ b/ReactiveObjC/NSObject+RACSelectorSignal.m @@ -26,6 +26,8 @@ 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; @@ -43,10 +45,7 @@ @implementation NSObject (RACSelectorSignal) -static void RACSwizzleForwardInvocation(Class baseClass, RACTuple *interopImplsInfo) { - NSDictionary* interopImpls = [interopImplsInfo first]; - pthread_mutex_t* mutex = [[interopImplsInfo second] pointerValue]; - +static void RACSwizzleForwardInvocation(Class baseClass) { Class statedClass = class_getSuperclass(baseClass); SEL forwardInvocationSEL = @selector(forwardInvocation:); @@ -63,6 +62,7 @@ static void RACSwizzleForwardInvocation(Class baseClass, RACTuple *interopImplsI id newForwardInvocation = ^(id self, NSInvocation *invocation) { SEL originalSelector = invocation.selector; SEL aliasSelector = RACAliasForSelector(originalSelector); + SEL interopAliasSelector = RACInteropAliasForSelector(originalSelector); RACSubject* subject = objc_getAssociatedObject(self, aliasSelector); @@ -80,14 +80,11 @@ static void RACSwizzleForwardInvocation(Class baseClass, RACTuple *interopImplsI Method method = class_getInstanceMethod(statedClass, originalSelector); const char* typeEncoding = method_getTypeEncoding(method); - pthread_mutex_lock(mutex); - IMP interopImpl = [[interopImpls objectForKey:[NSValue valueWithPointer:originalSelector]] pointerValue]; - pthread_mutex_unlock(mutex); - - if (interopImpl != nil) { + 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]; @@ -218,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); @@ -269,25 +267,14 @@ static void RACCheckTypeEncoding(const char *typeEncoding) { RACCheckTypeEncoding(typeEncoding); - Method dynamicImmediateMethod = rac_getImmediateInstanceMethod(class, selector); - if (dynamicImmediateMethod) { - IMP dynamicImmediateImpl = method_getImplementation(dynamicImmediateMethod); - if (dynamicImmediateImpl != _objc_msgForward) { - @synchronized (class) { - pthread_mutex_t* mutex = [objc_getAssociatedObject(class, RACInteropImplsLockKey) pointerValue]; - NSMutableDictionary* interopImpls = objc_getAssociatedObject(class, RACInteropImplsKey); - -#if !NS_BLOCK_ASSERTIONS - NSCAssert(mutex != nil && interopImpls != nil, @"The interop implementation dictionary and its lock should have been initialized at the time the runtime subclass is swizzled."); -#endif - - NSValue* key = [NSValue valueWithPointer:selector]; - - if ([interopImpls objectForKey:key] == nil) { - pthread_mutex_lock(mutex); - [interopImpls setObject:[NSValue valueWithPointer:dynamicImmediateImpl] - forKey:key]; - pthread_mutex_unlock(mutex); + @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 = class_addMethod(class, interopAliasSelector, dynamicImmediateImpl, typeEncoding); + NSCAssert(success, @"Unexpected race condition: `%@` has already been added, and subsequent attempts should have been ignored.", NSStringFromSelector(interopAliasSelector)); } } } @@ -302,18 +289,26 @@ static void RACCheckTypeEncoding(const char *typeEncoding) { } static SEL RACAliasForSelector(SEL originalSelector) { + 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 prefixedLength = length + RACSignalForSelectorAliasPrefixLength; + unsigned long finalLength = length + prefixLength; - char* buffer = malloc(length + RACSignalForSelectorAliasPrefixLength + 1); + char* buffer = malloc(finalLength + 1); @onExit { free(buffer); }; - memcpy(buffer, RACSignalForSelectorAliasPrefix, RACSignalForSelectorAliasPrefixLength); - memcpy(buffer + RACSignalForSelectorAliasPrefixLength, selectorString, length); - *(buffer + prefixedLength) = '\0'; + memcpy(buffer, prefix, prefixLength); + memcpy(buffer + prefixLength, selectorString, length); + *(buffer + finalLength) = '\0'; return sel_registerName(buffer); } @@ -330,27 +325,6 @@ static SEL RACAliasForSelector(SEL originalSelector) { return signature.UTF8String; } -static RACTuple* RACSetupInteropImplDictionary(Class baseClass) { - NSDictionary* interopImpls; - - pthread_mutex_t* mutex = malloc(sizeof(pthread_mutex_t)); - pthread_mutex_init(mutex, NULL); - - @synchronized (baseClass) { - interopImpls = objc_getAssociatedObject(baseClass, RACInteropImplsKey); - -#if !NS_BLOCK_ASSERTIONS - NSCAssert(interopImpls == nil, @"The interop implementation dictionary should be set only once for every runtime subclass."); -#endif - - interopImpls = [[NSMutableDictionary alloc] init]; - objc_setAssociatedObject(baseClass, RACInteropImplsKey, interopImpls, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - objc_setAssociatedObject(baseClass, RACInteropImplsLockKey, [NSValue valueWithPointer:mutex], OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } - - return RACTuplePack(interopImpls, [NSValue valueWithPointer:mutex]); -} - static Class RACSwizzleClass(NSObject *self) { Class statedClass = self.class; Class baseClass = object_getClass(self); @@ -377,7 +351,7 @@ static Class RACSwizzleClass(NSObject *self) { // implementation may be ignorant of methods added to this class. @synchronized (swizzledClasses()) { if (![swizzledClasses() containsObject:className]) { - RACSwizzleForwardInvocation(baseClass, RACSetupInteropImplDictionary(baseClass)); + RACSwizzleForwardInvocation(baseClass); RACSwizzleRespondsToSelector(baseClass); RACSwizzleGetClass(baseClass, statedClass); RACSwizzleGetClass(object_getClass(baseClass), statedClass); @@ -396,7 +370,7 @@ static Class RACSwizzleClass(NSObject *self) { subclass = objc_allocateClassPair(baseClass, subclassName, 0); if (subclass == nil) return nil; - RACSwizzleForwardInvocation(subclass, RACSetupInteropImplDictionary(subclass)); + RACSwizzleForwardInvocation(subclass); RACSwizzleRespondsToSelector(subclass); RACSwizzleGetClass(subclass, statedClass); From 7dfc280e55e8affb1d88bde65382f42c20ddb27f Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 20 Nov 2016 20:58:08 +0100 Subject: [PATCH 14/14] Marked a return value for an assertion with `__attribute__((unused))`. --- ReactiveObjC/NSObject+RACSelectorSignal.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactiveObjC/NSObject+RACSelectorSignal.m b/ReactiveObjC/NSObject+RACSelectorSignal.m index ed0b8eb7e..45fda680f 100644 --- a/ReactiveObjC/NSObject+RACSelectorSignal.m +++ b/ReactiveObjC/NSObject+RACSelectorSignal.m @@ -273,7 +273,7 @@ static void RACCheckTypeEncoding(const char *typeEncoding) { if (dynamicImmediateMethod) { IMP dynamicImmediateImpl = method_getImplementation(dynamicImmediateMethod); if (dynamicImmediateImpl != _objc_msgForward) { - BOOL success = class_addMethod(class, interopAliasSelector, dynamicImmediateImpl, typeEncoding); + 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)); } }