Messing with swizzling.

This commit is contained in:
Krunoslav Zaher 2015-11-22 23:48:22 +01:00
parent de65c8ff2f
commit 75fa95d4dc
10 changed files with 455 additions and 101 deletions

View File

@ -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;

View File

@ -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<Void> {
return rx_observeMessage("dealloc", replay: true).map { _ in () }
return rx_sentMessage("dealloc", replay: true).map { _ in () }
}
#endif
}

View File

@ -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

View File

@ -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];

View File

@ -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];
}

View File

@ -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

View File

@ -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<NSValue *> *classesThatSupportObservingByForwarding;
@property (nonatomic, strong) NSMutableDictionary<NSValue *, Class> *dynamicSublassByRealClass;
@property (nonatomic, strong) NSMutableDictionary<NSValue *, NSMutableSet<NSValue*>*> *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<RXMessageSentObserver> 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<RXMessageSentObserver> 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];
}];
}

View File

@ -9,7 +9,6 @@
#import <Foundation/Foundation.h>
#import "_RXDelegateProxy.h"
#import "_RXKVOObserver.h"
#import "_RX.h"
#import "_RXSwizzling.h"
//! Project version number for RxCocoa.

View File

@ -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)
}
}

View File

@ -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 = "<group>"; };
C69B64FF1BA88FAC00A7FA73 /* ObserverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserverTests.swift; sourceTree = "<group>"; };
C80103251C009F00009B2AE3 /* ObserveSentMessagesTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserveSentMessagesTest.swift; sourceTree = "<group>"; };
C801EB591B97951100C4D8C4 /* Observable+CreationTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+CreationTest.swift"; sourceTree = "<group>"; };
C80DDEDB1BCE9A03006A1832 /* Driver+Test.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Driver+Test.swift"; sourceTree = "<group>"; };
C80DDEDF1BCEE898006A1832 /* MainThreadPrimitiveHotObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainThreadPrimitiveHotObservable.swift; sourceTree = "<group>"; };
@ -440,6 +444,7 @@
C89CDB951BCDA1F1002063D9 /* Observable+SubscriptionTest.swift */,
C80DDEDB1BCE9A03006A1832 /* Driver+Test.swift */,
C8F6A1401BEFE04F007DF367 /* SubjectConcurrencyTest.swift */,
C80103251C009F00009B2AE3 /* ObserveSentMessagesTest.swift */,
);
path = Tests;
sourceTree = "<group>";
@ -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 */,