From 75fa95d4dc2ead7bb55c71061582be11b41a191a Mon Sep 17 00:00:00 2001 From: Krunoslav Zaher Date: Sun, 22 Nov 2015 23:48:22 +0100 Subject: [PATCH] Messing with swizzling. --- Rx.xcodeproj/project.pbxproj | 24 +- RxCocoa/Common/Observables/NSObject+Rx.swift | 10 +- RxCocoa/Common/_RX.h | 102 +++++- RxCocoa/Common/_RX.m | 6 +- RxCocoa/Common/_RXDelegateProxy.m | 6 +- RxCocoa/Common/_RXSwizzling.h | 2 +- RxCocoa/Common/_RXSwizzling.m | 324 ++++++++++++++---- RxCocoa/RxCocoa.h | 1 - .../Tests/ObserveSentMessagesTest.swift | 73 ++++ RxTests/RxTests.xcodeproj/project.pbxproj | 8 + 10 files changed, 455 insertions(+), 101 deletions(-) create mode 100644 RxTests/RxSwiftTests/Tests/ObserveSentMessagesTest.swift diff --git a/Rx.xcodeproj/project.pbxproj b/Rx.xcodeproj/project.pbxproj index 5a698a1a..c98f566c 100644 --- a/Rx.xcodeproj/project.pbxproj +++ b/Rx.xcodeproj/project.pbxproj @@ -196,8 +196,6 @@ C8093DA61B8A72BE0088E94D /* SubjectType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8093CC11B8A72BE0088E94D /* SubjectType.swift */; }; C8093DA71B8A72BE0088E94D /* Variable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8093CC21B8A72BE0088E94D /* Variable.swift */; }; C8093DA81B8A72BE0088E94D /* Variable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8093CC21B8A72BE0088E94D /* Variable.swift */; }; - C8093ECF1B8A732E0088E94D /* _RX.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E821B8A732E0088E94D /* _RX.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C8093ED01B8A732E0088E94D /* _RX.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E821B8A732E0088E94D /* _RX.h */; settings = {ATTRIBUTES = (Public, ); }; }; C8093ED11B8A732E0088E94D /* _RX.m in Sources */ = {isa = PBXBuildFile; fileRef = C8093E831B8A732E0088E94D /* _RX.m */; }; C8093ED21B8A732E0088E94D /* _RX.m in Sources */ = {isa = PBXBuildFile; fileRef = C8093E831B8A732E0088E94D /* _RX.m */; }; C8093ED31B8A732E0088E94D /* _RXDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E841B8A732E0088E94D /* _RXDelegateProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -208,8 +206,6 @@ C8093ED81B8A732E0088E94D /* _RXKVOObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E861B8A732E0088E94D /* _RXKVOObserver.h */; settings = {ATTRIBUTES = (Public, ); }; }; C8093ED91B8A732E0088E94D /* _RXKVOObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = C8093E871B8A732E0088E94D /* _RXKVOObserver.m */; }; C8093EDA1B8A732E0088E94D /* _RXKVOObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = C8093E871B8A732E0088E94D /* _RXKVOObserver.m */; }; - C8093EDB1B8A732E0088E94D /* _RXSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E881B8A732E0088E94D /* _RXSwizzling.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C8093EDC1B8A732E0088E94D /* _RXSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E881B8A732E0088E94D /* _RXSwizzling.h */; settings = {ATTRIBUTES = (Public, ); }; }; C8093EDD1B8A732E0088E94D /* _RXSwizzling.m in Sources */ = {isa = PBXBuildFile; fileRef = C8093E891B8A732E0088E94D /* _RXSwizzling.m */; }; C8093EDE1B8A732E0088E94D /* _RXSwizzling.m in Sources */ = {isa = PBXBuildFile; fileRef = C8093E891B8A732E0088E94D /* _RXSwizzling.m */; }; C8093EDF1B8A732E0088E94D /* CLLocationManager+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8093E8A1B8A732E0088E94D /* CLLocationManager+Rx.swift */; }; @@ -389,6 +385,10 @@ C8B145011BD2D80100267DCE /* ImmediateScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B144FF1BD2D80100267DCE /* ImmediateScheduler.swift */; }; C8B145021BD2D80100267DCE /* ImmediateScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B144FF1BD2D80100267DCE /* ImmediateScheduler.swift */; }; C8B145031BD2D80100267DCE /* ImmediateScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B144FF1BD2D80100267DCE /* ImmediateScheduler.swift */; }; + C8B763161C010765007E8C19 /* _RXSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E881B8A732E0088E94D /* _RXSwizzling.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C8B763171C010766007E8C19 /* _RXSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E881B8A732E0088E94D /* _RXSwizzling.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C8B763181C010766007E8C19 /* _RXSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E881B8A732E0088E94D /* _RXSwizzling.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C8B763191C010768007E8C19 /* _RXSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E881B8A732E0088E94D /* _RXSwizzling.h */; settings = {ATTRIBUTES = (Public, ); }; }; C8C3D9FE1B935EDF004D233E /* Zip+CollectionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C3D9FD1B935EDF004D233E /* Zip+CollectionType.swift */; }; C8C3D9FF1B935EDF004D233E /* Zip+CollectionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C3D9FD1B935EDF004D233E /* Zip+CollectionType.swift */; }; C8C3DA031B9390C4004D233E /* Just.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C3DA021B9390C4004D233E /* Just.swift */; }; @@ -579,8 +579,6 @@ C8F0C03D1BBBFBB9001B112F /* RxTableViewDataSourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88253F81B8A752B00B02D69 /* RxTableViewDataSourceType.swift */; }; C8F0C0411BBBFBB9001B112F /* RxCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093ECB1B8A732E0088E94D /* RxCocoa.h */; settings = {ATTRIBUTES = (Public, ); }; }; C8F0C0421BBBFBB9001B112F /* _RXDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E841B8A732E0088E94D /* _RXDelegateProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C8F0C0431BBBFBB9001B112F /* _RX.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E821B8A732E0088E94D /* _RX.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C8F0C0441BBBFBB9001B112F /* _RXSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E881B8A732E0088E94D /* _RXSwizzling.h */; settings = {ATTRIBUTES = (Public, ); }; }; C8F0C0451BBBFBB9001B112F /* _RXKVOObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E861B8A732E0088E94D /* _RXKVOObserver.h */; settings = {ATTRIBUTES = (Public, ); }; }; C8F0C04F1BBBFBCE001B112F /* ObservableConvertibleType+Blocking.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8093F581B8A73A20088E94D /* ObservableConvertibleType+Blocking.swift */; }; C8F6A0EF1BEE2CB6007DF367 /* ScheduledItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F6A0EE1BEE2CB6007DF367 /* ScheduledItemType.swift */; }; @@ -663,13 +661,11 @@ D203C5111BB9C53E00D02D00 /* UITableView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88254121B8A752B00B02D69 /* UITableView+Rx.swift */; }; D203C5121BB9C53E00D02D00 /* UITextField+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88254131B8A752B00B02D69 /* UITextField+Rx.swift */; }; D203C5131BB9C53E00D02D00 /* UITextView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88254141B8A752B00B02D69 /* UITextView+Rx.swift */; }; - D2138C7E1BB9BEBE00339B5C /* _RX.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E821B8A732E0088E94D /* _RX.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2138C7F1BB9BEBE00339B5C /* _RX.m in Sources */ = {isa = PBXBuildFile; fileRef = C8093E831B8A732E0088E94D /* _RX.m */; }; D2138C801BB9BEBE00339B5C /* _RXDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E841B8A732E0088E94D /* _RXDelegateProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2138C811BB9BEBE00339B5C /* _RXDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C8093E851B8A732E0088E94D /* _RXDelegateProxy.m */; }; D2138C821BB9BEBE00339B5C /* _RXKVOObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E861B8A732E0088E94D /* _RXKVOObserver.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2138C831BB9BEBE00339B5C /* _RXKVOObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = C8093E871B8A732E0088E94D /* _RXKVOObserver.m */; }; - D2138C841BB9BEBE00339B5C /* _RXSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = C8093E881B8A732E0088E94D /* _RXSwizzling.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2138C851BB9BEBE00339B5C /* _RXSwizzling.m in Sources */ = {isa = PBXBuildFile; fileRef = C8093E891B8A732E0088E94D /* _RXSwizzling.m */; }; D2138C861BB9BEBE00339B5C /* Observable+Bind.swift in Sources */ = {isa = PBXBuildFile; fileRef = C80D338E1B91EF9E0014629D /* Observable+Bind.swift */; }; D2138C871BB9BEBE00339B5C /* CLLocationManager+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8093E8A1B8A732E0088E94D /* CLLocationManager+Rx.swift */; }; @@ -1636,8 +1632,7 @@ files = ( C8093F4F1B8A732E0088E94D /* RxCocoa.h in Headers */, C8093ED31B8A732E0088E94D /* _RXDelegateProxy.h in Headers */, - C8093ECF1B8A732E0088E94D /* _RX.h in Headers */, - C8093EDB1B8A732E0088E94D /* _RXSwizzling.h in Headers */, + C8B763161C010765007E8C19 /* _RXSwizzling.h in Headers */, C8093ED71B8A732E0088E94D /* _RXKVOObserver.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1648,8 +1643,7 @@ files = ( C8093F501B8A732E0088E94D /* RxCocoa.h in Headers */, C8093ED41B8A732E0088E94D /* _RXDelegateProxy.h in Headers */, - C8093ED01B8A732E0088E94D /* _RX.h in Headers */, - C8093EDC1B8A732E0088E94D /* _RXSwizzling.h in Headers */, + C8B763171C010766007E8C19 /* _RXSwizzling.h in Headers */, C8093ED81B8A732E0088E94D /* _RXKVOObserver.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1695,8 +1689,7 @@ files = ( C8F0C0411BBBFBB9001B112F /* RxCocoa.h in Headers */, C8F0C0421BBBFBB9001B112F /* _RXDelegateProxy.h in Headers */, - C8F0C0431BBBFBB9001B112F /* _RX.h in Headers */, - C8F0C0441BBBFBB9001B112F /* _RXSwizzling.h in Headers */, + C8B763191C010768007E8C19 /* _RXSwizzling.h in Headers */, C8F0C0451BBBFBB9001B112F /* _RXKVOObserver.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1714,8 +1707,7 @@ files = ( D2138C8B1BB9BEC300339B5C /* RxCocoa.h in Headers */, D2138C801BB9BEBE00339B5C /* _RXDelegateProxy.h in Headers */, - D2138C7E1BB9BEBE00339B5C /* _RX.h in Headers */, - D2138C841BB9BEBE00339B5C /* _RXSwizzling.h in Headers */, + C8B763181C010766007E8C19 /* _RXSwizzling.h in Headers */, D2138C821BB9BEBE00339B5C /* _RXKVOObserver.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/RxCocoa/Common/Observables/NSObject+Rx.swift b/RxCocoa/Common/Observables/NSObject+Rx.swift index 07d23ac1..5ea6afd6 100644 --- a/RxCocoa/Common/Observables/NSObject+Rx.swift +++ b/RxCocoa/Common/Observables/NSObject+Rx.swift @@ -176,11 +176,11 @@ extension NSObject { #if !DISABLE_SWIZZLING - public func rx_observeMessage(selector: Selector) -> Observable<[AnyObject]> { - return rx_observeMessage(selector, replay: false) + public func rx_sentMessage(selector: Selector) -> Observable<[AnyObject]> { + return rx_sentMessage(selector, replay: false) } - private func rx_observeMessage(selector: Selector, replay: Bool) -> Observable<[AnyObject]> { + private func rx_sentMessage(selector: Selector, replay: Bool) -> Observable<[AnyObject]> { return rx_synchronized { let rxSelector = RX_selector(selector) let selectorReference = RX_reference_from_selector(rxSelector) @@ -196,7 +196,7 @@ extension NSObject { .OBJC_ASSOCIATION_RETAIN_NONATOMIC ) - RX_ensure_swizzled(self.dynamicType, selector) + RX_ensure_observing(self.dynamicType, selector) return subject.asObservable() } } @@ -210,7 +210,7 @@ extension NSObject { - returns: Observable sequence of object deallocating events. */ public var rx_deallocating: Observable { - return rx_observeMessage("dealloc", replay: true).map { _ in () } + return rx_sentMessage("dealloc", replay: true).map { _ in () } } #endif } diff --git a/RxCocoa/Common/_RX.h b/RxCocoa/Common/_RX.h index ac3c131b..6cb656f2 100644 --- a/RxCocoa/Common/_RX.h +++ b/RxCocoa/Common/_RX.h @@ -18,7 +18,107 @@ NSArray * __nonnull RX_extract_arguments(NSInvocation * __nonnull invocation); BOOL RX_is_method_with_description_void(struct objc_method_description method); -BOOL RX_is_method_void(NSInvocation * __nonnull invocation); +BOOL RX_is_method_signature_void(NSMethodSignature * __nonnull methodSignature); #define SEL_VALUE(x) [NSValue valueWithPointer:(x)] #define CLASS_VALUE(x) [NSValue valueWithNonretainedObject:(x)] + +// Inspired by http://p99.gforge.inria.fr + +// https://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html#SEC26 +#define RX_CAT2(_1, _2) _RX_CAT2(_1, _2) + +#define RX_ELEMENT_AT(n, ...) RX_CAT2(_RX_ELEMENT_AT_, n)(__VA_ARGS__) + +#define RX_COUNT(...) RX_ELEMENT_AT(6, ## __VA_ARGS__, 6, 5, 4, 3, 2, 1, 0) + +/** + #define JOIN(context, index, head, tail) head; tail + #define APPLY(context, index, item) item = (context)[index] + + RX_FOR(A, JOIN, APPLY, toto, tutu); + + toto = (A)[0]; tutu = (A)[1]; + */ +#define RX_FOR(context, join, generate, ...) RX_CAT2( _RX_FOR_, RX_COUNT(__VA_ARGS__))(context, 0, join, generate, ## __VA_ARGS__) + +/** + #define JOIN(context, index, head, tail) head tail + #define APPLY(context, index, item) item = (context)[index] + + RX_FOR(A, JOIN, APPLY, toto, tutu); + + , toto = (A)[0], tutu = (A)[1] + */ +#define RX_FOR_COMMA(context, generate, ...) RX_CAT2( _RX_FOR_COMMA_, RX_COUNT(__VA_ARGS__))(context, 0, generate, ## __VA_ARGS__) + +#define RX_INC(x) RX_CAT2(_RX_INC_, x) + +// element at + +#define _RX_ELEMENT_AT_0(x, ...) x +#define _RX_ELEMENT_AT_1(_0, x, ...) x +#define _RX_ELEMENT_AT_2(_0, _1, x, ...) x +#define _RX_ELEMENT_AT_3(_0, _1, _2, x, ...) x +#define _RX_ELEMENT_AT_4(_0, _1, _2, _3, x, ...) x +#define _RX_ELEMENT_AT_5(_0, _1, _2, _3, _4, x, ...) x +#define _RX_ELEMENT_AT_6(_0, _1, _2, _3, _4, _5, x, ...) x + +// rx for + +#define _RX_FOR_0(context, index, join, generate) + +#define _RX_FOR_1(context, index, join, generate, head) \ + generate(context, index, head) + +#define _RX_FOR_2(context, index, join, generate, head, ...) \ + join(context, index, generate(context, index, head), _RX_FOR_1(context, RX_INC(index), join, generate, __VA_ARGS__)) + +#define _RX_FOR_3(context, index, join, generate, head, ...) \ + join(context, index, generate(context, index, head), _RX_FOR_2(context, RX_INC(index), join, generate, __VA_ARGS__)) + +#define _RX_FOR_4(context, index, join, generate, head, ...) \ + join(context, index, generate(context, index, head), _RX_FOR_3(context, RX_INC(index), join, generate, __VA_ARGS__)) + +#define _RX_FOR_5(context, index, join, generate, head, ...) \ + join(context, index, generate(context, index, head), _RX_FOR_4(context, RX_INC(index), join, generate, __VA_ARGS__)) + +#define _RX_FOR_6(context, index, join, generate, head, ...) \ + join(context, index, generate(context, index, head), _RX_FOR_5(context, RX_INC(index), join, generate, __VA_ARGS__)) + +// rx for + +#define _RX_FOR_COMMA_0(context, index, generate) + +#define _RX_FOR_COMMA_1(context, index, generate, head) \ + , generate(context, index, head) + +#define _RX_FOR_COMMA_2(context, index, generate, head, ...) \ + , generate(context, index, head) _RX_FOR_COMMA_1(context, RX_INC(index), generate, __VA_ARGS__) + +#define _RX_FOR_COMMA_3(context, index, generate, head, ...) \ + , generate(context, index, head) _RX_FOR_COMMA_2(context, RX_INC(index), generate, __VA_ARGS__) + +#define _RX_FOR_COMMA_4(context, index, generate, head, ...) \ + , generate(context, index, head) _RX_FOR_COMMA_3(context, RX_INC(index), generate, __VA_ARGS__) + +#define _RX_FOR_COMMA_5(context, index, generate, head, ...) \ + , generate(context, index, head) _RX_FOR_COMMA_4(context, RX_INC(index), generate, __VA_ARGS__) + +#define _RX_FOR_COMMA_6(context, index, generate, head, ...) \ + , generate(context, index, head) _RX_FOR_COMMA_5(context, RX_INC(index), generate, __VA_ARGS__) + + +// rx inc + +#define _RX_INC_0 1 +#define _RX_INC_1 2 +#define _RX_INC_2 3 +#define _RX_INC_3 4 +#define _RX_INC_4 5 +#define _RX_INC_5 6 +#define _RX_INC_6 7 + +// rx cat + +#define _RX_CAT2(_1, _2) _1 ## _2 \ No newline at end of file diff --git a/RxCocoa/Common/_RX.m b/RxCocoa/Common/_RX.m index 745a9bdf..b8ea2f5b 100644 --- a/RxCocoa/Common/_RX.m +++ b/RxCocoa/Common/_RX.m @@ -61,8 +61,8 @@ id __nonnull RX_extract_argument_at_index(NSInvocation * __nonnull invocation, N } } -BOOL RX_is_method_void(NSInvocation * __nonnull invocation) { - const char *methodReturnType = invocation.methodSignature.methodReturnType; +BOOL RX_is_method_signature_void(NSMethodSignature * __nonnull methodSignature) { + const char *methodReturnType = methodSignature.methodReturnType; return strcmp(methodReturnType, @encode(void)) == 0; } @@ -75,7 +75,7 @@ NSArray *RX_extract_arguments(NSInvocation *invocation) { NSUInteger numberOfVisibleArguments = numberOfArguments - HIDDEN_ARGUMENT_COUNT; NSCParameterAssert(numberOfVisibleArguments >= 0); - NSCParameterAssert(RX_is_method_void(invocation)); + NSCParameterAssert(RX_is_method_signature_void(invocation.methodSignature)); NSMutableArray *arguments = [NSMutableArray arrayWithCapacity:numberOfVisibleArguments]; diff --git a/RxCocoa/Common/_RXDelegateProxy.m b/RxCocoa/Common/_RXDelegateProxy.m index e33b10d4..2e977c37 100644 --- a/RxCocoa/Common/_RXDelegateProxy.m +++ b/RxCocoa/Common/_RXDelegateProxy.m @@ -25,8 +25,6 @@ static NSMutableDictionary *forwardableSelectorsPerClass = nil; forwardableSelectorsPerClass = [[NSMutableDictionary alloc] init]; } - // NSLog(@"Class: %@", NSStringFromClass(self)); - NSMutableSet *allowedSelectors = [NSMutableSet set]; unsigned int count; @@ -41,7 +39,6 @@ static NSMutableDictionary *forwardableSelectorsPerClass = nil; for (unsigned int j = 0; j < protocolMethodCount; ++j) { struct objc_method_description method = methods[j]; if (RX_is_method_with_description_void(method)) { - // NSLog(@"Allowed selector: %@", NSStringFromSelector(method.name)); [allowedSelectors addObject:SEL_VALUE(method.name)]; } } @@ -88,12 +85,11 @@ static NSMutableDictionary *forwardableSelectorsPerClass = nil; } -(void)forwardInvocation:(NSInvocation *)anInvocation { - if (RX_is_method_void(anInvocation)) { + if (RX_is_method_signature_void(anInvocation.methodSignature)) { NSArray *arguments = RX_extract_arguments(anInvocation); [self interceptedSelector:anInvocation.selector withArguments:arguments]; } - //NSLog(@"Sent selector %@", NSStringFromSelector(anInvocation.selector)); if (self._forwardToDelegate && [self._forwardToDelegate respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:self._forwardToDelegate]; } diff --git a/RxCocoa/Common/_RXSwizzling.h b/RxCocoa/Common/_RXSwizzling.h index 79d9db2a..f19cd178 100644 --- a/RxCocoa/Common/_RXSwizzling.h +++ b/RxCocoa/Common/_RXSwizzling.h @@ -19,6 +19,6 @@ void * __nonnull RX_reference_from_selector(SEL __nonnull selector); @end -void RX_ensure_swizzled(Class __nonnull targetClass, SEL __nonnull selector); +void RX_ensure_observing(id __nonnull target, SEL __nonnull selector); #endif \ No newline at end of file diff --git a/RxCocoa/Common/_RXSwizzling.m b/RxCocoa/Common/_RXSwizzling.m index 017ac6b1..f8bf3fa5 100644 --- a/RxCocoa/Common/_RXSwizzling.m +++ b/RxCocoa/Common/_RXSwizzling.m @@ -14,38 +14,83 @@ #import "_RX.h" #import "_RXSwizzling.h" +typedef NSInvocation * NSInvocationRef; +typedef NSMethodSignature * NSMethodSignatureRef; +typedef unsigned int rx_uint; +typedef unsigned long rx_ulong; +typedef id (^rx_block)(id); + #if !DISABLE_SWIZZLING -SEL _Nonnull RX_selector(SEL __nonnull selector) { +#define ALWAYS(condition, message) if (!(condition)) { [NSException raise:@"RX Invalid Operator" format:@"%@", message]; } +#define ALWAYS_WITH_INFO(condition, message) NSAssert((condition), @"%@ [%@] > %@", NSStringFromClass(class), NSStringFromSelector(selector), (message)) +#define C_ALWAYS(condition, message) NSCAssert((condition), @"%@ [%@] > %@", NSStringFromClass(class), NSStringFromSelector(selector), (message)) + +#define RX_PREFIX @"_RX_" + +static int RxSwizzledClassKey = 0; + +SEL __nonnull RX_selector(SEL __nonnull selector) { NSString *selectorString = NSStringFromSelector(selector); - return NSSelectorFromString([@"_RX_" stringByAppendingString:selectorString]); + return NSSelectorFromString([RX_PREFIX stringByAppendingString:selectorString]); } +#define RX_ARG_id(value) ((value) ?: [NSNull null]) +#define RX_ARG_int(value) [NSNumber numberWithInt:value] +#define RX_ARG_long(value) [NSNumber numberWithLong:value] +#define RX_ARG_BOOL(value) [NSNumber numberWithBool:value] +#define RX_ARG_SEL(value) [NSNumber valueWithPointer:value] +#define RX_ARG_rx_uint(value) [NSNumber numberWithUnsignedInt:value] +#define RX_ARG_rx_ulong(value) [NSNumber numberWithUnsignedLong:value] +#define RX_ARG_rx_block(value) ((value) ?: [NSNull null]) + void * __nonnull RX_reference_from_selector(SEL __nonnull selector) { return selector; } -/*static Method RX_methodImplementation(Class __nonnull class, SEL __nonnull selector) { - NSCAssert(class != nil, @"Target class is nil"); - NSCAssert(selector != nil, @"Selector is nil"); +void RX_ensure_can_swizzle(Class __nonnull class, SEL __nonnull selector) { + Method existingMethod = class_getInstanceMethod(class, selector); - unsigned int methodCount = 0; + C_ALWAYS(existingMethod != nil, @"Method is nil"); + const char *encoding = method_getTypeEncoding(existingMethod); + NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:encoding]; - Method *methods = class_copyMethodList(class, &methodCount); - NSCAssert(methods != nil, @"Methods are nil"); - - @try { - for (unsigned int i = 0; i < methodCount; ++i) { - Method method = methods[i]; - if (method_getName(method) == selector) { - return method; - } - } + NSLog(@"%s", signature.methodReturnType); + NSUInteger numberOfArguments = signature.numberOfArguments; + for (NSUInteger i = 0; i < numberOfArguments; ++i) { + NSLog(@"%s", [signature getArgumentTypeAtIndex:i]); + [signature getArgumentTypeAtIndex:i]; } - @finally { - free(methods); + C_ALWAYS(strcmp(signature.methodReturnType, @encode(void)) == 0, @"Method is not void"); +} + +void RX_ForwardInvocation(id __nonnull self, NSInvocation *invocation) { + +} + +BOOL RX_RespondsToSelector(id __nonnull self, SEL selector) { + return NO; +} + +NSMethodSignatureRef RX_MethodSignature(id __nonnull self, SEL selector) { + Class class = object_getClass(self); + if (class == nil) { + return nil; } -}*/ + + Method method = class_getInstanceMethod(class, selector); + if (method == nil) { + return nil; + } + + const char *encoding = method_getTypeEncoding(method); + + if (encoding == nil) { + return nil; + } + + return [NSMethodSignature signatureWithObjCTypes:encoding]; +} // inspired by // https://github.com/mikeash/MAZeroingWeakRef/blob/master/Source/MAZeroingWeakRef.m @@ -55,6 +100,8 @@ void * __nonnull RX_reference_from_selector(SEL __nonnull selector) { @property (nonatomic, assign) pthread_mutex_t lock; +@property (nonatomic, strong) NSMutableSet *classesThatSupportObservingByForwarding; +@property (nonatomic, strong) NSMutableDictionary *dynamicSublassByRealClass; @property (nonatomic, strong) NSMutableDictionary*> *swizzledSelectorsByClass; @end @@ -75,7 +122,9 @@ static RXSwizzling *_instance = nil; -(instancetype)init { self = [super init]; if (!self) return nil; - + + self.classesThatSupportObservingByForwarding = [NSMutableSet set]; + self.dynamicSublassByRealClass = [NSMutableDictionary dictionary]; self.swizzledSelectorsByClass = [NSMutableDictionary dictionary]; pthread_mutexattr_t lock_attr; @@ -93,8 +142,169 @@ static RXSwizzling *_instance = nil; pthread_mutex_unlock(&_lock); } --(void)ensureSwizzledSelector:(SEL __nonnull)selector ofClass:(Class __nonnull)targetClass { - NSValue * __nonnull classValue = CLASS_VALUE(targetClass); +-(void)ensureSwizzled:(id __nonnull)target forObserving:(SEL __nonnull)selector { + __unused Class swizzlingImplementorClass = [self ensurePreparedForSwizzling:target]; + + /* + Method instanceMethod = class_getInstanceMethod(class, selector); + const char* methodEncoding = method_getTypeEncoding(instanceMethod); + NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:methodEncoding]; + + // if method signature is not void + if (!RX_is_method_signature_void(methodSignature)) { + + }*/ +} + +-(Class)ensurePreparedForSwizzling:(id __nonnull)target { + Class swizzlingClass = objc_getAssociatedObject(target, &RxSwizzledClassKey); + if (swizzlingClass != nil) { + return swizzlingClass; + } + + Class __nonnull wannaBeClass = [target class]; + // if possibly, only limit effect to one instance + if ([target class] == object_getClass(target)) { + Class dynamicFakeSubclass = [self ensureDynamicFakeSubclass:wannaBeClass]; + object_setClass(target, dynamicFakeSubclass); + objc_setAssociatedObject(target, &RxSwizzledClassKey, dynamicFakeSubclass, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return dynamicFakeSubclass; + } + + // biggest performance penalty, swizzling all instances of original class + objc_setAssociatedObject(target, &RxSwizzledClassKey, wannaBeClass, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return wannaBeClass; +} + +/** + If object don't have some weird behavior, claims it's the same class that runtime shows, + then dynamic subclass is created (only this instance will have performance hit). + + In case something weird is detected, then original base class is being swizzled and all instances + will have somewhat reduced performance. + + This is especially handy optimization for weak KVO. Nobody will swizzle for example `NSString`, + but to know when instance of a `NSString` was deallocated, performance hit will be only felt on a + single instance of `NSString`, not all instances of `NSString`s. + */ +-(Class)ensureDynamicFakeSubclass:(Class __nonnull)class { + Class dynamicFakeSubclass = [self.dynamicSublassByRealClass objectForKey:CLASS_VALUE(class)]; + if (dynamicFakeSubclass != nil) { + return dynamicFakeSubclass; + } + + NSString *dynamicFakeSublassName = [RX_PREFIX stringByAppendingString:NSStringFromClass(class)]; + dynamicFakeSubclass = objc_allocateClassPair(class, dynamicFakeSublassName.UTF8String, 0); + ALWAYS(dynamicFakeSubclass != nil, @"Class not generated"); + + [self ensureForwardingMethodsAreHandled:dynamicFakeSubclass toActAs:class]; + + [self.dynamicSublassByRealClass setObject:dynamicFakeSubclass forKey:CLASS_VALUE(class)]; + return dynamicFakeSubclass; +} + +-(void)ensureForwardingMethodsAreHandled:(Class __nonnull)class toActAs:(Class __nonnull)toActAs { + NSValue *classValue = CLASS_VALUE(class); + if ([self.classesThatSupportObservingByForwarding containsObject:classValue]) { + return; + } + + [self swizzleForwardInvocation:class]; + [self swizzleMethodSignatureForSelector:class]; + [self swizzleRespondsToSelector:class]; + [self swizzleClass:class toActAs:toActAs]; + + [self.classesThatSupportObservingByForwarding addObject:classValue]; +} + +#define FORWARD_BODY(invocation) RX_ForwardInvocation(self, NAME_CAT(_, 0, invocation)); +#define RESPONDS_TO_SELECTOR_BODY(selector) if (RX_RespondsToSelector(self, NAME_CAT(_, 0, selector))) return YES; +#define CLASS_BODY(...) return class; +#define METHOD_SIGNATURE_FOR_SELECTOR_BODY(selector) \ + NSMethodSignatureRef methodSignature = RX_MethodSignature(self, NAME_CAT(_, 0, selector)); \ + if (methodSignature != nil) { \ + return methodSignature; \ + } + +#define OBSERVE_BODY(...) \ + id action = objc_getAssociatedObject(self, rxSelector); \ + \ + if (action != nil) { \ + [action messageSentWithParameters:@[]]; \ + } \ + + +#define CAT(_1, _2, head, tail) RX_CAT2(head, tail) +#define SEPARATE_BY_UNDERSCORE(head, tail) RX_CAT2(RX_CAT2(head, _), tail) +#define UNDERSCORE_TYPE_CAT(_1, index, type) RX_CAT2(_, type) // generates -> , _type +#define NAME_CAT(_1, index, type) SEPARATE_BY_UNDERSCORE(type, index) // generates -> , type_0 +#define TYPE_AND_NAME_CAT(_1, index, type) type SEPARATE_BY_UNDERSCORE(type, index) // generates -> , type type_0 + +#define ARGUMENTS(...) RX_FOR_COMMA(_, NAME_CAT, ## __VA_ARGS__) +#define DECLARE_ARGUMENTS(...) RX_FOR_COMMA(_, TYPE_AND_NAME_CAT, ## __VA_ARGS__) + +#define GENERATE_METHOD_IDENTIFIER(...) RX_CAT2(swizzle, RX_FOR(_, CAT, UNDERSCORE_TYPE_CAT, ## __VA_ARGS__)) + +// generation of forwarding methods + +#define GENERATE_OBSERVE_METHOD_DECLARATION(...) -(void)GENERATE_METHOD_IDENTIFIER(__VA_ARGS__):(Class __nonnull)class selector:(SEL)selector { + +#define SWIZZLE_OBSERVE_METHOD(return_value, body, ...) \ + SWIZZLE_METHOD(return_value, GENERATE_OBSERVE_METHOD_DECLARATION(return_value, ## __VA_ARGS__), body, ## __VA_ARGS__) + +#define SWIZZLE_INFRASTRUCTURE_METHOD(return_value, method_name, parameters, method_selector, body, ...) \ + SWIZZLE_METHOD(return_value, -(void)method_name:(Class __nonnull)class parameters { SEL selector = @selector(method_selector); , body, __VA_ARGS__) + +#define SWIZZLE_METHOD(return_value, method_prototype, body, ...) \ +method_prototype \ + __unused SEL rxSelector = RX_selector(selector); \ + IMP (^newImplementationGenerator)() = ^() { \ + id newImplementation = ^return_value(__unsafe_unretained id self DECLARE_ARGUMENTS(__VA_ARGS__)) { \ + body(__VA_ARGS__) \ + \ + struct objc_super superInfo = { \ + .receiver = self, \ + .super_class = class_getSuperclass(class) \ + }; \ + \ + return_value (*msgSend)(struct objc_super *, SEL DECLARE_ARGUMENTS(__VA_ARGS__)) \ + = (__typeof__(msgSend))objc_msgSendSuper; \ + return msgSend(&superInfo, selector ARGUMENTS(__VA_ARGS__)); \ + }; \ + \ + return imp_implementationWithBlock(newImplementation); \ + }; \ + \ + IMP (^replacementImplementationGenerator)(IMP) = ^(IMP originalImplementation) { \ + __block return_value (*originalImplementationTyped)(__unsafe_unretained id, SEL DECLARE_ARGUMENTS(__VA_ARGS__) ) \ + = (__typeof__(originalImplementationTyped))(originalImplementation); \ + \ + id implementationReplacement = ^return_value(__unsafe_unretained id self DECLARE_ARGUMENTS(__VA_ARGS__) ) { \ + body(__VA_ARGS__) \ + \ + return originalImplementationTyped(self, selector ARGUMENTS(__VA_ARGS__)); \ + }; \ + \ + return imp_implementationWithBlock(implementationReplacement); \ + }; \ + \ + [self _ensureSwizzledSelector:selector \ + ofClass:class \ + newImplementationGenerator:newImplementationGenerator \ +replacementImplementationGenerator:replacementImplementationGenerator]; \ +} + +SWIZZLE_INFRASTRUCTURE_METHOD(void, swizzleForwardInvocation, , forwardInvocation:, FORWARD_BODY, NSInvocationRef) +SWIZZLE_INFRASTRUCTURE_METHOD(BOOL, swizzleRespondsToSelector, , respondsToSelector:, RESPONDS_TO_SELECTOR_BODY, SEL) +SWIZZLE_INFRASTRUCTURE_METHOD(Class __nonnull, swizzleClass, toActAs:(Class)actAsClass, class, CLASS_BODY) +SWIZZLE_INFRASTRUCTURE_METHOD(NSMethodSignatureRef, swizzleMethodSignatureForSelector, , methodSignatureForSelector:, METHOD_SIGNATURE_FOR_SELECTOR_BODY, SEL) + +-(void)_ensureSwizzledSelector:(SEL __nonnull)selector + ofClass:(Class __nonnull)class + newImplementationGenerator:(IMP(^)())newImplementationGenerator +replacementImplementationGenerator:(IMP (^)(IMP originalImplemenation))replacementImplementationGenerator { + + NSValue * __nonnull classValue = CLASS_VALUE(class); NSValue * __nonnull selectorValue = SEL_VALUE(selector); NSMutableSet *swizzledSelectorsForClass = self.swizzledSelectorsByClass[classValue]; @@ -103,7 +313,7 @@ static RXSwizzling *_instance = nil; return; } - DLOG(@"Rx is swizzling dealloc for: %@", targetClass); + DLOG(@"Rx is swizzling `%@` for `%@`", NSStringFromSelector(selector), class); if (swizzledSelectorsForClass == nil) { swizzledSelectorsForClass = [NSMutableSet set]; @@ -112,68 +322,44 @@ static RXSwizzling *_instance = nil; [swizzledSelectorsForClass addObject:selectorValue]; - NSAssert([[self.swizzledSelectorsByClass objectForKey:classValue] containsObject:selectorValue], @"Class should have been swizzled"); + ALWAYS([[self.swizzledSelectorsByClass objectForKey:classValue] containsObject:selectorValue], @"Class should have been swizzled"); - SEL rxSelector = RX_selector(selector); + Method existingMethod = class_getInstanceMethod(class, selector); - void (^basicImplementation)() = ^(__unsafe_unretained id self) { - id action = objc_getAssociatedObject(self, rxSelector); - - if (action != nil) { - [action messageSentWithParameters:@[]]; - } - }; - - id newImplementation = ^(__unsafe_unretained id self) { - basicImplementation(self); - struct objc_super superInfo = { - .receiver = self, - .super_class = class_getSuperclass(targetClass) - }; - - void (*msgSend)(struct objc_super *, SEL) = (__typeof__(msgSend))objc_msgSendSuper; - msgSend(&superInfo, selector); - }; - - IMP newImplementationIMP = imp_implementationWithBlock(newImplementation); - - Method existingMethod = class_getInstanceMethod(targetClass, selector); - - NSAssert(existingMethod != nil, @"Method for selector `%@` doesn't exist on `%@`.", NSStringFromSelector(selector), NSStringFromClass(targetClass)); + ALWAYS(existingMethod != nil, @"Method doesn't exist"); const char *encoding = method_getTypeEncoding(existingMethod); - if (class_addMethod(targetClass, selector, newImplementationIMP, encoding)) { - // new dealloc method added, job done + ALWAYS(encoding != nil, @"Encoding is nil"); + + IMP newImplementation = newImplementationGenerator(); + + if (class_addMethod(class, selector, newImplementation, encoding)) { + // new method added, job done return; } + imp_removeBlock(newImplementation); + // if add fails, that means that method already exists on targetClass Method existingMethodOnTargetClass = existingMethod; - // implementation needs to be replaced - __block void (*originalImplementation)(__unsafe_unretained id, SEL) = NULL; + IMP originalImplementation = method_getImplementation(existingMethodOnTargetClass); + IMP implementationReplacementIMP = replacementImplementationGenerator(originalImplementation); + ALWAYS(originalImplementation != nil, @"Method must exist."); + IMP originalImplementationAfterChange = method_setImplementation(existingMethodOnTargetClass, implementationReplacementIMP); + ALWAYS(originalImplementation != nil, @"Method must exist."); - id implementationReplacement = ^(__unsafe_unretained id self, SEL selector) { - basicImplementation(self, selector); - - originalImplementation(self, selector); - }; - - IMP implementationReplacementIMP = imp_implementationWithBlock(implementationReplacement); - - originalImplementation = (__typeof__(originalImplementation))method_getImplementation(existingMethodOnTargetClass); - NSAssert(originalImplementation != nil, @"Method must exist."); - originalImplementation = (__typeof__(originalImplementation))method_setImplementation(existingMethodOnTargetClass, implementationReplacementIMP); - NSAssert(originalImplementation != nil, @"Method must exist."); + // ¯\_(ツ)_/¯ + if (originalImplementationAfterChange != originalImplementation) { + NSLog(@"There was a problem swizzling `%@` on `%@`.\nYou have probably two libraries performing swizzling in runtime.\nWe didn't want to crash your program, but this is not good ...\nYou an solve this problem by either not using swizzling in this library, removing one of those other libraries, or making sure that swizzling parts are synchronized (only perform them on main thread).\nAnd yes, this message will self destruct when you clear the console, and since it's non deterministric, the problem could still exist and it will be hard for you to reproduce it.", NSStringFromSelector(selector), NSStringFromClass(class)); + } } @end -void RX_ensure_swizzled(Class __nonnull targetClass, SEL __nonnull selector) { - NSCAssert(targetClass != nil, @"Target class is nil"); - NSCAssert(selector != nil, @"Selector is nil"); +void RX_ensure_observing(id __nonnull target, SEL __nonnull selector) { [[RXSwizzling instance] performLocked:^{ - [[RXSwizzling instance] ensureSwizzledSelector:selector ofClass:targetClass]; + [[RXSwizzling instance] ensureSwizzled:target forObserving:selector]; }]; } diff --git a/RxCocoa/RxCocoa.h b/RxCocoa/RxCocoa.h index 730f04ae..cd2e28f4 100644 --- a/RxCocoa/RxCocoa.h +++ b/RxCocoa/RxCocoa.h @@ -9,7 +9,6 @@ #import #import "_RXDelegateProxy.h" #import "_RXKVOObserver.h" -#import "_RX.h" #import "_RXSwizzling.h" //! Project version number for RxCocoa. diff --git a/RxTests/RxSwiftTests/Tests/ObserveSentMessagesTest.swift b/RxTests/RxSwiftTests/Tests/ObserveSentMessagesTest.swift new file mode 100644 index 00000000..9f2e1937 --- /dev/null +++ b/RxTests/RxSwiftTests/Tests/ObserveSentMessagesTest.swift @@ -0,0 +1,73 @@ +// +// ObserveSentMessagesTest.swift +// RxTests +// +// Created by Krunoslav Zaher on 11/21/15. +// +// + +import Foundation +import XCTest + +class ObserveSentMessages : RxTest { + +} + +@objc public class SendMessageTest: NSObject { + var messages: [[AnyObject]] + + override init() { + self.messages = [] + super.init() + } + + @objc public func emptyMessage() -> Void { + messages.append([]) + } + + @objc public func message_Int32(a: Int32) -> Void { + messages.append([NSNumber(int: a)]) + } + + @objc public func message_Int64(a: Int64) -> Void { + messages.append([NSNumber(longLong: a)]) + } + + @objc public func message_UInt32(a: UInt32) -> Void { + messages.append([NSNumber(unsignedInt: a)]) + } + + @objc public func message_UInt64(a: UInt64) -> Void { + messages.append([NSNumber(unsignedLongLong: a)]) + } + + @objc public func message_String(a: String) -> Void { + messages.append([a]) + } + + @objc public func message_Object(a: AnyObject) -> Void { + messages.append([a]) + } + + @objc public func message_allSupportedParameters(p1: Bool, p2: AnyObject, p3: Int, p4: Int32, p5: Int64, p6: UInt32, p7: UInt64, p8: (AnyObject) -> (AnyObject), p9: String) { + + } +} + +extension ObserveSentMessages { + func test() { + let target = SendMessageTest() + + var messages = [[AnyObject]]() + + let d = target.rx_sentMessage("message_allSupportedParameters:p2:p3:p4:p5:p6:p7:p8:p9:").subscribeNext { n in + messages.append(n) + } + + target.message_allSupportedParameters(false, p2: SendMessageTest(), p3: -1, p4: -2, p5: 3, p6: 4, p7: 5, p8: { x in x.description }, p9: "last one :(") + + d.dispose() + + XCTAssertEqual(target.messages, messages) + } +} \ No newline at end of file diff --git a/RxTests/RxTests.xcodeproj/project.pbxproj b/RxTests/RxTests.xcodeproj/project.pbxproj index ac7ce0d5..f723cd2c 100644 --- a/RxTests/RxTests.xcodeproj/project.pbxproj +++ b/RxTests/RxTests.xcodeproj/project.pbxproj @@ -10,6 +10,9 @@ 5E5D10BB1B48355200432B25 /* Control+RxTests+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D10BA1B48355200432B25 /* Control+RxTests+UIKit.swift */; }; C69B65001BA88FAC00A7FA73 /* ObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69B64FF1BA88FAC00A7FA73 /* ObserverTests.swift */; }; C69B65011BA8957C00A7FA73 /* ObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69B64FF1BA88FAC00A7FA73 /* ObserverTests.swift */; }; + C80103261C009F00009B2AE3 /* ObserveSentMessagesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C80103251C009F00009B2AE3 /* ObserveSentMessagesTest.swift */; }; + C80103271C009F00009B2AE3 /* ObserveSentMessagesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C80103251C009F00009B2AE3 /* ObserveSentMessagesTest.swift */; }; + C80103281C009F00009B2AE3 /* ObserveSentMessagesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C80103251C009F00009B2AE3 /* ObserveSentMessagesTest.swift */; }; C801EB5A1B97951100C4D8C4 /* Observable+CreationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C801EB591B97951100C4D8C4 /* Observable+CreationTest.swift */; }; C801EB5B1B97951100C4D8C4 /* Observable+CreationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C801EB591B97951100C4D8C4 /* Observable+CreationTest.swift */; }; C80DDEDC1BCE9A03006A1832 /* Driver+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = C80DDEDB1BCE9A03006A1832 /* Driver+Test.swift */; }; @@ -186,6 +189,7 @@ /* Begin PBXFileReference section */ 5E5D10BA1B48355200432B25 /* Control+RxTests+UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Control+RxTests+UIKit.swift"; sourceTree = ""; }; C69B64FF1BA88FAC00A7FA73 /* ObserverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserverTests.swift; sourceTree = ""; }; + C80103251C009F00009B2AE3 /* ObserveSentMessagesTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserveSentMessagesTest.swift; sourceTree = ""; }; C801EB591B97951100C4D8C4 /* Observable+CreationTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+CreationTest.swift"; sourceTree = ""; }; C80DDEDB1BCE9A03006A1832 /* Driver+Test.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Driver+Test.swift"; sourceTree = ""; }; C80DDEDF1BCEE898006A1832 /* MainThreadPrimitiveHotObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainThreadPrimitiveHotObservable.swift; sourceTree = ""; }; @@ -440,6 +444,7 @@ C89CDB951BCDA1F1002063D9 /* Observable+SubscriptionTest.swift */, C80DDEDB1BCE9A03006A1832 /* Driver+Test.swift */, C8F6A1401BEFE04F007DF367 /* SubjectConcurrencyTest.swift */, + C80103251C009F00009B2AE3 /* ObserveSentMessagesTest.swift */, ); path = Tests; sourceTree = ""; @@ -635,6 +640,7 @@ C8E381231B2063CC008CDC33 /* Observable+Extensions.swift in Sources */, C84B8FC21B89D0D500C9CCCF /* BagTest.swift in Sources */, C81108511AF50E2A001C13E4 /* HotObservable.swift in Sources */, + C80103261C009F00009B2AE3 /* ObserveSentMessagesTest.swift in Sources */, C81108541AF50E2A001C13E4 /* Observable.Extensions.swift in Sources */, C8E9D2BD1BD422D80079D0DB /* Control+RxTests.swift in Sources */, C897EC3B1B10E000009C2CB0 /* BehaviorSubjectTest.swift in Sources */, @@ -679,6 +685,7 @@ C8E381291B207D03008CDC33 /* PrimitiveHotObservable.swift in Sources */, C868D0FA1BB76A29003D1474 /* PerformanceTools.swift in Sources */, C88BB8971B07E64B0064D411 /* MySubject.swift in Sources */, + C80103271C009F00009B2AE3 /* ObserveSentMessagesTest.swift in Sources */, C88BB8981B07E64B0064D411 /* UI+RxTests.swift in Sources */, C88BB8991B07E64B0064D411 /* Observable+BindingTest.swift in Sources */, C88BB89A1B07E64B0064D411 /* NSNotificationCenterTests.swift in Sources */, @@ -765,6 +772,7 @@ D2EBEB691BB9B7EF003A27DC /* DelegateProxyTest.swift in Sources */, D2EBEB701BB9B7EF003A27DC /* Observable+MultipleTest.swift in Sources */, D203C5141BB9C54A00D02D00 /* Control+RxTests+UIKit.swift in Sources */, + C80103281C009F00009B2AE3 /* ObserveSentMessagesTest.swift in Sources */, D2EBEB581BB9B7CC003A27DC /* TestObservable.swift in Sources */, C8E9D2BF1BD422D80079D0DB /* Control+RxTests.swift in Sources */, D2EBEB661BB9B7EF003A27DC /* BehaviorSubjectTest.swift in Sources */,