create RACTuple to encapsulate the idea of a collection that could contain nils

This commit is contained in:
joshaber
2012-04-12 11:32:17 -04:00
parent 0737d25e79
commit 09dfcd73cd
16 changed files with 168 additions and 1525 deletions

View File

@@ -30,10 +30,6 @@
881B3747152253810079220B /* NSObject+RACFastEnumeration.m in Sources */ = {isa = PBXBuildFile; fileRef = 881B3745152253810079220B /* NSObject+RACFastEnumeration.m */; };
881B37CC152260BF0079220B /* RACUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = 881B37CA152260BF0079220B /* RACUnit.h */; settings = {ATTRIBUTES = (Public, ); }; };
881B37CD152260BF0079220B /* RACUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = 881B37CB152260BF0079220B /* RACUnit.m */; };
882036E31509677A002428D3 /* EXTNil.h in Headers */ = {isa = PBXBuildFile; fileRef = 882036DD1509677A002428D3 /* EXTNil.h */; settings = {ATTRIBUTES = (Public, ); }; };
882036E41509677A002428D3 /* EXTNil.m in Sources */ = {isa = PBXBuildFile; fileRef = 882036DE1509677A002428D3 /* EXTNil.m */; };
882036E51509677A002428D3 /* EXTRuntimeExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 882036DF1509677A002428D3 /* EXTRuntimeExtensions.h */; };
882036E61509677A002428D3 /* EXTRuntimeExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 882036E01509677A002428D3 /* EXTRuntimeExtensions.m */; };
8820370A15096A4F002428D3 /* RACMaybe.h in Headers */ = {isa = PBXBuildFile; fileRef = 8820370815096A4F002428D3 /* RACMaybe.h */; settings = {ATTRIBUTES = (Public, ); }; };
8820370B15096A4F002428D3 /* RACMaybe.m in Sources */ = {isa = PBXBuildFile; fileRef = 8820370915096A4F002428D3 /* RACMaybe.m */; };
8820937C1501C8A600796685 /* RACSubscribableSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8820937B1501C8A600796685 /* RACSubscribableSpec.m */; };
@@ -47,8 +43,6 @@
8857BB7E152A2747009804CC /* NSObject+RACExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8857BB7C152A2747009804CC /* NSObject+RACExtensions.h */; };
8857BB7F152A2747009804CC /* NSObject+RACExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8857BB7D152A2747009804CC /* NSObject+RACExtensions.m */; };
8857BB82152A27A9009804CC /* NSObject+RACKVOWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 8857BB81152A27A9009804CC /* NSObject+RACKVOWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
886044D8152A9693008ABD0D /* NSArray+EXTNilSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 886044D6152A9693008ABD0D /* NSArray+EXTNilSupport.h */; settings = {ATTRIBUTES = (Public, ); }; };
886044D9152A9693008ABD0D /* NSArray+EXTNilSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 886044D7152A9693008ABD0D /* NSArray+EXTNilSupport.m */; };
8866784E1518D93E00DE77EC /* RACSubscribable+Operations.m in Sources */ = {isa = PBXBuildFile; fileRef = 8866784D1518D93D00DE77EC /* RACSubscribable+Operations.m */; };
886678711518DCD800DE77EC /* NSObject+RACPropertySubscribing.m in Sources */ = {isa = PBXBuildFile; fileRef = 886678701518DCD800DE77EC /* NSObject+RACPropertySubscribing.m */; };
8867D5F9152BDAC300321BD5 /* RACSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = 8867D5F7152BDAC300321BD5 /* RACSwizzling.h */; };
@@ -64,6 +58,8 @@
88977C861512EA7C00A09EC5 /* RACAsyncSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 88D4AB481510F8F10011494F /* RACAsyncSubject.m */; };
88977C871512EA7E00A09EC5 /* RACMaybe.m in Sources */ = {isa = PBXBuildFile; fileRef = 8820370915096A4F002428D3 /* RACMaybe.m */; };
88ABAB631509A67C0073CDA2 /* RACAsyncFunctionOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 88ABAB621509A5E40073CDA2 /* RACAsyncFunctionOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
88B76F8E153726B00053EAE2 /* RACTuple.h in Headers */ = {isa = PBXBuildFile; fileRef = 88B76F8C153726B00053EAE2 /* RACTuple.h */; settings = {ATTRIBUTES = (Public, ); }; };
88B76F8F153726B00053EAE2 /* RACTuple.m in Sources */ = {isa = PBXBuildFile; fileRef = 88B76F8D153726B00053EAE2 /* RACTuple.m */; };
88CBB39A1517A29F00DDC7D7 /* NSObject+RACSubscribable.h in Headers */ = {isa = PBXBuildFile; fileRef = 88CBB3981517A29E00DDC7D7 /* NSObject+RACSubscribable.h */; settings = {ATTRIBUTES = (Public, ); }; };
88CBB39B1517A29F00DDC7D7 /* NSObject+RACSubscribable.m in Sources */ = {isa = PBXBuildFile; fileRef = 88CBB3991517A29E00DDC7D7 /* NSObject+RACSubscribable.m */; };
88CDF7DE15000FCF00163A9F /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 88CDF7DD15000FCF00163A9F /* SenTestingKit.framework */; };
@@ -75,7 +71,7 @@
88D4AB4A1510F8F10011494F /* RACAsyncSubject.m in Sources */ = {isa = PBXBuildFile; fileRef = 88D4AB481510F8F10011494F /* RACAsyncSubject.m */; };
88DA309715071CBA00C19D0F /* RACValueTransformer.h in Headers */ = {isa = PBXBuildFile; fileRef = 88DA309515071CBA00C19D0F /* RACValueTransformer.h */; settings = {ATTRIBUTES = (); }; };
88DA309815071CBA00C19D0F /* RACValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DA309615071CBA00C19D0F /* RACValueTransformer.m */; };
88F5870215361BCD0084BD32 /* RACConnectableSubscribable.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F5870015361BCD0084BD32 /* RACConnectableSubscribable.h */; };
88F5870215361BCD0084BD32 /* RACConnectableSubscribable.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F5870015361BCD0084BD32 /* RACConnectableSubscribable.h */; settings = {ATTRIBUTES = (Public, ); }; };
88F5870315361BCD0084BD32 /* RACConnectableSubscribable.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F5870115361BCD0084BD32 /* RACConnectableSubscribable.m */; };
88F5870615361C170084BD32 /* RACConnectableSubscribable+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F5870515361C170084BD32 /* RACConnectableSubscribable+Private.h */; };
88F70068152D2D7B00B32771 /* NSObject+RACBindings.h in Headers */ = {isa = PBXBuildFile; fileRef = 88F70066152D2D7B00B32771 /* NSObject+RACBindings.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -185,10 +181,6 @@
881B3745152253810079220B /* NSObject+RACFastEnumeration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACFastEnumeration.m"; sourceTree = "<group>"; };
881B37CA152260BF0079220B /* RACUnit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACUnit.h; sourceTree = "<group>"; };
881B37CB152260BF0079220B /* RACUnit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACUnit.m; sourceTree = "<group>"; };
882036DD1509677A002428D3 /* EXTNil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTNil.h; sourceTree = "<group>"; };
882036DE1509677A002428D3 /* EXTNil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EXTNil.m; sourceTree = "<group>"; };
882036DF1509677A002428D3 /* EXTRuntimeExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTRuntimeExtensions.h; sourceTree = "<group>"; };
882036E01509677A002428D3 /* EXTRuntimeExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EXTRuntimeExtensions.m; sourceTree = "<group>"; };
8820370815096A4F002428D3 /* RACMaybe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACMaybe.h; sourceTree = "<group>"; };
8820370915096A4F002428D3 /* RACMaybe.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACMaybe.m; sourceTree = "<group>"; };
8820937B1501C8A600796685 /* RACSubscribableSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACSubscribableSpec.m; sourceTree = "<group>"; };
@@ -205,8 +197,6 @@
8857BB7C152A2747009804CC /* NSObject+RACExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACExtensions.h"; sourceTree = "<group>"; };
8857BB7D152A2747009804CC /* NSObject+RACExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACExtensions.m"; sourceTree = "<group>"; };
8857BB81152A27A9009804CC /* NSObject+RACKVOWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACKVOWrapper.h"; sourceTree = "<group>"; };
886044D6152A9693008ABD0D /* NSArray+EXTNilSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+EXTNilSupport.h"; sourceTree = "<group>"; };
886044D7152A9693008ABD0D /* NSArray+EXTNilSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+EXTNilSupport.m"; sourceTree = "<group>"; };
8866784D1518D93D00DE77EC /* RACSubscribable+Operations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RACSubscribable+Operations.m"; sourceTree = "<group>"; };
886678701518DCD800DE77EC /* NSObject+RACPropertySubscribing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACPropertySubscribing.m"; sourceTree = "<group>"; };
8867D5F7152BDAC300321BD5 /* RACSwizzling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACSwizzling.h; sourceTree = "<group>"; };
@@ -217,6 +207,8 @@
88977C58151296D600A09EC5 /* RACSubscribable+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RACSubscribable+Private.h"; sourceTree = "<group>"; };
88977C7E1512E69200A09EC5 /* RACSubscribable+Operations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RACSubscribable+Operations.h"; sourceTree = "<group>"; };
88ABAB621509A5E40073CDA2 /* RACAsyncFunctionOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RACAsyncFunctionOperation.h; sourceTree = "<group>"; };
88B76F8C153726B00053EAE2 /* RACTuple.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RACTuple.h; sourceTree = "<group>"; };
88B76F8D153726B00053EAE2 /* RACTuple.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RACTuple.m; sourceTree = "<group>"; };
88CBB3981517A29E00DDC7D7 /* NSObject+RACSubscribable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+RACSubscribable.h"; sourceTree = "<group>"; };
88CBB3991517A29E00DDC7D7 /* NSObject+RACSubscribable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+RACSubscribable.m"; sourceTree = "<group>"; };
88CDF7BF15000FCE00163A9F /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
@@ -298,25 +290,11 @@
name = Products;
sourceTree = "<group>";
};
882036DA1509677A002428D3 /* libextobjc */ = {
isa = PBXGroup;
children = (
882036DD1509677A002428D3 /* EXTNil.h */,
882036DE1509677A002428D3 /* EXTNil.m */,
882036DF1509677A002428D3 /* EXTRuntimeExtensions.h */,
882036E01509677A002428D3 /* EXTRuntimeExtensions.m */,
);
name = libextobjc;
path = ../../external/libextobjc;
sourceTree = "<group>";
};
886044CC152A95B5008ABD0D /* Extensions */ = {
isa = PBXGroup;
children = (
8857BB7C152A2747009804CC /* NSObject+RACExtensions.h */,
8857BB7D152A2747009804CC /* NSObject+RACExtensions.m */,
886044D6152A9693008ABD0D /* NSArray+EXTNilSupport.h */,
886044D7152A9693008ABD0D /* NSArray+EXTNilSupport.m */,
8867D5F7152BDAC300321BD5 /* RACSwizzling.h */,
8867D5F8152BDAC300321BD5 /* RACSwizzling.m */,
886DCD84152EA272004A666F /* NSObject+RACOperations.h */,
@@ -395,7 +373,6 @@
88CDF7C615000FCE00163A9F /* Supporting Files */ = {
isa = PBXGroup;
children = (
882036DA1509677A002428D3 /* libextobjc */,
88CDF7C715000FCE00163A9F /* ReactiveCocoa-Info.plist */,
88CDF7C815000FCE00163A9F /* InfoPlist.strings */,
88CDF7CD15000FCE00163A9F /* ReactiveCocoa-Prefix.pch */,
@@ -463,6 +440,8 @@
881B3745152253810079220B /* NSObject+RACFastEnumeration.m */,
881B37CA152260BF0079220B /* RACUnit.h */,
881B37CB152260BF0079220B /* RACUnit.m */,
88B76F8C153726B00053EAE2 /* RACTuple.h */,
88B76F8D153726B00053EAE2 /* RACTuple.m */,
);
name = Core;
sourceTree = "<group>";
@@ -496,7 +475,6 @@
88D4AB3E1510F6C30011494F /* RACReplaySubject.h in Headers */,
88D4AB491510F8F10011494F /* RACAsyncSubject.h in Headers */,
883A84DA1513964B006DB4C7 /* RACBehaviorSubject.h in Headers */,
882036E31509677A002428D3 /* EXTNil.h in Headers */,
883A84DF1513B5EC006DB4C7 /* RACDisposable.h in Headers */,
88CBB39A1517A29F00DDC7D7 /* NSObject+RACSubscribable.h in Headers */,
88977C801512E69200A09EC5 /* RACSubscribable+Operations.h in Headers */,
@@ -504,14 +482,13 @@
884476E4152367D100958F44 /* RACScopedDisposable.h in Headers */,
881B3746152253810079220B /* NSObject+RACFastEnumeration.h in Headers */,
88F70068152D2D7B00B32771 /* NSObject+RACBindings.h in Headers */,
886044D8152A9693008ABD0D /* NSArray+EXTNilSupport.h in Headers */,
8857BB82152A27A9009804CC /* NSObject+RACKVOWrapper.h in Headers */,
886DCD86152EA272004A666F /* NSObject+RACOperations.h in Headers */,
882036E51509677A002428D3 /* EXTRuntimeExtensions.h in Headers */,
88F5870215361BCD0084BD32 /* RACConnectableSubscribable.h in Headers */,
88B76F8E153726B00053EAE2 /* RACTuple.h in Headers */,
88DA309715071CBA00C19D0F /* RACValueTransformer.h in Headers */,
8857BB7E152A2747009804CC /* NSObject+RACExtensions.h in Headers */,
8867D5F9152BDAC300321BD5 /* RACSwizzling.h in Headers */,
88F5870215361BCD0084BD32 /* RACConnectableSubscribable.h in Headers */,
88F5870615361C170084BD32 /* RACConnectableSubscribable+Private.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -700,8 +677,6 @@
88037FCD1505648C001A5B19 /* NSButton+RACCommandSupport.m in Sources */,
88037FCF1505648C001A5B19 /* RACAsyncCommand.m in Sources */,
88DA309815071CBA00C19D0F /* RACValueTransformer.m in Sources */,
882036E41509677A002428D3 /* EXTNil.m in Sources */,
882036E61509677A002428D3 /* EXTRuntimeExtensions.m in Sources */,
8820370B15096A4F002428D3 /* RACMaybe.m in Sources */,
880B9177150B09190008488E /* RACSubject.m in Sources */,
88D4AB3F1510F6C30011494F /* RACReplaySubject.m in Sources */,
@@ -716,11 +691,11 @@
881B37CD152260BF0079220B /* RACUnit.m in Sources */,
884476E5152367D100958F44 /* RACScopedDisposable.m in Sources */,
8857BB7F152A2747009804CC /* NSObject+RACExtensions.m in Sources */,
886044D9152A9693008ABD0D /* NSArray+EXTNilSupport.m in Sources */,
8867D5FA152BDAC300321BD5 /* RACSwizzling.m in Sources */,
88F70069152D2D7B00B32771 /* NSObject+RACBindings.m in Sources */,
886DCD87152EA272004A666F /* NSObject+RACOperations.m in Sources */,
88F5870315361BCD0084BD32 /* RACConnectableSubscribable.m in Sources */,
88B76F8F153726B00053EAE2 /* RACTuple.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -1,16 +0,0 @@
//
// NSArray+EXTNilSupport.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 4/2/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSArray (EXTNilSupport)
- (id)rac_objectOrNilAtIndex:(NSUInteger)index;
@end

View File

@@ -1,20 +0,0 @@
//
// NSArray+EXTNilSupport.m
// ReactiveCocoa
//
// Created by Josh Abernathy on 4/2/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import "NSArray+EXTNilSupport.h"
#import "EXTNil.h"
@implementation NSArray (EXTNilSupport)
- (id)rac_objectOrNilAtIndex:(NSUInteger)index {
id object = [self objectAtIndex:index];
return [object isKindOfClass:[EXTNil class]] ? nil : object;
}
@end

View File

@@ -9,10 +9,11 @@
#import <Foundation/Foundation.h>
@class RACSubscribable;
@class RACTuple;
@interface NSObject (RACOperations)
- (RACSubscribable *)rac_whenAny:(NSArray *)keyPaths reduce:(id (^)(NSArray *xs))reduceBlock;
- (RACSubscribable *)rac_whenAny:(NSArray *)keyPaths reduce:(id (^)(RACTuple *xs))reduceBlock;
@end

View File

@@ -11,14 +11,14 @@
#import "NSObject+RACSubscribable.h"
#import "RACSubscribable.h"
#import "RACSubscribable+Operations.h"
#import "EXTNil.h"
#import "RACSubscriber.h"
#import "NSObject+RACFastEnumeration.h"
#import "RACTuple.h"
@implementation NSObject (RACOperations)
- (RACSubscribable *)rac_whenAny:(NSArray *)keyPaths reduce:(id (^)(NSArray *xs))reduceBlock {
- (RACSubscribable *)rac_whenAny:(NSArray *)keyPaths reduce:(id (^)(RACTuple *xs))reduceBlock {
NSParameterAssert(keyPaths != nil);
NSParameterAssert(reduceBlock != NULL);
@@ -26,13 +26,13 @@
return [RACSubscribable createSubscribable:^RACDisposable *(id<RACSubscriber> observer) {
NSObject *strongSelf = weakSelf;
NSArray * (^currentValues)(void) = ^{
RACTuple * (^currentValues)(void) = ^{
NSMutableArray *values = [NSMutableArray arrayWithCapacity:keyPaths.count];
for(NSString *keyPath in keyPaths) {
[values addObject:[strongSelf valueForKeyPath:keyPath] ? : [EXTNil null]];
[values addObject:[strongSelf valueForKeyPath:keyPath] ? : [NSNull null]];
}
return values;
return [RACTuple tupleWithObjectsFromArray:values];
};
[observer sendNext:reduceBlock(currentValues())];

View File

@@ -7,7 +7,7 @@
//
#import "RACMaybe.h"
#import "EXTNil.h"
#import "RACTuple.h"
@interface RACMaybe ()
@property (nonatomic, strong) id object;
@@ -25,7 +25,7 @@
+ (id)maybeWithObject:(id)object {
RACMaybe *maybe = [[self alloc] init];
maybe.object = object ? : [EXTNil null];
maybe.object = object ? : [RACTupleNil tupleNil];
return maybe;
}
@@ -43,4 +43,8 @@
return self.error != nil;
}
- (id)object {
return object == [RACTupleNil tupleNil] ? nil : object;
}
@end

View File

@@ -7,7 +7,6 @@
//
#import "RACReplaySubject.h"
#import "EXTNil.h"
#import "RACSubscriber.h"
@interface RACReplaySubject ()
@@ -33,7 +32,7 @@
- (RACDisposable *)subscribe:(id<RACSubscriber>)observer {
RACDisposable * disposable = [super subscribe:observer];
for(id value in self.valuesReceived) {
[observer sendNext:value];
[observer sendNext:[value isKindOfClass:[NSNull class]] ? nil : value];
}
return disposable;
@@ -45,7 +44,7 @@
- (void)sendNext:(id)value {
[super sendNext:value];
[self.valuesReceived addObject:value ? : [EXTNil null]];
[self.valuesReceived addObject:value ? : [NSNull null]];
while(self.valuesReceived.count > self.capacity) {
[self.valuesReceived removeObjectAtIndex:0];

View File

@@ -8,6 +8,7 @@
#import "RACSubscribable.h"
@class RACTuple;
@class RACConnectableSubscribable;
@@ -37,17 +38,17 @@
// Divide the `next`s of the subscribable into windows. When `openSubscribable` sends a next, a window is opened and the `closeBlock` is asked for a close subscribable. The window is closed when the close subscribable sends a `next`.
- (instancetype)windowWithStart:(id<RACSubscribable>)openSubscribable close:(id<RACSubscribable> (^)(id<RACSubscribable> start))closeBlock;
// Divide the `next`s into buffers with `bufferCount` items each.
// Divide the `next`s into buffers with `bufferCount` items each. The `next` will be a RACTuple of values.
- (instancetype)buffer:(NSUInteger)bufferCount;
// Divide the `next`s into buffers delivery every `interval` seconds.
// Divide the `next`s into buffers delivery every `interval` seconds. The `next` will be a RACTuple of values.
- (instancetype)bufferWithTime:(NSTimeInterval)interval;
// Take `count` `next`s and then completes.
- (instancetype)take:(NSUInteger)count;
// Combine the latest values from each of the subscribables once all the subscribables have sent a `next`.
+ (instancetype)combineLatest:(NSArray *)observables reduce:(id (^)(NSArray *xs))reduceBlock;
+ (instancetype)combineLatest:(NSArray *)observables reduce:(id (^)(RACTuple *xs))reduceBlock;
// Sends a `+[RACUnit defaultUnit]` when all the subscribables have sent a `next`.
+ (instancetype)whenAll:(NSArray *)observables;
@@ -109,7 +110,7 @@
// The source must be a subscribable of subscribables. Subscribe and send `next`s for the latest subscribable. This is mostly useful when combined with `-selectMany:`.
- (instancetype)switch;
// Add every `next` to an array. Note that this is a blocking call.
// Add every `next` to an array. Nils are represented by NSNulls. Note that this is a blocking call.
- (NSArray *)toArray;
// Creates and returns a connectable subscribable. This allows you to share 1 single subscription to the underlying subscribable.

View File

@@ -12,10 +12,10 @@
#import "NSObject+RACExtensions.h"
#import "RACBehaviorSubject.h"
#import "RACDisposable.h"
#import "EXTNil.h"
#import "RACUnit.h"
#import "RACMaybe.h"
#import "RACConnectableSubscribable+Private.h"
#import "RACTuple.h"
@implementation RACSubscribable (Operations)
@@ -275,13 +275,13 @@
__block RACDisposable *innerDisposable = nil;
RACDisposable *outerDisposable = [[self windowWithStart:windowOpenSubject close:^(id<RACSubscribable> start) {
return [[[RACSubscribable interval:interval] take:1] doNext:^(id x) {
[observer sendNext:values];
[observer sendNext:[RACTuple tupleWithObjectsFromArray:values]];
[values removeAllObjects];
[windowOpenSubject sendNext:[RACUnit defaultUnit]];
}];
}] subscribeNext:^(id x) {
innerDisposable = [x subscribeNext:^(id x) {
[values addObject:x ? : [EXTNil null]];
[values addObject:x ? : [RACTupleNil tupleNil]];
}];
} error:^(NSError *error) {
[observer sendError:error];
@@ -314,7 +314,7 @@
}];
}
+ (instancetype)combineLatest:(NSArray *)observables reduce:(id (^)(NSArray *xs))reduceBlock {
+ (instancetype)combineLatest:(NSArray *)observables reduce:(id (^)(RACTuple *xs))reduceBlock {
NSParameterAssert(reduceBlock != NULL);
return [RACSubscribable createSubscribable:^(id<RACSubscriber> observer) {
@@ -323,7 +323,7 @@
NSMutableDictionary *lastValues = [NSMutableDictionary dictionaryWithCapacity:observables.count];
for(id<RACSubscribable> observable in observables) {
RACDisposable *disposable = [observable subscribe:[RACSubscriber subscriberWithNext:^(id x) {
[lastValues setObject:x ? : [EXTNil null] forKey:[NSString stringWithFormat:@"%p", observable]];
[lastValues setObject:x ? : [NSNull null] forKey:[NSString stringWithFormat:@"%p", observable]];
if(lastValues.count == observables.count) {
NSMutableArray *orderedValues = [NSMutableArray arrayWithCapacity:observables.count];
@@ -331,7 +331,7 @@
[orderedValues addObject:[lastValues objectForKey:[NSString stringWithFormat:@"%p", o]]];
}
[observer sendNext:reduceBlock(orderedValues)];
[observer sendNext:reduceBlock([RACTuple tupleWithObjectsFromArray:orderedValues])];
}
} error:^(NSError *error) {
[observer sendError:error];
@@ -356,7 +356,7 @@
}
+ (instancetype)whenAll:(NSArray *)observables {
return [self combineLatest:observables reduce:^(NSArray *xs) { return [RACUnit defaultUnit]; }];
return [self combineLatest:observables reduce:^(RACTuple *xs) { return [RACUnit defaultUnit]; }];
}
+ (instancetype)merge:(NSArray *)observables {
@@ -679,7 +679,7 @@
NSMutableArray *values = [NSMutableArray array];
__block BOOL stop = NO;
[self subscribeNext:^(id x) {
[values addObject:x ? : [EXTNil null]];
[values addObject:x ? : [NSNull null]];
} error:^(NSError *error) {
stop = YES;
} completed:^{

View File

@@ -0,0 +1,30 @@
//
// RACTuple.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 4/12/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface RACTupleNil : NSObject
+ (RACTupleNil *)tupleNil;
@end
// A tuple is an ordered collection of objects. It may contain nils, represented by RACTupleNil.
@interface RACTuple : NSObject <NSCopying, NSFastEnumeration>
@property (nonatomic, readonly) NSUInteger count;
+ (id)tupleWithObjectsFromArray:(NSArray *)array;
+ (id)tupleWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;
// Returns the object at `index` or nil if the object is a RACTupleNil.
- (id)objectAtIndex:(NSUInteger)index;
- (NSArray *)allObjects;
@end

View File

@@ -0,0 +1,99 @@
//
// RACTuple.m
// ReactiveCocoa
//
// Created by Josh Abernathy on 4/12/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import "RACTuple.h"
@implementation RACTupleNil
+ (RACTupleNil *)tupleNil {
static dispatch_once_t onceToken;
static RACTupleNil *tupleNil = nil;
dispatch_once(&onceToken, ^{
tupleNil = [[self alloc] init];
});
return tupleNil;
}
@end
@interface RACTuple ()
@property (nonatomic, strong) NSArray *backingArray;
@end
@implementation RACTuple
- (id)init {
self = [super init];
if(self == nil) return nil;
self.backingArray = [NSArray array];
return self;
}
#pragma mark NSFastEnumeration
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len {
return [self.backingArray countByEnumeratingWithState:state objects:buffer count:len];
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
// we're immutable, bitches!
return self;
}
#pragma mark API
@synthesize backingArray;
+ (id)tupleWithObjectsFromArray:(NSArray *)array {
RACTuple *tuple = [[self alloc] init];
tuple.backingArray = [array copy];
return tuple;
}
+ (id)tupleWithObjects:(id)object, ... {
RACTuple *tuple = [[self alloc] init];
NSMutableArray *objects = [NSMutableArray array];
va_list args;
va_start(args, object);
for(id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) {
[objects addObject:currentObject];
}
va_end(args);
tuple.backingArray = [objects copy];
return tuple;
}
- (id)objectAtIndex:(NSUInteger)index {
id object = [self.backingArray objectAtIndex:index];
return object == [RACTupleNil tupleNil] ? nil : object;
}
- (NSArray *)allObjects {
return self.backingArray;
}
- (NSUInteger)count {
return self.backingArray.count;
}
@end

View File

@@ -14,7 +14,6 @@
#import <ReactiveCocoa/NSButton+RACCommandSupport.h>
#import <ReactiveCocoa/RACAsyncCommand.h>
#import <ReactiveCocoa/RACMaybe.h>
#import <ReactiveCocoa/EXTNil.h>
#import <ReactiveCocoa/RACAsyncFunctionOperation.h>
#import <ReactiveCocoa/RACSubject.h>
#import <ReactiveCocoa/RACReplaySubject.h>
@@ -28,3 +27,4 @@
#import <ReactiveCocoa/NSArray+EXTNilSupport.h>
#import <ReactiveCocoa/NSObject+RACBindings.h>
#import <ReactiveCocoa/NSObject+RACOperations.h>
#import <ReactiveCocoa/RACTuple.h>

View File

@@ -1,37 +0,0 @@
//
// EXTNil.h
// extobjc
//
// Created by Justin Spahr-Summers on 2011-04-25.
// Released into the public domain.
//
#import <Foundation/Foundation.h>
/**
* Like \c NSNull, this class provides a singleton object that can be used to
* represent a \c NULL or \c nil value. Unlike \c NSNull, this object behaves
* more similarly to a \c nil object, responding to messages with "zero" values.
* This eliminates the need for \c NSNull class or equality checks with
* collections that need to contain null values.
*
* This class will pretend to be \c NSNull when queried for its class or
* compared for equality, to keep compatibility with code that expects or uses
* \c NSNull.
*
* @note Because this class does still behave like an object in some ways, it
* will respond to certain \c NSObject protocol methods where an actually \c nil
* object would not.
*/
@interface EXTNil : NSProxy {
}
/**
* Returns the singleton \c EXTNil instance. This naming matches that of \c
* NSNull -- \c nil as a method name is unusable because it is a language
* keyword.
*/
+ (id)null;
@end

View File

@@ -1,89 +0,0 @@
//
// EXTNil.m
// extobjc
//
// Created by Justin Spahr-Summers on 2011-04-25.
// Released into the public domain.
//
#import "EXTNil.h"
#import "EXTRuntimeExtensions.h"
static id singleton = nil;
@implementation EXTNil
+ (void)initialize {
if (self == [EXTNil class]) {
if (!singleton)
singleton = [self alloc];
}
}
+ (EXTNil *)null {
return singleton;
}
- (id)init {
return self;
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone {
return self;
}
#pragma mark Forwarding machinery
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSUInteger returnLength = [[anInvocation methodSignature] methodReturnLength];
if (!returnLength) {
// nothing to do
return;
}
// set return value to all zero bits
char buffer[returnLength];
memset(buffer, 0, returnLength);
[anInvocation setReturnValue:buffer];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return ext_globalMethodSignatureForSelector(selector);
}
- (BOOL)respondsToSelector:(SEL)selector {
// behave like nil
return NO;
}
#pragma mark NSObject protocol
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return NO;
}
- (NSUInteger)hash {
return 0;
}
- (BOOL)isEqual:(id)obj {
return !obj || obj == self || [obj isEqual:[NSNull null]];
}
- (BOOL)isKindOfClass:(Class)class {
return [class isEqual:[EXTNil class]] || [class isEqual:[NSNull class]];
}
- (BOOL)isMemberOfClass:(Class)class {
return [class isEqual:[EXTNil class]] || [class isEqual:[NSNull class]];
}
- (BOOL)isProxy {
// not really a proxy -- we just inherit from NSProxy because it makes
// method signature lookup simpler
return NO;
}
@end

View File

@@ -1,372 +0,0 @@
//
// EXTRuntimeExtensions.h
// extobjc
//
// Created by Justin Spahr-Summers on 2011-03-05.
// Released into the public domain.
//
#import <objc/runtime.h>
/**
* A callback indicating that the given method failed to be added to the given
* class. The reason for the failure depends on the attempted task.
*/
typedef void (*ext_failedMethodCallback)(Class, Method);
/**
* Used with #ext_injectMethods to determine injection behavior.
*/
typedef enum {
/**
* Indicates that any existing methods on the destination class should be
* overwritten.
*/
ext_methodInjectionReplace = 0x00,
/**
* Avoid overwriting methods on the immediate destination class.
*/
ext_methodInjectionFailOnExisting = 0x01,
/**
* Avoid overriding methods implemented in any superclass of the destination
* class.
*/
ext_methodInjectionFailOnSuperclassExisting = 0x02,
/**
* Avoid overwriting methods implemented in the immediate destination class
* or any superclass. This is equivalent to
* <tt>ext_methodInjectionFailOnExisting | ext_methodInjectionFailOnSuperclassExisting</tt>.
*/
ext_methodInjectionFailOnAnyExisting = 0x03,
/**
* Ignore the \c +load class method. This does not affect instance method
* injection.
*/
ext_methodInjectionIgnoreLoad = 1U << 2,
/**
* Ignore the \c +initialize class method. This does not affect instance method
* injection.
*/
ext_methodInjectionIgnoreInitialize = 1U << 3
} ext_methodInjectionBehavior;
/**
* A mask for the overwriting behavior flags of #ext_methodInjectionBehavior.
*/
static const ext_methodInjectionBehavior ext_methodInjectionOverwriteBehaviorMask = 0x3;
/**
* Describes the memory management policy of a property.
*/
typedef enum {
/**
* The value is assigned.
*/
ext_propertyMemoryManagementPolicyAssign = 0,
/**
* The value is retained.
*/
ext_propertyMemoryManagementPolicyRetain,
/**
* The value is copied.
*/
ext_propertyMemoryManagementPolicyCopy
} ext_propertyMemoryManagementPolicy;
/**
* Describes the attributes and type information of a property.
*/
typedef struct {
/**
* Whether this property was declared with the \c readonly attribute.
*/
BOOL readonly;
/**
* Whether this property was declared with the \c nonatomic attribute.
*/
BOOL nonatomic;
/**
* Whether the property is a weak reference.
*/
BOOL weak;
/**
* Whether the property is eligible for garbage collection.
*/
BOOL canBeCollected;
/**
* Whether this property is defined with \c \@dynamic.
*/
BOOL dynamic;
/**
* The memory management policy for this property. This will always be
* #ext_propertyMemoryManagementPolicyAssign if #readonly is \c YES.
*/
ext_propertyMemoryManagementPolicy memoryManagementPolicy;
/**
* The selector for the getter of this property. This will reflect any
* custom \c getter= attribute provided in the property declaration, or the
* inferred getter name otherwise.
*/
SEL getter;
/**
* The selector for the setter of this property. This will reflect any
* custom \c setter= attribute provided in the property declaration, or the
* inferred setter name otherwise.
*
* @note If #readonly is \c YES, this value will represent what the setter
* \e would be, if the property were writable.
*/
SEL setter;
/**
* The backing instance variable for this property, or \c NULL if \c
* \c @synthesize was not used, and therefore no instance variable exists. This
* would also be the case if the property is implemented dynamically.
*/
const char *ivar;
/**
* If this property is defined as being an instance of a specific class,
* this will be the class object representing it.
*
* This will be \c nil if the property was defined as type \c id, if the
* property is not of an object type, or if the class could not be found at
* runtime.
*/
Class objectClass;
/**
* The type encoding for the value of this property. This is the type as it
* would be returned by the \c \@encode() directive.
*/
char type[];
} ext_propertyAttributes;
/**
* Iterates through the first \a count entries in \a methods and attempts to add
* each one to \a aClass. If a method by the same name already exists on \a
* aClass, it is \e not overridden. If \a checkSuperclasses is \c YES, and
* a method by the same name already exists on any superclass of \a aClass, it
* is not overridden.
*
* Returns the number of methods added successfully. For each method that fails
* to be added, \a failedToAddCallback (if provided) is invoked.
*/
unsigned ext_addMethods (Class aClass, Method *methods, unsigned count, BOOL checkSuperclasses, ext_failedMethodCallback failedToAddCallback);
/**
* Iterates through all instance and class methods of \a srcClass and attempts
* to add each one to \a dstClass. If a method by the same name already exists
* on \a aClass, it is \e not overridden. If \a checkSuperclasses is \c YES, and
* a method by the same name already exists on any superclass of \a aClass, it
* is not overridden.
*
* Returns whether all methods were added successfully. For each method that fails
* to be added, \a failedToAddCallback (if provided) is invoked.
*
* @note This ignores any \c +load method on \a srcClass. \a srcClass and \a
* dstClass must not be metaclasses.
*/
BOOL ext_addMethodsFromClass (Class srcClass, Class dstClass, BOOL checkSuperclasses, ext_failedMethodCallback failedToAddCallback);
/**
* Returns the superclass of \a receiver which immediately descends from \a
* superclass. If \a superclass is not in the hierarchy of \a receiver, or is
* equal to \a receiver, \c nil is returned.
*/
Class ext_classBeforeSuperclass (Class receiver, Class superclass);
/**
* Returns whether \a receiver is \a aClass, or inherits directly from it.
*/
BOOL ext_classIsKindOfClass (Class receiver, Class aClass);
/**
* Returns the full list of classes registered with the runtime, terminated with
* \c NULL. If \a count is not \c NULL, it is filled in with the total number of
* classes returned. You must \c free() the returned array.
*/
Class *ext_copyClassList (unsigned *count);
/**
* Looks through the complete list of classes registered with the runtime and
* finds all classes which conform to \a protocol. Returns \c *count classes
* terminated by a \c NULL. You must \c free() the returned array. If there are no
* classes conforming to \a protocol, \c NULL is returned.
*
* @note \a count may be \c NULL.
*/
Class *ext_copyClassListConformingToProtocol (Protocol *protocol, unsigned *count);
/**
* Returns a pointer to a structure containing information about \a property.
* You must \c free() the returned pointer. Returns \c NULL if there is an error
* obtaining information from \a property.
*/
ext_propertyAttributes *ext_copyPropertyAttributes (objc_property_t property);
/**
* Looks through the complete list of classes registered with the runtime and
* finds all classes which are descendant from \a aClass. Returns \c
* *subclassCount classes terminated by a \c NULL. You must \c free() the
* returned array. If there are no subclasses of \a aClass, \c NULL is
* returned.
*
* @note \a subclassCount may be \c NULL. \a aClass may be a metaclass to get
* all subclass metaclass objects.
*/
Class *ext_copySubclassList (Class aClass, unsigned *subclassCount);
/**
* Finds the instance method named \a aSelector on \a aClass and returns it, or
* returns \c NULL if no such instance method exists. Unlike \c
* class_getInstanceMethod(), this does not search superclasses.
*
* @note To get class methods in this manner, use a metaclass for \a aClass.
*/
Method ext_getImmediateInstanceMethod (Class aClass, SEL aSelector);
/**
* Returns the value of \c Ivar \a IVAR from instance \a OBJ. The instance
* variable must be of type \a TYPE, and is returned as such.
*
* @warning Depending on the platform, this may or may not work with aggregate
* or floating-point types.
*/
#define ext_getIvar(OBJ, IVAR, TYPE) \
((TYPE (*)(id, Ivar)object_getIvar)((OBJ), (IVAR)))
/**
* Returns the value of the instance variable identified by the string \a NAME
* from instance \a OBJ. The instance variable must be of type \a TYPE, and is
* returned as such.
*
* @note \a OBJ is evaluated twice.
*
* @warning Depending on the platform, this may or may not work with aggregate
* or floating-point types.
*/
#define ext_getIvarByName(OBJ, NAME, TYPE) \
ext_getIvar((OBJ), class_getInstanceVariable(object_getClass((OBJ)), (NAME)), TYPE)
/**
* Returns the accessor methods for \a property, as implemented in \a aClass or
* any of its superclasses. The getter, if implemented, is returned in \a
* getter, and the setter, if implemented, is returned in \a setter. If either
* \a getter or \a setter are \c NULL, that accessor is not returned. If either
* accessor is not implemented, the argument is left unmodified.
*
* Returns \c YES if a valid accessor was found, or \c NO if \a aClass and its
* superclasses do not implement \a property or if an error occurs.
*/
BOOL ext_getPropertyAccessorsForClass (objc_property_t property, Class aClass, Method *getter, Method *setter);
/**
* For all classes registered with the runtime, invokes \c
* methodSignatureForSelector: and \c instanceMethodSignatureForSelector: to
* determine a method signature for \a aSelector. If one or more valid
* signatures is found, the first one is returned. If no valid signatures were
* found, \c nil is returned.
*/
NSMethodSignature *ext_globalMethodSignatureForSelector (SEL aSelector);
/**
* Highly-configurable method injection. Adds the first \a count entries from \a
* methods into \a aClass according to \a behavior.
*
* Returns the number of methods added successfully. For each method that fails
* to be added, \a failedToAddCallback (if provided) is invoked.
*
* @note \c +load and \c +initialize methods are included in the number of
* successful methods when ignored for injection.
*/
unsigned ext_injectMethods (Class aClass, Method *methods, unsigned count, ext_methodInjectionBehavior behavior, ext_failedMethodCallback failedToAddCallback);
/**
* Invokes #ext_injectMethods with the instance methods and class methods from
* \a srcClass. #ext_methodInjectionIgnoreLoad is added to #behavior for class
* method injection.
*
* Returns whether all methods were added successfully. For each method that fails
* to be added, \a failedToAddCallback (if provided) is invoked.
*
* @note \c +load and \c +initialize methods are considered to be added
* successfully when ignored for injection.
*/
BOOL ext_injectMethodsFromClass (Class srcClass, Class dstClass, ext_methodInjectionBehavior behavior, ext_failedMethodCallback failedToAddCallback);
/**
* Loads a "special protocol" into an internal list. A special protocol is any
* protocol for which implementing classes need injection behavior (i.e., any
* class conforming to the protocol needs to be reflected upon). Returns \c NO
* if loading failed.
*
* Using this facility proceeds as follows:
*
* @li Each protocol is loaded with #ext_loadSpecialProtocol and a custom block
* that describes its injection behavior on each conforming class.
* @li Each protocol is marked as being ready for injection with
* #ext_specialProtocolReadyForInjection.
* @li The entire Objective-C class list is retrieved, and each special
* protocol's \a injectionBehavior block is run for all conforming classes.
*
* It is an error to call this function without later calling
* #ext_specialProtocolReadyForInjection as well.
*
* @note A special protocol X which conforms to another special protocol Y is
* always injected \e after Y.
*/
BOOL ext_loadSpecialProtocol (Protocol *protocol, void (^injectionBehavior)(Class destinationClass));
/**
* Marks a special protocol as being ready for injection. Injection is actually
* performed only after all special protocols have been marked in this way.
*
* @sa ext_loadSpecialProtocol
*/
void ext_specialProtocolReadyForInjection (Protocol *protocol);
/**
* "Removes" any instance method matching \a methodName from \a aClass. This
* removal can mean one of two things:
*
* @li If any superclass of \a aClass implements a method by the same name, the
* implementation of the closest such superclass is used.
* @li If no superclasses of \a aClass implement a method by the same name, the
* method is replaced with an implementation internal to the runtime, used for
* message forwarding.
*
* @warning Adding a method by the same name into a superclass of \a aClass \e
* after using this function may obscure it from the subclass.
*/
void ext_removeMethod (Class aClass, SEL methodName);
/**
* Iterates through the first \a count entries in \a methods and adds each one
* to \a aClass, replacing any existing implementation.
*/
void ext_replaceMethods (Class aClass, Method *methods, unsigned count);
/**
* Iterates through all instance and class methods of \a srcClass and adds each
* one to \a dstClass, replacing any existing implementation.
*
* @note This ignores any \c +load method on \a srcClass. \a srcClass and \a
* dstClass must not be metaclasses.
*/
void ext_replaceMethodsFromClass (Class srcClass, Class dstClass);

View File

@@ -1,932 +0,0 @@
//
// EXTRuntimeExtensions.m
// extobjc
//
// Created by Justin Spahr-Summers on 2011-03-05.
// Released into the public domain.
//
#import "EXTRuntimeExtensions.h"
#import <ctype.h>
#import <libkern/OSAtomic.h>
#import <objc/message.h>
#import <pthread.h>
#import <stdio.h>
#import <stdlib.h>
#import <string.h>
typedef NSMethodSignature *(*methodSignatureForSelectorIMP)(id, SEL, SEL);
typedef void (^ext_specialProtocolInjectionBlock)(Class);
// contains the information needed to reference a full special protocol
typedef struct {
// the actual protocol declaration (@protocol block)
__unsafe_unretained Protocol *protocol;
// the injection block associated with this protocol
//
// this block is RETAINED and must eventually be released by transferring it
// back to ARC
void *injectionBlock;
// whether this protocol is ready to be injected to its conforming classes
//
// this does NOT refer to a special protocol having been injected already
BOOL ready;
} EXTSpecialProtocol;
// the full list of special protocols (an array of EXTSpecialProtocol structs)
static EXTSpecialProtocol * restrict specialProtocols = NULL;
// the number of special protocols stored in the array
static size_t specialProtocolCount = 0;
// the total capacity of the array
// we use a doubling algorithm to amortize the cost of insertion, so this is
// generally going to be a power-of-two
static size_t specialProtocolCapacity = 0;
// the number of EXTSpecialProtocols which have been marked as ready for
// injection (though not necessary injected)
//
// in other words, the total count which have 'ready' set to YES
static size_t specialProtocolsReady = 0;
// a mutex is used to guard against multiple threads changing the above static
// variables
static pthread_mutex_t specialProtocolsLock = PTHREAD_MUTEX_INITIALIZER;
/**
* This function actually performs the hard work of special protocol injection.
* It obtains a full list of all classes registered with the Objective-C
* runtime, finds those conforming to special protocols, and then runs the
* injection blocks as appropriate.
*/
static void ext_injectSpecialProtocols (void) {
/*
* don't lock specialProtocolsLock in this function, as it is called only
* from public functions which already perform the synchronization
*/
/*
* This will sort special protocols in the order they should be loaded. If
* a special protocol conforms to another special protocol, the former
* will be prioritized above the latter.
*/
qsort_b(specialProtocols, specialProtocolCount, sizeof(EXTSpecialProtocol), ^(const void *a, const void *b){
// if the pointers are equal, it must be the same protocol
if (a == b)
return 0;
const EXTSpecialProtocol *protoA = a;
const EXTSpecialProtocol *protoB = b;
// A higher return value here means a higher priority
int (^protocolInjectionPriority)(const EXTSpecialProtocol *) = ^(const EXTSpecialProtocol *specialProtocol){
int runningTotal = 0;
for (size_t i = 0;i < specialProtocolCount;++i) {
// the pointer passed into this block is guaranteed to point
// into the 'specialProtocols' array, so we can compare the
// pointers directly for identity
if (specialProtocol == specialProtocols + i)
continue;
if (protocol_conformsToProtocol(specialProtocol->protocol, specialProtocols[i].protocol))
runningTotal++;
}
return runningTotal;
};
/*
* This will return:
* 0 if the protocols are equal in priority (such that load order does not matter)
* < 0 if A is more important than B
* > 0 if B is more important than A
*/
return protocolInjectionPriority(protoB) - protocolInjectionPriority(protoA);
});
unsigned classCount = 0;
Class *allClasses = ext_copyClassList(&classCount);
if (!classCount || !allClasses) {
fprintf(stderr, "ERROR: No classes registered with the runtime\n");
return;
}
/*
* set up an autorelease pool in case any Cocoa classes get used during
* the injection process or +initialize
*/
@autoreleasepool {
// loop through the special protocols, and apply each one to all the
// classes in turn
//
// ORDER IS IMPORTANT HERE: protocols have to be injected to all classes in
// the order in which they appear in specialProtocols. Consider classes
// X and Y that implement protocols A and B, respectively. B needs to get
// its implementation into Y before A gets into X.
for (size_t i = 0;i < specialProtocolCount;++i) {
Protocol *protocol = specialProtocols[i].protocol;
// transfer ownership of the injection block to ARC and remove it
// from the structure
ext_specialProtocolInjectionBlock injectionBlock = (__bridge_transfer id)specialProtocols[i].injectionBlock;
specialProtocols[i].injectionBlock = NULL;
// loop through all classes
for (unsigned classIndex = 0;classIndex < classCount;++classIndex) {
Class class = allClasses[classIndex];
// if this class doesn't conform to the protocol, continue to the
// next class immediately
if (!class_conformsToProtocol(class, protocol))
continue;
injectionBlock(class);
}
}
}
// free the allocated class list
free(allClasses);
// now that everything's injected, the special protocol list can also be
// destroyed
free(specialProtocols); specialProtocols = NULL;
specialProtocolCount = 0;
specialProtocolCapacity = 0;
specialProtocolsReady = 0;
}
unsigned ext_injectMethods (
Class aClass,
Method *methods,
unsigned count,
ext_methodInjectionBehavior behavior,
ext_failedMethodCallback failedToAddCallback
) {
unsigned successes = 0;
/*
* set up an autorelease pool in case any Cocoa classes invoke +initialize
* during this process
*/
@autoreleasepool {
BOOL isMeta = class_isMetaClass(aClass);
if (!isMeta) {
// clear any +load and +initialize ignore flags
behavior &= ~(ext_methodInjectionIgnoreLoad | ext_methodInjectionIgnoreInitialize);
}
for (unsigned methodIndex = 0;methodIndex < count;++methodIndex) {
Method method = methods[methodIndex];
SEL methodName = method_getName(method);
if (behavior & ext_methodInjectionIgnoreLoad) {
if (methodName == @selector(load)) {
++successes;
continue;
}
}
if (behavior & ext_methodInjectionIgnoreInitialize) {
if (methodName == @selector(initialize)) {
++successes;
continue;
}
}
BOOL success = YES;
IMP impl = method_getImplementation(method);
const char *type = method_getTypeEncoding(method);
switch (behavior & ext_methodInjectionOverwriteBehaviorMask) {
case ext_methodInjectionFailOnExisting:
success = class_addMethod(aClass, methodName, impl, type);
break;
case ext_methodInjectionFailOnAnyExisting:
if (class_getInstanceMethod(aClass, methodName)) {
success = NO;
break;
}
// else fall through
case ext_methodInjectionReplace:
class_replaceMethod(aClass, methodName, impl, type);
break;
case ext_methodInjectionFailOnSuperclassExisting:
{
Class superclass = class_getSuperclass(aClass);
if (superclass && class_getInstanceMethod(superclass, methodName))
success = NO;
else
class_replaceMethod(aClass, methodName, impl, type);
}
break;
default:
fprintf(stderr, "ERROR: Unrecognized method injection behavior: %i\n", (int)(behavior & ext_methodInjectionOverwriteBehaviorMask));
}
if (success)
++successes;
else
failedToAddCallback(aClass, method);
}
}
return successes;
}
BOOL ext_injectMethodsFromClass (
Class srcClass,
Class dstClass,
ext_methodInjectionBehavior behavior,
ext_failedMethodCallback failedToAddCallback)
{
unsigned count, addedCount;
BOOL success = YES;
count = 0;
Method *instanceMethods = class_copyMethodList(srcClass, &count);
addedCount = ext_injectMethods(
dstClass,
instanceMethods,
count,
behavior,
failedToAddCallback
);
free(instanceMethods);
if (addedCount < count)
success = NO;
count = 0;
Method *classMethods = class_copyMethodList(object_getClass(srcClass), &count);
// ignore +load
behavior |= ext_methodInjectionIgnoreLoad;
addedCount = ext_injectMethods(
object_getClass(dstClass),
classMethods,
count,
behavior,
failedToAddCallback
);
free(classMethods);
if (addedCount < count)
success = NO;
return success;
}
Class ext_classBeforeSuperclass (Class receiver, Class superclass) {
Class previousClass = nil;
while (![receiver isEqual:superclass]) {
previousClass = receiver;
receiver = class_getSuperclass(receiver);
}
return previousClass;
}
Class *ext_copyClassList (unsigned *count) {
// get the number of classes registered with the runtime
int classCount = objc_getClassList(NULL, 0);
if (!classCount) {
if (count)
*count = 0;
return NULL;
}
// allocate space for them plus NULL
Class *allClasses = (Class *)malloc(sizeof(Class) * (classCount + 1));
if (!allClasses) {
fprintf(stderr, "ERROR: Could allocate memory for all classes\n");
if (count)
*count = 0;
return NULL;
}
// and then actually pull the list of the class objects
classCount = objc_getClassList(allClasses, classCount);
allClasses[classCount] = NULL;
@autoreleasepool {
// weed out classes that do weird things when reflected upon
for (int i = 0;i < classCount;) {
Class class = allClasses[i];
BOOL keep = YES;
if (keep)
keep &= class_respondsToSelector(class, @selector(methodSignatureForSelector:));
if (keep) {
if (class_respondsToSelector(class, @selector(isProxy)))
keep &= ![class isProxy];
}
if (!keep) {
if (--classCount > i) {
memmove(allClasses + i, allClasses + i + 1, (classCount - i) * sizeof(*allClasses));
}
continue;
}
++i;
}
}
if (count)
*count = (unsigned)classCount;
return allClasses;
}
unsigned ext_addMethods (Class aClass, Method *methods, unsigned count, BOOL checkSuperclasses, ext_failedMethodCallback failedToAddCallback) {
ext_methodInjectionBehavior behavior = ext_methodInjectionFailOnExisting;
if (checkSuperclasses)
behavior |= ext_methodInjectionFailOnSuperclassExisting;
return ext_injectMethods(
aClass,
methods,
count,
behavior,
failedToAddCallback
);
}
BOOL ext_addMethodsFromClass (Class srcClass, Class dstClass, BOOL checkSuperclasses, ext_failedMethodCallback failedToAddCallback) {
ext_methodInjectionBehavior behavior = ext_methodInjectionFailOnExisting;
if (checkSuperclasses)
behavior |= ext_methodInjectionFailOnSuperclassExisting;
return ext_injectMethodsFromClass(srcClass, dstClass, behavior, failedToAddCallback);
}
BOOL ext_classIsKindOfClass (Class receiver, Class aClass) {
while (receiver) {
if (receiver == aClass)
return YES;
receiver = class_getSuperclass(receiver);
}
return NO;
}
Class *ext_copyClassListConformingToProtocol (Protocol *protocol, unsigned *count) {
Class *allClasses;
/*
* set up an autorelease pool in case any Cocoa classes invoke +initialize
* during this process
*/
@autoreleasepool {
unsigned classCount = 0;
allClasses = ext_copyClassList(&classCount);
// we're going to reuse allClasses for the return value, so returnIndex will
// keep track of the indices we replace with new values
unsigned returnIndex = 0;
for (unsigned classIndex = 0;classIndex < classCount;++classIndex) {
Class cls = allClasses[classIndex];
if (class_conformsToProtocol(cls, protocol))
allClasses[returnIndex++] = cls;
}
allClasses[returnIndex] = NULL;
if (count)
*count = returnIndex;
}
return allClasses;
}
ext_propertyAttributes *ext_copyPropertyAttributes (objc_property_t property) {
const char * const attrString = property_getAttributes(property);
if (!attrString) {
fprintf(stderr, "ERROR: Could not get attribute string from property %s\n", property_getName(property));
return NULL;
}
if (attrString[0] != 'T') {
fprintf(stderr, "ERROR: Expected attribute string \"%s\" for property %s to start with 'T'\n", attrString, property_getName(property));
return NULL;
}
const char *typeString = attrString + 1;
const char *next = NSGetSizeAndAlignment(typeString, NULL, NULL);
if (!next) {
fprintf(stderr, "ERROR: Could not read past type in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
return NULL;
}
size_t typeLength = next - typeString;
if (!typeLength) {
fprintf(stderr, "ERROR: Invalid type in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
return NULL;
}
// allocate enough space for the structure and the type string (plus a NUL)
ext_propertyAttributes *attributes = calloc(1, sizeof(ext_propertyAttributes) + typeLength + 1);
if (!attributes) {
fprintf(stderr, "ERROR: Could not allocate ext_propertyAttributes structure for attribute string \"%s\" for property %s\n", attrString, property_getName(property));
return NULL;
}
// copy the type string
strncpy(attributes->type, typeString, typeLength);
attributes->type[typeLength] = '\0';
// if this is an object type, and immediately followed by a quoted string...
if (*typeString == *(@encode(id)) && *next == '"') {
// we should be able to extract a class name
const char *className = next + 1;
next = strchr(className, '"');
if (!next) {
fprintf(stderr, "ERROR: Could not read class name in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
return NULL;
}
if (className != next) {
size_t classNameLength = next - className;
char trimmedName[classNameLength];
strncpy(trimmedName, className, classNameLength);
trimmedName[classNameLength] = '\0';
// attempt to look up the class in the runtime
attributes->objectClass = objc_getClass(trimmedName);
}
}
if (*next != '\0') {
// skip past any junk before the first flag
next = strchr(next, ',');
}
while (next && *next == ',') {
char flag = next[1];
next += 2;
switch (flag) {
case '\0':
break;
case 'R':
attributes->readonly = YES;
break;
case 'C':
attributes->memoryManagementPolicy = ext_propertyMemoryManagementPolicyCopy;
break;
case '&':
attributes->memoryManagementPolicy = ext_propertyMemoryManagementPolicyRetain;
break;
case 'N':
attributes->nonatomic = YES;
break;
case 'G':
case 'S':
{
const char *nextFlag = strchr(next, ',');
SEL name = NULL;
if (!nextFlag) {
// assume that the rest of the string is the selector
const char *selectorString = next;
next = "";
name = sel_registerName(selectorString);
} else {
size_t selectorLength = nextFlag - next;
if (!selectorLength) {
fprintf(stderr, "ERROR: Found zero length selector name in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
goto errorOut;
}
char selectorString[selectorLength + 1];
strncpy(selectorString, next, selectorLength);
selectorString[selectorLength] = '\0';
name = sel_registerName(selectorString);
next = nextFlag;
}
if (flag == 'G')
attributes->getter = name;
else
attributes->setter = name;
}
break;
case 'D':
attributes->dynamic = YES;
attributes->ivar = NULL;
break;
case 'V':
// assume that the rest of the string (if present) is the ivar name
if (*next == '\0') {
// if there's nothing there, let's assume this is dynamic
attributes->ivar = NULL;
} else {
attributes->ivar = next;
next = "";
}
break;
case 'W':
attributes->weak = YES;
break;
case 'P':
attributes->canBeCollected = YES;
break;
case 't':
fprintf(stderr, "ERROR: Old-style type encoding is unsupported in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
// skip over this type encoding
while (*next != ',' && *next != '\0')
++next;
break;
default:
fprintf(stderr, "ERROR: Unrecognized attribute string flag '%c' in attribute string \"%s\" for property %s\n", flag, attrString, property_getName(property));
}
}
if (next && *next != '\0') {
fprintf(stderr, "Warning: Unparsed data \"%s\" in attribute string \"%s\" for property %s\n", next, attrString, property_getName(property));
}
if (!attributes->getter) {
// use the property name as the getter by default
attributes->getter = sel_registerName(property_getName(property));
}
if (!attributes->setter) {
const char *propertyName = property_getName(property);
size_t propertyNameLength = strlen(propertyName);
// we want to transform the name to setProperty: style
size_t setterLength = propertyNameLength + 4;
char setterName[setterLength + 1];
strncpy(setterName, "set", 3);
strncpy(setterName + 3, propertyName, propertyNameLength);
// capitalize property name for the setter
setterName[3] = (char)toupper(setterName[3]);
setterName[setterLength - 1] = ':';
setterName[setterLength] = '\0';
attributes->setter = sel_registerName(setterName);
}
return attributes;
errorOut:
free(attributes);
return NULL;
}
Class *ext_copySubclassList (Class targetClass, unsigned *subclassCount) {
unsigned classCount = 0;
Class *allClasses = ext_copyClassList(&classCount);
if (!allClasses || !classCount) {
fprintf(stderr, "ERROR: No classes registered with the runtime, cannot find %s!\n", class_getName(targetClass));
return NULL;
}
// we're going to reuse allClasses for the return value, so returnIndex will
// keep track of the indices we replace with new values
unsigned returnIndex = 0;
BOOL isMeta = class_isMetaClass(targetClass);
for (unsigned classIndex = 0;classIndex < classCount;++classIndex) {
Class cls = allClasses[classIndex];
Class superclass = class_getSuperclass(cls);
while (superclass != NULL) {
if (isMeta) {
if (object_getClass(superclass) == targetClass)
break;
} else if (superclass == targetClass)
break;
superclass = class_getSuperclass(superclass);
}
if (!superclass)
continue;
// at this point, 'cls' is definitively a subclass of targetClass
if (isMeta)
cls = object_getClass(cls);
allClasses[returnIndex++] = cls;
}
allClasses[returnIndex] = NULL;
if (subclassCount)
*subclassCount = returnIndex;
return allClasses;
}
Method ext_getImmediateInstanceMethod (Class aClass, SEL aSelector) {
unsigned methodCount = 0;
Method *methods = class_copyMethodList(aClass, &methodCount);
Method foundMethod = NULL;
for (unsigned methodIndex = 0;methodIndex < methodCount;++methodIndex) {
if (method_getName(methods[methodIndex]) == aSelector) {
foundMethod = methods[methodIndex];
break;
}
}
free(methods);
return foundMethod;
}
BOOL ext_getPropertyAccessorsForClass (objc_property_t property, Class aClass, Method *getter, Method *setter) {
ext_propertyAttributes *attributes = ext_copyPropertyAttributes(property);
if (!attributes)
return NO;
SEL getterName = attributes->getter;
SEL setterName = attributes->setter;
free(attributes);
attributes = NO;
/*
* set up an autorelease pool in case this sends aClass its first message
*/
@autoreleasepool {
Method foundGetter = class_getInstanceMethod(aClass, getterName);
if (!foundGetter) {
return NO;
}
if (getter)
*getter = foundGetter;
if (setter) {
Method foundSetter = class_getInstanceMethod(aClass, setterName);
if (foundSetter)
*setter = foundSetter;
}
}
return YES;
}
NSMethodSignature *ext_globalMethodSignatureForSelector (SEL aSelector) {
// set up a simplistic cache to avoid repeatedly scouring every class in the
// runtime
static const size_t selectorCacheLength = 1 << 8;
static const uintptr_t selectorCacheMask = (selectorCacheLength - 1);
static void * volatile selectorCache[selectorCacheLength];
const char *cachedType = selectorCache[(uintptr_t)aSelector & selectorCacheMask];
if (cachedType) {
return [NSMethodSignature signatureWithObjCTypes:cachedType];
}
unsigned classCount = 0;
Class *classes = ext_copyClassList(&classCount);
if (!classes)
return nil;
NSMethodSignature *signature = nil;
/*
* set up an autorelease pool in case any Cocoa classes invoke +initialize
* during this process
*/
@autoreleasepool {
for (unsigned i = 0;i < classCount;++i) {
Class cls = classes[i];
Method method;
method = class_getInstanceMethod(cls, aSelector);
if (!method)
method = class_getClassMethod(cls, aSelector);
if (method) {
const char *type = method_getTypeEncoding(method);
uintptr_t cacheLocation = ((uintptr_t)aSelector & selectorCacheMask);
// this doesn't need to be a barrier, and we don't care whether
// it succeeds, since our only goal is to make things faster in
// the future
OSAtomicCompareAndSwapPtr(selectorCache[cacheLocation], (void *)type, selectorCache + cacheLocation);
signature = [NSMethodSignature signatureWithObjCTypes:type];
break;
}
}
}
free(classes);
return signature;
}
BOOL ext_loadSpecialProtocol (Protocol *protocol, void (^injectionBehavior)(Class destinationClass)) {
@autoreleasepool {
NSCParameterAssert(protocol != nil);
NSCParameterAssert(injectionBehavior != nil);
// lock the mutex to prevent accesses from other threads while we perform
// this work
if (pthread_mutex_lock(&specialProtocolsLock) != 0) {
fprintf(stderr, "ERROR: Could not synchronize on special protocol data\n");
return NO;
}
// if we've hit the hard maximum for number of special protocols, we can't
// continue
if (specialProtocolCount == SIZE_MAX) {
pthread_mutex_unlock(&specialProtocolsLock);
return NO;
}
// if the array has no more space, we will need to allocate additional
// entries
if (specialProtocolCount >= specialProtocolCapacity) {
size_t newCapacity;
if (specialProtocolCapacity == 0)
// if there are no entries, make space for just one
newCapacity = 1;
else {
// otherwise, double the current capacity
newCapacity = specialProtocolCapacity << 1;
// if the new capacity is less than the current capacity, that's
// unsigned integer overflow
if (newCapacity < specialProtocolCapacity) {
// set it to the maximum possible instead
newCapacity = SIZE_MAX;
// if the new capacity is still not greater than the current
// (for instance, if it was already SIZE_MAX), we can't continue
if (newCapacity <= specialProtocolCapacity) {
pthread_mutex_unlock(&specialProtocolsLock);
return NO;
}
}
}
// we have a new capacity, so resize the list of all special protocols
// to add the new entries
void * restrict ptr = realloc(specialProtocols, sizeof(*specialProtocols) * newCapacity);
if (!ptr) {
// the allocation failed, abort
pthread_mutex_unlock(&specialProtocolsLock);
return NO;
}
specialProtocols = ptr;
specialProtocolCapacity = newCapacity;
}
// at this point, there absolutely must be at least one empty entry in the
// array
assert(specialProtocolCount < specialProtocolCapacity);
// disable warning about "leaking" this block, which is released in
// ext_injectSpecialProtocols()
#ifndef __clang_analyzer__
ext_specialProtocolInjectionBlock copiedBlock = [injectionBehavior copy];
// construct a new EXTSpecialProtocol structure and add it to the first
// empty space in the array
specialProtocols[specialProtocolCount] = (EXTSpecialProtocol){
.protocol = protocol,
.injectionBlock = (__bridge_retained void *)copiedBlock,
.ready = NO
};
#endif
++specialProtocolCount;
pthread_mutex_unlock(&specialProtocolsLock);
}
// success!
return YES;
}
void ext_specialProtocolReadyForInjection (Protocol *protocol) {
@autoreleasepool {
NSCParameterAssert(protocol != nil);
// lock the mutex to prevent accesses from other threads while we perform
// this work
if (pthread_mutex_lock(&specialProtocolsLock) != 0) {
fprintf(stderr, "ERROR: Could not synchronize on special protocol data\n");
return;
}
// loop through all the special protocols in our list, trying to find the
// one associated with 'protocol'
for (size_t i = 0;i < specialProtocolCount;++i) {
if (specialProtocols[i].protocol == protocol) {
// found the matching special protocol, check to see if it's
// already ready
if (!specialProtocols[i].ready) {
// if it's not, mark it as being ready now
specialProtocols[i].ready = YES;
// since this special protocol was in our array, and it was not
// loaded, the total number of protocols loaded must be less
// than the total count at this point in time
assert(specialProtocolsReady < specialProtocolCount);
// ... and then increment the total number of special protocols
// loaded  if it now matches the total count of special
// protocols, begin the injection process
if (++specialProtocolsReady == specialProtocolCount)
ext_injectSpecialProtocols();
}
break;
}
}
pthread_mutex_unlock(&specialProtocolsLock);
}
}
void ext_removeMethod (Class aClass, SEL methodName) {
Method existingMethod = ext_getImmediateInstanceMethod(aClass, methodName);
if (!existingMethod) {
return;
}
/*
* set up an autorelease pool in case any Cocoa classes invoke +initialize
* during this process
*/
@autoreleasepool {
Method superclassMethod = NULL;
Class superclass = class_getSuperclass(aClass);
if (superclass)
superclassMethod = class_getInstanceMethod(superclass, methodName);
if (superclassMethod) {
method_setImplementation(existingMethod, method_getImplementation(superclassMethod));
} else {
// since we now know that the method doesn't exist on any
// superclass, get an IMP internal to the runtime for message forwarding
IMP forward = class_getMethodImplementation(superclass, methodName);
method_setImplementation(existingMethod, forward);
}
}
}
void ext_replaceMethods (Class aClass, Method *methods, unsigned count) {
ext_injectMethods(
aClass,
methods,
count,
ext_methodInjectionReplace,
NULL
);
}
void ext_replaceMethodsFromClass (Class srcClass, Class dstClass) {
ext_injectMethodsFromClass(srcClass, dstClass, ext_methodInjectionReplace, NULL);
}