diff --git a/submodules/SSignalKit/.gitignore b/submodules/SSignalKit/.gitignore
new file mode 100644
index 0000000000..3d0bd32fb5
--- /dev/null
+++ b/submodules/SSignalKit/.gitignore
@@ -0,0 +1,25 @@
+fastlane/README.md
+fastlane/report.xml
+fastlane/test_output/*
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata
+*.xccheckout
+*.xcscmblueprint
+*.moved-aside
+DerivedData
+*.hmap
+*.ipa
+*.xcuserstate
+.DS_Store
+*.dSYM
+*.dSYM.zip
+*.ipa
+*/xcuserdata/*
+SSignalKit.xcodeproj/*
diff --git a/submodules/SSignalKit/BUCK b/submodules/SSignalKit/BUCK
new file mode 100644
index 0000000000..415648422d
--- /dev/null
+++ b/submodules/SSignalKit/BUCK
@@ -0,0 +1,37 @@
+load('//tools:buck_utils.bzl', 'config_with_updated_linker_flags', 'configs_with_config', 'combined_config')
+load('//tools:buck_defs.bzl', 'SHARED_CONFIGS', 'EXTENSION_LIB_SPECIFIC_CONFIG')
+
+apple_library(
+ name = 'SwiftSignalKit',
+ srcs = glob([
+ 'SwiftSignalKit/*.swift'
+ ]),
+ configs = configs_with_config(combined_config([SHARED_CONFIGS, EXTENSION_LIB_SPECIFIC_CONFIG])),
+ modular = True,
+ visibility = ['PUBLIC'],
+ frameworks = [
+ '$SDKROOT/System/Library/Frameworks/Foundation.framework',
+ ],
+)
+
+apple_library(
+ name = 'SSignalKit',
+ srcs = glob([
+ 'SSignalKit/*.m',
+ ]),
+ configs = configs_with_config(combined_config([SHARED_CONFIGS, EXTENSION_LIB_SPECIFIC_CONFIG])),
+ headers = glob([
+ 'SSignalKit/*.h',
+ ]),
+ header_namespace = 'SSignalKit',
+ exported_headers = glob([
+ 'SSignalKit/*.h',
+ ]),
+ modular = True,
+ compiler_flags = ['-w'],
+ preprocessor_flags = ['-fobjc-arc'],
+ visibility = ['PUBLIC'],
+ frameworks = [
+ '$SDKROOT/System/Library/Frameworks/Foundation.framework',
+ ],
+)
diff --git a/submodules/SSignalKit/SSignalKit/Info.plist b/submodules/SSignalKit/SSignalKit/Info.plist
new file mode 100644
index 0000000000..d3de8eefb6
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/Info.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSPrincipalClass
+
+
+
diff --git a/submodules/SSignalKit/SSignalKit/SAtomic.h b/submodules/SSignalKit/SSignalKit/SAtomic.h
new file mode 100644
index 0000000000..6d3e35b4dc
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SAtomic.h
@@ -0,0 +1,12 @@
+#import
+
+@interface SAtomic : NSObject
+
+- (instancetype)initWithValue:(id)value;
+- (instancetype)initWithValue:(id)value recursive:(bool)recursive;
+- (id)swap:(id)newValue;
+- (id)value;
+- (id)modify:(id (^)(id))f;
+- (id)with:(id (^)(id))f;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SAtomic.m b/submodules/SSignalKit/SSignalKit/SAtomic.m
new file mode 100644
index 0000000000..5f7f3d1032
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SAtomic.m
@@ -0,0 +1,93 @@
+#import "SAtomic.h"
+
+#import
+
+@interface SAtomic ()
+{
+ pthread_mutex_t _lock;
+ pthread_mutexattr_t _attr;
+ bool _isRecursive;
+ id _value;
+}
+
+@end
+
+@implementation SAtomic
+
+- (instancetype)initWithValue:(id)value
+{
+ self = [super init];
+ if (self != nil)
+ {
+ pthread_mutex_init(&_lock, NULL);
+ _value = value;
+ }
+ return self;
+}
+
+- (instancetype)initWithValue:(id)value recursive:(bool)recursive {
+ self = [super init];
+ if (self != nil)
+ {
+ _isRecursive = recursive;
+
+ if (recursive) {
+ pthread_mutexattr_init(&_attr);
+ pthread_mutexattr_settype(&_attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&_lock, &_attr);
+ } else {
+ pthread_mutex_init(&_lock, NULL);
+ }
+
+ _value = value;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ if (_isRecursive) {
+ pthread_mutexattr_destroy(&_attr);
+ }
+ pthread_mutex_destroy(&_lock);
+}
+
+- (id)swap:(id)newValue
+{
+ id previousValue = nil;
+ pthread_mutex_lock(&_lock);
+ previousValue = _value;
+ _value = newValue;
+ pthread_mutex_unlock(&_lock);
+ return previousValue;
+}
+
+- (id)value
+{
+ id previousValue = nil;
+ pthread_mutex_lock(&_lock);
+ previousValue = _value;
+ pthread_mutex_unlock(&_lock);
+
+ return previousValue;
+}
+
+- (id)modify:(id (^)(id))f
+{
+ id newValue = nil;
+ pthread_mutex_lock(&_lock);
+ newValue = f(_value);
+ _value = newValue;
+ pthread_mutex_unlock(&_lock);
+ return newValue;
+}
+
+- (id)with:(id (^)(id))f
+{
+ id result = nil;
+ pthread_mutex_lock(&_lock);
+ result = f(_value);
+ pthread_mutex_unlock(&_lock);
+ return result;
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SBag.h b/submodules/SSignalKit/SSignalKit/SBag.h
new file mode 100644
index 0000000000..7b79ead30f
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SBag.h
@@ -0,0 +1,11 @@
+#import
+
+@interface SBag : NSObject
+
+- (NSInteger)addItem:(id)item;
+- (void)enumerateItems:(void (^)(id))block;
+- (void)removeItem:(NSInteger)key;
+- (bool)isEmpty;
+- (NSArray *)copyItems;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SBag.m b/submodules/SSignalKit/SSignalKit/SBag.m
new file mode 100644
index 0000000000..c83f206235
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SBag.m
@@ -0,0 +1,74 @@
+#import "SBag.h"
+
+@interface SBag ()
+{
+ NSInteger _nextKey;
+ NSMutableArray *_items;
+ NSMutableArray *_itemKeys;
+}
+
+@end
+
+@implementation SBag
+
+- (instancetype)init
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _items = [[NSMutableArray alloc] init];
+ _itemKeys = [[NSMutableArray alloc] init];
+ }
+ return self;
+}
+
+- (NSInteger)addItem:(id)item
+{
+ if (item == nil)
+ return -1;
+
+ NSInteger key = _nextKey;
+ [_items addObject:item];
+ [_itemKeys addObject:@(key)];
+ _nextKey++;
+
+ return key;
+}
+
+- (void)enumerateItems:(void (^)(id))block
+{
+ if (block)
+ {
+ for (id item in _items)
+ {
+ block(item);
+ }
+ }
+}
+
+- (void)removeItem:(NSInteger)key
+{
+ NSUInteger index = 0;
+ for (NSNumber *itemKey in _itemKeys)
+ {
+ if ([itemKey integerValue] == key)
+ {
+ [_items removeObjectAtIndex:index];
+ [_itemKeys removeObjectAtIndex:index];
+ break;
+ }
+ index++;
+ }
+}
+
+- (bool)isEmpty
+{
+ return _items.count == 0;
+}
+
+- (NSArray *)copyItems
+{
+ return [[NSArray alloc] initWithArray:_items];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SBlockDisposable.h b/submodules/SSignalKit/SSignalKit/SBlockDisposable.h
new file mode 100644
index 0000000000..2914604cba
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SBlockDisposable.h
@@ -0,0 +1,7 @@
+#import
+
+@interface SBlockDisposable : NSObject
+
+- (instancetype)initWithBlock:(void (^)())block;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SBlockDisposable.m b/submodules/SSignalKit/SSignalKit/SBlockDisposable.m
new file mode 100644
index 0000000000..b8d1f989d4
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SBlockDisposable.m
@@ -0,0 +1,58 @@
+#import "SBlockDisposable.h"
+
+#import
+#import
+
+@interface SBlockDisposable ()
+{
+ void *_block;
+}
+
+@end
+
+@implementation SBlockDisposable
+
+- (instancetype)initWithBlock:(void (^)())block
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _block = (__bridge_retained void *)[block copy];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ void *block = _block;
+ if (block != NULL)
+ {
+ if (OSAtomicCompareAndSwapPtr(block, 0, &_block))
+ {
+ if (block != nil)
+ {
+ __strong id strongBlock = (__bridge_transfer id)block;
+ strongBlock = nil;
+ }
+ }
+ }
+}
+
+- (void)dispose
+{
+ void *block = _block;
+ if (block != NULL)
+ {
+ if (OSAtomicCompareAndSwapPtr(block, 0, &_block))
+ {
+ if (block != nil)
+ {
+ __strong id strongBlock = (__bridge_transfer id)block;
+ ((dispatch_block_t)strongBlock)();
+ strongBlock = nil;
+ }
+ }
+ }
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SDisposable.h b/submodules/SSignalKit/SSignalKit/SDisposable.h
new file mode 100644
index 0000000000..49d9762dae
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SDisposable.h
@@ -0,0 +1,7 @@
+#import
+
+@protocol SDisposable
+
+- (void)dispose;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SDisposableSet.h b/submodules/SSignalKit/SSignalKit/SDisposableSet.h
new file mode 100644
index 0000000000..7d7515c968
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SDisposableSet.h
@@ -0,0 +1,10 @@
+#import
+
+@class SSignal;
+
+@interface SDisposableSet : NSObject
+
+- (void)add:(id)disposable;
+- (void)remove:(id)disposable;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SDisposableSet.m b/submodules/SSignalKit/SSignalKit/SDisposableSet.m
new file mode 100644
index 0000000000..18cb3be965
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SDisposableSet.m
@@ -0,0 +1,95 @@
+#import "SDisposableSet.h"
+
+#import "SSignal.h"
+
+#import
+
+@interface SDisposableSet ()
+{
+ OSSpinLock _lock;
+ bool _disposed;
+ id _singleDisposable;
+ NSArray *_multipleDisposables;
+}
+
+@end
+
+@implementation SDisposableSet
+
+- (void)add:(id)disposable
+{
+ if (disposable == nil)
+ return;
+
+ bool dispose = false;
+
+ OSSpinLockLock(&_lock);
+ dispose = _disposed;
+ if (!dispose)
+ {
+ if (_multipleDisposables != nil)
+ {
+ NSMutableArray *multipleDisposables = [[NSMutableArray alloc] initWithArray:_multipleDisposables];
+ [multipleDisposables addObject:disposable];
+ _multipleDisposables = multipleDisposables;
+ }
+ else if (_singleDisposable != nil)
+ {
+ NSMutableArray *multipleDisposables = [[NSMutableArray alloc] initWithObjects:_singleDisposable, disposable, nil];
+ _multipleDisposables = multipleDisposables;
+ _singleDisposable = nil;
+ }
+ else
+ {
+ _singleDisposable = disposable;
+ }
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (dispose)
+ [disposable dispose];
+}
+
+- (void)remove:(id)disposable {
+ OSSpinLockLock(&_lock);
+ if (_multipleDisposables != nil)
+ {
+ NSMutableArray *multipleDisposables = [[NSMutableArray alloc] initWithArray:_multipleDisposables];
+ [multipleDisposables removeObject:disposable];
+ _multipleDisposables = multipleDisposables;
+ }
+ else if (_singleDisposable == disposable)
+ {
+ _singleDisposable = nil;
+ }
+ OSSpinLockUnlock(&_lock);
+}
+
+- (void)dispose
+{
+ id singleDisposable = nil;
+ NSArray *multipleDisposables = nil;
+
+ OSSpinLockLock(&_lock);
+ if (!_disposed)
+ {
+ _disposed = true;
+ singleDisposable = _singleDisposable;
+ multipleDisposables = _multipleDisposables;
+ _singleDisposable = nil;
+ _multipleDisposables = nil;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (singleDisposable != nil)
+ [singleDisposable dispose];
+ if (multipleDisposables != nil)
+ {
+ for (id disposable in multipleDisposables)
+ {
+ [disposable dispose];
+ }
+ }
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SMetaDisposable.h b/submodules/SSignalKit/SSignalKit/SMetaDisposable.h
new file mode 100644
index 0000000000..8938f9eacb
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SMetaDisposable.h
@@ -0,0 +1,7 @@
+#import
+
+@interface SMetaDisposable : NSObject
+
+- (void)setDisposable:(id)disposable;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SMetaDisposable.m b/submodules/SSignalKit/SSignalKit/SMetaDisposable.m
new file mode 100644
index 0000000000..4e9c8e4fab
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SMetaDisposable.m
@@ -0,0 +1,53 @@
+#import "SMetaDisposable.h"
+
+#import
+
+@interface SMetaDisposable ()
+{
+ OSSpinLock _lock;
+ bool _disposed;
+ id _disposable;
+}
+
+@end
+
+@implementation SMetaDisposable
+
+- (void)setDisposable:(id)disposable
+{
+ id previousDisposable = nil;
+ bool dispose = false;
+
+ OSSpinLockLock(&_lock);
+ dispose = _disposed;
+ if (!dispose)
+ {
+ previousDisposable = _disposable;
+ _disposable = disposable;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (previousDisposable != nil)
+ [previousDisposable dispose];
+
+ if (dispose)
+ [disposable dispose];
+}
+
+- (void)dispose
+{
+ id disposable = nil;
+
+ OSSpinLockLock(&_lock);
+ if (!_disposed)
+ {
+ disposable = _disposable;
+ _disposed = true;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (disposable != nil)
+ [disposable dispose];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SMulticastSignalManager.h b/submodules/SSignalKit/SSignalKit/SMulticastSignalManager.h
new file mode 100644
index 0000000000..243f015a52
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SMulticastSignalManager.h
@@ -0,0 +1,11 @@
+#import
+
+@interface SMulticastSignalManager : NSObject
+
+- (SSignal *)multicastedSignalForKey:(NSString *)key producer:(SSignal *(^)())producer;
+- (void)startStandaloneSignalIfNotRunningForKey:(NSString *)key producer:(SSignal *(^)())producer;
+
+- (SSignal *)multicastedPipeForKey:(NSString *)key;
+- (void)putNext:(id)next toMulticastedPipeForKey:(NSString *)key;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SMulticastSignalManager.m b/submodules/SSignalKit/SSignalKit/SMulticastSignalManager.m
new file mode 100644
index 0000000000..1094852dd5
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SMulticastSignalManager.m
@@ -0,0 +1,171 @@
+#import "SMulticastSignalManager.h"
+
+#import "SSignal+Multicast.h"
+#import "SSignal+SideEffects.h"
+#import "SBag.h"
+#import "SMetaDisposable.h"
+#import "SBlockDisposable.h"
+
+#import
+
+@interface SMulticastSignalManager ()
+{
+ OSSpinLock _lock;
+ NSMutableDictionary *_multicastSignals;
+ NSMutableDictionary *_standaloneSignalDisposables;
+ NSMutableDictionary *_pipeListeners;
+}
+
+@end
+
+@implementation SMulticastSignalManager
+
+- (instancetype)init
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _multicastSignals = [[NSMutableDictionary alloc] init];
+ _standaloneSignalDisposables = [[NSMutableDictionary alloc] init];
+ _pipeListeners = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ NSArray *disposables = nil;
+ OSSpinLockLock(&_lock);
+ disposables = [_standaloneSignalDisposables allValues];
+ OSSpinLockUnlock(&_lock);
+
+ for (id disposable in disposables)
+ {
+ [disposable dispose];
+ }
+}
+
+- (SSignal *)multicastedSignalForKey:(NSString *)key producer:(SSignal *(^)())producer
+{
+ if (key == nil)
+ {
+ if (producer)
+ return producer();
+ else
+ return nil;
+ }
+
+ SSignal *signal = nil;
+ OSSpinLockLock(&_lock);
+ signal = _multicastSignals[key];
+ if (signal == nil)
+ {
+ __weak SMulticastSignalManager *weakSelf = self;
+ if (producer)
+ signal = producer();
+ if (signal != nil)
+ {
+ signal = [[signal onDispose:^
+ {
+ __strong SMulticastSignalManager *strongSelf = weakSelf;
+ if (strongSelf != nil)
+ {
+ OSSpinLockLock(&strongSelf->_lock);
+ [strongSelf->_multicastSignals removeObjectForKey:key];
+ OSSpinLockUnlock(&strongSelf->_lock);
+ }
+ }] multicast];
+ _multicastSignals[key] = signal;
+ }
+ }
+ OSSpinLockUnlock(&_lock);
+
+ return signal;
+}
+
+- (void)startStandaloneSignalIfNotRunningForKey:(NSString *)key producer:(SSignal *(^)())producer
+{
+ if (key == nil)
+ return;
+
+ bool produce = false;
+ OSSpinLockLock(&_lock);
+ if (_standaloneSignalDisposables[key] == nil)
+ {
+ _standaloneSignalDisposables[key] = [[SMetaDisposable alloc] init];
+ produce = true;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (produce)
+ {
+ __weak SMulticastSignalManager *weakSelf = self;
+ id disposable = [producer() startWithNext:nil error:^(__unused id error)
+ {
+ __strong SMulticastSignalManager *strongSelf = weakSelf;
+ if (strongSelf != nil)
+ {
+ OSSpinLockLock(&strongSelf->_lock);
+ [strongSelf->_standaloneSignalDisposables removeObjectForKey:key];
+ OSSpinLockUnlock(&strongSelf->_lock);
+ }
+ } completed:^
+ {
+ __strong SMulticastSignalManager *strongSelf = weakSelf;
+ if (strongSelf != nil)
+ {
+ OSSpinLockLock(&strongSelf->_lock);
+ [strongSelf->_standaloneSignalDisposables removeObjectForKey:key];
+ OSSpinLockUnlock(&strongSelf->_lock);
+ }
+ }];
+
+ OSSpinLockLock(&_lock);
+ [(SMetaDisposable *)_standaloneSignalDisposables[key] setDisposable:disposable];
+ OSSpinLockUnlock(&_lock);
+ }
+}
+
+- (SSignal *)multicastedPipeForKey:(NSString *)key
+{
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ OSSpinLockLock(&_lock);
+ SBag *bag = _pipeListeners[key];
+ if (bag == nil)
+ {
+ bag = [[SBag alloc] init];
+ _pipeListeners[key] = bag;
+ }
+ NSInteger index = [bag addItem:[^(id next)
+ {
+ [subscriber putNext:next];
+ } copy]];
+ OSSpinLockUnlock(&_lock);
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ OSSpinLockLock(&_lock);
+ SBag *bag = _pipeListeners[key];
+ [bag removeItem:index];
+ if ([bag isEmpty]) {
+ [_pipeListeners removeObjectForKey:key];
+ }
+ OSSpinLockUnlock(&_lock);
+ }];
+ }];
+}
+
+- (void)putNext:(id)next toMulticastedPipeForKey:(NSString *)key
+{
+ OSSpinLockLock(&_lock);
+ NSArray *pipeListeners = [(SBag *)_pipeListeners[key] copyItems];
+ OSSpinLockUnlock(&_lock);
+
+ for (void (^listener)(id) in pipeListeners)
+ {
+ listener(next);
+ }
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SQueue.h b/submodules/SSignalKit/SSignalKit/SQueue.h
new file mode 100644
index 0000000000..228334c888
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SQueue.h
@@ -0,0 +1,19 @@
+#import
+
+@interface SQueue : NSObject
+
++ (SQueue *)mainQueue;
++ (SQueue *)concurrentDefaultQueue;
++ (SQueue *)concurrentBackgroundQueue;
+
++ (SQueue *)wrapConcurrentNativeQueue:(dispatch_queue_t)nativeQueue;
+
+- (void)dispatch:(dispatch_block_t)block;
+- (void)dispatchSync:(dispatch_block_t)block;
+- (void)dispatch:(dispatch_block_t)block synchronous:(bool)synchronous;
+
+- (dispatch_queue_t)_dispatch_queue;
+
+- (bool)isCurrentQueue;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SQueue.m b/submodules/SSignalKit/SSignalKit/SQueue.m
new file mode 100644
index 0000000000..d5b5553af8
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SQueue.m
@@ -0,0 +1,124 @@
+#import "SQueue.h"
+
+static const void *SQueueSpecificKey = &SQueueSpecificKey;
+
+@interface SQueue ()
+{
+ dispatch_queue_t _queue;
+ void *_queueSpecific;
+ bool _specialIsMainQueue;
+}
+
+@end
+
+@implementation SQueue
+
++ (SQueue *)mainQueue
+{
+ static SQueue *queue = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^
+ {
+ queue = [[SQueue alloc] initWithNativeQueue:dispatch_get_main_queue() queueSpecific:NULL];
+ queue->_specialIsMainQueue = true;
+ });
+
+ return queue;
+}
+
++ (SQueue *)concurrentDefaultQueue
+{
+ static SQueue *queue = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^
+ {
+ queue = [[SQueue alloc] initWithNativeQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) queueSpecific:NULL];
+ });
+
+ return queue;
+}
+
++ (SQueue *)concurrentBackgroundQueue
+{
+ static SQueue *queue = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^
+ {
+ queue = [[SQueue alloc] initWithNativeQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) queueSpecific:NULL];
+ });
+
+ return queue;
+}
+
++ (SQueue *)wrapConcurrentNativeQueue:(dispatch_queue_t)nativeQueue
+{
+ return [[SQueue alloc] initWithNativeQueue:nativeQueue queueSpecific:NULL];
+}
+
+- (instancetype)init
+{
+ dispatch_queue_t queue = dispatch_queue_create(NULL, NULL);
+ dispatch_queue_set_specific(queue, SQueueSpecificKey, (__bridge void *)self, NULL);
+ return [self initWithNativeQueue:queue queueSpecific:(__bridge void *)self];
+}
+
+- (instancetype)initWithNativeQueue:(dispatch_queue_t)queue queueSpecific:(void *)queueSpecific
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _queue = queue;
+ _queueSpecific = queueSpecific;
+ }
+ return self;
+}
+
+- (dispatch_queue_t)_dispatch_queue
+{
+ return _queue;
+}
+
+- (void)dispatch:(dispatch_block_t)block
+{
+ if (_queueSpecific != NULL && dispatch_get_specific(SQueueSpecificKey) == _queueSpecific)
+ block();
+ else if (_specialIsMainQueue && [NSThread isMainThread])
+ block();
+ else
+ dispatch_async(_queue, block);
+}
+
+- (void)dispatchSync:(dispatch_block_t)block
+{
+ if (_queueSpecific != NULL && dispatch_get_specific(SQueueSpecificKey) == _queueSpecific)
+ block();
+ else if (_specialIsMainQueue && [NSThread isMainThread])
+ block();
+ else
+ dispatch_sync(_queue, block);
+}
+
+- (void)dispatch:(dispatch_block_t)block synchronous:(bool)synchronous {
+ if (_queueSpecific != NULL && dispatch_get_specific(SQueueSpecificKey) == _queueSpecific)
+ block();
+ else if (_specialIsMainQueue && [NSThread isMainThread])
+ block();
+ else {
+ if (synchronous) {
+ dispatch_sync(_queue, block);
+ } else {
+ dispatch_async(_queue, block);
+ }
+ }
+}
+
+- (bool)isCurrentQueue
+{
+ if (_queueSpecific != NULL && dispatch_get_specific(SQueueSpecificKey) == _queueSpecific)
+ return true;
+ else if (_specialIsMainQueue && [NSThread isMainThread])
+ return true;
+ return false;
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Accumulate.h b/submodules/SSignalKit/SSignalKit/SSignal+Accumulate.h
new file mode 100644
index 0000000000..1ea8a51ed0
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Accumulate.h
@@ -0,0 +1,8 @@
+#import
+
+@interface SSignal (Accumulate)
+
+- (SSignal *)reduceLeft:(id)value with:(id (^)(id, id))f;
+- (SSignal *)reduceLeftWithPassthrough:(id)value with:(id (^)(id, id, void (^)(id)))f;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Accumulate.m b/submodules/SSignalKit/SSignalKit/SSignal+Accumulate.m
new file mode 100644
index 0000000000..e237e204d0
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Accumulate.m
@@ -0,0 +1,52 @@
+#import "SSignal+Accumulate.h"
+
+@implementation SSignal (Accumulate)
+
+- (SSignal *)reduceLeft:(id)value with:(id (^)(id, id))f
+{
+ return [[SSignal alloc] initWithGenerator:^(SSubscriber *subscriber)
+ {
+ __block id intermediateResult = value;
+
+ return [self startWithNext:^(id next)
+ {
+ intermediateResult = f(intermediateResult, next);
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ if (intermediateResult != nil)
+ [subscriber putNext:intermediateResult];
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+- (SSignal *)reduceLeftWithPassthrough:(id)value with:(id (^)(id, id, void (^)(id)))f
+{
+ return [[SSignal alloc] initWithGenerator:^(SSubscriber *subscriber)
+ {
+ __block id intermediateResult = value;
+
+ void (^emit)(id) = ^(id next)
+ {
+ [subscriber putNext:next];
+ };
+
+ return [self startWithNext:^(id next)
+ {
+ intermediateResult = f(intermediateResult, next, emit);
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ if (intermediateResult != nil)
+ [subscriber putNext:intermediateResult];
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Catch.h b/submodules/SSignalKit/SSignalKit/SSignal+Catch.h
new file mode 100644
index 0000000000..dc8898c062
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Catch.h
@@ -0,0 +1,9 @@
+#import
+
+@interface SSignal (Catch)
+
+- (SSignal *)catch:(SSignal *(^)(id error))f;
+- (SSignal *)restart;
+- (SSignal *)retryIf:(bool (^)(id error))predicate;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Catch.m b/submodules/SSignalKit/SSignalKit/SSignal+Catch.m
new file mode 100644
index 0000000000..f61e277750
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Catch.m
@@ -0,0 +1,147 @@
+#import "SSignal+Catch.h"
+
+#import "SMetaDisposable.h"
+#import "SDisposableSet.h"
+#import "SBlockDisposable.h"
+#import "SAtomic.h"
+
+@implementation SSignal (Catch)
+
+- (SSignal *)catch:(SSignal *(^)(id error))f
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ SDisposableSet *disposable = [[SDisposableSet alloc] init];
+
+ [disposable add:[self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ SSignal *signal = f(error);
+ [disposable add:[signal startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }]];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }]];
+
+ return disposable;
+ }];
+}
+
+static dispatch_block_t recursiveBlock(void (^block)(dispatch_block_t recurse))
+{
+ return ^
+ {
+ block(recursiveBlock(block));
+ };
+}
+
+- (SSignal *)restart
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ SAtomic *shouldRestart = [[SAtomic alloc] initWithValue:@true];
+
+ SMetaDisposable *currentDisposable = [[SMetaDisposable alloc] init];
+
+ void (^start)() = recursiveBlock(^(dispatch_block_t recurse)
+ {
+ NSNumber *currentShouldRestart = [shouldRestart with:^id(NSNumber *current)
+ {
+ return current;
+ }];
+
+ if ([currentShouldRestart boolValue])
+ {
+ id disposable = [self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ recurse();
+ }];
+ [currentDisposable setDisposable:disposable];
+ }
+ });
+
+ start();
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ [currentDisposable dispose];
+
+ [shouldRestart modify:^id(__unused id current)
+ {
+ return @false;
+ }];
+ }];
+ }];
+}
+
+- (SSignal *)retryIf:(bool (^)(id error))predicate {
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ SAtomic *shouldRestart = [[SAtomic alloc] initWithValue:@true];
+
+ SMetaDisposable *currentDisposable = [[SMetaDisposable alloc] init];
+
+ void (^start)() = recursiveBlock(^(dispatch_block_t recurse)
+ {
+ NSNumber *currentShouldRestart = [shouldRestart with:^id(NSNumber *current)
+ {
+ return current;
+ }];
+
+ if ([currentShouldRestart boolValue])
+ {
+ id disposable = [self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ if (predicate(error)) {
+ recurse();
+ } else {
+ [subscriber putError:error];
+ }
+ } completed:^
+ {
+ [shouldRestart modify:^id(__unused id current) {
+ return @false;
+ }];
+ [subscriber putCompletion];
+ }];
+ [currentDisposable setDisposable:disposable];
+ } else {
+ [subscriber putCompletion];
+ }
+ });
+
+ start();
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ [currentDisposable dispose];
+
+ [shouldRestart modify:^id(__unused id current)
+ {
+ return @false;
+ }];
+ }];
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Combine.h b/submodules/SSignalKit/SSignalKit/SSignal+Combine.h
new file mode 100644
index 0000000000..d84e065311
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Combine.h
@@ -0,0 +1,10 @@
+#import
+
+@interface SSignal (Combine)
+
++ (SSignal *)combineSignals:(NSArray *)signals;
++ (SSignal *)combineSignals:(NSArray *)signals withInitialStates:(NSArray *)initialStates;
+
++ (SSignal *)mergeSignals:(NSArray *)signals;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Combine.m b/submodules/SSignalKit/SSignalKit/SSignal+Combine.m
new file mode 100644
index 0000000000..4df70c113d
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Combine.m
@@ -0,0 +1,177 @@
+#import "SSignal+Combine.h"
+
+#import "SAtomic.h"
+#import "SDisposableSet.h"
+#import "SSignal+Single.h"
+
+@interface SSignalCombineState : NSObject
+
+@property (nonatomic, strong, readonly) NSDictionary *latestValues;
+@property (nonatomic, strong, readonly) NSArray *completedStatuses;
+@property (nonatomic) bool error;
+
+@end
+
+@implementation SSignalCombineState
+
+- (instancetype)initWithLatestValues:(NSDictionary *)latestValues completedStatuses:(NSArray *)completedStatuses error:(bool)error
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _latestValues = latestValues;
+ _completedStatuses = completedStatuses;
+ _error = error;
+ }
+ return self;
+}
+
+@end
+
+@implementation SSignal (Combine)
+
++ (SSignal *)combineSignals:(NSArray *)signals
+{
+ if (signals.count == 0)
+ return [SSignal single:@[]];
+ else
+ return [self combineSignals:signals withInitialStates:nil];
+}
+
++ (SSignal *)combineSignals:(NSArray *)signals withInitialStates:(NSArray *)initialStates
+{
+ return [[SSignal alloc] initWithGenerator:^(SSubscriber *subscriber)
+ {
+ NSMutableArray *completedStatuses = [[NSMutableArray alloc] init];
+ for (NSUInteger i = 0; i < signals.count; i++)
+ {
+ [completedStatuses addObject:@false];
+ }
+ NSMutableDictionary *initialLatestValues = [[NSMutableDictionary alloc] init];
+ for (NSUInteger i = 0; i < initialStates.count; i++)
+ {
+ initialLatestValues[@(i)] = initialStates[i];
+ }
+ SAtomic *combineState = [[SAtomic alloc] initWithValue:[[SSignalCombineState alloc] initWithLatestValues:initialLatestValues completedStatuses:completedStatuses error:false]];
+
+ SDisposableSet *compositeDisposable = [[SDisposableSet alloc] init];
+
+ NSUInteger index = 0;
+ NSUInteger count = signals.count;
+ for (SSignal *signal in signals)
+ {
+ id disposable = [signal startWithNext:^(id next)
+ {
+ SSignalCombineState *currentState = [combineState modify:^id(SSignalCombineState *state)
+ {
+ NSMutableDictionary *latestValues = [[NSMutableDictionary alloc] initWithDictionary:state.latestValues];
+ latestValues[@(index)] = next;
+ return [[SSignalCombineState alloc] initWithLatestValues:latestValues completedStatuses:state.completedStatuses error:state.error];
+ }];
+ NSMutableArray *latestValues = [[NSMutableArray alloc] init];
+ for (NSUInteger i = 0; i < count; i++)
+ {
+ id value = currentState.latestValues[@(i)];
+ if (value == nil)
+ {
+ latestValues = nil;
+ break;
+ }
+ latestValues[i] = value;
+ }
+ if (latestValues != nil)
+ [subscriber putNext:latestValues];
+ }
+ error:^(id error)
+ {
+ __block bool hadError = false;
+ [combineState modify:^id(SSignalCombineState *state)
+ {
+ hadError = state.error;
+ return [[SSignalCombineState alloc] initWithLatestValues:state.latestValues completedStatuses:state.completedStatuses error:true];
+ }];
+ if (!hadError)
+ [subscriber putError:error];
+ } completed:^
+ {
+ __block bool wasCompleted = false;
+ __block bool isCompleted = false;
+ [combineState modify:^id(SSignalCombineState *state)
+ {
+ NSMutableArray *completedStatuses = [[NSMutableArray alloc] initWithArray:state.completedStatuses];
+ bool everyStatusWasCompleted = true;
+ for (NSNumber *nStatus in completedStatuses)
+ {
+ if (![nStatus boolValue])
+ {
+ everyStatusWasCompleted = false;
+ break;
+ }
+ }
+ completedStatuses[index] = @true;
+ bool everyStatusIsCompleted = true;
+ for (NSNumber *nStatus in completedStatuses)
+ {
+ if (![nStatus boolValue])
+ {
+ everyStatusIsCompleted = false;
+ break;
+ }
+ }
+
+ wasCompleted = everyStatusWasCompleted;
+ isCompleted = everyStatusIsCompleted;
+
+ return [[SSignalCombineState alloc] initWithLatestValues:state.latestValues completedStatuses:completedStatuses error:state.error];
+ }];
+ if (!wasCompleted && isCompleted)
+ [subscriber putCompletion];
+ }];
+ [compositeDisposable add:disposable];
+ index++;
+ }
+
+ return compositeDisposable;
+ }];
+}
+
++ (SSignal *)mergeSignals:(NSArray *)signals
+{
+ if (signals.count == 0)
+ return [SSignal complete];
+
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ SDisposableSet *disposables = [[SDisposableSet alloc] init];
+ SAtomic *completedStates = [[SAtomic alloc] initWithValue:[[NSSet alloc] init]];
+
+ NSInteger index = -1;
+ NSUInteger count = signals.count;
+ for (SSignal *signal in signals)
+ {
+ index++;
+
+ id disposable = [signal startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ NSSet *set = [completedStates modify:^id(NSSet *set)
+ {
+ return [set setByAddingObject:@(index)];
+ }];
+ if (set.count == count)
+ [subscriber putCompletion];
+ }];
+
+ [disposables add:disposable];
+ }
+
+ return disposables;
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Dispatch.h b/submodules/SSignalKit/SSignalKit/SSignal+Dispatch.h
new file mode 100644
index 0000000000..a22679b372
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Dispatch.h
@@ -0,0 +1,14 @@
+#import
+
+#import
+#import
+
+@interface SSignal (Dispatch)
+
+- (SSignal *)deliverOn:(SQueue *)queue;
+- (SSignal *)deliverOnThreadPool:(SThreadPool *)threadPool;
+- (SSignal *)startOn:(SQueue *)queue;
+- (SSignal *)startOnThreadPool:(SThreadPool *)threadPool;
+- (SSignal *)throttleOn:(SQueue *)queue delay:(NSTimeInterval)delay;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Dispatch.m b/submodules/SSignalKit/SSignalKit/SSignal+Dispatch.m
new file mode 100644
index 0000000000..3929f07eff
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Dispatch.m
@@ -0,0 +1,212 @@
+#import "SSignal+Dispatch.h"
+#import "SAtomic.h"
+#import "STimer.h"
+#import "SBlockDisposable.h"
+#import "SMetaDisposable.h"
+
+@interface SSignal_ThrottleContainer : NSObject
+
+@property (nonatomic, strong, readonly) id value;
+@property (nonatomic, readonly) bool committed;
+@property (nonatomic, readonly) bool last;
+
+@end
+
+@implementation SSignal_ThrottleContainer
+
+- (instancetype)initWithValue:(id)value committed:(bool)committed last:(bool)last {
+ self = [super init];
+ if (self != nil) {
+ _value = value;
+ _committed = committed;
+ _last = last;
+ }
+ return self;
+}
+
+@end
+
+@implementation SSignal (Dispatch)
+
+- (SSignal *)deliverOn:(SQueue *)queue
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ return [self startWithNext:^(id next)
+ {
+ [queue dispatch:^
+ {
+ [subscriber putNext:next];
+ }];
+ } error:^(id error)
+ {
+ [queue dispatch:^
+ {
+ [subscriber putError:error];
+ }];
+ } completed:^
+ {
+ [queue dispatch:^
+ {
+ [subscriber putCompletion];
+ }];
+ }];
+ }];
+}
+
+- (SSignal *)deliverOnThreadPool:(SThreadPool *)threadPool
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ SThreadPoolQueue *queue = [threadPool nextQueue];
+ return [self startWithNext:^(id next)
+ {
+ SThreadPoolTask *task = [[SThreadPoolTask alloc] initWithBlock:^(bool (^cancelled)())
+ {
+ if (!cancelled())
+ [subscriber putNext:next];
+ }];
+ [queue addTask:task];
+ } error:^(id error)
+ {
+ SThreadPoolTask *task = [[SThreadPoolTask alloc] initWithBlock:^(bool (^cancelled)())
+ {
+ if (!cancelled())
+ [subscriber putError:error];
+ }];
+ [queue addTask:task];
+ } completed:^
+ {
+ SThreadPoolTask *task = [[SThreadPoolTask alloc] initWithBlock:^(bool (^cancelled)())
+ {
+ if (!cancelled())
+ [subscriber putCompletion];
+ }];
+ [queue addTask:task];
+ }];
+ }];
+}
+
+- (SSignal *)startOn:(SQueue *)queue
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ __block bool isCancelled = false;
+ SMetaDisposable *disposable = [[SMetaDisposable alloc] init];
+ [disposable setDisposable:[[SBlockDisposable alloc] initWithBlock:^
+ {
+ isCancelled = true;
+ }]];
+
+ [queue dispatch:^
+ {
+ if (!isCancelled)
+ {
+ [disposable setDisposable:[self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }]];
+ }
+ }];
+
+ return disposable;
+ }];
+}
+
+- (SSignal *)startOnThreadPool:(SThreadPool *)threadPool
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ SMetaDisposable *disposable = [[SMetaDisposable alloc] init];
+
+ SThreadPoolTask *task = [[SThreadPoolTask alloc] initWithBlock:^(bool (^cancelled)())
+ {
+ if (cancelled && cancelled())
+ return;
+
+ [disposable setDisposable:[self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }]];
+ }];
+
+ [disposable setDisposable:[[SBlockDisposable alloc] initWithBlock:^
+ {
+ [task cancel];
+ }]];
+
+ [threadPool addTask:task];
+
+ return disposable;
+ }];
+}
+
+- (SSignal *)throttleOn:(SQueue *)queue delay:(NSTimeInterval)delay
+{
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) {
+ SAtomic *value = [[SAtomic alloc] initWithValue:nil];
+ STimer *timer = [[STimer alloc] initWithTimeout:delay repeat:false completion:^{
+ [value modify:^id(SSignal_ThrottleContainer *container) {
+ if (container != nil) {
+ if (!container.committed) {
+ [subscriber putNext:container.value];
+ container = [[SSignal_ThrottleContainer alloc] initWithValue:container.value committed:true last:container.last];
+ }
+
+ if (container.last) {
+ [subscriber putCompletion];
+ }
+ }
+ return container;
+ }];
+ } queue:queue];
+
+ return [[self deliverOn:queue] startWithNext:^(id next) {
+ [value modify:^id(SSignal_ThrottleContainer *container) {
+ if (container == nil) {
+ container = [[SSignal_ThrottleContainer alloc] initWithValue:next committed:false last:false];
+ }
+ return container;
+ }];
+ [timer invalidate];
+ [timer start];
+ } error:^(id error) {
+ [timer invalidate];
+ [subscriber putError:error];
+ } completed:^{
+ [timer invalidate];
+ __block bool start = false;
+ [value modify:^id(SSignal_ThrottleContainer *container) {
+ bool wasCommitted = false;
+ if (container == nil) {
+ wasCommitted = true;
+ container = [[SSignal_ThrottleContainer alloc] initWithValue:nil committed:true last:true];
+ } else {
+ wasCommitted = container.committed;
+ container = [[SSignal_ThrottleContainer alloc] initWithValue:container.value committed:container.committed last:true];
+ }
+ start = wasCommitted;
+ return container;
+ }];
+ if (start) {
+ [timer start];
+ } else {
+ [timer fireAndInvalidate];
+ }
+ }];
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Mapping.h b/submodules/SSignalKit/SSignalKit/SSignal+Mapping.h
new file mode 100644
index 0000000000..81a16816e2
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Mapping.h
@@ -0,0 +1,9 @@
+#import
+
+@interface SSignal (Mapping)
+
+- (SSignal *)map:(id (^)(id))f;
+- (SSignal *)filter:(bool (^)(id))f;
+- (SSignal *)ignoreRepeated;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Mapping.m b/submodules/SSignalKit/SSignalKit/SSignal+Mapping.m
new file mode 100644
index 0000000000..cd3a3ece0a
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Mapping.m
@@ -0,0 +1,83 @@
+#import "SSignal+Mapping.h"
+
+#import "SAtomic.h"
+
+@interface SSignalIgnoreRepeatedState: NSObject
+
+@property (nonatomic, strong) id value;
+@property (nonatomic) bool hasValue;
+
+@end
+
+@implementation SSignalIgnoreRepeatedState
+
+@end
+
+@implementation SSignal (Mapping)
+
+- (SSignal *)map:(id (^)(id))f
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ return [self startWithNext:^(id next)
+ {
+ [subscriber putNext:f(next)];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+- (SSignal *)filter:(bool (^)(id))f
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ return [self startWithNext:^(id next)
+ {
+ if (f(next))
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+- (SSignal *)ignoreRepeated {
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) {
+ SAtomic *state = [[SAtomic alloc] initWithValue:[[SSignalIgnoreRepeatedState alloc] init]];
+
+ return [self startWithNext:^(id next) {
+ bool shouldPassthrough = [[state with:^id(SSignalIgnoreRepeatedState *state) {
+ if (!state.hasValue) {
+ state.hasValue = true;
+ state.value = next;
+ return @true;
+ } else if ((state.value == nil && next == nil) || [(id)state.value isEqual:next]) {
+ return @false;
+ }
+ state.value = next;
+ return @true;
+ }] boolValue];
+
+ if (shouldPassthrough) {
+ [subscriber putNext:next];
+ }
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Meta.h b/submodules/SSignalKit/SSignalKit/SSignal+Meta.h
new file mode 100644
index 0000000000..2d90df13f8
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Meta.h
@@ -0,0 +1,22 @@
+#import
+
+@class SQueue;
+
+@interface SSignal (Meta)
+
+- (SSignal *)switchToLatest;
+- (SSignal *)mapToSignal:(SSignal *(^)(id))f;
+- (SSignal *)mapToQueue:(SSignal *(^)(id))f;
+- (SSignal *)mapToThrottled:(SSignal *(^)(id))f;
+- (SSignal *)then:(SSignal *)signal;
+- (SSignal *)queue;
+- (SSignal *)throttled;
++ (SSignal *)defer:(SSignal *(^)())generator;
+
+@end
+
+@interface SSignalQueue : NSObject
+
+- (SSignal *)enqueue:(SSignal *)signal;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Meta.m b/submodules/SSignalKit/SSignalKit/SSignal+Meta.m
new file mode 100644
index 0000000000..f6ad4124e6
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Meta.m
@@ -0,0 +1,325 @@
+#import "SSignal+Meta.h"
+
+#import "SDisposableSet.h"
+#import "SMetaDisposable.h"
+#import "SSignal+Mapping.h"
+#import "SAtomic.h"
+#import "SSignal+Pipe.h"
+
+#import
+
+@interface SSignalQueueState : NSObject
+{
+ OSSpinLock _lock;
+ bool _executingSignal;
+ bool _terminated;
+
+ id _disposable;
+ SMetaDisposable *_currentDisposable;
+ SSubscriber *_subscriber;
+
+ NSMutableArray *_queuedSignals;
+ bool _queueMode;
+ bool _throttleMode;
+}
+
+@end
+
+@implementation SSignalQueueState
+
+- (instancetype)initWithSubscriber:(SSubscriber *)subscriber queueMode:(bool)queueMode throttleMode:(bool)throttleMode
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _subscriber = subscriber;
+ _currentDisposable = [[SMetaDisposable alloc] init];
+ _queuedSignals = queueMode ? [[NSMutableArray alloc] init] : nil;
+ _queueMode = queueMode;
+ _throttleMode = throttleMode;
+ }
+ return self;
+}
+
+- (void)beginWithDisposable:(id)disposable
+{
+ _disposable = disposable;
+}
+
+- (void)enqueueSignal:(SSignal *)signal
+{
+ bool startSignal = false;
+ OSSpinLockLock(&_lock);
+ if (_queueMode && _executingSignal) {
+ if (_throttleMode) {
+ [_queuedSignals removeAllObjects];
+ }
+ [_queuedSignals addObject:signal];
+ }
+ else
+ {
+ _executingSignal = true;
+ startSignal = true;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (startSignal)
+ {
+ __weak SSignalQueueState *weakSelf = self;
+ id disposable = [signal startWithNext:^(id next)
+ {
+ [_subscriber putNext:next];
+ } error:^(id error)
+ {
+ [_subscriber putError:error];
+ } completed:^
+ {
+ __strong SSignalQueueState *strongSelf = weakSelf;
+ if (strongSelf != nil) {
+ [strongSelf headCompleted];
+ }
+ }];
+
+ [_currentDisposable setDisposable:disposable];
+ }
+}
+
+- (void)headCompleted
+{
+ SSignal *nextSignal = nil;
+
+ bool terminated = false;
+ OSSpinLockLock(&_lock);
+ _executingSignal = false;
+
+ if (_queueMode)
+ {
+ if (_queuedSignals.count != 0)
+ {
+ nextSignal = _queuedSignals[0];
+ [_queuedSignals removeObjectAtIndex:0];
+ _executingSignal = true;
+ }
+ else
+ terminated = _terminated;
+ }
+ else
+ terminated = _terminated;
+ OSSpinLockUnlock(&_lock);
+
+ if (terminated)
+ [_subscriber putCompletion];
+ else if (nextSignal != nil)
+ {
+ __weak SSignalQueueState *weakSelf = self;
+ id disposable = [nextSignal startWithNext:^(id next)
+ {
+ [_subscriber putNext:next];
+ } error:^(id error)
+ {
+ [_subscriber putError:error];
+ } completed:^
+ {
+ __strong SSignalQueueState *strongSelf = weakSelf;
+ if (strongSelf != nil) {
+ [strongSelf headCompleted];
+ }
+ }];
+
+ [_currentDisposable setDisposable:disposable];
+ }
+}
+
+- (void)beginCompletion
+{
+ bool executingSignal = false;
+ OSSpinLockLock(&_lock);
+ executingSignal = _executingSignal;
+ _terminated = true;
+ OSSpinLockUnlock(&_lock);
+
+ if (!executingSignal)
+ [_subscriber putCompletion];
+}
+
+- (void)dispose
+{
+ [_currentDisposable dispose];
+ [_disposable dispose];
+}
+
+@end
+
+@implementation SSignal (Meta)
+
+- (SSignal *)switchToLatest
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ SSignalQueueState *state = [[SSignalQueueState alloc] initWithSubscriber:subscriber queueMode:false throttleMode:false];
+
+ [state beginWithDisposable:[self startWithNext:^(id next)
+ {
+ [state enqueueSignal:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [state beginCompletion];
+ }]];
+
+ return state;
+ }];
+}
+
+- (SSignal *)mapToSignal:(SSignal *(^)(id))f
+{
+ return [[self map:f] switchToLatest];
+}
+
+- (SSignal *)mapToQueue:(SSignal *(^)(id))f
+{
+ return [[self map:f] queue];
+}
+
+- (SSignal *)mapToThrottled:(SSignal *(^)(id))f {
+ return [[self map:f] throttled];
+}
+
+- (SSignal *)then:(SSignal *)signal
+{
+ return [[SSignal alloc] initWithGenerator:^(SSubscriber *subscriber)
+ {
+ SDisposableSet *compositeDisposable = [[SDisposableSet alloc] init];
+
+ SMetaDisposable *currentDisposable = [[SMetaDisposable alloc] init];
+ [compositeDisposable add:currentDisposable];
+
+ [currentDisposable setDisposable:[self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [compositeDisposable add:[signal startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }]];
+ }]];
+
+ return compositeDisposable;
+ }];
+}
+
+- (SSignal *)queue
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ SSignalQueueState *state = [[SSignalQueueState alloc] initWithSubscriber:subscriber queueMode:true throttleMode:false];
+
+ [state beginWithDisposable:[self startWithNext:^(id next)
+ {
+ [state enqueueSignal:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [state beginCompletion];
+ }]];
+
+ return state;
+ }];
+}
+
+- (SSignal *)throttled {
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) {
+ SSignalQueueState *state = [[SSignalQueueState alloc] initWithSubscriber:subscriber queueMode:true throttleMode:true];
+ [state beginWithDisposable:[self startWithNext:^(id next)
+ {
+ [state enqueueSignal:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [state beginCompletion];
+ }]];
+
+ return state;
+ }];
+}
+
++ (SSignal *)defer:(SSignal *(^)())generator
+{
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ return [generator() startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+@end
+
+@interface SSignalQueue () {
+ SPipe *_pipe;
+ id _disposable;
+}
+
+@end
+
+@implementation SSignalQueue
+
+- (instancetype)init {
+ self = [super init];
+ if (self != nil) {
+ _pipe = [[SPipe alloc] init];
+ _disposable = [[_pipe.signalProducer() queue] startWithNext:nil];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [_disposable dispose];
+}
+
+- (SSignal *)enqueue:(SSignal *)signal {
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) {
+ SPipe *disposePipe = [[SPipe alloc] init];
+
+ SSignal *proxy = [[[[signal onNext:^(id next) {
+ [subscriber putNext:next];
+ }] onError:^(id error) {
+ [subscriber putError:error];
+ }] onCompletion:^{
+ [subscriber putCompletion];
+ }] catch:^SSignal *(__unused id error) {
+ return [SSignal complete];
+ }];
+
+ _pipe.sink([proxy takeUntilReplacement:disposePipe.signalProducer()]);
+
+ return [[SBlockDisposable alloc] initWithBlock:^{
+ disposePipe.sink([SSignal complete]);
+ }];
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Multicast.h b/submodules/SSignalKit/SSignalKit/SSignal+Multicast.h
new file mode 100644
index 0000000000..e0720cc103
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Multicast.h
@@ -0,0 +1,7 @@
+#import
+
+@interface SSignal (Multicast)
+
+- (SSignal *)multicast;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Multicast.m b/submodules/SSignalKit/SSignalKit/SSignal+Multicast.m
new file mode 100644
index 0000000000..92976cff4a
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Multicast.m
@@ -0,0 +1,158 @@
+#import "SSignal+Multicast.h"
+
+#import
+#import "SBag.h"
+#import "SBlockDisposable.h"
+
+typedef enum {
+ SSignalMulticastStateReady,
+ SSignalMulticastStateStarted,
+ SSignalMulticastStateCompleted
+} SSignalMulticastState;
+
+@interface SSignalMulticastSubscribers : NSObject
+{
+ volatile OSSpinLock _lock;
+ SBag *_subscribers;
+ SSignalMulticastState _state;
+ id _disposable;
+}
+
+@end
+
+@implementation SSignalMulticastSubscribers
+
+- (instancetype)init
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _subscribers = [[SBag alloc] init];
+ }
+ return self;
+}
+
+- (void)setDisposable:(id)disposable
+{
+ [_disposable dispose];
+ _disposable = disposable;
+}
+
+- (id)addSubscriber:(SSubscriber *)subscriber start:(bool *)start
+{
+ OSSpinLockLock(&_lock);
+ NSInteger index = [_subscribers addItem:subscriber];
+ switch (_state) {
+ case SSignalMulticastStateReady:
+ *start = true;
+ _state = SSignalMulticastStateStarted;
+ break;
+ default:
+ break;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ [self remove:index];
+ }];
+}
+
+- (void)remove:(NSInteger)index
+{
+ id currentDisposable = nil;
+
+ OSSpinLockLock(&_lock);
+ [_subscribers removeItem:index];
+ switch (_state) {
+ case SSignalMulticastStateStarted:
+ if ([_subscribers isEmpty])
+ {
+ currentDisposable = _disposable;
+ _disposable = nil;
+ }
+ break;
+ default:
+ break;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ [currentDisposable dispose];
+}
+
+- (void)notifyNext:(id)next
+{
+ NSArray *currentSubscribers = nil;
+ OSSpinLockLock(&_lock);
+ currentSubscribers = [_subscribers copyItems];
+ OSSpinLockUnlock(&_lock);
+
+ for (SSubscriber *subscriber in currentSubscribers)
+ {
+ [subscriber putNext:next];
+ }
+}
+
+- (void)notifyError:(id)error
+{
+ NSArray *currentSubscribers = nil;
+ OSSpinLockLock(&_lock);
+ currentSubscribers = [_subscribers copyItems];
+ _state = SSignalMulticastStateCompleted;
+ OSSpinLockUnlock(&_lock);
+
+ for (SSubscriber *subscriber in currentSubscribers)
+ {
+ [subscriber putError:error];
+ }
+}
+
+- (void)notifyCompleted
+{
+ NSArray *currentSubscribers = nil;
+ OSSpinLockLock(&_lock);
+ currentSubscribers = [_subscribers copyItems];
+ _state = SSignalMulticastStateCompleted;
+ OSSpinLockUnlock(&_lock);
+
+ for (SSubscriber *subscriber in currentSubscribers)
+ {
+ [subscriber putCompletion];
+ }
+}
+
+@end
+
+@implementation SSignal (Multicast)
+
+- (SSignal *)multicast
+{
+ SSignalMulticastSubscribers *subscribers = [[SSignalMulticastSubscribers alloc] init];
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ bool start = false;
+ id currentDisposable = [subscribers addSubscriber:subscriber start:&start];
+ if (start)
+ {
+ id disposable = [self startWithNext:^(id next)
+ {
+ [subscribers notifyNext:next];
+ } error:^(id error)
+ {
+ [subscribers notifyError:error];
+ } completed:^
+ {
+ [subscribers notifyCompleted];
+ }];
+
+ [subscribers setDisposable:[[SBlockDisposable alloc] initWithBlock:^
+ {
+ [disposable dispose];
+ }]];
+ }
+
+ return currentDisposable;
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Pipe.h b/submodules/SSignalKit/SSignalKit/SSignal+Pipe.h
new file mode 100644
index 0000000000..2a21ee4cf7
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Pipe.h
@@ -0,0 +1,11 @@
+#import
+
+@interface SPipe : NSObject
+
+@property (nonatomic, copy, readonly) SSignal *(^signalProducer)();
+@property (nonatomic, copy, readonly) void (^sink)(id);
+
+- (instancetype)initWithReplay:(bool)replay;
+
+@end
+
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Pipe.m b/submodules/SSignalKit/SSignalKit/SSignal+Pipe.m
new file mode 100644
index 0000000000..ab9496a2d2
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Pipe.m
@@ -0,0 +1,103 @@
+#import "SSignal+Pipe.h"
+
+#import "SBlockDisposable.h"
+#import "SAtomic.h"
+#import "SBag.h"
+
+@interface SPipeReplayState : NSObject
+
+@property (nonatomic, readonly) bool hasReceivedValue;
+@property (nonatomic, strong, readonly) id recentValue;
+
+@end
+
+@implementation SPipeReplayState
+
+- (instancetype)initWithReceivedValue:(bool)receivedValue recentValue:(id)recentValue
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _hasReceivedValue = receivedValue;
+ _recentValue = recentValue;
+ }
+ return self;
+}
+
+@end
+
+@implementation SPipe
+
+- (instancetype)init
+{
+ return [self initWithReplay:false];
+}
+
+- (instancetype)initWithReplay:(bool)replay
+{
+ self = [super init];
+ if (self != nil)
+ {
+ SAtomic *subscribers = [[SAtomic alloc] initWithValue:[[SBag alloc] init]];
+ SAtomic *replayState = replay ? [[SAtomic alloc] initWithValue:[[SPipeReplayState alloc] initWithReceivedValue:false recentValue:nil]] : nil;
+
+ _signalProducer = [^SSignal *
+ {
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ __block NSUInteger index = 0;
+ [subscribers with:^id(SBag *bag)
+ {
+ index = [bag addItem:[^(id next)
+ {
+ [subscriber putNext:next];
+ } copy]];
+ return nil;
+ }];
+
+ if (replay)
+ {
+ [replayState with:^id(SPipeReplayState *state)
+ {
+ if (state.hasReceivedValue)
+ [subscriber putNext:state.recentValue];
+ return nil;
+ }];
+ }
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ [subscribers with:^id(SBag *bag)
+ {
+ [bag removeItem:index];
+ return nil;
+ }];
+ }];
+ }];
+ } copy];
+
+ _sink = [^(id next)
+ {
+ NSArray *items = [subscribers with:^id(SBag *bag)
+ {
+ return [bag copyItems];
+ }];
+
+ for (void (^item)(id) in items)
+ {
+ item(next);
+ }
+
+ if (replay)
+ {
+ [replayState modify:^id(__unused SPipeReplayState *state)
+ {
+ return [[SPipeReplayState alloc] initWithReceivedValue:true recentValue:next];
+ }];
+ }
+ } copy];
+ }
+ return self;
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+SideEffects.h b/submodules/SSignalKit/SSignalKit/SSignal+SideEffects.h
new file mode 100644
index 0000000000..8107fcf4cb
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+SideEffects.h
@@ -0,0 +1,13 @@
+#import
+
+@interface SSignal (SideEffects)
+
+- (SSignal *)onStart:(void (^)())f;
+- (SSignal *)onNext:(void (^)(id next))f;
+- (SSignal *)afterNext:(void (^)(id next))f;
+- (SSignal *)onError:(void (^)(id error))f;
+- (SSignal *)onCompletion:(void (^)())f;
+- (SSignal *)afterCompletion:(void (^)())f;
+- (SSignal *)onDispose:(void (^)())f;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+SideEffects.m b/submodules/SSignalKit/SSignalKit/SSignal+SideEffects.m
new file mode 100644
index 0000000000..3d78102d2d
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+SideEffects.m
@@ -0,0 +1,141 @@
+#import "SSignal+SideEffects.h"
+
+#import "SBlockDisposable.h"
+#import "SDisposableSet.h"
+
+@implementation SSignal (SideEffects)
+
+- (SSignal *)onStart:(void (^)())f
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ f();
+ return [self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+- (SSignal *)onNext:(void (^)(id next))f
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ return [self startWithNext:^(id next)
+ {
+ f(next);
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+- (SSignal *)afterNext:(void (^)(id next))f
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ return [self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ f(next);
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+- (SSignal *)onError:(void (^)(id error))f
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ return [self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ f(error);
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+- (SSignal *)onCompletion:(void (^)())f
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ return [self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ f();
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+- (SSignal *)afterCompletion:(void (^)())f {
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ return [self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ f();
+ }];
+ }];
+}
+
+- (SSignal *)onDispose:(void (^)())f
+{
+ return [[SSignal alloc] initWithGenerator:^(SSubscriber *subscriber)
+ {
+ SDisposableSet *compositeDisposable = [[SDisposableSet alloc] init];
+
+ [compositeDisposable add:[self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }]];
+
+ [compositeDisposable add:[[SBlockDisposable alloc] initWithBlock:^
+ {
+ f();
+ }]];
+
+ return compositeDisposable;
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Single.h b/submodules/SSignalKit/SSignalKit/SSignal+Single.h
new file mode 100644
index 0000000000..75f48ff569
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Single.h
@@ -0,0 +1,10 @@
+#import
+
+@interface SSignal (Single)
+
++ (SSignal *)single:(id)next;
++ (SSignal *)fail:(id)error;
++ (SSignal *)never;
++ (SSignal *)complete;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Single.m b/submodules/SSignalKit/SSignalKit/SSignal+Single.m
new file mode 100644
index 0000000000..6da5943459
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Single.m
@@ -0,0 +1,41 @@
+#import "SSignal+Single.h"
+
+@implementation SSignal (Single)
+
++ (SSignal *)single:(id)next
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ [subscriber putNext:next];
+ [subscriber putCompletion];
+ return nil;
+ }];
+}
+
++ (SSignal *)fail:(id)error
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ [subscriber putError:error];
+ return nil;
+ }];
+}
+
++ (SSignal *)never
+{
+ return [[SSignal alloc] initWithGenerator:^id (__unused SSubscriber *subscriber)
+ {
+ return nil;
+ }];
+}
+
++ (SSignal *)complete
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ [subscriber putCompletion];
+ return nil;
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Take.h b/submodules/SSignalKit/SSignalKit/SSignal+Take.h
new file mode 100644
index 0000000000..a4d1ff23eb
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Take.h
@@ -0,0 +1,9 @@
+#import
+
+@interface SSignal (Take)
+
+- (SSignal *)take:(NSUInteger)count;
+- (SSignal *)takeLast;
+- (SSignal *)takeUntilReplacement:(SSignal *)replacement;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Take.m b/submodules/SSignalKit/SSignalKit/SSignal+Take.m
new file mode 100644
index 0000000000..e5db9fa034
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Take.m
@@ -0,0 +1,122 @@
+#import "SSignal+Take.h"
+
+#import "SAtomic.h"
+
+@interface SSignal_ValueContainer : NSObject
+
+@property (nonatomic, strong, readonly) id value;
+
+@end
+
+@implementation SSignal_ValueContainer
+
+- (instancetype)initWithValue:(id)value {
+ self = [super init];
+ if (self != nil) {
+ _value = value;
+ }
+ return self;
+}
+
+@end
+
+@implementation SSignal (Take)
+
+- (SSignal *)take:(NSUInteger)count
+{
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ SAtomic *counter = [[SAtomic alloc] initWithValue:@(0)];
+ return [self startWithNext:^(id next)
+ {
+ __block bool passthrough = false;
+ __block bool complete = false;
+ [counter modify:^id(NSNumber *currentCount)
+ {
+ NSUInteger updatedCount = [currentCount unsignedIntegerValue] + 1;
+ if (updatedCount <= count)
+ passthrough = true;
+ if (updatedCount == count)
+ complete = true;
+ return @(updatedCount);
+ }];
+
+ if (passthrough)
+ [subscriber putNext:next];
+ if (complete)
+ [subscriber putCompletion];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+- (SSignal *)takeLast
+{
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ SAtomic *last = [[SAtomic alloc] initWithValue:nil];
+ return [self startWithNext:^(id next)
+ {
+ [last swap:[[SSignal_ValueContainer alloc] initWithValue:next]];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ SSignal_ValueContainer *value = [last with:^id(id value) {
+ return value;
+ }];
+ if (value != nil)
+ {
+ [subscriber putNext:value.value];
+ }
+ [subscriber putCompletion];
+ }];
+ }];
+}
+
+- (SSignal *)takeUntilReplacement:(SSignal *)replacement {
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) {
+ SDisposableSet *disposable = [[SDisposableSet alloc] init];
+
+ SMetaDisposable *selfDisposable = [[SMetaDisposable alloc] init];
+ SMetaDisposable *replacementDisposable = [[SMetaDisposable alloc] init];
+
+ [disposable add:selfDisposable];
+ [disposable add:replacementDisposable];
+
+ [disposable add:[replacement startWithNext:^(SSignal *next) {
+ [selfDisposable dispose];
+
+ [replacementDisposable setDisposable:[next startWithNext:^(id next) {
+ [subscriber putNext:next];
+ } error:^(id error) {
+ [subscriber putError:error];
+ } completed:^{
+ [subscriber putCompletion];
+ }]];
+ } error:^(id error) {
+ [subscriber putError:error];
+ } completed:^{
+ }]];
+
+ [selfDisposable setDisposable:[self startWithNext:^(id next) {
+ [subscriber putNext:next];
+ } error:^(id error) {
+ [replacementDisposable dispose];
+ [subscriber putError:error];
+ } completed:^{
+ [replacementDisposable dispose];
+ [subscriber putCompletion];
+ }]];
+
+ return disposable;
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Timing.h b/submodules/SSignalKit/SSignalKit/SSignal+Timing.h
new file mode 100644
index 0000000000..4b5d50c90e
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Timing.h
@@ -0,0 +1,11 @@
+#import
+
+#import
+
+@interface SSignal (Timing)
+
+- (SSignal *)delay:(NSTimeInterval)seconds onQueue:(SQueue *)queue;
+- (SSignal *)timeout:(NSTimeInterval)seconds onQueue:(SQueue *)queue orSignal:(SSignal *)signal;
+- (SSignal *)wait:(NSTimeInterval)seconds;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Timing.m b/submodules/SSignalKit/SSignalKit/SSignal+Timing.m
new file mode 100644
index 0000000000..3d0dbc4a97
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal+Timing.m
@@ -0,0 +1,109 @@
+#import "SSignal+Timing.h"
+
+#import "SMetaDisposable.h"
+#import "SDisposableSet.h"
+#import "SBlockDisposable.h"
+
+#import "SSignal+Dispatch.h"
+
+#import "STimer.h"
+
+@implementation SSignal (Timing)
+
+- (SSignal *)delay:(NSTimeInterval)seconds onQueue:(SQueue *)queue
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ SMetaDisposable *disposable = [[SMetaDisposable alloc] init];
+
+ STimer *timer = [[STimer alloc] initWithTimeout:seconds repeat:false completion:^
+ {
+ [disposable setDisposable:[self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }]];
+ } queue:queue];
+
+ [timer start];
+
+ [disposable setDisposable:[[SBlockDisposable alloc] initWithBlock:^
+ {
+ [timer invalidate];
+ }]];
+
+ return disposable;
+ }];
+}
+
+- (SSignal *)timeout:(NSTimeInterval)seconds onQueue:(SQueue *)queue orSignal:(SSignal *)signal
+{
+ return [[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber)
+ {
+ SMetaDisposable *disposable = [[SMetaDisposable alloc] init];
+
+ STimer *timer = [[STimer alloc] initWithTimeout:seconds repeat:false completion:^
+ {
+ [disposable setDisposable:[signal startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ }]];
+ } queue:queue];
+ [timer start];
+
+ [disposable setDisposable:[self startWithNext:^(id next)
+ {
+ [timer invalidate];
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [timer invalidate];
+ [subscriber putError:error];
+ } completed:^
+ {
+ [timer invalidate];
+ [subscriber putCompletion];
+ }]];
+
+ return disposable;
+ }];
+}
+
+- (SSignal *)wait:(NSTimeInterval)seconds
+{
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
+
+ id disposable = [self startWithNext:^(id next)
+ {
+ dispatch_semaphore_signal(semaphore);
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ dispatch_semaphore_signal(semaphore);
+ [subscriber putError:error];
+ } completed:^
+ {
+ dispatch_semaphore_signal(semaphore);
+ [subscriber putCompletion];
+ }];
+
+ dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(seconds * NSEC_PER_SEC)));
+
+ return disposable;
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignal.h b/submodules/SSignalKit/SSignalKit/SSignal.h
new file mode 100644
index 0000000000..46b7385d36
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal.h
@@ -0,0 +1,18 @@
+#import
+
+@interface SSignal : NSObject
+{
+@public
+ id (^_generator)(SSubscriber *);
+}
+
+- (instancetype)initWithGenerator:(id (^)(SSubscriber *))generator;
+
+- (id)startWithNext:(void (^)(id next))next error:(void (^)(id error))error completed:(void (^)())completed;
+- (id)startWithNext:(void (^)(id next))next;
+- (id)startWithNext:(void (^)(id next))next completed:(void (^)())completed;
+
+- (SSignal *)trace:(NSString *)name;
+
+@end
+
diff --git a/submodules/SSignalKit/SSignalKit/SSignal.m b/submodules/SSignalKit/SSignalKit/SSignal.m
new file mode 100644
index 0000000000..6bf3ff4dde
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignal.m
@@ -0,0 +1,107 @@
+#import "SSignal.h"
+
+#import "SBlockDisposable.h"
+
+@interface SSubscriberDisposable : NSObject
+{
+ SSubscriber *_subscriber;
+ id _disposable;
+}
+
+@end
+
+@implementation SSubscriberDisposable
+
+- (instancetype)initWithSubscriber:(SSubscriber *)subscriber disposable:(id)disposable
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _subscriber = subscriber;
+ _disposable = disposable;
+ }
+ return self;
+}
+
+- (void)dispose
+{
+ [_subscriber _markTerminatedWithoutDisposal];
+ [_disposable dispose];
+}
+
+@end
+
+@interface SSignal ()
+{
+}
+
+@end
+
+@implementation SSignal
+
+- (instancetype)initWithGenerator:(id (^)(SSubscriber *))generator
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _generator = [generator copy];
+ }
+ return self;
+}
+
+- (id)startWithNext:(void (^)(id next))next error:(void (^)(id error))error completed:(void (^)())completed traceName:(NSString *)traceName
+{
+ STracingSubscriber *subscriber = [[STracingSubscriber alloc] initWithName:traceName next:next error:error completed:completed];
+ id disposable = _generator(subscriber);
+ [subscriber _assignDisposable:disposable];
+ return [[SSubscriberDisposable alloc] initWithSubscriber:subscriber disposable:disposable];
+}
+
+- (id)startWithNext:(void (^)(id next))next error:(void (^)(id error))error completed:(void (^)())completed
+{
+ SSubscriber *subscriber = [[SSubscriber alloc] initWithNext:next error:error completed:completed];
+ id disposable = _generator(subscriber);
+ [subscriber _assignDisposable:disposable];
+ return [[SSubscriberDisposable alloc] initWithSubscriber:subscriber disposable:disposable];
+}
+
+- (id)startWithNext:(void (^)(id next))next
+{
+ SSubscriber *subscriber = [[SSubscriber alloc] initWithNext:next error:nil completed:nil];
+ id disposable = _generator(subscriber);
+ [subscriber _assignDisposable:disposable];
+ return [[SSubscriberDisposable alloc] initWithSubscriber:subscriber disposable:disposable];
+}
+
+- (id)startWithNext:(void (^)(id next))next completed:(void (^)())completed
+{
+ SSubscriber *subscriber = [[SSubscriber alloc] initWithNext:next error:nil completed:completed];
+ id disposable = _generator(subscriber);
+ [subscriber _assignDisposable:disposable];
+ return [[SSubscriberDisposable alloc] initWithSubscriber:subscriber disposable:disposable];
+}
+
+- (SSignal *)trace:(NSString *)name
+{
+#ifdef DEBUG
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ NSString *traceName = [[NSString alloc] initWithFormat:@"%@#0x%x", name, (int)random()];
+ NSLog(@"trace(%@ start)", traceName);
+ return [self startWithNext:^(id next)
+ {
+ [subscriber putNext:next];
+ } error:^(id error)
+ {
+ [subscriber putError:error];
+ } completed:^
+ {
+ [subscriber putCompletion];
+ } traceName:traceName];
+ }];
+#else
+ return self;
+#endif
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SSignalKit.h b/submodules/SSignalKit/SSignalKit/SSignalKit.h
new file mode 100644
index 0000000000..9842a184e1
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSignalKit.h
@@ -0,0 +1,45 @@
+//
+// SSignalKit.h
+// SSignalKit
+//
+// Created by Peter on 31/01/15.
+// Copyright (c) 2015 Telegram. All rights reserved.
+//
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+#import
+#else
+#import
+#endif
+
+//! Project version number for SSignalKit.
+FOUNDATION_EXPORT double SSignalKitVersionNumber;
+
+//! Project version string for SSignalKit.
+FOUNDATION_EXPORT const unsigned char SSignalKitVersionString[];
+
+// In this header, you should import all the public headers of your framework using statements like #import
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
diff --git a/submodules/SSignalKit/SSignalKit/SSubscriber.h b/submodules/SSignalKit/SSignalKit/SSubscriber.h
new file mode 100644
index 0000000000..ce4fee5678
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSubscriber.h
@@ -0,0 +1,22 @@
+#import
+
+@interface SSubscriber : NSObject
+{
+}
+
+- (instancetype)initWithNext:(void (^)(id))next error:(void (^)(id))error completed:(void (^)())completed;
+
+- (void)_assignDisposable:(id)disposable;
+- (void)_markTerminatedWithoutDisposal;
+
+- (void)putNext:(id)next;
+- (void)putError:(id)error;
+- (void)putCompletion;
+
+@end
+
+@interface STracingSubscriber : SSubscriber
+
+- (instancetype)initWithName:(NSString *)name next:(void (^)(id))next error:(void (^)(id))error completed:(void (^)())completed;
+
+@end
\ No newline at end of file
diff --git a/submodules/SSignalKit/SSignalKit/SSubscriber.m b/submodules/SSignalKit/SSignalKit/SSubscriber.m
new file mode 100644
index 0000000000..e565b7748d
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SSubscriber.m
@@ -0,0 +1,276 @@
+#import "SSubscriber.h"
+
+#import
+
+@interface SSubscriberBlocks : NSObject {
+ @public
+ void (^_next)(id);
+ void (^_error)(id);
+ void (^_completed)();
+}
+
+@end
+
+@implementation SSubscriberBlocks
+
+- (instancetype)initWithNext:(void (^)(id))next error:(void (^)(id))error completed:(void (^)())completed {
+ self = [super init];
+ if (self != nil) {
+ _next = [next copy];
+ _error = [error copy];
+ _completed = [completed copy];
+ }
+ return self;
+}
+
+@end
+
+@interface SSubscriber ()
+{
+ @protected
+ OSSpinLock _lock;
+ bool _terminated;
+ id _disposable;
+ SSubscriberBlocks *_blocks;
+}
+
+@end
+
+@implementation SSubscriber
+
+- (instancetype)initWithNext:(void (^)(id))next error:(void (^)(id))error completed:(void (^)())completed
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _blocks = [[SSubscriberBlocks alloc] initWithNext:next error:error completed:completed];
+ }
+ return self;
+}
+
+- (void)_assignDisposable:(id)disposable
+{
+ bool dispose = false;
+ OSSpinLockLock(&_lock);
+ if (_terminated) {
+ dispose = true;
+ } else {
+ _disposable = disposable;
+ }
+ OSSpinLockUnlock(&_lock);
+ if (dispose) {
+ [disposable dispose];
+ }
+}
+
+- (void)_markTerminatedWithoutDisposal
+{
+ OSSpinLockLock(&_lock);
+ SSubscriberBlocks *blocks = nil;
+ if (!_terminated)
+ {
+ blocks = _blocks;
+ _blocks = nil;
+
+ _terminated = true;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (blocks) {
+ blocks = nil;
+ }
+}
+
+- (void)putNext:(id)next
+{
+ SSubscriberBlocks *blocks = nil;
+
+ OSSpinLockLock(&_lock);
+ if (!_terminated) {
+ blocks = _blocks;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (blocks && blocks->_next) {
+ blocks->_next(next);
+ }
+}
+
+- (void)putError:(id)error
+{
+ bool shouldDispose = false;
+ SSubscriberBlocks *blocks = nil;
+
+ OSSpinLockLock(&_lock);
+ if (!_terminated)
+ {
+ blocks = _blocks;
+ _blocks = nil;
+
+ shouldDispose = true;
+ _terminated = true;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (blocks && blocks->_error) {
+ blocks->_error(error);
+ }
+
+ if (shouldDispose)
+ [self->_disposable dispose];
+}
+
+- (void)putCompletion
+{
+ bool shouldDispose = false;
+ SSubscriberBlocks *blocks = nil;
+
+ OSSpinLockLock(&_lock);
+ if (!_terminated)
+ {
+ blocks = _blocks;
+ _blocks = nil;
+
+ shouldDispose = true;
+ _terminated = true;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (blocks && blocks->_completed)
+ blocks->_completed();
+
+ if (shouldDispose)
+ [self->_disposable dispose];
+}
+
+- (void)dispose
+{
+ [self->_disposable dispose];
+}
+
+@end
+
+@interface STracingSubscriber ()
+{
+ NSString *_name;
+}
+
+@end
+
+@implementation STracingSubscriber
+
+- (instancetype)initWithName:(NSString *)name next:(void (^)(id))next error:(void (^)(id))error completed:(void (^)())completed
+{
+ self = [super initWithNext:next error:error completed:completed];
+ if (self != nil)
+ {
+ _name = name;
+ }
+ return self;
+}
+
+/*- (void)_assignDisposable:(id)disposable
+{
+ if (_terminated)
+ [disposable dispose];
+ else
+ _disposable = disposable;
+}
+
+- (void)_markTerminatedWithoutDisposal
+{
+ OSSpinLockLock(&_lock);
+ if (!_terminated)
+ {
+ NSLog(@"trace(%@ terminated)", _name);
+ _terminated = true;
+ _next = nil;
+ _error = nil;
+ _completed = nil;
+ }
+ OSSpinLockUnlock(&_lock);
+}
+
+- (void)putNext:(id)next
+{
+ void (^fnext)(id) = nil;
+
+ OSSpinLockLock(&_lock);
+ if (!_terminated)
+ fnext = self->_next;
+ OSSpinLockUnlock(&_lock);
+
+ if (fnext)
+ {
+ NSLog(@"trace(%@ next: %@)", _name, next);
+ fnext(next);
+ }
+ else
+ NSLog(@"trace(%@ next: %@, not accepted)", _name, next);
+}
+
+- (void)putError:(id)error
+{
+ bool shouldDispose = false;
+ void (^ferror)(id) = nil;
+
+ OSSpinLockLock(&_lock);
+ if (!_terminated)
+ {
+ ferror = self->_error;
+ shouldDispose = true;
+ self->_next = nil;
+ self->_error = nil;
+ self->_completed = nil;
+ _terminated = true;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (ferror)
+ {
+ NSLog(@"trace(%@ error: %@)", _name, error);
+ ferror(error);
+ }
+ else
+ NSLog(@"trace(%@ error: %@, not accepted)", _name, error);
+
+ if (shouldDispose)
+ [self->_disposable dispose];
+}
+
+- (void)putCompletion
+{
+ bool shouldDispose = false;
+ void (^completed)() = nil;
+
+ OSSpinLockLock(&_lock);
+ if (!_terminated)
+ {
+ completed = self->_completed;
+ shouldDispose = true;
+ self->_next = nil;
+ self->_error = nil;
+ self->_completed = nil;
+ _terminated = true;
+ }
+ OSSpinLockUnlock(&_lock);
+
+ if (completed)
+ {
+ NSLog(@"trace(%@ completed)", _name);
+ completed();
+ }
+ else
+ NSLog(@"trace(%@ completed, not accepted)", _name);
+
+ if (shouldDispose)
+ [self->_disposable dispose];
+}
+
+- (void)dispose
+{
+ NSLog(@"trace(%@ dispose)", _name);
+ [self->_disposable dispose];
+}*/
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SThreadPool.h b/submodules/SSignalKit/SSignalKit/SThreadPool.h
new file mode 100644
index 0000000000..69d0565938
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SThreadPool.h
@@ -0,0 +1,15 @@
+#import
+
+#import
+#import
+
+@interface SThreadPool : NSObject
+
+- (instancetype)initWithThreadCount:(NSUInteger)threadCount threadPriority:(double)threadPriority;
+
+- (void)addTask:(SThreadPoolTask *)task;
+
+- (SThreadPoolQueue *)nextQueue;
+- (void)_workOnQueue:(SThreadPoolQueue *)queue block:(void (^)())block;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SThreadPool.m b/submodules/SSignalKit/SSignalKit/SThreadPool.m
new file mode 100644
index 0000000000..060ee29c92
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SThreadPool.m
@@ -0,0 +1,128 @@
+#import "SThreadPool.h"
+
+#import
+#import
+#import "SQueue.h"
+
+@interface SThreadPool ()
+{
+ SQueue *_managementQueue;
+ NSMutableArray *_threads;
+
+ NSMutableArray *_queues;
+ NSMutableArray *_takenQueues;
+
+ pthread_mutex_t _mutex;
+ pthread_cond_t _cond;
+}
+
+@end
+
+@implementation SThreadPool
+
++ (void)threadEntryPoint:(SThreadPool *)threadPool
+{
+ SThreadPoolQueue *queue = nil;
+
+ while (true)
+ {
+ SThreadPoolTask *task = nil;
+
+ pthread_mutex_lock(&threadPool->_mutex);
+
+ if (queue != nil)
+ {
+ [threadPool->_takenQueues removeObject:queue];
+ if ([queue _hasTasks])
+ [threadPool->_queues addObject:queue];
+ }
+
+ while (true)
+ {
+ while (threadPool->_queues.count == 0)
+ pthread_cond_wait(&threadPool->_cond, &threadPool->_mutex);
+
+ queue = threadPool->_queues.firstObject;
+ task = [queue _popFirstTask];
+
+ if (queue != nil)
+ {
+ [threadPool->_takenQueues addObject:queue];
+ [threadPool->_queues removeObjectAtIndex:0];
+
+ break;
+ }
+ }
+ pthread_mutex_unlock(&threadPool->_mutex);
+
+ @autoreleasepool
+ {
+ [task execute];
+ }
+ }
+}
+
+- (instancetype)init
+{
+ return [self initWithThreadCount:2 threadPriority:0.5];
+}
+
+- (instancetype)initWithThreadCount:(NSUInteger)threadCount threadPriority:(double)threadPriority
+{
+ self = [super init];
+ if (self != nil)
+ {
+ pthread_mutex_init(&_mutex, 0);
+ pthread_cond_init(&_cond, 0);
+
+ _managementQueue = [[SQueue alloc] init];
+
+ [_managementQueue dispatch:^
+ {
+ _threads = [[NSMutableArray alloc] init];
+ _queues = [[NSMutableArray alloc] init];
+ _takenQueues = [[NSMutableArray alloc] init];
+ for (NSUInteger i = 0; i < threadCount; i++)
+ {
+ NSThread *thread = [[NSThread alloc] initWithTarget:[SThreadPool class] selector:@selector(threadEntryPoint:) object:self];
+ thread.name = [[NSString alloc] initWithFormat:@"SThreadPool-%p-%d", self, (int)i];
+ [thread setThreadPriority:threadPriority];
+ [_threads addObject:thread];
+ [thread start];
+ }
+ }];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ pthread_mutex_destroy(&_mutex);
+ pthread_cond_destroy(&_cond);
+}
+
+- (void)addTask:(SThreadPoolTask *)task
+{
+ SThreadPoolQueue *tempQueue = [self nextQueue];
+ [tempQueue addTask:task];
+}
+
+- (SThreadPoolQueue *)nextQueue
+{
+ return [[SThreadPoolQueue alloc] initWithThreadPool:self];
+}
+
+- (void)_workOnQueue:(SThreadPoolQueue *)queue block:(void (^)())block
+{
+ [_managementQueue dispatch:^
+ {
+ pthread_mutex_lock(&_mutex);
+ block();
+ if (![_queues containsObject:queue] && ![_takenQueues containsObject:queue])
+ [_queues addObject:queue];
+ pthread_cond_broadcast(&_cond);
+ pthread_mutex_unlock(&_mutex);
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SThreadPoolQueue.h b/submodules/SSignalKit/SSignalKit/SThreadPoolQueue.h
new file mode 100644
index 0000000000..3d8d53b00c
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SThreadPoolQueue.h
@@ -0,0 +1,13 @@
+#import
+
+@class SThreadPool;
+@class SThreadPoolTask;
+
+@interface SThreadPoolQueue : NSObject
+
+- (instancetype)initWithThreadPool:(SThreadPool *)threadPool;
+- (void)addTask:(SThreadPoolTask *)task;
+- (SThreadPoolTask *)_popFirstTask;
+- (bool)_hasTasks;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SThreadPoolQueue.m b/submodules/SSignalKit/SSignalKit/SThreadPoolQueue.m
new file mode 100644
index 0000000000..ff857e642d
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SThreadPoolQueue.m
@@ -0,0 +1,51 @@
+#import "SThreadPoolQueue.h"
+
+#import "SThreadPool.h"
+
+@interface SThreadPoolQueue ()
+{
+ __weak SThreadPool *_threadPool;
+ NSMutableArray *_tasks;
+}
+
+@end
+
+@implementation SThreadPoolQueue
+
+- (instancetype)initWithThreadPool:(SThreadPool *)threadPool
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _threadPool = threadPool;
+ _tasks = [[NSMutableArray alloc] init];
+ }
+ return self;
+}
+
+- (void)addTask:(SThreadPoolTask *)task
+{
+ SThreadPool *threadPool = _threadPool;
+ [threadPool _workOnQueue:self block:^
+ {
+ [_tasks addObject:task];
+ }];
+}
+
+- (SThreadPoolTask *)_popFirstTask
+{
+ if (_tasks.count != 0)
+ {
+ SThreadPoolTask *task = _tasks[0];
+ [_tasks removeObjectAtIndex:0];
+ return task;
+ }
+ return nil;
+}
+
+- (bool)_hasTasks
+{
+ return _tasks.count != 0;
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SThreadPoolTask.h b/submodules/SSignalKit/SSignalKit/SThreadPoolTask.h
new file mode 100644
index 0000000000..e8da985ca0
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SThreadPoolTask.h
@@ -0,0 +1,9 @@
+#import
+
+@interface SThreadPoolTask : NSObject
+
+- (instancetype)initWithBlock:(void (^)(bool (^)()))block;
+- (void)execute;
+- (void)cancel;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SThreadPoolTask.m b/submodules/SSignalKit/SSignalKit/SThreadPoolTask.m
new file mode 100644
index 0000000000..c967022e01
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SThreadPoolTask.m
@@ -0,0 +1,53 @@
+#import "SThreadPoolTask.h"
+
+@interface SThreadPoolTaskState : NSObject
+{
+ @public
+ bool _cancelled;
+}
+
+@end
+
+@implementation SThreadPoolTaskState
+
+@end
+
+@interface SThreadPoolTask ()
+{
+ void (^_block)(bool (^)());
+ SThreadPoolTaskState *_state;
+}
+
+@end
+
+@implementation SThreadPoolTask
+
+- (instancetype)initWithBlock:(void (^)(bool (^)()))block
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _block = [block copy];
+ _state = [[SThreadPoolTaskState alloc] init];
+ }
+ return self;
+}
+
+- (void)execute
+{
+ if (_state->_cancelled)
+ return;
+
+ SThreadPoolTaskState *state = _state;
+ _block(^bool
+ {
+ return state->_cancelled;
+ });
+}
+
+- (void)cancel
+{
+ _state->_cancelled = true;
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/STimer.h b/submodules/SSignalKit/SSignalKit/STimer.h
new file mode 100644
index 0000000000..621e4232b2
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/STimer.h
@@ -0,0 +1,14 @@
+#import
+
+@class SQueue;
+
+@interface STimer : NSObject
+
+- (id)initWithTimeout:(NSTimeInterval)timeout repeat:(bool)repeat completion:(dispatch_block_t)completion queue:(SQueue *)queue;
+- (id)initWithTimeout:(NSTimeInterval)timeout repeat:(bool)repeat completion:(dispatch_block_t)completion nativeQueue:(dispatch_queue_t)nativeQueue;
+
+- (void)start;
+- (void)invalidate;
+- (void)fireAndInvalidate;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/STimer.m b/submodules/SSignalKit/SSignalKit/STimer.m
new file mode 100644
index 0000000000..235c2f883a
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/STimer.m
@@ -0,0 +1,83 @@
+#import "STimer.h"
+
+#import "SQueue.h"
+
+@interface STimer ()
+{
+ dispatch_source_t _timer;
+ NSTimeInterval _timeout;
+ NSTimeInterval _timeoutDate;
+ bool _repeat;
+ dispatch_block_t _completion;
+ dispatch_queue_t _nativeQueue;
+}
+
+@end
+
+@implementation STimer
+
+- (id)initWithTimeout:(NSTimeInterval)timeout repeat:(bool)repeat completion:(dispatch_block_t)completion queue:(SQueue *)queue {
+ return [self initWithTimeout:timeout repeat:repeat completion:completion nativeQueue:queue._dispatch_queue];
+}
+
+- (id)initWithTimeout:(NSTimeInterval)timeout repeat:(bool)repeat completion:(dispatch_block_t)completion nativeQueue:(dispatch_queue_t)nativeQueue
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _timeoutDate = INT_MAX;
+
+ _timeout = timeout;
+ _repeat = repeat;
+ _completion = [completion copy];
+ _nativeQueue = nativeQueue;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if (_timer != nil)
+ {
+ dispatch_source_cancel(_timer);
+ _timer = nil;
+ }
+}
+
+- (void)start
+{
+ _timeoutDate = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970 + _timeout;
+
+ _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _nativeQueue);
+ dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_timeout * NSEC_PER_SEC)), _repeat ? (int64_t)(_timeout * NSEC_PER_SEC) : DISPATCH_TIME_FOREVER, 0);
+
+ dispatch_source_set_event_handler(_timer, ^
+ {
+ if (_completion)
+ _completion();
+ if (!_repeat)
+ [self invalidate];
+ });
+ dispatch_resume(_timer);
+}
+
+- (void)fireAndInvalidate
+{
+ if (_completion)
+ _completion();
+
+ [self invalidate];
+}
+
+- (void)invalidate
+{
+ _timeoutDate = 0;
+
+ if (_timer != nil)
+ {
+ dispatch_source_cancel(_timer);
+ _timer = nil;
+ }
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SVariable.h b/submodules/SSignalKit/SSignalKit/SVariable.h
new file mode 100644
index 0000000000..47d51a1608
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SVariable.h
@@ -0,0 +1,12 @@
+#import
+
+@class SSignal;
+
+@interface SVariable : NSObject
+
+- (instancetype)init;
+
+- (void)set:(SSignal *)signal;
+- (SSignal *)signal;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit/SVariable.m b/submodules/SSignalKit/SSignalKit/SVariable.m
new file mode 100644
index 0000000000..45eea46a2c
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit/SVariable.m
@@ -0,0 +1,93 @@
+#import "SVariable.h"
+
+#import
+
+#import "SSignal.h"
+#import "SBag.h"
+#import "SBlockDisposable.h"
+#import "SMetaDisposable.h"
+
+@interface SVariable ()
+{
+ OSSpinLock _lock;
+ id _value;
+ bool _hasValue;
+ SBag *_subscribers;
+ SMetaDisposable *_disposable;
+}
+
+@end
+
+@implementation SVariable
+
+- (instancetype)init
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _subscribers = [[SBag alloc] init];
+ _disposable = [[SMetaDisposable alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [_disposable dispose];
+}
+
+- (SSignal *)signal
+{
+ return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ OSSpinLockLock(&self->_lock);
+ id currentValue = _value;
+ bool hasValue = _hasValue;
+ NSInteger index = [self->_subscribers addItem:[^(id value)
+ {
+ [subscriber putNext:value];
+ } copy]];
+ OSSpinLockUnlock(&self->_lock);
+
+ if (hasValue)
+ {
+ [subscriber putNext:currentValue];
+ }
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ OSSpinLockLock(&self->_lock);
+ [self->_subscribers removeItem:index];
+ OSSpinLockUnlock(&self->_lock);
+ }];
+ }];
+}
+
+- (void)set:(SSignal *)signal
+{
+ OSSpinLockLock(&_lock);
+ _hasValue = false;
+ OSSpinLockUnlock(&_lock);
+
+ __weak SVariable *weakSelf = self;
+ [_disposable setDisposable:[signal startWithNext:^(id next)
+ {
+ __strong SVariable *strongSelf = weakSelf;
+ if (strongSelf != nil)
+ {
+ NSArray *subscribers = nil;
+ OSSpinLockLock(&strongSelf->_lock);
+ strongSelf->_value = next;
+ strongSelf->_hasValue = true;
+ subscribers = [strongSelf->_subscribers copyItems];
+ OSSpinLockUnlock(&strongSelf->_lock);
+
+ for (void (^subscriber)(id) in subscribers)
+ {
+ subscriber(next);
+ }
+ }
+ }]];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKitTests/DeallocatingObject.h b/submodules/SSignalKit/SSignalKitTests/DeallocatingObject.h
new file mode 100644
index 0000000000..00f3c9cc8b
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKitTests/DeallocatingObject.h
@@ -0,0 +1,7 @@
+#import
+
+@interface DeallocatingObject : NSObject
+
+- (instancetype)initWithDeallocated:(bool *)deallocated;
+
+@end
diff --git a/submodules/SSignalKit/SSignalKitTests/DeallocatingObject.m b/submodules/SSignalKit/SSignalKitTests/DeallocatingObject.m
new file mode 100644
index 0000000000..38cff658e6
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKitTests/DeallocatingObject.m
@@ -0,0 +1,27 @@
+#import "DeallocatingObject.h"
+
+@interface DeallocatingObject ()
+{
+ bool *_deallocated;
+}
+
+@end
+
+@implementation DeallocatingObject
+
+- (instancetype)initWithDeallocated:(bool *)deallocated
+{
+ self = [super init];
+ if (self != nil)
+ {
+ _deallocated = deallocated;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ *_deallocated = true;
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKitTests/Info.plist b/submodules/SSignalKit/SSignalKitTests/Info.plist
new file mode 100644
index 0000000000..ba72822e87
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKitTests/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+
+
diff --git a/submodules/SSignalKit/SSignalKitTests/SDisposableTests.m b/submodules/SSignalKit/SSignalKitTests/SDisposableTests.m
new file mode 100644
index 0000000000..3c71105e4e
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKitTests/SDisposableTests.m
@@ -0,0 +1,318 @@
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+#import
+#else
+#import
+#endif
+#import
+
+#import
+
+@import SSignalKit;
+
+#import "DeallocatingObject.h"
+
+@interface SDisposableTests : XCTestCase
+
+@end
+
+@implementation SDisposableTests
+
+- (void)setUp
+{
+ [super setUp];
+}
+
+- (void)tearDown
+{
+ [super tearDown];
+}
+
+- (void)testBlockDisposableDisposed
+{
+ bool deallocated = false;
+ __block bool disposed = false;
+ {
+ DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated];
+ dispatch_block_t block = ^{
+ [object description];
+ disposed = true;
+ };
+ SBlockDisposable *disposable = [[SBlockDisposable alloc] initWithBlock:[block copy]];
+ object = nil;
+ block = nil;
+ [disposable dispose];
+ }
+
+ XCTAssertTrue(deallocated);
+ XCTAssertTrue(disposed);
+}
+
+- (void)testBlockDisposableNotDisposed
+{
+ bool deallocated = false;
+ __block bool disposed = false;
+ {
+ DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated];
+ dispatch_block_t block = ^{
+ [object description];
+ disposed = true;
+ };
+ SBlockDisposable *disposable = [[SBlockDisposable alloc] initWithBlock:[block copy]];
+ [disposable description];
+ }
+
+ XCTAssertTrue(deallocated);
+ XCTAssertFalse(disposed);
+}
+
+- (void)testMetaDisposableDisposed
+{
+ bool deallocated = false;
+ __block bool disposed = false;
+ {
+ DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated];
+ dispatch_block_t block = ^{
+ [object description];
+ disposed = true;
+ };
+ SBlockDisposable *blockDisposable = [[SBlockDisposable alloc] initWithBlock:[block copy]];
+
+ SMetaDisposable *metaDisposable = [[SMetaDisposable alloc] init];
+ [metaDisposable setDisposable:blockDisposable];
+ [metaDisposable dispose];
+ }
+
+ XCTAssertTrue(deallocated);
+ XCTAssertTrue(disposed);
+}
+
+- (void)testMetaDisposableDisposedMultipleTimes
+{
+ bool deallocated1 = false;
+ __block bool disposed1 = false;
+ bool deallocated2 = false;
+ __block bool disposed2 = false;
+ {
+ DeallocatingObject *object1 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated1];
+ dispatch_block_t block1 = ^{
+ [object1 description];
+ disposed1 = true;
+ };
+ SBlockDisposable *blockDisposable1 = [[SBlockDisposable alloc] initWithBlock:[block1 copy]];
+
+ DeallocatingObject *object2 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated2];
+ dispatch_block_t block2 = ^{
+ [object2 description];
+ disposed2 = true;
+ };
+ SBlockDisposable *blockDisposable2 = [[SBlockDisposable alloc] initWithBlock:[block2 copy]];
+
+ SMetaDisposable *metaDisposable = [[SMetaDisposable alloc] init];
+ [metaDisposable setDisposable:blockDisposable1];
+ [metaDisposable setDisposable:blockDisposable2];
+ [metaDisposable dispose];
+ }
+
+ XCTAssertTrue(deallocated1);
+ XCTAssertTrue(disposed1);
+ XCTAssertTrue(deallocated2);
+ XCTAssertTrue(disposed2);
+}
+
+- (void)testMetaDisposableNotDisposed
+{
+ bool deallocated = false;
+ __block bool disposed = false;
+ {
+ DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated];
+ dispatch_block_t block = ^{
+ [object description];
+ disposed = true;
+ };
+ SBlockDisposable *blockDisposable = [[SBlockDisposable alloc] initWithBlock:[block copy]];
+
+ SMetaDisposable *metaDisposable = [[SMetaDisposable alloc] init];
+ [metaDisposable setDisposable:blockDisposable];
+ }
+
+ XCTAssertTrue(deallocated);
+ XCTAssertFalse(disposed);
+}
+
+- (void)testDisposableSetSingleDisposed
+{
+ bool deallocated = false;
+ __block bool disposed = false;
+ {
+ DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated];
+ dispatch_block_t block = ^{
+ [object description];
+ disposed = true;
+ };
+ SBlockDisposable *blockDisposable = [[SBlockDisposable alloc] initWithBlock:[block copy]];
+
+ SDisposableSet *disposableSet = [[SDisposableSet alloc] init];
+ [disposableSet add:blockDisposable];
+ [disposableSet dispose];
+ }
+
+ XCTAssertTrue(deallocated);
+ XCTAssertTrue(disposed);
+}
+
+- (void)testDisposableSetMultipleDisposed
+{
+ bool deallocated1 = false;
+ __block bool disposed1 = false;
+ bool deallocated2 = false;
+ __block bool disposed2 = false;
+ {
+ DeallocatingObject *object1 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated1];
+ dispatch_block_t block1 = ^{
+ [object1 description];
+ disposed1 = true;
+ };
+ SBlockDisposable *blockDisposable1 = [[SBlockDisposable alloc] initWithBlock:[block1 copy]];
+
+ DeallocatingObject *object2 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated2];
+ dispatch_block_t block2 = ^{
+ [object2 description];
+ disposed2 = true;
+ };
+ SBlockDisposable *blockDisposable2 = [[SBlockDisposable alloc] initWithBlock:[block2 copy]];
+
+ SDisposableSet *disposableSet = [[SDisposableSet alloc] init];
+ [disposableSet add:blockDisposable1];
+ [disposableSet add:blockDisposable2];
+ [disposableSet dispose];
+ }
+
+ XCTAssertTrue(deallocated1);
+ XCTAssertTrue(disposed1);
+ XCTAssertTrue(deallocated2);
+ XCTAssertTrue(disposed2);
+}
+
+- (void)testDisposableSetSingleNotDisposed
+{
+ bool deallocated = false;
+ __block bool disposed = false;
+ {
+ DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated];
+ dispatch_block_t block = ^{
+ [object description];
+ disposed = true;
+ };
+ SBlockDisposable *blockDisposable = [[SBlockDisposable alloc] initWithBlock:[block copy]];
+
+ SDisposableSet *disposableSet = [[SDisposableSet alloc] init];
+ [disposableSet add:blockDisposable];
+ }
+
+ XCTAssertTrue(deallocated);
+ XCTAssertFalse(disposed);
+}
+
+- (void)testDisposableSetMultipleNotDisposed
+{
+ bool deallocated1 = false;
+ __block bool disposed1 = false;
+ bool deallocated2 = false;
+ __block bool disposed2 = false;
+ {
+ DeallocatingObject *object1 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated1];
+ dispatch_block_t block1 = ^{
+ [object1 description];
+ disposed1 = true;
+ };
+ SBlockDisposable *blockDisposable1 = [[SBlockDisposable alloc] initWithBlock:[block1 copy]];
+
+ DeallocatingObject *object2 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated2];
+ dispatch_block_t block2 = ^{
+ [object2 description];
+ disposed2 = true;
+ };
+ SBlockDisposable *blockDisposable2 = [[SBlockDisposable alloc] initWithBlock:[block2 copy]];
+
+ SDisposableSet *disposableSet = [[SDisposableSet alloc] init];
+ [disposableSet add:blockDisposable1];
+ [disposableSet add:blockDisposable2];
+ }
+
+ XCTAssertTrue(deallocated1);
+ XCTAssertFalse(disposed1);
+ XCTAssertTrue(deallocated2);
+ XCTAssertFalse(disposed2);
+}
+
+- (void)testMetaDisposableAlreadyDisposed
+{
+ bool deallocated1 = false;
+ __block bool disposed1 = false;
+ bool deallocated2 = false;
+ __block bool disposed2 = false;
+
+ @autoreleasepool
+ {
+ DeallocatingObject *object1 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated1];
+ dispatch_block_t block1 = ^{
+ [object1 description];
+ disposed1 = true;
+ };
+ SBlockDisposable *blockDisposable1 = [[SBlockDisposable alloc] initWithBlock:[block1 copy]];
+
+ DeallocatingObject *object2 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated2];
+ dispatch_block_t block2 = ^{
+ [object2 description];
+ disposed2 = true;
+ };
+ SBlockDisposable *blockDisposable2 = [[SBlockDisposable alloc] initWithBlock:[block2 copy]];
+
+ SMetaDisposable *metaDisposable = [[SMetaDisposable alloc] init];
+ [metaDisposable setDisposable:blockDisposable1];
+ [metaDisposable dispose];
+ [metaDisposable setDisposable:blockDisposable2];
+ }
+
+ XCTAssertTrue(deallocated1);
+ XCTAssertTrue(disposed1);
+ XCTAssertTrue(deallocated2);
+ XCTAssertTrue(disposed2);
+}
+
+- (void)testDisposableSetAlreadyDisposed
+{
+ bool deallocated1 = false;
+ __block bool disposed1 = false;
+ bool deallocated2 = false;
+ __block bool disposed2 = false;
+
+ @autoreleasepool
+ {
+ DeallocatingObject *object1 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated1];
+ dispatch_block_t block1 = ^{
+ [object1 description];
+ disposed1 = true;
+ };
+ SBlockDisposable *blockDisposable1 = [[SBlockDisposable alloc] initWithBlock:[block1 copy]];
+
+ DeallocatingObject *object2 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated2];
+ dispatch_block_t block2 = ^{
+ [object2 description];
+ disposed2 = true;
+ };
+ SBlockDisposable *blockDisposable2 = [[SBlockDisposable alloc] initWithBlock:[block2 copy]];
+
+ SMetaDisposable *metaDisposable = [[SMetaDisposable alloc] init];
+ [metaDisposable setDisposable:blockDisposable1];
+ [metaDisposable dispose];
+ [metaDisposable setDisposable:blockDisposable2];
+ }
+
+ XCTAssertTrue(deallocated1);
+ XCTAssertTrue(disposed1);
+ XCTAssertTrue(deallocated2);
+ XCTAssertTrue(disposed2);
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKitTests/SSignalBasicTests.m b/submodules/SSignalKit/SSignalKitTests/SSignalBasicTests.m
new file mode 100644
index 0000000000..56ca98337c
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKitTests/SSignalBasicTests.m
@@ -0,0 +1,764 @@
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+#import
+#else
+#import
+#endif
+#import
+
+@import SSignalKit;
+
+#import "DeallocatingObject.h"
+
+@interface DisposableHolder : NSObject {
+}
+
+@property (nonatomic, strong) id disposable;
+
+@end
+
+@implementation DisposableHolder
+
+- (instancetype)init {
+ self = [super init];
+ if (self != nil) {
+ _disposable = [[[SSignal single:nil] delay:1.0 onQueue:[SQueue concurrentDefaultQueue]] startWithNext:^(__unused id next){
+ [self description];
+ }];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [_disposable dispose];
+}
+
+@end
+
+@interface SSignalBasicTests : XCTestCase
+
+@end
+
+@implementation SSignalBasicTests
+
+- (void)setUp
+{
+ [super setUp];
+}
+
+- (void)tearDown
+{
+ [super tearDown];
+}
+
+- (void)testSignalGenerated
+{
+ __block bool deallocated = false;
+ __block bool disposed = false;
+ __block bool generated = false;
+
+ {
+ DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated];
+ SSignal *signal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ [subscriber putNext:@1];
+ [object description];
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ [object description];
+ disposed = true;
+ }];
+ }];
+ id disposable = [signal startWithNext:^(__unused id next)
+ {
+ generated = true;
+ [object description];
+ } error:nil completed:nil];
+ [disposable dispose];
+ }
+
+ XCTAssertTrue(deallocated);
+ XCTAssertTrue(disposed);
+ XCTAssertTrue(generated);
+}
+
+- (void)testSignalGeneratedCompleted
+{
+ __block bool deallocated = false;
+ __block bool disposed = false;
+ __block bool generated = false;
+ __block bool completed = false;
+
+ {
+ DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated];
+ SSignal *signal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ [subscriber putNext:@1];
+ [subscriber putCompletion];
+ [object description];
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ [object description];
+ disposed = true;
+ }];
+ }];
+ id disposable = [signal startWithNext:^(__unused id next)
+ {
+ [object description];
+ generated = true;
+ } error:nil completed:^
+ {
+ [object description];
+ completed = true;
+ }];
+ [disposable dispose];
+ }
+
+ XCTAssertTrue(deallocated);
+ XCTAssertTrue(disposed);
+ XCTAssertTrue(generated);
+ XCTAssertTrue(completed);
+}
+
+- (void)testSignalGeneratedError
+{
+ __block bool deallocated = false;
+ __block bool disposed = false;
+ __block bool generated = false;
+ __block bool error = false;
+
+ {
+ DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated];
+ SSignal *signal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ [subscriber putNext:@1];
+ [subscriber putError:@1];
+ [object description];
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ [object description];
+ disposed = true;
+ }];
+ }];
+ id disposable = [signal startWithNext:^(__unused id next)
+ {
+ generated = true;
+ } error:^(__unused id value)
+ {
+ error = true;
+ } completed:nil];
+ [disposable dispose];
+ }
+
+ XCTAssertTrue(deallocated);
+ XCTAssertTrue(disposed);
+ XCTAssertTrue(generated);
+ XCTAssertTrue(error);
+}
+
+- (void)testMap
+{
+ bool deallocated = false;
+ __block bool disposed = false;
+ __block bool generated = false;
+
+ {
+ @autoreleasepool
+ {
+ DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated];
+ SSignal *signal = [[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ [subscriber putNext:@1];
+ [object description];
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ [object description];
+ disposed = true;
+ }];
+ }] map:^id(id value)
+ {
+ [object description];
+ return @([value intValue] * 2);
+ }];
+
+ id disposable = [signal startWithNext:^(id value)
+ {
+ generated = [value isEqual:@2];
+ } error:nil completed:nil];
+ [disposable dispose];
+ }
+ }
+
+ XCTAssertTrue(deallocated);
+ XCTAssertTrue(disposed);
+ XCTAssertTrue(generated);
+}
+
+- (void)testSubscriberDisposal
+{
+ __block bool disposed = false;
+ __block bool generated = false;
+
+ dispatch_queue_t queue = dispatch_queue_create(NULL, 0);
+
+ @autoreleasepool
+ {
+ SSignal *signal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ dispatch_async(queue, ^
+ {
+ usleep(200);
+ [subscriber putNext:@1];
+ });
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ disposed = true;
+ }];
+ }];
+
+ id disposable = [signal startWithNext:^(id value)
+ {
+ generated = true;
+ } error:nil completed:nil];
+ NSLog(@"dispose");
+ [disposable dispose];
+ }
+
+ dispatch_barrier_sync(queue, ^
+ {
+ });
+
+ XCTAssertTrue(disposed);
+ XCTAssertFalse(generated);
+}
+
+- (void)testThen
+{
+ __block bool generatedFirst = false;
+ __block bool disposedFirst = false;
+ __block bool generatedSecond = false;
+ __block bool disposedSecond = false;
+ __block int result = 0;
+
+ SSignal *signal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ generatedFirst = true;
+ [subscriber putNext:@(1)];
+ [subscriber putCompletion];
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ disposedFirst = true;
+ }];
+ }];
+
+ signal = [signal then:[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ generatedSecond = true;
+ [subscriber putNext:@(2)];
+ [subscriber putCompletion];
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ disposedSecond = true;
+ }];
+ }]];
+
+ [signal startWithNext:^(id next)
+ {
+ result += [next intValue];
+ }];
+
+ XCTAssertTrue(generatedFirst);
+ XCTAssertTrue(disposedFirst);
+ XCTAssertTrue(generatedSecond);
+ XCTAssertTrue(disposedSecond);
+ XCTAssert(result == 3);
+}
+
+- (void)testSwitchToLatest
+{
+ __block int result = 0;
+ __block bool disposedOne = false;
+ __block bool disposedTwo = false;
+ __block bool disposedThree = false;
+ __block bool completedAll = false;
+
+ bool deallocatedOne = false;
+ bool deallocatedTwo = false;
+ bool deallocatedThree = false;
+
+ @autoreleasepool
+ {
+ DeallocatingObject *objectOne = [[DeallocatingObject alloc] initWithDeallocated:&deallocatedOne];
+ DeallocatingObject *objectTwo = [[DeallocatingObject alloc] initWithDeallocated:&deallocatedTwo];
+ DeallocatingObject *objectThree = [[DeallocatingObject alloc] initWithDeallocated:&deallocatedThree];
+
+ SSignal *one = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ [subscriber putNext:@(1)];
+ [subscriber putCompletion];
+ __unused id a0 = [objectOne description];
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ __unused id a0 = [objectOne description];
+ disposedOne = true;
+ }];
+ }];
+ SSignal *two = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ [subscriber putNext:@(2)];
+ [subscriber putCompletion];
+ __unused id a1 = [objectTwo description];
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ __unused id a1 = [objectOne description];
+ disposedTwo = true;
+ }];
+ }];
+ SSignal *three = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ [subscriber putNext:@(3)];
+ [subscriber putCompletion];
+ __unused id a0 = [objectThree description];
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ __unused id a1 = [objectOne description];
+ disposedThree = true;
+ }];
+ }];
+
+ SSignal *signal = [[[[SSignal single:one] then:[SSignal single:two]] then:[SSignal single:three]] switchToLatest];
+ [signal startWithNext:^(id next)
+ {
+ result += [next intValue];
+ } error:nil completed:^
+ {
+ completedAll = true;
+ }];
+ }
+
+ XCTAssert(result == 6);
+ XCTAssertTrue(disposedOne);
+ XCTAssertTrue(disposedTwo);
+ XCTAssertTrue(disposedThree);
+ XCTAssertTrue(deallocatedOne);
+ XCTAssertTrue(deallocatedTwo);
+ XCTAssertTrue(deallocatedThree);
+ XCTAssertTrue(completedAll);
+}
+
+- (void)testSwitchToLatestError
+{
+ __block bool errorGenerated = false;
+
+ SSignal *one = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ [subscriber putError:nil];
+ return nil;
+ }];
+
+ [one startWithNext:^(__unused id next)
+ {
+
+ } error:^(__unused id error)
+ {
+ errorGenerated = true;
+ } completed:^
+ {
+
+ }];
+
+ XCTAssertTrue(errorGenerated);
+}
+
+- (void)testSwitchToLatestCompleted
+{
+ __block bool completedAll = false;
+
+ SSignal *one = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ [subscriber putCompletion];
+ return nil;
+ }];
+
+ [one startWithNext:^(__unused id next)
+ {
+
+ } error:^(__unused id error)
+ {
+ } completed:^
+ {
+ completedAll = true;
+ }];
+
+ XCTAssertTrue(completedAll);
+}
+
+- (void)testQueue
+{
+ dispatch_queue_t queue = dispatch_queue_create(NULL, 0);
+
+ __block bool disposedFirst = false;
+ __block bool disposedSecond = false;
+ __block bool disposedThird = false;
+ __block int result = 0;
+
+ SSignal *firstSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ dispatch_async(queue, ^
+ {
+ usleep(100);
+ [subscriber putNext:@1];
+ [subscriber putCompletion];
+ });
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ disposedFirst = true;
+ }];
+ }];
+
+ SSignal *secondSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ dispatch_async(queue, ^
+ {
+ usleep(100);
+ [subscriber putNext:@2];
+ [subscriber putCompletion];
+ });
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ disposedSecond = true;
+ }];
+ }];
+
+ SSignal *thirdSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ dispatch_async(queue, ^
+ {
+ usleep(100);
+ [subscriber putNext:@3];
+ [subscriber putCompletion];
+ });
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ disposedThird = true;
+ }];
+ }];
+
+ SSignal *signal = [[[[SSignal single:firstSignal] then:[SSignal single:secondSignal]] then:[SSignal single:thirdSignal]] queue];
+ [signal startWithNext:^(id next)
+ {
+ result += [next intValue];
+ }];
+
+ usleep(1000);
+
+ XCTAssertEqual(result, 6);
+ XCTAssertTrue(disposedFirst);
+ XCTAssertTrue(disposedSecond);
+ XCTAssertTrue(disposedThird);
+}
+
+- (void)testQueueInterrupted
+{
+ dispatch_queue_t queue = dispatch_queue_create(NULL, 0);
+
+ __block bool disposedFirst = false;
+ __block bool disposedSecond = false;
+ __block bool disposedThird = false;
+ __block bool startedThird = false;
+ __block int result = 0;
+
+ SSignal *firstSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ dispatch_async(queue, ^
+ {
+ usleep(100);
+ [subscriber putNext:@1];
+ [subscriber putCompletion];
+ });
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ disposedFirst = true;
+ }];
+ }];
+
+ SSignal *secondSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ dispatch_async(queue, ^
+ {
+ usleep(100);
+ [subscriber putNext:@2];
+ [subscriber putError:nil];
+ });
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ disposedSecond = true;
+ }];
+ }];
+
+ SSignal *thirdSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ startedThird = true;
+
+ dispatch_async(queue, ^
+ {
+ usleep(100);
+ [subscriber putNext:@3];
+ [subscriber putCompletion];
+ });
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ disposedThird = true;
+ }];
+ }];
+
+ SSignal *signal = [[[[SSignal single:firstSignal] then:[SSignal single:secondSignal]] then:[SSignal single:thirdSignal]] queue];
+ [signal startWithNext:^(id next)
+ {
+ result += [next intValue];
+ }];
+
+ usleep(1000);
+
+ XCTAssertEqual(result, 3);
+ XCTAssertTrue(disposedFirst);
+ XCTAssertTrue(disposedSecond);
+ XCTAssertFalse(startedThird);
+ XCTAssertFalse(disposedThird);
+}
+
+- (void)testQueueDisposed
+{
+ dispatch_queue_t queue = dispatch_queue_create(NULL, 0);
+
+ __block bool disposedFirst = false;
+ __block bool disposedSecond = false;
+ __block bool disposedThird = false;
+ __block bool startedFirst = false;
+ __block bool startedSecond = false;
+ __block bool startedThird = false;
+ __block int result = 0;
+
+ SSignal *firstSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ startedFirst = true;
+
+ __block bool cancelled = false;
+ dispatch_async(queue, ^
+ {
+ if (!cancelled)
+ {
+ usleep(100);
+ [subscriber putNext:@1];
+ [subscriber putCompletion];
+ }
+ });
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ cancelled = true;
+ disposedFirst = true;
+ }];
+ }];
+
+ SSignal *secondSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ startedSecond = true;
+
+ __block bool cancelled = false;
+ dispatch_async(queue, ^
+ {
+ if (!cancelled)
+ {
+ usleep(100);
+ [subscriber putNext:@2];
+ [subscriber putError:nil];
+ }
+ });
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ cancelled = true;
+ disposedSecond = true;
+ }];
+ }];
+
+ SSignal *thirdSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ startedThird = true;
+
+ dispatch_async(queue, ^
+ {
+ usleep(100);
+ [subscriber putNext:@3];
+ [subscriber putCompletion];
+ });
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ disposedThird = true;
+ }];
+ }];
+
+ SSignal *signal = [[[[SSignal single:firstSignal] then:[SSignal single:secondSignal]] then:[SSignal single:thirdSignal]] queue];
+ [[signal startWithNext:^(id next)
+ {
+ result += [next intValue];
+ }] dispose];
+
+ usleep(1000);
+
+ XCTAssertEqual(result, 0);
+ XCTAssertTrue(disposedFirst);
+ XCTAssertFalse(disposedSecond);
+ XCTAssertFalse(disposedThird);
+
+ XCTAssertTrue(startedFirst);
+ XCTAssertFalse(startedSecond);
+ XCTAssertFalse(startedThird);
+}
+
+- (void)testRestart
+{
+ SSignal *signal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ [[SQueue concurrentDefaultQueue] dispatch:^
+ {
+ [subscriber putNext:@1];
+ [subscriber putCompletion];
+ }];
+
+ return [[SBlockDisposable alloc] initWithBlock:^
+ {
+ }];
+ }];
+
+ __block int result = 0;
+
+ [[[signal restart] take:3] startWithNext:^(id next)
+ {
+ result += [next intValue];
+ } error:^(id error) {
+
+ } completed:^{
+
+ }];
+
+ usleep(100 * 1000);
+
+ XCTAssertEqual(result, 3);
+}
+
+- (void)testPipe
+{
+ SPipe *pipe = [[SPipe alloc] init];
+
+ __block int result1 = 0;
+ id disposable1 = [pipe.signalProducer() startWithNext:^(id next)
+ {
+ result1 += [next intValue];
+ }];
+
+ __block int result2 = 0;
+ id disposable2 = [pipe.signalProducer() startWithNext:^(id next)
+ {
+ result2 += [next intValue];
+ }];
+
+ pipe.sink(@1);
+
+ XCTAssertEqual(result1, 1);
+ XCTAssertEqual(result2, 1);
+
+ [disposable1 dispose];
+
+ pipe.sink(@1);
+
+ XCTAssertEqual(result1, 1);
+ XCTAssertEqual(result2, 2);
+
+ [disposable2 dispose];
+
+ pipe.sink(@1);
+
+ XCTAssertEqual(result1, 1);
+ XCTAssertEqual(result2, 2);
+}
+
+- (void)testDisposableDeadlock {
+ @autoreleasepool {
+ DisposableHolder *holder = [[DisposableHolder alloc] init];
+ holder = nil;
+ sleep(10);
+ }
+}
+
+- (void)testRetryIfNoError {
+ SSignal *s = [[SSignal single:@1] retryIf:^bool(__unused id error) {
+ return true;
+ }];
+ [s startWithNext:^(id next) {
+ XCTAssertEqual(next, @1);
+ }];
+}
+
+- (void)testRetryErrorNoMatch {
+ SSignal *s = [[SSignal fail:@false] retryIf:^bool(id error) {
+ return false;
+ }];
+}
+
+- (void)testRetryErrorMatch {
+ __block counter = 1;
+ SSignal *s = [[[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber) {
+ if (counter == 1) {
+ counter++;
+ [subscriber putError:@true];
+ } else {
+ [subscriber putNext:@(counter)];
+ }
+ return nil;
+ }] retryIf:^bool(id error) {
+ return [error boolValue];
+ }];
+
+ __block int value = 0;
+ [s startWithNext:^(id next) {
+ value = [next intValue];
+ }];
+
+ XCTAssertEqual(value, 2);
+}
+
+- (void)testRetryErrorFailNoMatch {
+ __block counter = 1;
+ SSignal *s = [[[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber) {
+ if (counter == 1) {
+ counter++;
+ [subscriber putError:@true];
+ } else {
+ [subscriber putError:@false];
+ }
+ return nil;
+ }] retryIf:^bool(id error) {
+ return [error boolValue];
+ }];
+
+ __block bool errorMatches = false;
+ [s startWithNext:nil error:^(id error) {
+ errorMatches = ![error boolValue];
+ } completed:nil];
+
+ XCTAssert(errorMatches);
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKitTests/SSignalPerformanceTests.m b/submodules/SSignalKit/SSignalKitTests/SSignalPerformanceTests.m
new file mode 100644
index 0000000000..f5384d75bd
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKitTests/SSignalPerformanceTests.m
@@ -0,0 +1,50 @@
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+#import
+#else
+#import
+#endif
+#import
+
+@import SSignalKit;
+
+@interface SSignalPerformanceTests : XCTestCase
+
+@end
+
+@implementation SSignalPerformanceTests
+
+- (void)setUp
+{
+ [super setUp];
+}
+
+- (void)tearDown
+{
+ [super tearDown];
+}
+
+- (void)testMap
+{
+ [self measureBlock:^
+ {
+ SSignal *signal = [[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
+ {
+ [subscriber putNext:@1];
+ [subscriber putCompletion];
+ return nil;
+ }] map:^id (id value)
+ {
+ return value;
+ }];
+
+ for (int i = 0; i < 100000; i++)
+ {
+ [signal startWithNext:^(__unused id next)
+ {
+
+ }];
+ }
+ }];
+}
+
+@end
diff --git a/submodules/SSignalKit/SSignalKit_Xcode.xcodeproj/project.pbxproj b/submodules/SSignalKit/SSignalKit_Xcode.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000..9eeafa5aba
--- /dev/null
+++ b/submodules/SSignalKit/SSignalKit_Xcode.xcodeproj/project.pbxproj
@@ -0,0 +1,2348 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ D0085AE71B28285400EAF753 /* SSignal+Timing.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AB11B28285400EAF753 /* SSignal+Timing.m */; };
+ D0085AE81B28285400EAF753 /* SThreadPool.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AB21B28285400EAF753 /* SThreadPool.m */; };
+ D0085AE91B28285400EAF753 /* SQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AB31B28285400EAF753 /* SQueue.m */; };
+ D0085AEA1B28285400EAF753 /* SSignal+Take.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AB41B28285400EAF753 /* SSignal+Take.m */; };
+ D0085AEB1B28285400EAF753 /* SSignal+Meta.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AB51B28285400EAF753 /* SSignal+Meta.m */; };
+ D0085AEC1B28285400EAF753 /* SSignal+Accumulate.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AB61B28285400EAF753 /* SSignal+Accumulate.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085AED1B28285400EAF753 /* SSignal+Single.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AB71B28285400EAF753 /* SSignal+Single.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085AEE1B28285400EAF753 /* SSignal.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AB81B28285400EAF753 /* SSignal.m */; };
+ D0085AEF1B28285400EAF753 /* SSignalKit.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AB91B28285400EAF753 /* SSignalKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085AF01B28285400EAF753 /* SMulticastSignalManager.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085ABA1B28285400EAF753 /* SMulticastSignalManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085AF11B28285400EAF753 /* SMulticastSignalManager.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085ABB1B28285400EAF753 /* SMulticastSignalManager.m */; };
+ D0085AF21B28285400EAF753 /* SSignal+Combine.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085ABC1B28285400EAF753 /* SSignal+Combine.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085AF31B28285400EAF753 /* SSignal+Combine.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085ABD1B28285400EAF753 /* SSignal+Combine.m */; };
+ D0085AF41B28285400EAF753 /* SSignal+Pipe.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085ABE1B28285400EAF753 /* SSignal+Pipe.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085AF51B28285400EAF753 /* SSignal+Pipe.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085ABF1B28285400EAF753 /* SSignal+Pipe.m */; };
+ D0085AF61B28285400EAF753 /* SSignal+SideEffects.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AC01B28285400EAF753 /* SSignal+SideEffects.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085AF71B28285400EAF753 /* SSignal+SideEffects.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AC11B28285400EAF753 /* SSignal+SideEffects.m */; };
+ D0085AF81B28285400EAF753 /* SSignal+Catch.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AC21B28285400EAF753 /* SSignal+Catch.m */; };
+ D0085AF91B28285400EAF753 /* SSignal+Catch.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AC31B28285400EAF753 /* SSignal+Catch.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085AFA1B28285400EAF753 /* SSignal+Take.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AC41B28285400EAF753 /* SSignal+Take.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085AFB1B28285400EAF753 /* SSignal+Accumulate.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AC51B28285400EAF753 /* SSignal+Accumulate.m */; };
+ D0085AFC1B28285400EAF753 /* SQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AC61B28285400EAF753 /* SQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085AFD1B28285400EAF753 /* SSignal+Meta.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AC71B28285400EAF753 /* SSignal+Meta.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085AFE1B28285400EAF753 /* SSignal+Timing.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AC81B28285400EAF753 /* SSignal+Timing.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085AFF1B28285400EAF753 /* SSubscriber.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AC91B28285400EAF753 /* SSubscriber.m */; };
+ D0085B001B28285400EAF753 /* SSignal+Dispatch.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085ACA1B28285400EAF753 /* SSignal+Dispatch.m */; };
+ D0085B011B28285400EAF753 /* SThreadPoolTask.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085ACB1B28285400EAF753 /* SThreadPoolTask.m */; };
+ D0085B021B28285400EAF753 /* SThreadPoolQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085ACC1B28285400EAF753 /* SThreadPoolQueue.m */; };
+ D0085B031B28285400EAF753 /* SThreadPoolQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085ACD1B28285400EAF753 /* SThreadPoolQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B041B28285400EAF753 /* SThreadPool.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085ACE1B28285400EAF753 /* SThreadPool.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B051B28285400EAF753 /* SThreadPoolTask.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085ACF1B28285400EAF753 /* SThreadPoolTask.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B061B28285400EAF753 /* SDisposableSet.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AD01B28285400EAF753 /* SDisposableSet.m */; };
+ D0085B071B28285400EAF753 /* SMetaDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AD11B28285400EAF753 /* SMetaDisposable.m */; };
+ D0085B081B28285400EAF753 /* SSignal+Multicast.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AD21B28285400EAF753 /* SSignal+Multicast.m */; };
+ D0085B091B28285400EAF753 /* SSubscriber.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AD31B28285400EAF753 /* SSubscriber.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B0A1B28285400EAF753 /* SDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AD41B28285400EAF753 /* SDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B0B1B28285400EAF753 /* SSignal.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AD51B28285400EAF753 /* SSignal.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B0C1B28285400EAF753 /* SSignal+Mapping.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AD61B28285400EAF753 /* SSignal+Mapping.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B0D1B28285400EAF753 /* SSignal+Mapping.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AD71B28285400EAF753 /* SSignal+Mapping.m */; };
+ D0085B0E1B28285400EAF753 /* SSignal+Single.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AD81B28285400EAF753 /* SSignal+Single.m */; };
+ D0085B0F1B28285400EAF753 /* SAtomic.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AD91B28285400EAF753 /* SAtomic.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B101B28285400EAF753 /* SAtomic.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085ADA1B28285400EAF753 /* SAtomic.m */; };
+ D0085B111B28285400EAF753 /* SBag.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085ADB1B28285400EAF753 /* SBag.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B121B28285400EAF753 /* SBag.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085ADC1B28285400EAF753 /* SBag.m */; };
+ D0085B131B28285400EAF753 /* SBlockDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085ADD1B28285400EAF753 /* SBlockDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B141B28285400EAF753 /* SBlockDisposable.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085ADE1B28285400EAF753 /* SBlockDisposable.m */; };
+ D0085B151B28285400EAF753 /* SDisposableSet.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085ADF1B28285400EAF753 /* SDisposableSet.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B181B28285400EAF753 /* SMetaDisposable.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AE21B28285400EAF753 /* SMetaDisposable.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B191B28285400EAF753 /* SSignal+Dispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AE31B28285400EAF753 /* SSignal+Dispatch.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B1A1B28285400EAF753 /* SSignal+Multicast.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AE41B28285400EAF753 /* SSignal+Multicast.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B1B1B28285400EAF753 /* STimer.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085AE51B28285400EAF753 /* STimer.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B1C1B28285400EAF753 /* STimer.m in Sources */ = {isa = PBXBuildFile; fileRef = D0085AE61B28285400EAF753 /* STimer.m */; };
+ D0085B271B282B9800EAF753 /* SwiftSignalKit.h in Headers */ = {isa = PBXBuildFile; fileRef = D0085B261B282B9800EAF753 /* SwiftSignalKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0085B2D1B282B9800EAF753 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0085B221B282B9800EAF753 /* SwiftSignalKit.framework */; };
+ D0085B501B282BEE00EAF753 /* Signal_Timing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B3C1B282BEE00EAF753 /* Signal_Timing.swift */; };
+ D0085B511B282BEE00EAF753 /* Signal_SideEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B3D1B282BEE00EAF753 /* Signal_SideEffects.swift */; };
+ D0085B521B282BEE00EAF753 /* Signal_Dispatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B3E1B282BEE00EAF753 /* Signal_Dispatch.swift */; };
+ D0085B531B282BEE00EAF753 /* ThreadPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B3F1B282BEE00EAF753 /* ThreadPool.swift */; };
+ D0085B541B282BEE00EAF753 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B401B282BEE00EAF753 /* Timer.swift */; };
+ D0085B551B282BEE00EAF753 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B411B282BEE00EAF753 /* Queue.swift */; };
+ D0085B561B282BEE00EAF753 /* ValuePipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B421B282BEE00EAF753 /* ValuePipe.swift */; };
+ D0085B571B282BEE00EAF753 /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B431B282BEE00EAF753 /* Bag.swift */; };
+ D0085B581B282BEE00EAF753 /* Signal_Take.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B441B282BEE00EAF753 /* Signal_Take.swift */; };
+ D0085B591B282BEE00EAF753 /* Signal_Catch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B451B282BEE00EAF753 /* Signal_Catch.swift */; };
+ D0085B5A1B282BEE00EAF753 /* Signal_Single.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B461B282BEE00EAF753 /* Signal_Single.swift */; };
+ D0085B5B1B282BEE00EAF753 /* Signal_Meta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B471B282BEE00EAF753 /* Signal_Meta.swift */; };
+ D0085B5C1B282BEE00EAF753 /* Signal_Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B481B282BEE00EAF753 /* Signal_Combine.swift */; };
+ D0085B5D1B282BEE00EAF753 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B491B282BEE00EAF753 /* Atomic.swift */; };
+ D0085B5E1B282BEE00EAF753 /* Signal_Reduce.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B4A1B282BEE00EAF753 /* Signal_Reduce.swift */; };
+ D0085B5F1B282BEE00EAF753 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B4B1B282BEE00EAF753 /* Signal.swift */; };
+ D0085B601B282BEE00EAF753 /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B4C1B282BEE00EAF753 /* Disposable.swift */; };
+ D0085B611B282BEE00EAF753 /* Signal_Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B4D1B282BEE00EAF753 /* Signal_Mapping.swift */; };
+ D0085B621B282BEE00EAF753 /* Subscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B4E1B282BEE00EAF753 /* Subscriber.swift */; };
+ D0085B661B282C2800EAF753 /* SwiftSignalKitFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B631B282C2800EAF753 /* SwiftSignalKitFunctionsTests.swift */; };
+ D0085B671B282C2800EAF753 /* DeallocatingObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B641B282C2800EAF753 /* DeallocatingObject.swift */; };
+ D0085B681B282C2800EAF753 /* SwiftSignalKitBasicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B651B282C2800EAF753 /* SwiftSignalKitBasicTests.swift */; };
+ D02720B11CD0E005006F1506 /* PerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02720B01CD0E005006F1506 /* PerformanceTests.swift */; };
+ D0445DE41A7C2CA500267924 /* SSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0445DD81A7C2CA500267924 /* SSignalKit.framework */; };
+ D0445E571A7C3FB400267924 /* SDisposableTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D0445E561A7C3FB400267924 /* SDisposableTests.m */; };
+ D0467D1820D7F7BC0055C28F /* Signal_Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0467D1720D7F7BC0055C28F /* Signal_Loop.swift */; };
+ D0467D1920D7F7BC0055C28F /* Signal_Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0467D1720D7F7BC0055C28F /* Signal_Loop.swift */; };
+ D053B4001F16881000E2D58A /* QueueLocalObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D053B3FF1F16881000E2D58A /* QueueLocalObject.swift */; };
+ D053B4011F16881000E2D58A /* QueueLocalObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D053B3FF1F16881000E2D58A /* QueueLocalObject.swift */; };
+ D05F09A81C9EF77100BB6F96 /* Multicast.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05F09A71C9EF77100BB6F96 /* Multicast.swift */; };
+ D066BD0A1C7FE06700D7A576 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D066BD091C7FE06700D7A576 /* Lock.swift */; };
+ D06F106C1A85561E00485185 /* SSignalBasicTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D06F106B1A85561E00485185 /* SSignalBasicTests.m */; };
+ D06F10711A855E2D00485185 /* DeallocatingObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D06F10701A855E2D00485185 /* DeallocatingObject.m */; };
+ D06F10731A85882000485185 /* SSignalPerformanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D06F10721A85882000485185 /* SSignalPerformanceTests.m */; };
+ D07A5CFD1BBDE6E400451791 /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07A5CFC1BBDE6E400451791 /* Promise.swift */; };
+ D09FD73E1BA9BAB900FF0A4F /* SVariable.h in Headers */ = {isa = PBXBuildFile; fileRef = D09FD73C1BA9BAB900FF0A4F /* SVariable.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D09FD73F1BA9BAB900FF0A4F /* SVariable.m in Sources */ = {isa = PBXBuildFile; fileRef = D09FD73D1BA9BAB900FF0A4F /* SVariable.m */; };
+ D0B417F11D7DFA63004562A4 /* SwiftSignalKitMac.h in Headers */ = {isa = PBXBuildFile; fileRef = D0B417EF1D7DFA63004562A4 /* SwiftSignalKitMac.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ D0B417F61D7DFAAB004562A4 /* Signal_Timing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B3C1B282BEE00EAF753 /* Signal_Timing.swift */; };
+ D0B417F71D7DFAAB004562A4 /* Signal_SideEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B3D1B282BEE00EAF753 /* Signal_SideEffects.swift */; };
+ D0B417F81D7DFAAB004562A4 /* Signal_Dispatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B3E1B282BEE00EAF753 /* Signal_Dispatch.swift */; };
+ D0B417F91D7DFAAB004562A4 /* ThreadPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B3F1B282BEE00EAF753 /* ThreadPool.swift */; };
+ D0B417FA1D7DFAAB004562A4 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B401B282BEE00EAF753 /* Timer.swift */; };
+ D0B417FB1D7DFAAB004562A4 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B411B282BEE00EAF753 /* Queue.swift */; };
+ D0B417FC1D7DFAAB004562A4 /* ValuePipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B421B282BEE00EAF753 /* ValuePipe.swift */; };
+ D0B417FD1D7DFAAB004562A4 /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B431B282BEE00EAF753 /* Bag.swift */; };
+ D0B417FE1D7DFAAB004562A4 /* Signal_Take.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B441B282BEE00EAF753 /* Signal_Take.swift */; };
+ D0B417FF1D7DFAAB004562A4 /* Signal_Catch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B451B282BEE00EAF753 /* Signal_Catch.swift */; };
+ D0B418001D7DFAAB004562A4 /* Signal_Single.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B461B282BEE00EAF753 /* Signal_Single.swift */; };
+ D0B418011D7DFAAB004562A4 /* Signal_Meta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B471B282BEE00EAF753 /* Signal_Meta.swift */; };
+ D0B418021D7DFAAB004562A4 /* Signal_Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B481B282BEE00EAF753 /* Signal_Combine.swift */; };
+ D0B418031D7DFAAB004562A4 /* Signal_Merge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC1A391CA3284F0056AE9A /* Signal_Merge.swift */; };
+ D0B418041D7DFAAB004562A4 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B491B282BEE00EAF753 /* Atomic.swift */; };
+ D0B418051D7DFAAB004562A4 /* Signal_Reduce.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B4A1B282BEE00EAF753 /* Signal_Reduce.swift */; };
+ D0B418061D7DFAAB004562A4 /* Signal_Materialize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD17B11CC17C83007C5650 /* Signal_Materialize.swift */; };
+ D0B418071D7DFAAB004562A4 /* Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B4B1B282BEE00EAF753 /* Signal.swift */; };
+ D0B418081D7DFAAB004562A4 /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B4C1B282BEE00EAF753 /* Disposable.swift */; };
+ D0B418091D7DFAAB004562A4 /* Signal_Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B4D1B282BEE00EAF753 /* Signal_Mapping.swift */; };
+ D0B4180A1D7DFAAB004562A4 /* Subscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0085B4E1B282BEE00EAF753 /* Subscriber.swift */; };
+ D0B4180B1D7DFAAB004562A4 /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07A5CFC1BBDE6E400451791 /* Promise.swift */; };
+ D0B4180C1D7DFAAB004562A4 /* Multicast.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05F09A71C9EF77100BB6F96 /* Multicast.swift */; };
+ D0B4180D1D7DFAAB004562A4 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D066BD091C7FE06700D7A576 /* Lock.swift */; };
+ D0CD17B21CC17C83007C5650 /* Signal_Materialize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD17B11CC17C83007C5650 /* Signal_Materialize.swift */; };
+ D0FC1A3A1CA3284F0056AE9A /* Signal_Merge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FC1A391CA3284F0056AE9A /* Signal_Merge.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ D0085B2E1B282B9800EAF753 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = D0445DCF1A7C2CA500267924 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = D0085B211B282B9800EAF753;
+ remoteInfo = SwiftSignalKit;
+ };
+ D0445DE51A7C2CA500267924 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = D0445DCF1A7C2CA500267924 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = D0445DD71A7C2CA500267924;
+ remoteInfo = SSignalKit;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ D0085AB11B28285400EAF753 /* SSignal+Timing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SSignal+Timing.m"; sourceTree = ""; };
+ D0085AB21B28285400EAF753 /* SThreadPool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SThreadPool.m; sourceTree = ""; };
+ D0085AB31B28285400EAF753 /* SQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQueue.m; sourceTree = ""; };
+ D0085AB41B28285400EAF753 /* SSignal+Take.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SSignal+Take.m"; sourceTree = ""; };
+ D0085AB51B28285400EAF753 /* SSignal+Meta.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SSignal+Meta.m"; sourceTree = ""; };
+ D0085AB61B28285400EAF753 /* SSignal+Accumulate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SSignal+Accumulate.h"; sourceTree = ""; };
+ D0085AB71B28285400EAF753 /* SSignal+Single.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SSignal+Single.h"; sourceTree = ""; };
+ D0085AB81B28285400EAF753 /* SSignal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSignal.m; sourceTree = ""; };
+ D0085AB91B28285400EAF753 /* SSignalKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSignalKit.h; sourceTree = ""; };
+ D0085ABA1B28285400EAF753 /* SMulticastSignalManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SMulticastSignalManager.h; sourceTree = ""; };
+ D0085ABB1B28285400EAF753 /* SMulticastSignalManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SMulticastSignalManager.m; sourceTree = ""; };
+ D0085ABC1B28285400EAF753 /* SSignal+Combine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SSignal+Combine.h"; sourceTree = ""; };
+ D0085ABD1B28285400EAF753 /* SSignal+Combine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SSignal+Combine.m"; sourceTree = ""; };
+ D0085ABE1B28285400EAF753 /* SSignal+Pipe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SSignal+Pipe.h"; sourceTree = ""; };
+ D0085ABF1B28285400EAF753 /* SSignal+Pipe.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SSignal+Pipe.m"; sourceTree = ""; };
+ D0085AC01B28285400EAF753 /* SSignal+SideEffects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SSignal+SideEffects.h"; sourceTree = ""; };
+ D0085AC11B28285400EAF753 /* SSignal+SideEffects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SSignal+SideEffects.m"; sourceTree = ""; };
+ D0085AC21B28285400EAF753 /* SSignal+Catch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SSignal+Catch.m"; sourceTree = ""; };
+ D0085AC31B28285400EAF753 /* SSignal+Catch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SSignal+Catch.h"; sourceTree = ""; };
+ D0085AC41B28285400EAF753 /* SSignal+Take.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SSignal+Take.h"; sourceTree = ""; };
+ D0085AC51B28285400EAF753 /* SSignal+Accumulate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SSignal+Accumulate.m"; sourceTree = ""; };
+ D0085AC61B28285400EAF753 /* SQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQueue.h; sourceTree = ""; };
+ D0085AC71B28285400EAF753 /* SSignal+Meta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SSignal+Meta.h"; sourceTree = ""; };
+ D0085AC81B28285400EAF753 /* SSignal+Timing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SSignal+Timing.h"; sourceTree = ""; };
+ D0085AC91B28285400EAF753 /* SSubscriber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSubscriber.m; sourceTree = ""; };
+ D0085ACA1B28285400EAF753 /* SSignal+Dispatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SSignal+Dispatch.m"; sourceTree = ""; };
+ D0085ACB1B28285400EAF753 /* SThreadPoolTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SThreadPoolTask.m; sourceTree = ""; };
+ D0085ACC1B28285400EAF753 /* SThreadPoolQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SThreadPoolQueue.m; sourceTree = ""; };
+ D0085ACD1B28285400EAF753 /* SThreadPoolQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SThreadPoolQueue.h; sourceTree = ""; };
+ D0085ACE1B28285400EAF753 /* SThreadPool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SThreadPool.h; sourceTree = ""; };
+ D0085ACF1B28285400EAF753 /* SThreadPoolTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SThreadPoolTask.h; sourceTree = ""; };
+ D0085AD01B28285400EAF753 /* SDisposableSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDisposableSet.m; sourceTree = ""; };
+ D0085AD11B28285400EAF753 /* SMetaDisposable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SMetaDisposable.m; sourceTree = ""; };
+ D0085AD21B28285400EAF753 /* SSignal+Multicast.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SSignal+Multicast.m"; sourceTree = ""; };
+ D0085AD31B28285400EAF753 /* SSubscriber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSubscriber.h; sourceTree = ""; };
+ D0085AD41B28285400EAF753 /* SDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDisposable.h; sourceTree = ""; };
+ D0085AD51B28285400EAF753 /* SSignal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSignal.h; sourceTree = ""; };
+ D0085AD61B28285400EAF753 /* SSignal+Mapping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SSignal+Mapping.h"; sourceTree = ""; };
+ D0085AD71B28285400EAF753 /* SSignal+Mapping.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SSignal+Mapping.m"; sourceTree = ""; };
+ D0085AD81B28285400EAF753 /* SSignal+Single.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SSignal+Single.m"; sourceTree = ""; };
+ D0085AD91B28285400EAF753 /* SAtomic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SAtomic.h; sourceTree = ""; };
+ D0085ADA1B28285400EAF753 /* SAtomic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SAtomic.m; sourceTree = ""; };
+ D0085ADB1B28285400EAF753 /* SBag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBag.h; sourceTree = ""; };
+ D0085ADC1B28285400EAF753 /* SBag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBag.m; sourceTree = ""; };
+ D0085ADD1B28285400EAF753 /* SBlockDisposable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBlockDisposable.h; sourceTree = "