diff --git a/Assets/logo.png b/Assets/logo.png index b671ba5..6abd8de 100644 Binary files a/Assets/logo.png and b/Assets/logo.png differ diff --git a/Cartfile.private b/Cartfile.private deleted file mode 100644 index c2786fb..0000000 --- a/Cartfile.private +++ /dev/null @@ -1,2 +0,0 @@ -github "Quick/Quick" -github "Quick/Nimble" diff --git a/Cartfile.resolved b/Cartfile.resolved deleted file mode 100644 index 22669d2..0000000 --- a/Cartfile.resolved +++ /dev/null @@ -1,2 +0,0 @@ -github "Quick/Nimble" "v3.0.0" -github "Quick/Quick" "v0.8.0" diff --git a/ReactiveKit/Operation/NoError.swift b/Package.swift similarity index 90% rename from ReactiveKit/Operation/NoError.swift rename to Package.swift index 475a300..dc58d75 100644 --- a/ReactiveKit/Operation/NoError.swift +++ b/Package.swift @@ -1,7 +1,7 @@ // // The MIT License (MIT) // -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,5 +22,8 @@ // THE SOFTWARE. // -public enum NoError: ErrorType { -} +import PackageDescription + +let package = Package( + name: "ReactiveKit" +) diff --git a/README.md b/README.md index c5637dd..207fab7 100644 --- a/README.md +++ b/README.md @@ -3,386 +3,421 @@ __ReactiveKit__ is a collection of Swift frameworks for reactive and functional reactive programming. * [ReactiveKit](https://github.com/ReactiveKit/ReactiveKit) - A Swift Reactive Programming Kit. -* [ReactiveFoundation](https://github.com/ReactiveKit/ReactiveFoundation) - NSFoundation extensions like type-safe KVO. * [ReactiveUIKit](https://github.com/ReactiveKit/ReactiveUIKit) - UIKit extensions that enable bindings. -## Observables +## Reactive Programming -Updating the UI or performing other actions when underlying data changes is such a tedious task. It would be great if it could be done in automatic and safe fashion. `Observable` tries to solve that problem. It wraps mutable state into an object which enables observation of that state. Whenever the state changes, an observer can be notified. +Apps transform data. They take some data as input or generate data by themselves, transform that data into another data and output new data to the user. An app could take computer-friendly response from an API, transform it to a user-friendly text with a photo or video and render an article to the user. An app could take readings from the magnetometer, transform them into an orientation angle and render a nice needle to the user. There are many examples, but the pattern is obvious. -To create the observable, just initialize it with the initial value. +Basic premise of reactive programming is that the output should be derived from the input in such way that whenever the input changes, the output is changed too. Whenever new magnetometer readings are received, needle is updated. In addition to that, if the input is derived into the output using functional constructs like pure or higher-order functions one gets functional reactive programming. -```swift -let name = Observable("Jim") -``` +ReactiveKit is a framework that provides mechanisms for leveraging functional reactive paradigm. It's based on ReactiveX API, but with flavours of its own. Instead of one *Observable* type, ReactiveKit offers two types, *Operation* and *Stream*, that are same on all fronts except that the former **can** error-out and the latter **cannot**. ReactiveKit also provides weak binding mechanism as well as reactive collection types. + +## Stream -> `nil` is valid value for observables that wrap optional type. +Main type that ReactiveKit provides is `Stream`. It's used to represent a stream of events. Event can be anything from a button tap to a voice command. -Observables are useful only if they are being observed. To register an observer, use `observe` method. You pass it a closure that accepts one argument - latest value. +Stream event is defined by `StreamEvent` type and looks like this: ```swift -name.observe { value in - print("Hi \(value)!") +public enum StreamEvent { + case Next(T) + case Completed } ``` -> When you register the observer, it will be immediately invoked with the current value of the observable so that snippet will print "Hi Jim!". +Valid streams produce zero or more `.Next` events and always complete with `.Completed` event. Each `.Next` event contains an associated element - the actual value or object produced by the stream. + +### Creating Streams -To change value of the observable afterwards, just set the `value` property. +There are many ways to create streams. Main one is by using the constructor that accepts a producer closure. The closure has one argument - an observer to which you send events. To send next element, use `next` method of the observer. When there are no more elements to be generated, send completion event using `completed` method. For example, to create a stream that produces first three positive integers do: ```swift -name.value = "Jim Kirk" // Prints: Hi Jim Kirk! -``` +let counter = Stream { observer in -Setting the value invokes all registered observers automatically. That's why we call this reactive programming. + // send first three positive integers + observer.next(1) + observer.next(2) + observer.next(3) -> Observers registered with `observe` method will be by default invoked on the main thread (queue). You can change default behaviour by passing another [execution context](#threading) to the `observe` method. + // complete + observer.completed() -Observables cannot fail and they are guaranteed to always have a value. That makes them safe to represent the data that UI displays. To facilitate that use, observables are made to be bindable. They can be bound to any type conforming to `BindableType` protocol - observables being part of that company themselves. + return NotDisposable +} +``` -ReactiveUIKit extends various UIKit objects with observable properties. That makes bindings as simple as +> Producer closure expects you to return a disposable. More about disposables can be found [here](#cancellation). + +This is just an example of how to manually create streams. In reality, when you need to convert sequence to a stream, you will use following constructor. ```swift -name.bindTo(nameLabel.rText) +let counter = Stream.sequence([1, 2, 3]) ``` -Actually, because it's only natural to bind text to a label, as simple as: +To create a stream that produces an integer every second, do ```swift -name.bindTo(nameLabel) +let counter = Stream.interval(1, queue: Queue.main) ``` -> Observables provided by ReactiveUIKit will update the target object on the main thread (queue) by default. That means that you can update the observable from a background thread without worrying how your UI will be updated - it will always happen on the main thread. You can change default behaviour by passing another exection context to the `bindTo` method. - - -## Observable Collections +> Note that this constructor requires a queue on which the events will be produced. -When working with collections knowing that the collection changed is usually not enough. Often we need to know how exactly did the collection change - what elements were updated, what inserted and what deleted. `ObservableCollection` enables exactly that. It wraps a collection in order to provide mechanisms for observation of fine-grained changes done to the collection itself. Events generated by observable collection contain both the new state of the collection (the collection itself) plus the information about what elements were inserted, updated or deleted. +For more constructors, refer to the code reference. -To provide observable collection, just initialize it with the initial value. The type of the value you provide determines the type of the observable collection. You can provide an array, a dictionary or a set. +### Observing Streams +Stream is only useful if it's being observed. To observe stream, use `observe` method: ```swift -let uniqueNumbers = ObservableCollection(Set([0, 1, 2])) +counter.observe { event in + print(event) +} ``` -```swift -let options = ObservableCollection(["enabled": "yes"]) -``` +That will print following: -```swift -let names: ObservableCollection(["Steve", "Tim"]) +``` +Next(1) +Next(2) +Next(3) +Completed ``` -When observing observable collection, events you receive will be a structs that contain detailed description of changes that happened. +Most of the time we are interested only in the elements that the stream produces. Elements are associated with `.Next` events and to observe just them you do: ```swift -names.observe { e in - print("array: \(e.collection), inserts: \(e.inserts), updates: \(e.updates), deletes: \(e.deletes)") +counter.observeNext { element in + print(element) } ``` -You work with the observable collection like you'd work with the collection it encapsulates. +That will print: -```swift -names.append("John") // prints: array ["Steve", "Tim", "John"], inserts: [2], updates: [], deletes: [] -names.removeLast() // prints: array ["Steve", "Tim"], inserts: [], updates: [], deletes: [2] -names[1] = "Mark" // prints: array ["Steve", "Mark"], inserts: [], updates: [1], deletes: [] ``` +1 +2 +3 +``` + +> Observing the stream actually starts the production of events. In other words, that producer closure we passed in the constructor is called only when you register an observer. If you register more that one observer, producer closure will be called once for each of them. + +> Observers will be by default invoked on the thread (queue) on which the producer generates events. You can change that behaviour by passing another [execution context](#threading) using the `observeOn` method. + +### Transforming Streams -Observable collections can be mapped, filtered and sorted. Let's say we have following obserable array: +Streams can be transformed into another streams. Methods that transform streams are often called _operators_. For example, to convert our stream of positive integers into a stream of positive even integers we can do ```swift -let numbers: ObservableCollection([2, 3, 1]) +let evenCounter = counter.map { $0 * 2 } ``` -When we then do this: +or to convert it to a stream of integers divisible by three -``` -let doubleNumbers = numbers.map { $0 * 2 } -let evenNumbers = numbers.filter { $0 % 2 == 0 } -let sortedNumbers = numbers.sort(<) +```swift +let divisibleByThree = counter.filter { $0 % 3 == 0 } ``` -Modifying `numbers` will automatically update all derived arrays: +or to convert each element to another stream that just triples that element and merge those new streams by concatenating them one after another ```swift -numbers.append(4) +let tripled = counter.flatMap(.Concat) { number in + return Stream.sequence(Array(count: 3, repeatedValue: number)) +} +``` -Assert(doubleNumbers.collection == [4, 6, 2, 8]) -Assert(evenNumbers.collection == [2, 4]) -Assert(sortedNumbers.collection == [1, 2, 3, 4]) -``` +and so on... There are many operators available. For more info on them, check out code reference. -That enables us to build powerful UI bindings. With ReactiveUIKit, observable collection containing an array can be bound to `UITableView` or `UICollectionView`. Just provide a closure that creates cells to the `bindTo` method. +### Sharing Results -```swift -let posts: ObservableCollection<[Post]> = ... +Whenever the observer is registered, the stream producer is executed all over again. To share results of a single execution, use `shareReplay` method. -posts.bindTo(tableView) { indexPath, posts, tableView in - let cell = tableView.dequeueCellWithIdentifier("PostCell", forIndexPath: indexPath) as! PostCell - cell.post = posts[indexPath.row] - return cell -} +```swift +let sharedCounter = counter.shareReplay() ``` -Subsequent changes done to the `posts` array will then be automatically reflected in the table view. +### Cancellation -To bind observable dictionary or set to table or collection view, first you have to convert it to the observable array. Because sorting any collection outputs an array, just do that. +Observing the stream returns a disposable object. When the disposable object gets disposed, it will notify the producer to stop producing events and also disable further event dispatches. + +If you do ```swift -let sortedOptions = options.sort { - $0.0.localizedCaseInsensitiveCompare($1.0) == NSComparisonResult.OrderedAscending -} +let disposable = aStream.observeNext(...) ``` -The resulting `sortedOptions` is of type `ObservableCollection<[(String, String)]>` - an observable array of key-value pairs sorted alphabetically by the key that can be bound to a table or collection view. +and later need to cancel the stream, just call `dispose`. -> Same threading rules apply for observable collection bindings as for observable bindings. You can safely modify the collection from a background thread and be confident that the UI updates occur on the main thread. +```swift +disposable.dispose() +``` -### Array diff +From that point on the stream will not send any more events and the underlying task will be cancelled. -When you need to replace an array with another array, but need an event to contains fine-grained changes (for example to update table/collection view with nice animations), you can use method `replace:performDiff:`. For example, if you have +### Bindings + +Streams cannot fail and that makes them safe to represent the data that UI displays. To facilitate that use, streams are made to be bindable. They can be bound to any type conforming to `BindableType` protocol. + +ReactiveUIKit framework extends various UIKit objects with bindable properties. For example, given ```swift -let numbers: ObservableCollection([1, 2, 3]) +let name: Stream = ... ``` -and you do +you can do ```swift -numbers.replace([0, 1, 3, 4], performDiff: true) +name.bindTo(nameLabel.rText) ``` -then the observed event will contain: +Actually, because it's only natural to bind text to a label, you can do: ```swift -Assert(event.collection == [0, 1, 3, 4]) -Assert(event.inserts == [0, 3]) -Assert(event.deletes == [1]) +name.bindTo(nameLabel) ``` -If that array was bound to a table or a collection view, the view would automatically animate only the changes from the *merge*. Helpful, isn't it. +> Bindable properties provided by ReactiveUIKit will update the target object on the main thread (queue) by default. That means that the stream can generate events on a background thread without you worrying how the UI will be updated - it will always happen on the main thread. -## Operation +Bindings will automatically dispose themselves (i.e. cancel source streams) when the binding target gets deallocated. For example, if we do -State change events are not the only events worth reacting upon. We can also react upon work being done. Anything that produces results can be made reactive. To enable that, ReactiveKit provides `Operation` type. Operation wraps a work that produces results into something that can be observed. +```swift +blurredImage().bindTo(imageView) +``` + +then the image processing will be automatically cancelled if the image view gets deallocated. Isn't that cool! + +### Hot Streams -To create an operation, pass a closure that performs actual work to the constructor. Closure has one argument - the observer whom you send events regarding operation state. To send one or more results, use `next` method of the observer. When operation successfully completes call `success` method, otherwise send the error using `failure` method. +If you need hot streams, i.e. streams that can generate events regardless of the observers, you can use `PushStream` type: ```swift -func fetchImage(url: NSURL) -> Operation { - return Operation { observer in - let request = Alamofire.request(.GET, url: url).response { request, response, data, error in - if let error = error { - observer.failure(error) - } else { - observer.next(UIImage(imageWithData: data!)) - observer.success() - } - } - return BlockDisposable { - request.cancel() - } - } +let numbers = PushStream() + +numbers.observerNext { num in + print(num) } -``` -> Closure should return a disposable that will cancel the operation. If operation cannot be cancelled, return `nil`. +numbers.next(1) // prints: 1 +numbers.next(2) // prints: 2 +... +``` -Operation can send any number of `.Next` events followed by one terminating event - either a `.Success` or a `.Failure`. No events will ever be sent (accepted) after the terminating event has been sent. +## Operation -Creating the operation doesn't do any work by itself. To start producing results, operation has to be started. Operation will be automatically started when you register an observer to it. +Another important type provided by ReactiveKit is `Operation`. It's just like the `Stream`, but the one that can error-out. Operations are used to represents tasks that can fail like fetching a network resource, reading a file and similar. Operations error-out by sending failure event. Here is how `OperationEvent` type is defined: ```swift -fetchImage(url: ...).observe { event in - switch event { - case .Next(let image): - print("Operation produced an image \(image).") - case .Success: - print("Operation completed successfully.") - case .Failure(let error): - print("Operation failed with error \(error).") - } +public enum OperationEvent { + case Next(T) + case Failure(E) + case Completed } ``` -> Observers registered with `observe` method will be by default invoked on the main thread (queue). You can change default behaviour by passing another [execution context](#threading) to the `observe` method. - -The observer you register with the operation is actually the one that will be passed to the closure that was provided in operation constructor (the one that does the actual work) - just wrapped into a struct that simplifies sending result. You see how the operation is just a light wrapper around a closure, but that abstraction enables powerful paradigm. +Valid operations produce zero or more `.Next` events and always terminate with either a `.Completed` event or a `.Failure` event. -### Observing results +Operations can be created, transformed and observed like streams. Additionally, `Operation` provides few additional methods to handle errors. -When you're interested in just results of the operation and you don't care when it completes or if it fails, you can use `*Next` family of methods. To observe results of the operation, you would use `observeNext`. +One way to try to recover from an error is to just retry the operation again. To do so, just do ```swift -fetchImage(url: ...).observeNext { image in - imageView.image = image -} +let betterFetch = fetchImage(url: ...).retry(3) ``` -### Bindings +and smile thinking about how many number of lines would that take in the imperative paradigm. -To bind results with ReactiveUIKit, do something like: +Errors that cannot be handled with retry will happen eventually. To recover from those, you can use `flatMapError`. It's an operator that maps an error into another operation. ```swift -fetchImage(url: ...).bindNextTo(imageView) +fetchCurrentUser(token) + .flatMapError { error in + return Operation.just(User.Anonymous) + } + .observeNext { user in + print("Authenticated as \(user.fullname).") + } ``` -> `bindNextTo` by default delivers result on the main queue so you don't have to worry about threads. +### Converting Operations to Streams -### Sharing results -Whenever the observer is registered, the operation starts all over again. To share results of a single operation run, use `shareNext` method. +Operations are not bindable so at one point you'll want to convert them to streams. Worst way to do so is to just ignore and log any error that happens: ```swift -let image = fetchImage(url: ...).shareNext() -image.bindTo(imageView1) -image.bindTo(imageView2) +let image = fetchImage(url: ...).toStream(logError: true) ``` -> Method `shareNext` buffers results of the operation using `ObservableBuffer` type. To learn more about that, continue reading. +Better way is to provide a default value in case of an error: -### Transformations +```swift +let image = fetchImage(url: ...).toStream(recoverWith: Assets.placeholderImage) +``` -Operations can be transformed into another operations. For example, to create an operation that fetches and then blurs the image, we would just map the operation we already have for image fetching. +Most powerful way is to `flatMapError` into another stream: ```swift -func fetchAndBlurImage(url: NSURL) -> Operation { - return fetchImage(url: url).map { $0.blurred() } +let image = fetchImage(url: ...).flatMapError { error in + return Stream ... } ``` -If we expect lousy network, we can have our fetch operation retry few times before giving up. +There is no best way. Errors suck. + +## Property + +Property wraps mutable state into an object that enables observation of that state. Whenever the state changes, an observer can be notified. + +To create the property, just initialize it with the initial value. ```swift -fetchImage(url: ...).retry(3).bindNextTo(imageView) +let name = Property("Jim") ``` -> The operation will be retried only if it fails. -Operations enable us to model business logic using simple composition. Let's say we have an operation that does the authentication and the one that can fetch current user for the given authentication token. +> `nil` is valid value for properties that wrap optional type. + +Properties are streams just like streams of `Stream` type. They can be transformed into another streams, observed and bound in the same manner as streams can be. + +For example, you can register an observer with `observe` or `observeNext` methods. ```swift -func authenticate(username: String, password: String) -> Operation -func fetchCurrentUser(token: Token) -> Operation +name.observeNext { value in + print("Hi \(value)!") +} ``` -When we then need to get a user for given login, we do: +> When you register an observer, it will be immediately invoked with the current value of the property so that snippet will print "Hi Jim!". + +To change value of the property afterwards, just set the `value` property. ```swift -authenticate(username: ..., password: ...) - .flatMap(.Latest) { token in - return fetchCurrentUser(token) - } - .observeNext { user in - print("Authenticated as \(user.fullname).") - } +name.value = "Jim Kirk" // Prints: Hi Jim Kirk! ``` -### Cancellation -Observing the operation (or the observable, for that matter) returns a disposable object. When the disposable object gets disposed, it will cancel the operation (and all ancestor operations if our operation was a composition of multiple operations). So, store it in a variable +## Collection Property + +When working with collections knowing that the collection changed is usually not enough. Often we need to know how exactly did the collection change - what elements were updated, what inserted and what deleted. `CollectionProperty` enables exactly that. It wraps a collection in order to provide mechanisms for observation of fine-grained changes done to the collection itself. Events generated by collection property contain both the new state of the collection (the collection itself) plus the information about what elements were inserted, updated or deleted. + +To provide collection property, just initialize it with the initial value. The type of the value you provide determines the type of the collection property. You can provide an array, a dictionary or a set. + ```swift -let disposable = fetchImage(url: ...).observe(...) +let uniqueNumbers = CollectionProperty(Set([0, 1, 2])) ``` -and when you later need to cancel the operation, just call `dispose`. - ```swift -disposable.dispose() +let options = CollectionProperty(["enabled": "yes"]) ``` -From that point on the operation will not send any more events and the underlying task will be cancelled. +```swift +let names: CollectionProperty(["Steve", "Tim"]) +``` -Bindings will automatically dispose themselves (i.e. cancel source operations) when the binding target gets deallocated. For example, if we do +When observing collection property, events you receive will be structs that contain detailed description of changes that happened. ```swift -fetchImage(url: ...).bindNextTo(imageView) +names.observeNext { e in + print("array: \(e.collection), inserts: \(e.inserts), updates: \(e.updates), deletes: \(e.deletes)") +} ``` -then the image downloading will be automatically cancelled when the image view is deallocated. Isn't that cool! +You work with the collection property like you'd work with the collection it encapsulates. +```swift +names.append("John") // prints: array ["Steve", "Tim", "John"], inserts: [2], updates: [], deletes: [] +names.removeLast() // prints: array ["Steve", "Tim"], inserts: [], updates: [], deletes: [2] +names[1] = "Mark" // prints: array ["Steve", "Mark"], inserts: [], updates: [1], deletes: [] +``` -## Streams - -Observable, observable collection and operation are all streams that conform to `StreamType` protocol. Basic requirement of a stream is that it produces events that can be observed. +Collection properties can be mapped, filtered and sorted. Let's say we have following collection property: ```swift -public protocol StreamType { - typealias Event - func observe(on context: ExecutionContext, sink: Event -> ()) -> DisposableType -} +let numbers = CollectionProperty([2, 3, 1]) ``` -Observable, observable collection and operation differ in events they generate and whether their observation can cause side-effects or not. +When we then do this: + +``` +let doubleNumbers = numbers.map { $0 * 2 } +let evenNumbers = numbers.filter { $0 % 2 == 0 } +let sortedNumbers = numbers.sort(<) +``` -Observable generates events of the same type it encapsulates. +Modifying `numbers` will automatically update all derived arrays: ```swift -Observable(0).observe { (event: Int) in ... } +numbers.append(4) + +Assert(doubleNumbers.collection == [4, 6, 2, 8]) +Assert(evenNumbers.collection == [2, 4]) +Assert(sortedNumbers.collection == [1, 2, 3, 4]) ``` -On the other hand, observable collection generates events of `ObservableCollectionEvent` type. It's a struct that contains both the collection itself plus the change-set that describes performed operation. +That enables us to build powerful UI bindings. With ReactiveUIKit, collection property containing an array can be bound to `UITableView` or `UICollectionView`. Just provide a closure that creates cells to the `bindTo` method. ```swift -ObservableCollection<[Int]>([0, 1, 2]).observe { (event: ObservableCollectionEvent<[Int]>) in ... } +let posts: CollectionProperty <[Post]> = ... + +posts.bindTo(tableView) { indexPath, posts, tableView in + let cell = tableView.dequeueCellWithIdentifier("PostCell", forIndexPath: indexPath) as! PostCell + cell.post = posts[indexPath.row] + return cell +} ``` -```swift -public struct ObservableCollectionEvent { - - public let collection: Collection +Subsequent changes done to the `posts` array will then be automatically reflected in the table view. - public let inserts: [Collection.Index] - public let deletes: [Collection.Index] - public let updates: [Collection.Index] +To bind observable dictionary or set to table or collection view, first you have to convert it to the observable array. Because sorting any collection outputs an array, just do that. + +```swift +let sortedOptions = options.sort { + $0.0.localizedCaseInsensitiveCompare($1.0) == NSComparisonResult.OrderedAscending } ``` +The resulting `sortedOptions` is of type `ObservableCollection<[(String, String)]>` - an observable array of key-value pairs sorted alphabetically by the key that can be bound to a table or collection view. -Both observable and observable collection represent so called *hot streams*. It means that observing them does not perform any work and no side effects are generated. They are both subclasses of `ActiveStream` type. The type represents a hot stream that can buffer events. In case of the observable and observable collection it buffers only one (latest) event, so each time you register an observer, it will be immediately called with the latest event - which is actually the current value of the observable. +> Same threading rules apply for observable collection bindings as for observable bindings. You can safely modify the collection from a background thread and be confident that the UI updates occur on the main thread. -`Operation` is a bit different. It's built upon `Stream` type. It represents *cold stream*. Cold streams don't do any work until they are observed. Once you register an observer, the stream executes underlying operation and side effect might be performed. +### Array diff -Operation generates events of `OperationEvent` type. +When you need to replace an array with another array, but need an event to contains fine-grained changes (for example to update table/collection view with nice animations), you can use method `replace:performDiff:`. For example, if you have ```swift -Operation(...).observe { (event: OperationEvent ) in ... } +let numbers: CollectionProperty([1, 2, 3]) ``` -It's an enum defined like this: +and you do ```swift -public enum OperationEvent { - case Next(Value) - case Failure(Error) - case Succes +numbers.replace([0, 1, 3, 4], performDiff: true) ``` -## Threading - -ReactiveKit uses simple concept of execution contexts inspired by [BrightFutures](https://github.com/Thomvis/BrightFutures) to handle threading. +then the observed event will contain: -When you want to receive events on the same thread on which they were generated, just pass `nil` for the execution context parameter. When you want to receive them on a specific dispatch queue, just use `context` extension of dispatch queue wrapper type `Queue`, for example: `Queue.main.context`. +```swift +Assert(event.collection == [0, 1, 3, 4]) +Assert(event.inserts == [0, 3]) +Assert(event.deletes == [1]) +``` -## Why another FRP framework? +If that array was bound to a table or a collection view, the view would automatically animate only the changes from the *merge*. Helpful, isn't it. -With Swift Bond I tried to make Model-View-ViewModel architecture for iOS apps as simple as possible, but as the framework grow it was becoming more and more reactive. That conflicted with its premise of being simple binding library. -ReactiveKit is a continuation of that project, but with different approach. It's based on streams inspired by ReactiveCocoa and RxSwift. It then builds upon those streams reactive types optimized for specific domains - `Operation` for asynchronous operations, `Observable` for observable variables and `ObservableCollection` for observable collections - making them simple and intuitive. +## Threading -Main advantages over some other frameworks are clear separation of types that can cause side effects vs. those that cannot, less confusion around hot and cold streams (signals/producers), simplified threading and provided observable collection types with out-of-the box bindings for respective UI components. +ReactiveKit uses simple concept of execution contexts inspired by [BrightFutures](https://github.com/Thomvis/BrightFutures) to handle threading. +When you want to receive events on a specific dispatch queue, just use `context` extension of dispatch queue wrapper type `Queue`, for example: `Queue.main.context`, and pass it to the `observeOn` stream operator. ## Requirements * iOS 8.0+ / OS X 10.9+ / tvOS 9.0+ / watchOS 2.0+ -* Xcode 7.1+ +* Xcode 7.3+ ## Communication -* If you need help, use Stack Overflow. (Tag '**ReactiveKit**') * If you'd like to ask a general question, use Stack Overflow. +* If you'd like to ask a quick question or chat about the project, try [Gitter](https://gitter.im/ReactiveKit/General). * If you found a bug, open an issue. * If you have a feature request, open an issue. * If you want to contribute, submit a pull request (include unit tests). @@ -392,9 +427,8 @@ Main advantages over some other frameworks are clear separation of types that ca ### CocoaPods ``` -pod 'ReactiveKit', '~> 1.0' -pod 'ReactiveUIKit', '~> 1.0' -pod 'ReactiveFoundation', '~> 1.0' +pod 'ReactiveKit', '~> 2.0-beta1' +pod 'ReactiveUIKit', '~> 2.0-beta1' ``` ### Carthage @@ -402,14 +436,30 @@ pod 'ReactiveFoundation', '~> 1.0' ``` github "ReactiveKit/ReactiveKit" github "ReactiveKit/ReactiveUIKit" -github "ReactiveKit/ReactiveFoundation" ``` +## Migration + +### Migration from v1.x to v2.0 + +* `Observable` is renamed to `Property` +* `ObservableCollection` is renamed to `CollectionProperty` +* `Stream` can now completable (with `.Completed` event) +* `observe` method of `Stream` is renamed to `observeNext` +* `shareNext` is renamed to `shareReplay`. +* `Stream` and `Operation` now have consistent API-s +* `Operation`is no longer bindable. Convert it to `Stream` first. +* Execution context can now be set only using `executeOn` and `observeOn` methods. +* A number of new operators is introduced based on ReactiveX API. +* Project is restructured and should be available as a Swift package. +* Documentation is updated to put `Stream` type in focus. +* ReactiveFoundation is now part of ReactiveKit. + ## License The MIT License (MIT) -Copyright (c) 2015 Srdan Rasic (@srdanrasic) +Copyright (c) 2015-2016 Srdan Rasic (@srdanrasic) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/ReactiveKit.podspec b/ReactiveKit.podspec index 06763f8..4bfe20d 100644 --- a/ReactiveKit.podspec +++ b/ReactiveKit.podspec @@ -1,18 +1,18 @@ Pod::Spec.new do |s| s.name = "ReactiveKit" - s.version = "1.1.3" + s.version = "2.0.0-beta1" s.summary = "A Swift Reactive Programming Framework" s.description = "ReactiveKit is a collection of Swift frameworks for reactive and functional reactive programming." s.homepage = "https://github.com/ReactiveKit/ReactiveKit" s.license = 'MIT' s.author = { "Srdan Rasic" => "srdan.rasic@gmail.com" } - s.source = { :git => "https://github.com/ReactiveKit/ReactiveKit.git", :tag => "v1.1.3" } + s.source = { :git => "https://github.com/ReactiveKit/ReactiveKit.git", :tag => "v2.0.0-beta1" } s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.9' s.watchos.deployment_target = '2.0' s.tvos.deployment_target = '9.0' - s.source_files = 'ReactiveKit/**/*.{h,swift}' + s.source_files = 'Sources/*.swift', 'ReactiveKit/*.h' s.requires_arc = true end diff --git a/ReactiveKit.xcodeproj/project.pbxproj b/ReactiveKit.xcodeproj/project.pbxproj index 32fc498..235aa2f 100644 --- a/ReactiveKit.xcodeproj/project.pbxproj +++ b/ReactiveKit.xcodeproj/project.pbxproj @@ -7,142 +7,61 @@ objects = { /* Begin PBXBuildFile section */ - 1648A99F1BF12CE9007A185C /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1648A99E1BF12CE9007A185C /* Result.swift */; }; - 16C33B2D1BEFBA2D00A0DBE0 /* StreamType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE061BEB6BBE00723476 /* StreamType.swift */; }; - 16C33B2E1BEFBA2D00A0DBE0 /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE051BEB6BBE00723476 /* Stream.swift */; }; - 16C33B2F1BEFBA2D00A0DBE0 /* ActiveStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE041BEB6BBE00723476 /* ActiveStream.swift */; }; - 16C33B301BEFBA2D00A0DBE0 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2C1BEB6BE100723476 /* Operation.swift */; }; - 16C33B311BEFBA2D00A0DBE0 /* OperationEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2D1BEB6BE100723476 /* OperationEvent.swift */; }; - 16C33B321BEFBA2D00A0DBE0 /* OperationSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2E1BEB6BE100723476 /* OperationSink.swift */; }; - 16C33B331BEFBA2D00A0DBE0 /* Stream+Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2B1BEB6BE100723476 /* Stream+Operation.swift */; }; - 16C33B341BEFBA2D00A0DBE0 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF61BEB6BBE00723476 /* Observable.swift */; }; - 16C33B351BEFBA2D00A0DBE0 /* ObservableCollection+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF81BEB6BBE00723476 /* ObservableCollection+Array.swift */; }; - 16C33B361BEFBA2D00A0DBE0 /* ObservableCollection+Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF91BEB6BBE00723476 /* ObservableCollection+Dictionary.swift */; }; - 16C33B371BEFBA2D00A0DBE0 /* ObservableCollection+Set.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFA1BEB6BBE00723476 /* ObservableCollection+Set.swift */; }; - 16C33B381BEFBA2D00A0DBE0 /* ObservableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFB1BEB6BBE00723476 /* ObservableCollection.swift */; }; - 16C33B391BEFBA2D00A0DBE0 /* ObservableCollectionEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFC1BEB6BBE00723476 /* ObservableCollectionEvent.swift */; }; - 16C33B3A1BEFBA2D00A0DBE0 /* BlockDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEB1BEB6BBE00723476 /* BlockDisposable.swift */; }; - 16C33B3B1BEFBA2D00A0DBE0 /* CompositeDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEC1BEB6BBE00723476 /* CompositeDisposable.swift */; }; - 16C33B3C1BEFBA2D00A0DBE0 /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDED1BEB6BBE00723476 /* Disposable.swift */; }; - 16C33B3D1BEFBA2D00A0DBE0 /* DisposeBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEE1BEB6BBE00723476 /* DisposeBag.swift */; }; - 16C33B3E1BEFBA2D00A0DBE0 /* SerialDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEF1BEB6BBE00723476 /* SerialDisposable.swift */; }; - 16C33B3F1BEFBA2D00A0DBE0 /* SimpleDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF01BEB6BBE00723476 /* SimpleDisposable.swift */; }; - 16C33B401BEFBA2D00A0DBE0 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF21BEB6BBE00723476 /* Lock.swift */; }; - 16C33B411BEFBA2D00A0DBE0 /* Reference.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF31BEB6BBE00723476 /* Reference.swift */; }; - 16C33B431BEFBA2D00A0DBE0 /* NoError.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2A1BEB6BE100723476 /* NoError.swift */; }; - 16C33B441BEFBA2D00A0DBE0 /* Bindable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFE1BEB6BBE00723476 /* Bindable.swift */; }; - 16C33B451BEFBA2D00A0DBE0 /* ExecutionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFF1BEB6BBE00723476 /* ExecutionContext.swift */; }; - 16C33B461BEFBA2D00A0DBE0 /* OptionalType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE001BEB6BBE00723476 /* OptionalType.swift */; }; - 16C33B471BEFBA2D00A0DBE0 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE011BEB6BBE00723476 /* Queue.swift */; }; - 16C33B491BEFBA2D00A0DBE0 /* StreamType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE061BEB6BBE00723476 /* StreamType.swift */; }; - 16C33B4A1BEFBA2D00A0DBE0 /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE051BEB6BBE00723476 /* Stream.swift */; }; - 16C33B4B1BEFBA2D00A0DBE0 /* ActiveStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE041BEB6BBE00723476 /* ActiveStream.swift */; }; - 16C33B4C1BEFBA2D00A0DBE0 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2C1BEB6BE100723476 /* Operation.swift */; }; - 16C33B4D1BEFBA2D00A0DBE0 /* OperationEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2D1BEB6BE100723476 /* OperationEvent.swift */; }; - 16C33B4E1BEFBA2D00A0DBE0 /* OperationSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2E1BEB6BE100723476 /* OperationSink.swift */; }; - 16C33B4F1BEFBA2D00A0DBE0 /* Stream+Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2B1BEB6BE100723476 /* Stream+Operation.swift */; }; - 16C33B501BEFBA2D00A0DBE0 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF61BEB6BBE00723476 /* Observable.swift */; }; - 16C33B511BEFBA2D00A0DBE0 /* ObservableCollection+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF81BEB6BBE00723476 /* ObservableCollection+Array.swift */; }; - 16C33B521BEFBA2D00A0DBE0 /* ObservableCollection+Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF91BEB6BBE00723476 /* ObservableCollection+Dictionary.swift */; }; - 16C33B531BEFBA2D00A0DBE0 /* ObservableCollection+Set.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFA1BEB6BBE00723476 /* ObservableCollection+Set.swift */; }; - 16C33B541BEFBA2D00A0DBE0 /* ObservableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFB1BEB6BBE00723476 /* ObservableCollection.swift */; }; - 16C33B551BEFBA2D00A0DBE0 /* ObservableCollectionEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFC1BEB6BBE00723476 /* ObservableCollectionEvent.swift */; }; - 16C33B561BEFBA2D00A0DBE0 /* BlockDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEB1BEB6BBE00723476 /* BlockDisposable.swift */; }; - 16C33B571BEFBA2D00A0DBE0 /* CompositeDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEC1BEB6BBE00723476 /* CompositeDisposable.swift */; }; - 16C33B581BEFBA2D00A0DBE0 /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDED1BEB6BBE00723476 /* Disposable.swift */; }; - 16C33B591BEFBA2D00A0DBE0 /* DisposeBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEE1BEB6BBE00723476 /* DisposeBag.swift */; }; - 16C33B5A1BEFBA2D00A0DBE0 /* SerialDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEF1BEB6BBE00723476 /* SerialDisposable.swift */; }; - 16C33B5B1BEFBA2D00A0DBE0 /* SimpleDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF01BEB6BBE00723476 /* SimpleDisposable.swift */; }; - 16C33B5C1BEFBA2D00A0DBE0 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF21BEB6BBE00723476 /* Lock.swift */; }; - 16C33B5D1BEFBA2D00A0DBE0 /* Reference.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF31BEB6BBE00723476 /* Reference.swift */; }; - 16C33B5F1BEFBA2D00A0DBE0 /* NoError.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2A1BEB6BE100723476 /* NoError.swift */; }; - 16C33B601BEFBA2D00A0DBE0 /* Bindable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFE1BEB6BBE00723476 /* Bindable.swift */; }; - 16C33B611BEFBA2D00A0DBE0 /* ExecutionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFF1BEB6BBE00723476 /* ExecutionContext.swift */; }; - 16C33B621BEFBA2D00A0DBE0 /* OptionalType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE001BEB6BBE00723476 /* OptionalType.swift */; }; - 16C33B631BEFBA2D00A0DBE0 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE011BEB6BBE00723476 /* Queue.swift */; }; - 16C33B651BEFBA2E00A0DBE0 /* StreamType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE061BEB6BBE00723476 /* StreamType.swift */; }; - 16C33B661BEFBA2E00A0DBE0 /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE051BEB6BBE00723476 /* Stream.swift */; }; - 16C33B671BEFBA2E00A0DBE0 /* ActiveStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE041BEB6BBE00723476 /* ActiveStream.swift */; }; - 16C33B681BEFBA2E00A0DBE0 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2C1BEB6BE100723476 /* Operation.swift */; }; - 16C33B691BEFBA2E00A0DBE0 /* OperationEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2D1BEB6BE100723476 /* OperationEvent.swift */; }; - 16C33B6A1BEFBA2E00A0DBE0 /* OperationSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2E1BEB6BE100723476 /* OperationSink.swift */; }; - 16C33B6B1BEFBA2E00A0DBE0 /* Stream+Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2B1BEB6BE100723476 /* Stream+Operation.swift */; }; - 16C33B6C1BEFBA2E00A0DBE0 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF61BEB6BBE00723476 /* Observable.swift */; }; - 16C33B6D1BEFBA2E00A0DBE0 /* ObservableCollection+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF81BEB6BBE00723476 /* ObservableCollection+Array.swift */; }; - 16C33B6E1BEFBA2E00A0DBE0 /* ObservableCollection+Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF91BEB6BBE00723476 /* ObservableCollection+Dictionary.swift */; }; - 16C33B6F1BEFBA2E00A0DBE0 /* ObservableCollection+Set.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFA1BEB6BBE00723476 /* ObservableCollection+Set.swift */; }; - 16C33B701BEFBA2E00A0DBE0 /* ObservableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFB1BEB6BBE00723476 /* ObservableCollection.swift */; }; - 16C33B711BEFBA2E00A0DBE0 /* ObservableCollectionEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFC1BEB6BBE00723476 /* ObservableCollectionEvent.swift */; }; - 16C33B721BEFBA2E00A0DBE0 /* BlockDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEB1BEB6BBE00723476 /* BlockDisposable.swift */; }; - 16C33B731BEFBA2E00A0DBE0 /* CompositeDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEC1BEB6BBE00723476 /* CompositeDisposable.swift */; }; - 16C33B741BEFBA2E00A0DBE0 /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDED1BEB6BBE00723476 /* Disposable.swift */; }; - 16C33B751BEFBA2E00A0DBE0 /* DisposeBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEE1BEB6BBE00723476 /* DisposeBag.swift */; }; - 16C33B761BEFBA2E00A0DBE0 /* SerialDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEF1BEB6BBE00723476 /* SerialDisposable.swift */; }; - 16C33B771BEFBA2E00A0DBE0 /* SimpleDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF01BEB6BBE00723476 /* SimpleDisposable.swift */; }; - 16C33B781BEFBA2E00A0DBE0 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF21BEB6BBE00723476 /* Lock.swift */; }; - 16C33B791BEFBA2E00A0DBE0 /* Reference.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF31BEB6BBE00723476 /* Reference.swift */; }; - 16C33B7B1BEFBA2E00A0DBE0 /* NoError.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2A1BEB6BE100723476 /* NoError.swift */; }; - 16C33B7C1BEFBA2E00A0DBE0 /* Bindable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFE1BEB6BBE00723476 /* Bindable.swift */; }; - 16C33B7D1BEFBA2E00A0DBE0 /* ExecutionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFF1BEB6BBE00723476 /* ExecutionContext.swift */; }; - 16C33B7E1BEFBA2E00A0DBE0 /* OptionalType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE001BEB6BBE00723476 /* OptionalType.swift */; }; - 16C33B7F1BEFBA2E00A0DBE0 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE011BEB6BBE00723476 /* Queue.swift */; }; 16C33B831BEFBAC800A0DBE0 /* ReactiveKit.h in Headers */ = {isa = PBXBuildFile; fileRef = ECBCCDD31BEB6B9A00723476 /* ReactiveKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16C33B841BEFBAC900A0DBE0 /* ReactiveKit.h in Headers */ = {isa = PBXBuildFile; fileRef = ECBCCDD31BEB6B9A00723476 /* ReactiveKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16C33B851BEFBAC900A0DBE0 /* ReactiveKit.h in Headers */ = {isa = PBXBuildFile; fileRef = ECBCCDD31BEB6B9A00723476 /* ReactiveKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 16F0B8761BEFC3850071847A /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16F0B8751BEFC3850071847A /* Quick.framework */; }; - 16F0B8781BEFC3890071847A /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16F0B8771BEFC3890071847A /* Nimble.framework */; }; - EC0DF29A1BF48FF400DFF3E6 /* MutableObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0DF2991BF48FF400DFF3E6 /* MutableObservable.swift */; }; - EC0DF29B1BF48FF400DFF3E6 /* MutableObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0DF2991BF48FF400DFF3E6 /* MutableObservable.swift */; }; - EC0DF29C1BF48FF400DFF3E6 /* MutableObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0DF2991BF48FF400DFF3E6 /* MutableObservable.swift */; }; - EC0DF29D1BF48FF400DFF3E6 /* MutableObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0DF2991BF48FF400DFF3E6 /* MutableObservable.swift */; }; - EC0DF29F1BF4909C00DFF3E6 /* MutableObservableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0DF29E1BF4909C00DFF3E6 /* MutableObservableCollection.swift */; }; - EC0DF2A01BF4909C00DFF3E6 /* MutableObservableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0DF29E1BF4909C00DFF3E6 /* MutableObservableCollection.swift */; }; - EC0DF2A11BF4909C00DFF3E6 /* MutableObservableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0DF29E1BF4909C00DFF3E6 /* MutableObservableCollection.swift */; }; - EC0DF2A21BF4909C00DFF3E6 /* MutableObservableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC0DF29E1BF4909C00DFF3E6 /* MutableObservableCollection.swift */; }; - EC2C7A0D1C02ED71006BFEE1 /* PerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2C7A0C1C02ED71006BFEE1 /* PerformanceTests.swift */; }; - EC2C7A421C031030006BFEE1 /* ObservableBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2C7A411C031030006BFEE1 /* ObservableBuffer.swift */; }; - EC2C7A431C031030006BFEE1 /* ObservableBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2C7A411C031030006BFEE1 /* ObservableBuffer.swift */; }; - EC2C7A441C031030006BFEE1 /* ObservableBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2C7A411C031030006BFEE1 /* ObservableBuffer.swift */; }; - EC2C7A451C031030006BFEE1 /* ObservableBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2C7A411C031030006BFEE1 /* ObservableBuffer.swift */; }; - EC2C7A461C0314BC006BFEE1 /* StreamSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDDF1BEB6B9B00723476 /* StreamSpec.swift */; }; - EC2C7A471C0314BC006BFEE1 /* OperationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC835BE11BEC923400463098 /* OperationSpec.swift */; }; - EC2C7A481C0314BC006BFEE1 /* ObservableCollectionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16EABACF1C01AD82008B20BD /* ObservableCollectionSpec.swift */; }; - EC7591EC1C08710A001F31B3 /* ArrayDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7591EB1C08710A001F31B3 /* ArrayDiff.swift */; }; - EC7591ED1C08710A001F31B3 /* ArrayDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7591EB1C08710A001F31B3 /* ArrayDiff.swift */; }; - EC7591EE1C08710A001F31B3 /* ArrayDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7591EB1C08710A001F31B3 /* ArrayDiff.swift */; }; - EC7591EF1C08710A001F31B3 /* ArrayDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7591EB1C08710A001F31B3 /* ArrayDiff.swift */; }; - EC7591F11C0871EF001F31B3 /* ArrayDiffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7591F01C0871EF001F31B3 /* ArrayDiffTests.swift */; }; - EC9549B31BF1EFA2000FC2BF /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1648A99E1BF12CE9007A185C /* Result.swift */; }; - EC9549B41BF1EFA3000FC2BF /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1648A99E1BF12CE9007A185C /* Result.swift */; }; - EC9549B51BF1EFA4000FC2BF /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1648A99E1BF12CE9007A185C /* Result.swift */; }; + EC8A99E71CABD9B50042A6AD /* CollectionProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DB1CABD9B50042A6AD /* CollectionProperty.swift */; }; + EC8A99E81CABD9B50042A6AD /* CollectionProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DB1CABD9B50042A6AD /* CollectionProperty.swift */; }; + EC8A99E91CABD9B50042A6AD /* CollectionProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DB1CABD9B50042A6AD /* CollectionProperty.swift */; }; + EC8A99EA1CABD9B50042A6AD /* CollectionProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DB1CABD9B50042A6AD /* CollectionProperty.swift */; }; + EC8A99EB1CABD9B50042A6AD /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DC1CABD9B50042A6AD /* Disposable.swift */; }; + EC8A99EC1CABD9B50042A6AD /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DC1CABD9B50042A6AD /* Disposable.swift */; }; + EC8A99ED1CABD9B50042A6AD /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DC1CABD9B50042A6AD /* Disposable.swift */; }; + EC8A99EE1CABD9B50042A6AD /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DC1CABD9B50042A6AD /* Disposable.swift */; }; + EC8A99EF1CABD9B50042A6AD /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DD1CABD9B50042A6AD /* Event.swift */; }; + EC8A99F01CABD9B50042A6AD /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DD1CABD9B50042A6AD /* Event.swift */; }; + EC8A99F11CABD9B50042A6AD /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DD1CABD9B50042A6AD /* Event.swift */; }; + EC8A99F21CABD9B50042A6AD /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DD1CABD9B50042A6AD /* Event.swift */; }; + EC8A99F31CABD9B50042A6AD /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DE1CABD9B50042A6AD /* Foundation.swift */; }; + EC8A99F41CABD9B50042A6AD /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DE1CABD9B50042A6AD /* Foundation.swift */; }; + EC8A99F51CABD9B50042A6AD /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DE1CABD9B50042A6AD /* Foundation.swift */; }; + EC8A99F61CABD9B50042A6AD /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DE1CABD9B50042A6AD /* Foundation.swift */; }; + EC8A99F71CABD9B50042A6AD /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DF1CABD9B50042A6AD /* Observer.swift */; }; + EC8A99F81CABD9B50042A6AD /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DF1CABD9B50042A6AD /* Observer.swift */; }; + EC8A99F91CABD9B50042A6AD /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DF1CABD9B50042A6AD /* Observer.swift */; }; + EC8A99FA1CABD9B50042A6AD /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99DF1CABD9B50042A6AD /* Observer.swift */; }; + EC8A99FB1CABD9B50042A6AD /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E01CABD9B50042A6AD /* Operation.swift */; }; + EC8A99FC1CABD9B50042A6AD /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E01CABD9B50042A6AD /* Operation.swift */; }; + EC8A99FD1CABD9B50042A6AD /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E01CABD9B50042A6AD /* Operation.swift */; }; + EC8A99FE1CABD9B50042A6AD /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E01CABD9B50042A6AD /* Operation.swift */; }; + EC8A99FF1CABD9B50042A6AD /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E11CABD9B50042A6AD /* Property.swift */; }; + EC8A9A001CABD9B50042A6AD /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E11CABD9B50042A6AD /* Property.swift */; }; + EC8A9A011CABD9B50042A6AD /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E11CABD9B50042A6AD /* Property.swift */; }; + EC8A9A021CABD9B50042A6AD /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E11CABD9B50042A6AD /* Property.swift */; }; + EC8A9A031CABD9B50042A6AD /* RawStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E21CABD9B50042A6AD /* RawStream.swift */; }; + EC8A9A041CABD9B50042A6AD /* RawStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E21CABD9B50042A6AD /* RawStream.swift */; }; + EC8A9A051CABD9B50042A6AD /* RawStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E21CABD9B50042A6AD /* RawStream.swift */; }; + EC8A9A061CABD9B50042A6AD /* RawStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E21CABD9B50042A6AD /* RawStream.swift */; }; + EC8A9A071CABD9B50042A6AD /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E31CABD9B50042A6AD /* Stream.swift */; }; + EC8A9A081CABD9B50042A6AD /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E31CABD9B50042A6AD /* Stream.swift */; }; + EC8A9A091CABD9B50042A6AD /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E31CABD9B50042A6AD /* Stream.swift */; }; + EC8A9A0A1CABD9B50042A6AD /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E31CABD9B50042A6AD /* Stream.swift */; }; + EC8A9A0B1CABD9B50042A6AD /* Subjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E41CABD9B50042A6AD /* Subjects.swift */; }; + EC8A9A0C1CABD9B50042A6AD /* Subjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E41CABD9B50042A6AD /* Subjects.swift */; }; + EC8A9A0D1CABD9B50042A6AD /* Subjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E41CABD9B50042A6AD /* Subjects.swift */; }; + EC8A9A0E1CABD9B50042A6AD /* Subjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E41CABD9B50042A6AD /* Subjects.swift */; }; + EC8A9A0F1CABD9B50042A6AD /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E51CABD9B50042A6AD /* System.swift */; }; + EC8A9A101CABD9B50042A6AD /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E51CABD9B50042A6AD /* System.swift */; }; + EC8A9A111CABD9B50042A6AD /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E51CABD9B50042A6AD /* System.swift */; }; + EC8A9A121CABD9B50042A6AD /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E51CABD9B50042A6AD /* System.swift */; }; + EC8A9A131CABD9B50042A6AD /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E61CABD9B50042A6AD /* Threading.swift */; }; + EC8A9A141CABD9B50042A6AD /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E61CABD9B50042A6AD /* Threading.swift */; }; + EC8A9A151CABD9B50042A6AD /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E61CABD9B50042A6AD /* Threading.swift */; }; + EC8A9A161CABD9B50042A6AD /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A99E61CABD9B50042A6AD /* Threading.swift */; }; + EC8A9A191CABDA830042A6AD /* ArrayDiffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A9A171CABDA5F0042A6AD /* ArrayDiffTests.swift */; }; + EC8A9A1A1CABDA830042A6AD /* PerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A9A181CABDA5F0042A6AD /* PerformanceTests.swift */; }; ECBCCDD41BEB6B9A00723476 /* ReactiveKit.h in Headers */ = {isa = PBXBuildFile; fileRef = ECBCCDD31BEB6B9A00723476 /* ReactiveKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; ECBCCDDB1BEB6B9B00723476 /* ReactiveKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECBCCDD01BEB6B9A00723476 /* ReactiveKit.framework */; }; - ECBCCE0D1BEB6BBE00723476 /* BlockDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEB1BEB6BBE00723476 /* BlockDisposable.swift */; }; - ECBCCE0E1BEB6BBE00723476 /* CompositeDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEC1BEB6BBE00723476 /* CompositeDisposable.swift */; }; - ECBCCE0F1BEB6BBE00723476 /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDED1BEB6BBE00723476 /* Disposable.swift */; }; - ECBCCE101BEB6BBE00723476 /* DisposeBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEE1BEB6BBE00723476 /* DisposeBag.swift */; }; - ECBCCE111BEB6BBE00723476 /* SerialDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDEF1BEB6BBE00723476 /* SerialDisposable.swift */; }; - ECBCCE121BEB6BBE00723476 /* SimpleDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF01BEB6BBE00723476 /* SimpleDisposable.swift */; }; - ECBCCE131BEB6BBE00723476 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF21BEB6BBE00723476 /* Lock.swift */; }; - ECBCCE141BEB6BBE00723476 /* Reference.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF31BEB6BBE00723476 /* Reference.swift */; }; - ECBCCE161BEB6BBE00723476 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF61BEB6BBE00723476 /* Observable.swift */; }; - ECBCCE171BEB6BBE00723476 /* ObservableCollection+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF81BEB6BBE00723476 /* ObservableCollection+Array.swift */; }; - ECBCCE181BEB6BBE00723476 /* ObservableCollection+Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDF91BEB6BBE00723476 /* ObservableCollection+Dictionary.swift */; }; - ECBCCE191BEB6BBE00723476 /* ObservableCollection+Set.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFA1BEB6BBE00723476 /* ObservableCollection+Set.swift */; }; - ECBCCE1A1BEB6BBE00723476 /* ObservableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFB1BEB6BBE00723476 /* ObservableCollection.swift */; }; - ECBCCE1B1BEB6BBE00723476 /* ObservableCollectionEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFC1BEB6BBE00723476 /* ObservableCollectionEvent.swift */; }; - ECBCCE1C1BEB6BBE00723476 /* Bindable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFE1BEB6BBE00723476 /* Bindable.swift */; }; - ECBCCE1D1BEB6BBE00723476 /* ExecutionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCDFF1BEB6BBE00723476 /* ExecutionContext.swift */; }; - ECBCCE1E1BEB6BBE00723476 /* OptionalType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE001BEB6BBE00723476 /* OptionalType.swift */; }; - ECBCCE1F1BEB6BBE00723476 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE011BEB6BBE00723476 /* Queue.swift */; }; - ECBCCE211BEB6BBE00723476 /* ActiveStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE041BEB6BBE00723476 /* ActiveStream.swift */; }; - ECBCCE221BEB6BBE00723476 /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE051BEB6BBE00723476 /* Stream.swift */; }; - ECBCCE231BEB6BBE00723476 /* StreamType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE061BEB6BBE00723476 /* StreamType.swift */; }; - ECBCCE2F1BEB6BE100723476 /* NoError.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2A1BEB6BE100723476 /* NoError.swift */; }; - ECBCCE301BEB6BE100723476 /* Stream+Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2B1BEB6BE100723476 /* Stream+Operation.swift */; }; - ECBCCE311BEB6BE100723476 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2C1BEB6BE100723476 /* Operation.swift */; }; - ECBCCE321BEB6BE100723476 /* OperationEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2D1BEB6BE100723476 /* OperationEvent.swift */; }; - ECBCCE331BEB6BE100723476 /* OperationSink.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCCE2E1BEB6BE100723476 /* OperationSink.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -156,55 +75,31 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 1648A99E1BF12CE9007A185C /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; + 162CB7461CB451D200FB6375 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 1671707E1BF4F62A001786CE /* ReactiveKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = ReactiveKit.podspec; sourceTree = SOURCE_ROOT; }; - 1671707F1BF4F64E001786CE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; + 1671707F1BF4F64E001786CE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; lineEnding = 0; path = README.md; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.markdown; }; 16C33AF91BEFB72500A0DBE0 /* ReactiveKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 16C33B161BEFB9CB00A0DBE0 /* ReactiveKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 16C33B241BEFBA0100A0DBE0 /* ReactiveKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 16EABACF1C01AD82008B20BD /* ObservableCollectionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableCollectionSpec.swift; sourceTree = ""; }; - 16F0B8751BEFC3850071847A /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = "../Carthage/Checkouts/Quick/build/Debug-iphoneos/Quick.framework"; sourceTree = ""; }; - 16F0B8771BEFC3890071847A /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = "../Carthage/Checkouts/Nimble/build/Debug-iphoneos/Nimble.framework"; sourceTree = ""; }; - EC0DF2991BF48FF400DFF3E6 /* MutableObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutableObservable.swift; sourceTree = ""; }; - EC0DF29E1BF4909C00DFF3E6 /* MutableObservableCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutableObservableCollection.swift; sourceTree = ""; }; - EC2C7A0C1C02ED71006BFEE1 /* PerformanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = ""; }; - EC2C7A411C031030006BFEE1 /* ObservableBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ObservableBuffer.swift; path = ReactiveKit/ObservableBuffer/ObservableBuffer.swift; sourceTree = SOURCE_ROOT; }; - EC7591EB1C08710A001F31B3 /* ArrayDiff.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayDiff.swift; sourceTree = ""; }; - EC7591F01C0871EF001F31B3 /* ArrayDiffTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayDiffTests.swift; sourceTree = ""; }; - EC835BE11BEC923400463098 /* OperationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationSpec.swift; sourceTree = ""; }; - EC835C001BECB0FB00463098 /* ReactiveKitPlayground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = ReactiveKitPlayground.playground; sourceTree = SOURCE_ROOT; }; + EC8A99DB1CABD9B50042A6AD /* CollectionProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CollectionProperty.swift; path = Sources/CollectionProperty.swift; sourceTree = SOURCE_ROOT; }; + EC8A99DC1CABD9B50042A6AD /* Disposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Disposable.swift; path = Sources/Disposable.swift; sourceTree = SOURCE_ROOT; }; + EC8A99DD1CABD9B50042A6AD /* Event.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Event.swift; path = Sources/Event.swift; sourceTree = SOURCE_ROOT; }; + EC8A99DE1CABD9B50042A6AD /* Foundation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Foundation.swift; path = Sources/Foundation.swift; sourceTree = SOURCE_ROOT; }; + EC8A99DF1CABD9B50042A6AD /* Observer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Observer.swift; path = Sources/Observer.swift; sourceTree = SOURCE_ROOT; }; + EC8A99E01CABD9B50042A6AD /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Operation.swift; path = Sources/Operation.swift; sourceTree = SOURCE_ROOT; }; + EC8A99E11CABD9B50042A6AD /* Property.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Property.swift; path = Sources/Property.swift; sourceTree = SOURCE_ROOT; }; + EC8A99E21CABD9B50042A6AD /* RawStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RawStream.swift; path = Sources/RawStream.swift; sourceTree = SOURCE_ROOT; }; + EC8A99E31CABD9B50042A6AD /* Stream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Stream.swift; path = Sources/Stream.swift; sourceTree = SOURCE_ROOT; }; + EC8A99E41CABD9B50042A6AD /* Subjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Subjects.swift; path = Sources/Subjects.swift; sourceTree = SOURCE_ROOT; }; + EC8A99E51CABD9B50042A6AD /* System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = System.swift; path = Sources/System.swift; sourceTree = SOURCE_ROOT; }; + EC8A99E61CABD9B50042A6AD /* Threading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Threading.swift; path = Sources/Threading.swift; sourceTree = SOURCE_ROOT; }; + EC8A9A171CABDA5F0042A6AD /* ArrayDiffTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ArrayDiffTests.swift; path = Tests/ArrayDiffTests.swift; sourceTree = SOURCE_ROOT; }; + EC8A9A181CABDA5F0042A6AD /* PerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PerformanceTests.swift; path = Tests/PerformanceTests.swift; sourceTree = SOURCE_ROOT; }; ECBCCDD01BEB6B9A00723476 /* ReactiveKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - ECBCCDD31BEB6B9A00723476 /* ReactiveKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactiveKit.h; sourceTree = ""; }; + ECBCCDD31BEB6B9A00723476 /* ReactiveKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ReactiveKit.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; ECBCCDD51BEB6B9A00723476 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; ECBCCDDA1BEB6B9B00723476 /* ReactiveKit-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "ReactiveKit-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - ECBCCDDF1BEB6B9B00723476 /* StreamSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamSpec.swift; sourceTree = ""; }; ECBCCDE11BEB6B9B00723476 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - ECBCCDEB1BEB6BBE00723476 /* BlockDisposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockDisposable.swift; sourceTree = ""; }; - ECBCCDEC1BEB6BBE00723476 /* CompositeDisposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompositeDisposable.swift; sourceTree = ""; }; - ECBCCDED1BEB6BBE00723476 /* Disposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Disposable.swift; sourceTree = ""; }; - ECBCCDEE1BEB6BBE00723476 /* DisposeBag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisposeBag.swift; sourceTree = ""; }; - ECBCCDEF1BEB6BBE00723476 /* SerialDisposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SerialDisposable.swift; sourceTree = ""; }; - ECBCCDF01BEB6BBE00723476 /* SimpleDisposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleDisposable.swift; sourceTree = ""; }; - ECBCCDF21BEB6BBE00723476 /* Lock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lock.swift; sourceTree = ""; }; - ECBCCDF31BEB6BBE00723476 /* Reference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reference.swift; sourceTree = ""; }; - ECBCCDF61BEB6BBE00723476 /* Observable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; - ECBCCDF81BEB6BBE00723476 /* ObservableCollection+Array.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObservableCollection+Array.swift"; sourceTree = ""; }; - ECBCCDF91BEB6BBE00723476 /* ObservableCollection+Dictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObservableCollection+Dictionary.swift"; sourceTree = ""; }; - ECBCCDFA1BEB6BBE00723476 /* ObservableCollection+Set.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObservableCollection+Set.swift"; sourceTree = ""; }; - ECBCCDFB1BEB6BBE00723476 /* ObservableCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableCollection.swift; sourceTree = ""; }; - ECBCCDFC1BEB6BBE00723476 /* ObservableCollectionEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableCollectionEvent.swift; sourceTree = ""; }; - ECBCCDFE1BEB6BBE00723476 /* Bindable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bindable.swift; sourceTree = ""; }; - ECBCCDFF1BEB6BBE00723476 /* ExecutionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExecutionContext.swift; sourceTree = ""; }; - ECBCCE001BEB6BBE00723476 /* OptionalType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalType.swift; sourceTree = ""; }; - ECBCCE011BEB6BBE00723476 /* Queue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Queue.swift; sourceTree = ""; }; - ECBCCE041BEB6BBE00723476 /* ActiveStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveStream.swift; sourceTree = ""; }; - ECBCCE051BEB6BBE00723476 /* Stream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stream.swift; sourceTree = ""; }; - ECBCCE061BEB6BBE00723476 /* StreamType.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = StreamType.swift; sourceTree = ""; tabWidth = 2; }; - ECBCCE2A1BEB6BE100723476 /* NoError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NoError.swift; path = ../Operation/NoError.swift; sourceTree = ""; }; - ECBCCE2B1BEB6BE100723476 /* Stream+Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Stream+Operation.swift"; sourceTree = ""; }; - ECBCCE2C1BEB6BE100723476 /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = ""; tabWidth = 2; }; - ECBCCE2D1BEB6BE100723476 /* OperationEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationEvent.swift; sourceTree = ""; }; - ECBCCE2E1BEB6BE100723476 /* OperationSink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationSink.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -240,8 +135,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 16F0B8781BEFC3890071847A /* Nimble.framework in Frameworks */, - 16F0B8761BEFC3850071847A /* Quick.framework in Frameworks */, ECBCCDDB1BEB6B9B00723476 /* ReactiveKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -249,40 +142,13 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 16F0B8791BEFC3A10071847A /* Dependencies */ = { - isa = PBXGroup; - children = ( - 16F0B8771BEFC3890071847A /* Nimble.framework */, - 16F0B8751BEFC3850071847A /* Quick.framework */, - ); - name = Dependencies; - sourceTree = ""; - }; - EC2C7A401C031006006BFEE1 /* ObservableBuffer */ = { - isa = PBXGroup; - children = ( - EC2C7A411C031030006BFEE1 /* ObservableBuffer.swift */, - ); - name = ObservableBuffer; - path = ObservableCollection; - sourceTree = ""; - }; - EC835BF21BECB0D500463098 /* Playgrounds */ = { - isa = PBXGroup; - children = ( - EC835C001BECB0FB00463098 /* ReactiveKitPlayground.playground */, - ); - name = Playgrounds; - path = ReactiveKitTests; - sourceTree = ""; - }; ECBCCDC61BEB6B9A00723476 = { isa = PBXGroup; children = ( 1671707F1BF4F64E001786CE /* README.md */, 1671707E1BF4F62A001786CE /* ReactiveKit.podspec */, + 162CB7461CB451D200FB6375 /* Package.swift */, ECBCCDD21BEB6B9A00723476 /* ReactiveKit */, - EC835BF21BECB0D500463098 /* Playgrounds */, ECBCCDDE1BEB6B9B00723476 /* ReactiveKitTests */, ECBCCDD11BEB6B9A00723476 /* Products */, ); @@ -303,16 +169,20 @@ ECBCCDD21BEB6B9A00723476 /* ReactiveKit */ = { isa = PBXGroup; children = ( - ECBCCDD31BEB6B9A00723476 /* ReactiveKit.h */, ECBCCDD51BEB6B9A00723476 /* Info.plist */, - ECBCCE031BEB6BBE00723476 /* Streams */, - ECBCCE291BEB6BE100723476 /* Operation */, - ECBCCDF51BEB6BBE00723476 /* Observable */, - ECBCCDF71BEB6BBE00723476 /* ObservableCollection */, - EC2C7A401C031006006BFEE1 /* ObservableBuffer */, - ECBCCDEA1BEB6BBE00723476 /* Disposables */, - ECBCCDF11BEB6BBE00723476 /* Internals */, - ECBCCDFD1BEB6BBE00723476 /* Other */, + ECBCCDD31BEB6B9A00723476 /* ReactiveKit.h */, + EC8A99DD1CABD9B50042A6AD /* Event.swift */, + EC8A99DF1CABD9B50042A6AD /* Observer.swift */, + EC8A99DC1CABD9B50042A6AD /* Disposable.swift */, + EC8A99E21CABD9B50042A6AD /* RawStream.swift */, + EC8A99E41CABD9B50042A6AD /* Subjects.swift */, + EC8A99E01CABD9B50042A6AD /* Operation.swift */, + EC8A99E31CABD9B50042A6AD /* Stream.swift */, + EC8A99E11CABD9B50042A6AD /* Property.swift */, + EC8A99DB1CABD9B50042A6AD /* CollectionProperty.swift */, + EC8A99DE1CABD9B50042A6AD /* Foundation.swift */, + EC8A99E51CABD9B50042A6AD /* System.swift */, + EC8A99E61CABD9B50042A6AD /* Threading.swift */, ); path = ReactiveKit; sourceTree = ""; @@ -320,96 +190,13 @@ ECBCCDDE1BEB6B9B00723476 /* ReactiveKitTests */ = { isa = PBXGroup; children = ( - ECBCCDDF1BEB6B9B00723476 /* StreamSpec.swift */, - EC835BE11BEC923400463098 /* OperationSpec.swift */, - 16EABACF1C01AD82008B20BD /* ObservableCollectionSpec.swift */, - EC2C7A0C1C02ED71006BFEE1 /* PerformanceTests.swift */, - EC7591F01C0871EF001F31B3 /* ArrayDiffTests.swift */, - 16F0B8791BEFC3A10071847A /* Dependencies */, + EC8A9A171CABDA5F0042A6AD /* ArrayDiffTests.swift */, + EC8A9A181CABDA5F0042A6AD /* PerformanceTests.swift */, ECBCCDE11BEB6B9B00723476 /* Info.plist */, ); path = ReactiveKitTests; sourceTree = ""; }; - ECBCCDEA1BEB6BBE00723476 /* Disposables */ = { - isa = PBXGroup; - children = ( - ECBCCDEB1BEB6BBE00723476 /* BlockDisposable.swift */, - ECBCCDEC1BEB6BBE00723476 /* CompositeDisposable.swift */, - ECBCCDED1BEB6BBE00723476 /* Disposable.swift */, - ECBCCDEE1BEB6BBE00723476 /* DisposeBag.swift */, - ECBCCDEF1BEB6BBE00723476 /* SerialDisposable.swift */, - ECBCCDF01BEB6BBE00723476 /* SimpleDisposable.swift */, - ); - path = Disposables; - sourceTree = ""; - }; - ECBCCDF11BEB6BBE00723476 /* Internals */ = { - isa = PBXGroup; - children = ( - ECBCCDF21BEB6BBE00723476 /* Lock.swift */, - ECBCCDF31BEB6BBE00723476 /* Reference.swift */, - EC7591EB1C08710A001F31B3 /* ArrayDiff.swift */, - ); - path = Internals; - sourceTree = ""; - }; - ECBCCDF51BEB6BBE00723476 /* Observable */ = { - isa = PBXGroup; - children = ( - ECBCCDF61BEB6BBE00723476 /* Observable.swift */, - EC0DF2991BF48FF400DFF3E6 /* MutableObservable.swift */, - ); - path = Observable; - sourceTree = ""; - }; - ECBCCDF71BEB6BBE00723476 /* ObservableCollection */ = { - isa = PBXGroup; - children = ( - ECBCCDF81BEB6BBE00723476 /* ObservableCollection+Array.swift */, - ECBCCDF91BEB6BBE00723476 /* ObservableCollection+Dictionary.swift */, - ECBCCDFA1BEB6BBE00723476 /* ObservableCollection+Set.swift */, - ECBCCDFB1BEB6BBE00723476 /* ObservableCollection.swift */, - ECBCCDFC1BEB6BBE00723476 /* ObservableCollectionEvent.swift */, - EC0DF29E1BF4909C00DFF3E6 /* MutableObservableCollection.swift */, - ); - path = ObservableCollection; - sourceTree = ""; - }; - ECBCCDFD1BEB6BBE00723476 /* Other */ = { - isa = PBXGroup; - children = ( - ECBCCE2A1BEB6BE100723476 /* NoError.swift */, - ECBCCDFE1BEB6BBE00723476 /* Bindable.swift */, - ECBCCE001BEB6BBE00723476 /* OptionalType.swift */, - ECBCCE011BEB6BBE00723476 /* Queue.swift */, - ECBCCDFF1BEB6BBE00723476 /* ExecutionContext.swift */, - 1648A99E1BF12CE9007A185C /* Result.swift */, - ); - path = Other; - sourceTree = ""; - }; - ECBCCE031BEB6BBE00723476 /* Streams */ = { - isa = PBXGroup; - children = ( - ECBCCE061BEB6BBE00723476 /* StreamType.swift */, - ECBCCE051BEB6BBE00723476 /* Stream.swift */, - ECBCCE041BEB6BBE00723476 /* ActiveStream.swift */, - ); - path = Streams; - sourceTree = ""; - }; - ECBCCE291BEB6BE100723476 /* Operation */ = { - isa = PBXGroup; - children = ( - ECBCCE2C1BEB6BE100723476 /* Operation.swift */, - ECBCCE2E1BEB6BE100723476 /* OperationSink.swift */, - ECBCCE2D1BEB6BE100723476 /* OperationEvent.swift */, - ECBCCE2B1BEB6BE100723476 /* Stream+Operation.swift */, - ); - path = Operation; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -544,7 +331,7 @@ ECBCCDC71BEB6B9A00723476 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0710; + LastSwiftUpdateCheck = 0730; LastUpgradeCheck = 0710; ORGANIZATIONNAME = "Srdan Rasic"; TargetAttributes = { @@ -629,37 +416,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 16C33B3B1BEFBA2D00A0DBE0 /* CompositeDisposable.swift in Sources */, - 16C33B3C1BEFBA2D00A0DBE0 /* Disposable.swift in Sources */, - 16C33B441BEFBA2D00A0DBE0 /* Bindable.swift in Sources */, - 16C33B361BEFBA2D00A0DBE0 /* ObservableCollection+Dictionary.swift in Sources */, - 16C33B341BEFBA2D00A0DBE0 /* Observable.swift in Sources */, - 16C33B331BEFBA2D00A0DBE0 /* Stream+Operation.swift in Sources */, - EC0DF29B1BF48FF400DFF3E6 /* MutableObservable.swift in Sources */, - EC7591ED1C08710A001F31B3 /* ArrayDiff.swift in Sources */, - 16C33B431BEFBA2D00A0DBE0 /* NoError.swift in Sources */, - 16C33B321BEFBA2D00A0DBE0 /* OperationSink.swift in Sources */, - 16C33B401BEFBA2D00A0DBE0 /* Lock.swift in Sources */, - 16C33B2F1BEFBA2D00A0DBE0 /* ActiveStream.swift in Sources */, - 16C33B451BEFBA2D00A0DBE0 /* ExecutionContext.swift in Sources */, - 16C33B391BEFBA2D00A0DBE0 /* ObservableCollectionEvent.swift in Sources */, - 16C33B411BEFBA2D00A0DBE0 /* Reference.swift in Sources */, - 16C33B2D1BEFBA2D00A0DBE0 /* StreamType.swift in Sources */, - EC2C7A431C031030006BFEE1 /* ObservableBuffer.swift in Sources */, - 16C33B381BEFBA2D00A0DBE0 /* ObservableCollection.swift in Sources */, - 16C33B471BEFBA2D00A0DBE0 /* Queue.swift in Sources */, - 16C33B2E1BEFBA2D00A0DBE0 /* Stream.swift in Sources */, - 16C33B3E1BEFBA2D00A0DBE0 /* SerialDisposable.swift in Sources */, - 16C33B371BEFBA2D00A0DBE0 /* ObservableCollection+Set.swift in Sources */, - 16C33B311BEFBA2D00A0DBE0 /* OperationEvent.swift in Sources */, - 16C33B3F1BEFBA2D00A0DBE0 /* SimpleDisposable.swift in Sources */, - 16C33B301BEFBA2D00A0DBE0 /* Operation.swift in Sources */, - EC0DF2A01BF4909C00DFF3E6 /* MutableObservableCollection.swift in Sources */, - EC9549B51BF1EFA4000FC2BF /* Result.swift in Sources */, - 16C33B351BEFBA2D00A0DBE0 /* ObservableCollection+Array.swift in Sources */, - 16C33B461BEFBA2D00A0DBE0 /* OptionalType.swift in Sources */, - 16C33B3A1BEFBA2D00A0DBE0 /* BlockDisposable.swift in Sources */, - 16C33B3D1BEFBA2D00A0DBE0 /* DisposeBag.swift in Sources */, + EC8A9A041CABD9B50042A6AD /* RawStream.swift in Sources */, + EC8A9A001CABD9B50042A6AD /* Property.swift in Sources */, + EC8A99E81CABD9B50042A6AD /* CollectionProperty.swift in Sources */, + EC8A99F81CABD9B50042A6AD /* Observer.swift in Sources */, + EC8A9A141CABD9B50042A6AD /* Threading.swift in Sources */, + EC8A9A0C1CABD9B50042A6AD /* Subjects.swift in Sources */, + EC8A99F01CABD9B50042A6AD /* Event.swift in Sources */, + EC8A99F41CABD9B50042A6AD /* Foundation.swift in Sources */, + EC8A99EC1CABD9B50042A6AD /* Disposable.swift in Sources */, + EC8A9A081CABD9B50042A6AD /* Stream.swift in Sources */, + EC8A99FC1CABD9B50042A6AD /* Operation.swift in Sources */, + EC8A9A101CABD9B50042A6AD /* System.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -667,37 +435,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 16C33B571BEFBA2D00A0DBE0 /* CompositeDisposable.swift in Sources */, - 16C33B581BEFBA2D00A0DBE0 /* Disposable.swift in Sources */, - 16C33B601BEFBA2D00A0DBE0 /* Bindable.swift in Sources */, - 16C33B521BEFBA2D00A0DBE0 /* ObservableCollection+Dictionary.swift in Sources */, - 16C33B501BEFBA2D00A0DBE0 /* Observable.swift in Sources */, - 16C33B4F1BEFBA2D00A0DBE0 /* Stream+Operation.swift in Sources */, - EC0DF29C1BF48FF400DFF3E6 /* MutableObservable.swift in Sources */, - EC7591EE1C08710A001F31B3 /* ArrayDiff.swift in Sources */, - 16C33B5F1BEFBA2D00A0DBE0 /* NoError.swift in Sources */, - 16C33B4E1BEFBA2D00A0DBE0 /* OperationSink.swift in Sources */, - 16C33B5C1BEFBA2D00A0DBE0 /* Lock.swift in Sources */, - 16C33B4B1BEFBA2D00A0DBE0 /* ActiveStream.swift in Sources */, - 16C33B611BEFBA2D00A0DBE0 /* ExecutionContext.swift in Sources */, - 16C33B551BEFBA2D00A0DBE0 /* ObservableCollectionEvent.swift in Sources */, - 16C33B5D1BEFBA2D00A0DBE0 /* Reference.swift in Sources */, - 16C33B491BEFBA2D00A0DBE0 /* StreamType.swift in Sources */, - EC2C7A441C031030006BFEE1 /* ObservableBuffer.swift in Sources */, - 16C33B541BEFBA2D00A0DBE0 /* ObservableCollection.swift in Sources */, - 16C33B631BEFBA2D00A0DBE0 /* Queue.swift in Sources */, - 16C33B4A1BEFBA2D00A0DBE0 /* Stream.swift in Sources */, - 16C33B5A1BEFBA2D00A0DBE0 /* SerialDisposable.swift in Sources */, - 16C33B531BEFBA2D00A0DBE0 /* ObservableCollection+Set.swift in Sources */, - 16C33B4D1BEFBA2D00A0DBE0 /* OperationEvent.swift in Sources */, - 16C33B5B1BEFBA2D00A0DBE0 /* SimpleDisposable.swift in Sources */, - 16C33B4C1BEFBA2D00A0DBE0 /* Operation.swift in Sources */, - EC0DF2A11BF4909C00DFF3E6 /* MutableObservableCollection.swift in Sources */, - EC9549B41BF1EFA3000FC2BF /* Result.swift in Sources */, - 16C33B511BEFBA2D00A0DBE0 /* ObservableCollection+Array.swift in Sources */, - 16C33B621BEFBA2D00A0DBE0 /* OptionalType.swift in Sources */, - 16C33B561BEFBA2D00A0DBE0 /* BlockDisposable.swift in Sources */, - 16C33B591BEFBA2D00A0DBE0 /* DisposeBag.swift in Sources */, + EC8A9A051CABD9B50042A6AD /* RawStream.swift in Sources */, + EC8A9A011CABD9B50042A6AD /* Property.swift in Sources */, + EC8A99E91CABD9B50042A6AD /* CollectionProperty.swift in Sources */, + EC8A99F91CABD9B50042A6AD /* Observer.swift in Sources */, + EC8A9A151CABD9B50042A6AD /* Threading.swift in Sources */, + EC8A9A0D1CABD9B50042A6AD /* Subjects.swift in Sources */, + EC8A99F11CABD9B50042A6AD /* Event.swift in Sources */, + EC8A99F51CABD9B50042A6AD /* Foundation.swift in Sources */, + EC8A99ED1CABD9B50042A6AD /* Disposable.swift in Sources */, + EC8A9A091CABD9B50042A6AD /* Stream.swift in Sources */, + EC8A99FD1CABD9B50042A6AD /* Operation.swift in Sources */, + EC8A9A111CABD9B50042A6AD /* System.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -705,37 +454,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 16C33B731BEFBA2E00A0DBE0 /* CompositeDisposable.swift in Sources */, - 16C33B741BEFBA2E00A0DBE0 /* Disposable.swift in Sources */, - 16C33B7C1BEFBA2E00A0DBE0 /* Bindable.swift in Sources */, - 16C33B6E1BEFBA2E00A0DBE0 /* ObservableCollection+Dictionary.swift in Sources */, - 16C33B6C1BEFBA2E00A0DBE0 /* Observable.swift in Sources */, - 16C33B6B1BEFBA2E00A0DBE0 /* Stream+Operation.swift in Sources */, - EC0DF29D1BF48FF400DFF3E6 /* MutableObservable.swift in Sources */, - EC7591EF1C08710A001F31B3 /* ArrayDiff.swift in Sources */, - 16C33B7B1BEFBA2E00A0DBE0 /* NoError.swift in Sources */, - 16C33B6A1BEFBA2E00A0DBE0 /* OperationSink.swift in Sources */, - 16C33B781BEFBA2E00A0DBE0 /* Lock.swift in Sources */, - 16C33B671BEFBA2E00A0DBE0 /* ActiveStream.swift in Sources */, - 16C33B7D1BEFBA2E00A0DBE0 /* ExecutionContext.swift in Sources */, - 16C33B711BEFBA2E00A0DBE0 /* ObservableCollectionEvent.swift in Sources */, - 16C33B791BEFBA2E00A0DBE0 /* Reference.swift in Sources */, - 16C33B651BEFBA2E00A0DBE0 /* StreamType.swift in Sources */, - EC2C7A451C031030006BFEE1 /* ObservableBuffer.swift in Sources */, - 16C33B701BEFBA2E00A0DBE0 /* ObservableCollection.swift in Sources */, - 16C33B7F1BEFBA2E00A0DBE0 /* Queue.swift in Sources */, - 16C33B661BEFBA2E00A0DBE0 /* Stream.swift in Sources */, - 16C33B761BEFBA2E00A0DBE0 /* SerialDisposable.swift in Sources */, - 16C33B6F1BEFBA2E00A0DBE0 /* ObservableCollection+Set.swift in Sources */, - 16C33B691BEFBA2E00A0DBE0 /* OperationEvent.swift in Sources */, - 16C33B771BEFBA2E00A0DBE0 /* SimpleDisposable.swift in Sources */, - 16C33B681BEFBA2E00A0DBE0 /* Operation.swift in Sources */, - EC0DF2A21BF4909C00DFF3E6 /* MutableObservableCollection.swift in Sources */, - EC9549B31BF1EFA2000FC2BF /* Result.swift in Sources */, - 16C33B6D1BEFBA2E00A0DBE0 /* ObservableCollection+Array.swift in Sources */, - 16C33B7E1BEFBA2E00A0DBE0 /* OptionalType.swift in Sources */, - 16C33B721BEFBA2E00A0DBE0 /* BlockDisposable.swift in Sources */, - 16C33B751BEFBA2E00A0DBE0 /* DisposeBag.swift in Sources */, + EC8A9A061CABD9B50042A6AD /* RawStream.swift in Sources */, + EC8A9A021CABD9B50042A6AD /* Property.swift in Sources */, + EC8A99EA1CABD9B50042A6AD /* CollectionProperty.swift in Sources */, + EC8A99FA1CABD9B50042A6AD /* Observer.swift in Sources */, + EC8A9A161CABD9B50042A6AD /* Threading.swift in Sources */, + EC8A9A0E1CABD9B50042A6AD /* Subjects.swift in Sources */, + EC8A99F21CABD9B50042A6AD /* Event.swift in Sources */, + EC8A99F61CABD9B50042A6AD /* Foundation.swift in Sources */, + EC8A99EE1CABD9B50042A6AD /* Disposable.swift in Sources */, + EC8A9A0A1CABD9B50042A6AD /* Stream.swift in Sources */, + EC8A99FE1CABD9B50042A6AD /* Operation.swift in Sources */, + EC8A9A121CABD9B50042A6AD /* System.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -743,37 +473,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - ECBCCE1B1BEB6BBE00723476 /* ObservableCollectionEvent.swift in Sources */, - ECBCCE301BEB6BE100723476 /* Stream+Operation.swift in Sources */, - ECBCCE211BEB6BBE00723476 /* ActiveStream.swift in Sources */, - ECBCCE101BEB6BBE00723476 /* DisposeBag.swift in Sources */, - ECBCCE1F1BEB6BBE00723476 /* Queue.swift in Sources */, - EC0DF29A1BF48FF400DFF3E6 /* MutableObservable.swift in Sources */, - ECBCCE181BEB6BBE00723476 /* ObservableCollection+Dictionary.swift in Sources */, - EC7591EC1C08710A001F31B3 /* ArrayDiff.swift in Sources */, - ECBCCE311BEB6BE100723476 /* Operation.swift in Sources */, - ECBCCE331BEB6BE100723476 /* OperationSink.swift in Sources */, - ECBCCE191BEB6BBE00723476 /* ObservableCollection+Set.swift in Sources */, - ECBCCE1D1BEB6BBE00723476 /* ExecutionContext.swift in Sources */, - ECBCCE231BEB6BBE00723476 /* StreamType.swift in Sources */, - ECBCCE141BEB6BBE00723476 /* Reference.swift in Sources */, - ECBCCE0D1BEB6BBE00723476 /* BlockDisposable.swift in Sources */, - EC2C7A421C031030006BFEE1 /* ObservableBuffer.swift in Sources */, - ECBCCE221BEB6BBE00723476 /* Stream.swift in Sources */, - ECBCCE0F1BEB6BBE00723476 /* Disposable.swift in Sources */, - ECBCCE131BEB6BBE00723476 /* Lock.swift in Sources */, - ECBCCE2F1BEB6BE100723476 /* NoError.swift in Sources */, - ECBCCE1A1BEB6BBE00723476 /* ObservableCollection.swift in Sources */, - ECBCCE321BEB6BE100723476 /* OperationEvent.swift in Sources */, - ECBCCE1C1BEB6BBE00723476 /* Bindable.swift in Sources */, - ECBCCE161BEB6BBE00723476 /* Observable.swift in Sources */, - ECBCCE171BEB6BBE00723476 /* ObservableCollection+Array.swift in Sources */, - EC0DF29F1BF4909C00DFF3E6 /* MutableObservableCollection.swift in Sources */, - 1648A99F1BF12CE9007A185C /* Result.swift in Sources */, - ECBCCE121BEB6BBE00723476 /* SimpleDisposable.swift in Sources */, - ECBCCE1E1BEB6BBE00723476 /* OptionalType.swift in Sources */, - ECBCCE0E1BEB6BBE00723476 /* CompositeDisposable.swift in Sources */, - ECBCCE111BEB6BBE00723476 /* SerialDisposable.swift in Sources */, + EC8A9A031CABD9B50042A6AD /* RawStream.swift in Sources */, + EC8A99FF1CABD9B50042A6AD /* Property.swift in Sources */, + EC8A99E71CABD9B50042A6AD /* CollectionProperty.swift in Sources */, + EC8A99F71CABD9B50042A6AD /* Observer.swift in Sources */, + EC8A9A131CABD9B50042A6AD /* Threading.swift in Sources */, + EC8A9A0B1CABD9B50042A6AD /* Subjects.swift in Sources */, + EC8A99EF1CABD9B50042A6AD /* Event.swift in Sources */, + EC8A99F31CABD9B50042A6AD /* Foundation.swift in Sources */, + EC8A99EB1CABD9B50042A6AD /* Disposable.swift in Sources */, + EC8A9A071CABD9B50042A6AD /* Stream.swift in Sources */, + EC8A99FB1CABD9B50042A6AD /* Operation.swift in Sources */, + EC8A9A0F1CABD9B50042A6AD /* System.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -781,11 +492,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - EC7591F11C0871EF001F31B3 /* ArrayDiffTests.swift in Sources */, - EC2C7A461C0314BC006BFEE1 /* StreamSpec.swift in Sources */, - EC2C7A481C0314BC006BFEE1 /* ObservableCollectionSpec.swift in Sources */, - EC2C7A471C0314BC006BFEE1 /* OperationSpec.swift in Sources */, - EC2C7A0D1C02ED71006BFEE1 /* PerformanceTests.swift in Sources */, + EC8A9A191CABDA830042A6AD /* ArrayDiffTests.swift in Sources */, + EC8A9A1A1CABDA830042A6AD /* PerformanceTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -803,6 +511,7 @@ 16C33B0A1BEFB72500A0DBE0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -814,6 +523,7 @@ PRODUCT_NAME = ReactiveKit; SDKROOT = appletvos; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; }; @@ -822,6 +532,7 @@ 16C33B0B1BEFB72500A0DBE0 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -842,6 +553,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -853,6 +565,7 @@ PRODUCT_NAME = ReactiveKit; SDKROOT = watchos; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; @@ -862,6 +575,7 @@ isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -881,6 +595,7 @@ 16C33B2A1BEFBA0100A0DBE0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -894,12 +609,14 @@ PRODUCT_NAME = ReactiveKit; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 16C33B2B1BEFBA0100A0DBE0 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1010,6 +727,7 @@ ECBCCDE51BEB6B9B00723476 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -1020,12 +738,14 @@ PRODUCT_BUNDLE_IDENTIFIER = ReactiveKit.ReactiveKit; PRODUCT_NAME = ReactiveKit; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; ECBCCDE61BEB6B9B00723476 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; diff --git a/ReactiveKit.xcworkspace/contents.xcworkspacedata b/ReactiveKit.xcworkspace/contents.xcworkspacedata index d0be4a6..c6c1ea1 100644 --- a/ReactiveKit.xcworkspace/contents.xcworkspacedata +++ b/ReactiveKit.xcworkspace/contents.xcworkspacedata @@ -2,12 +2,9 @@ - - + location = "group:ReactiveKitPlayground.playground"> + location = "group:ReactiveKit.xcodeproj"> diff --git a/ReactiveKit/Disposables/BlockDisposable.swift b/ReactiveKit/Disposables/BlockDisposable.swift deleted file mode 100644 index f7b912a..0000000 --- a/ReactiveKit/Disposables/BlockDisposable.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// A disposable that executes the given block upon disposing. -public final class BlockDisposable: DisposableType { - - public var isDisposed: Bool { - return handler == nil - } - - private var handler: (() -> ())? - private let lock = RecursiveLock(name: "com.ReactiveKit.ReactiveKit.BlockDisposable") - - public init(_ handler: () -> ()) { - self.handler = handler - } - - public func dispose() { - lock.lock() - handler?() - handler = nil - lock.unlock() - } -} - - -public class DeinitDisposable: DisposableType { - - public var otherDisposable: DisposableType? = nil - - public var isDisposed: Bool { - return otherDisposable == nil - } - - public init(disposable: DisposableType) { - otherDisposable = disposable - } - - public func dispose() { - otherDisposable?.dispose() - } - - deinit { - otherDisposable?.dispose() - } -} diff --git a/ReactiveKit/Disposables/CompositeDisposable.swift b/ReactiveKit/Disposables/CompositeDisposable.swift deleted file mode 100644 index 05f35df..0000000 --- a/ReactiveKit/Disposables/CompositeDisposable.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// A disposable that disposes a collection of disposables upon disposing. -public final class CompositeDisposable: DisposableType { - - public private(set) var isDisposed: Bool = false - private var disposables: [DisposableType] = [] - private let lock = RecursiveLock(name: "com.ReactiveKit.ReactiveKit.CompositeDisposable") - - public convenience init() { - self.init([]) - } - - public init(_ disposables: [DisposableType]) { - self.disposables = disposables - } - - public func addDisposable(disposable: DisposableType) { - lock.lock() - if isDisposed { - disposable.dispose() - } else { - disposables.append(disposable) - self.disposables = disposables.filter { $0.isDisposed == false } - } - lock.unlock() - } - - public func dispose() { - lock.lock() - isDisposed = true - for disposable in disposables { - disposable.dispose() - } - disposables = [] - lock.unlock() - } -} - -public func += (left: CompositeDisposable, right: DisposableType) { - left.addDisposable(right) -} diff --git a/ReactiveKit/Disposables/Disposable.swift b/ReactiveKit/Disposables/Disposable.swift deleted file mode 100644 index dbc5f3d..0000000 --- a/ReactiveKit/Disposables/Disposable.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// Objects conforming to this protocol can dispose or cancel connection or task. -public protocol DisposableType { - - /// Disposes or cancels a connection or a task. - func dispose() - - /// Returns `true` is already disposed. - var isDisposed: Bool { get } -} diff --git a/ReactiveKit/Disposables/DisposeBag.swift b/ReactiveKit/Disposables/DisposeBag.swift deleted file mode 100644 index f490d02..0000000 --- a/ReactiveKit/Disposables/DisposeBag.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// A disposable container that will dispose a collection of disposables upon deinit. -public final class DisposeBag: DisposableType { - private var disposables: [DisposableType] = [] - - /// This will return true whenever the bag is empty. - public var isDisposed: Bool { - return disposables.count == 0 - } - - public init() { - } - - /// Adds the given disposable to the bag. - /// DisposableType will be disposed when the bag is deinitialized. - public func addDisposable(disposable: DisposableType) { - disposables.append(disposable) - } - - /// Disposes all disposables that are currenty in the bag. - public func dispose() { - for disposable in disposables { - disposable.dispose() - } - disposables = [] - } - - deinit { - dispose() - } -} - -public extension DisposableType { - public func disposeIn(disposeBag: DisposeBag) { - disposeBag.addDisposable(self) - } -} diff --git a/ReactiveKit/Disposables/SerialDisposable.swift b/ReactiveKit/Disposables/SerialDisposable.swift deleted file mode 100644 index 3d3cfec..0000000 --- a/ReactiveKit/Disposables/SerialDisposable.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// A disposable that disposes other disposable. -public final class SerialDisposable: DisposableType { - - public private(set) var isDisposed: Bool = false - private let lock = RecursiveLock(name: "com.ReactiveKit.ReactiveKit.SerialDisposable") - - /// Will dispose other disposable immediately if self is already disposed. - public var otherDisposable: DisposableType? { - didSet { - lock.lock() - if isDisposed { - otherDisposable?.dispose() - } - lock.unlock() - } - } - - public init(otherDisposable: DisposableType?) { - self.otherDisposable = otherDisposable - } - - public func dispose() { - lock.lock() - if !isDisposed { - isDisposed = true - otherDisposable?.dispose() - } - lock.unlock() - } -} diff --git a/ReactiveKit/Disposables/SimpleDisposable.swift b/ReactiveKit/Disposables/SimpleDisposable.swift deleted file mode 100644 index 8b4c22a..0000000 --- a/ReactiveKit/Disposables/SimpleDisposable.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// A disposable that just encapsulates disposed state. -public final class SimpleDisposable: DisposableType { - public private(set) var isDisposed: Bool = false - - public func dispose() { - isDisposed = true - } - - public init() {} -} diff --git a/ReactiveKit/Info.plist b/ReactiveKit/Info.plist index 0c61092..7e7479f 100644 --- a/ReactiveKit/Info.plist +++ b/ReactiveKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.1.3 + 2.0.0 CFBundleSignature ???? CFBundleVersion diff --git a/ReactiveKit/Internals/ArrayDiff.swift b/ReactiveKit/Internals/ArrayDiff.swift deleted file mode 100644 index acc32f6..0000000 --- a/ReactiveKit/Internals/ArrayDiff.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -// Created by Dapeng Gao on 20/10/15. -// The central idea of this algorithm is taken from https://github.com/jflinter/Dwifft - -internal enum DiffStep { - case Insert(element: T, index: Int) - case Delete(element: T, index: Int) -} - -extension Array where Element: Equatable { - - internal static func diff(x: [Element], _ y: [Element]) -> [DiffStep] { - - if x.count == 0 { - return zip(y, y.indices).map(DiffStep.Insert) - } - - if y.count == 0 { - return zip(x, x.indices).map(DiffStep.Delete) - } - - // Use dynamic programming to generate a table such that `table[i][j]` represents - // the length of the longest common substring (LCS) between `x[0..] = [] - for var i = xLen, j = yLen; i > 0 || j > 0; { - if i == 0 { - j-- - backtrack.append(.Insert(element: y[j], index: j)) - } else if j == 0 { - i-- - backtrack.append(.Delete(element: x[i], index: i)) - } else if table[i][j] == table[i][j - 1] { - j-- - backtrack.append(.Insert(element: y[j], index: j)) - } else if table[i][j] == table[i - 1][j] { - i-- - backtrack.append(.Delete(element: x[i], index: i)) - } else { - i-- - j-- - } - } - - // Reverse the result - return backtrack.reverse() - } -} diff --git a/ReactiveKit/Internals/Reference.swift b/ReactiveKit/Internals/Reference.swift deleted file mode 100644 index edeb4ce..0000000 --- a/ReactiveKit/Internals/Reference.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// A simple wrapper around an optional that can retain or release given optional at will. -internal final class Reference { - - /// Encapsulated optional object. - internal weak var object: T? - - /// Used to strongly reference (retain) encapsulated object. - private var strongReference: T? - - /// Creates the wrapper and strongly references the given object. - internal init(_ object: T) { - self.object = object - self.strongReference = object - } - - /// Relinquishes strong reference to the object, but keeps weak one. - /// If object it not strongly referenced by anyone else, it will be deallocated. - internal func release() { - strongReference = nil - } - - /// Re-establishes a strong reference to the object if it's still alive, - /// otherwise it doesn't do anything useful. - internal func retain() { - strongReference = object - } -} diff --git a/ReactiveKit/Observable/MutableObservable.swift b/ReactiveKit/Observable/MutableObservable.swift deleted file mode 100644 index 5381655..0000000 --- a/ReactiveKit/Observable/MutableObservable.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public struct MutableObservable: ObservableType { - - private var observable: Observable - - public var value: Value { - get { - return observable.value - } - set { - observable.value = newValue - } - } - - public init(_ value: Value) { - observable = Observable(value) - } - - public func observe(on context: ExecutionContext? = ImmediateOnMainExecutionContext, observer: Value -> ()) -> DisposableType { - return observable.observe(on: context, observer: observer) - } - - public func silentUpdate(value: Value) { - observable.silentUpdate(value) - } -} diff --git a/ReactiveKit/ObservableBuffer/ObservableBuffer.swift b/ReactiveKit/ObservableBuffer/ObservableBuffer.swift deleted file mode 100644 index 56e8ded..0000000 --- a/ReactiveKit/ObservableBuffer/ObservableBuffer.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public final class ObservableBuffer: ActiveStream { - - public var buffer: [Event] = [] - public let limit: Int - - public init(limit: Int = Int.max) { - self.limit = limit - super.init() - } - - public init(limit: Int = Int.max, @noescape producer: Observer -> DisposableType?) { - self.limit = limit - super.init(producer: producer) - } - - public override func next(event: Event) { - buffer.append(event) - if buffer.count > limit { - buffer = Array(buffer.suffixFrom(1)) - } - super.next(event) - } - - public override func observe(on context: ExecutionContext? = ImmediateOnMainExecutionContext, observer: Observer) -> DisposableType { - let disposable = super.observe(on: context, observer: observer) - for event in buffer { - observer(event) - } - return disposable - } -} diff --git a/ReactiveKit/ObservableCollection/MutableObservableCollection.swift b/ReactiveKit/ObservableCollection/MutableObservableCollection.swift deleted file mode 100644 index d9cbf31..0000000 --- a/ReactiveKit/ObservableCollection/MutableObservableCollection.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public struct MutableObservableCollection: ObservableCollectionType { - - private var observableCollection: ObservableCollection - - public var collection: Collection { - get { - return observableCollection.collection - } - } - - public init(_ collection: Collection) { - observableCollection = ObservableCollection(collection) - } - - public func next(event: ObservableCollectionEvent) { - observableCollection.next(event) - } - - public func observe(on context: ExecutionContext? = ImmediateOnMainExecutionContext, observer: ObservableCollectionEvent -> ()) -> DisposableType { - return observableCollection.observe(on: context, observer: observer) - } - - // MARK: CollectionType conformance - - public func generate() -> Collection.Generator { - return collection.generate() - } - - public func underestimateCount() -> Int { - return collection.underestimateCount() - } - - public var startIndex: Collection.Index { - return collection.startIndex - } - - public var endIndex: Collection.Index { - return collection.endIndex - } - - public var isEmpty: Bool { - return collection.isEmpty - } - - public var count: Collection.Index.Distance { - return collection.count - } - - public subscript(index: Collection.Index) -> Collection.Generator.Element { - get { - return collection[index] - } - } -} diff --git a/ReactiveKit/ObservableCollection/ObservableCollection+Array.swift b/ReactiveKit/ObservableCollection/ObservableCollection+Array.swift deleted file mode 100644 index 8f047b0..0000000 --- a/ReactiveKit/ObservableCollection/ObservableCollection+Array.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -extension ObservableCollectionType where Collection == Array { - - /// Append `newElement` to the array. - public func append(newElement: Collection.Generator.Element) { - var new = collection - new.append(newElement) - next(ObservableCollectionEvent(collection: new, inserts: [collection.count], deletes: [], updates: [])) - } - - /// Insert `newElement` at index `i`. - public func insert(newElement: Collection.Generator.Element, atIndex: Int) { - var new = collection - new.insert(newElement, atIndex: atIndex) - next(ObservableCollectionEvent(collection: new, inserts: [atIndex], deletes: [], updates: [])) - } - - /// Insert elements `newElements` at index `i`. - public func insertContentsOf(newElements: [Collection.Generator.Element], at index: Collection.Index) { - var new = collection - new.insertContentsOf(newElements, at: index) - next(ObservableCollectionEvent(collection: new, inserts: Array(index.. Collection.Generator.Element { - var new = collection - let element = new.removeAtIndex(index) - next(ObservableCollectionEvent(collection: new, inserts: [], deletes: [index], updates: [])) - return element - } - - /// Remove an element from the end of the array in O(1). - public func removeLast() -> Collection.Generator.Element { - var new = collection - let element = new.removeLast() - next(ObservableCollectionEvent(collection: new, inserts: [], deletes: [new.count], updates: [])) - return element - } - - /// Remove all elements from the array. - public func removeAll() { - let deletes = Array(0.. Collection.Generator.Element { - get { - return collection[index] - } - set { - var new = collection - new[index] = newValue - next(ObservableCollectionEvent(collection: new, inserts: [], deletes: [], updates: [index])) - } - } -} - -extension ObservableCollectionType where Collection == Array, Element: Equatable, Index == Int { - - /// Replace current array with the new array and send change events. - /// - /// - Parameters: - /// - newCollection: The array to replace current array with. - /// - performDiff: When `true`, difference between the current array and the new array will be calculated - /// and the sent event will contain exact description of which elements were inserted and which deleted.\n - /// When `false`, the sent event contains current array indices as `deletes` indices and new array indices as - /// `insertes` indices. - /// - /// - Complexity: O(1) if `performDiff == false`. Otherwise O(`collection.count * newCollection.count`). - public func replace(newCollection: Collection, performDiff: Bool) { - if performDiff { - var inserts: [Int] = [] - var deletes: [Int] = [] - - inserts.reserveCapacity(collection.count) - deletes.reserveCapacity(collection.count) - - let diff = Collection.diff(collection, newCollection) - - for diffStep in diff { - switch diffStep { - case .Insert(_, let index): inserts.append(index) - case .Delete(_, let index): deletes.append(index) - } - } - - next(ObservableCollectionEvent(collection: newCollection, inserts: inserts, deletes: deletes, updates: [])) - } else { - replace(newCollection) - } - } -} diff --git a/ReactiveKit/ObservableCollection/ObservableCollection+Dictionary.swift b/ReactiveKit/ObservableCollection/ObservableCollection+Dictionary.swift deleted file mode 100644 index fef2076..0000000 --- a/ReactiveKit/ObservableCollection/ObservableCollection+Dictionary.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public protocol DictionaryIndexType { - typealias Key: Hashable - typealias Value - func successor() -> DictionaryIndex -} - -extension DictionaryIndex: DictionaryIndexType {} - -extension ObservableCollectionType where Index: DictionaryIndexType, Collection == Dictionary { - - public func indexForKey(key: Index.Key) -> Collection.Index? { - return collection.indexForKey(key) - } - - public subscript (key: Index.Key) -> Index.Value? { - get { - return collection[key] - } - set { - if let value = newValue { - updateValue(value, forKey: key) - } else { - removeValueForKey(key) - } - } - } - - public subscript (position: Collection.Index) -> Collection.Generator.Element { - get { - return collection[position] - } - } - - public func updateValue(value: Index.Value, forKey key: Index.Key) -> Index.Value? { - var new = collection - if let index = new.indexForKey(key) { - let oldValue = new.updateValue(value, forKey: key) - next(ObservableCollectionEvent(collection: new, inserts: [], deletes: [], updates: [index])) - return oldValue - } else { - new.updateValue(value, forKey: key) - let index = new.indexForKey(key)! - next(ObservableCollectionEvent(collection: new, inserts: [index], deletes: [], updates: [])) - return nil - } - } - - public func removeValueForKey(key: Index.Key) -> Index.Value? { - if let index = collection.indexForKey(key) { - var new = collection - let oldValue = new.removeValueForKey(key) - next(ObservableCollectionEvent(collection: new, inserts: [], deletes: [index], updates: [])) - return oldValue - } else { - return nil - } - } -} - diff --git a/ReactiveKit/ObservableCollection/ObservableCollection+Set.swift b/ReactiveKit/ObservableCollection/ObservableCollection+Set.swift deleted file mode 100644 index 183c544..0000000 --- a/ReactiveKit/ObservableCollection/ObservableCollection+Set.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -extension ObservableCollectionType where Element: Hashable, Collection == Set { - - public func contains(member: Collection.Generator.Element) -> Bool { - return collection.contains(member) - } - - public func indexOf(member: Collection.Generator.Element) -> SetIndex? { - return collection.indexOf(member) - } - - public subscript (position: SetIndex) -> Collection.Generator.Element { - get { - return collection[position] - } - } - - public func insert(member: Collection.Generator.Element) { - var new = collection - new.insert(member) - - if let index = collection.indexOf(member) { - next(ObservableCollectionEvent(collection: new, inserts: [], deletes: [], updates: [index])) - } else { - next(ObservableCollectionEvent(collection: new, inserts: [new.indexOf(member)!], deletes: [], updates: [])) - } - } - - public func remove(member: Collection.Generator.Element) -> Collection.Generator.Element? { - var new = collection - - if let index = collection.indexOf(member) { - let old = new.removeAtIndex(index) - next(ObservableCollectionEvent(collection: new, inserts: [], deletes: [index], updates: [])) - return old - } else { - return nil - } - } -} diff --git a/ReactiveKit/ObservableCollection/ObservableCollection.swift b/ReactiveKit/ObservableCollection/ObservableCollection.swift deleted file mode 100644 index 13fd5ed..0000000 --- a/ReactiveKit/ObservableCollection/ObservableCollection.swift +++ /dev/null @@ -1,186 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public protocol ObservableCollectionType: CollectionType, StreamType { - typealias Collection: CollectionType - typealias Index = Collection.Index - typealias Element = Collection.Generator.Element - - var collection: Collection { get } - func next(event: ObservableCollectionEvent) - - func observe(on context: ExecutionContext?, observer: ObservableCollectionEvent -> ()) -> DisposableType -} - -public final class ObservableCollection: ActiveStream>, ObservableCollectionType { - - private var _collection: Collection! = nil - - public var collection: Collection { - return _collection - } - - public init(_ collection: Collection) { - _collection = collection - super.init() - } - - public override init(@noescape producer: (ObservableCollectionEvent -> ()) -> DisposableType?) { - super.init(producer: producer) - } - - public override func next(event: ObservableCollectionEvent) { - _collection = event.collection - super.next(event) - } - - public override func observe(on context: ExecutionContext? = ImmediateOnMainExecutionContext, observer: Observer) -> DisposableType { - let disposable = super.observe(on: context, observer: observer) - observer(ObservableCollectionEvent.initial(collection)) - return disposable - } - - /** - Allows updating the observable collection without generation of the events. - - ``` - array.silentUpdate { array in - array.append(5) - } - ``` - */ - public func silentUpdate(@noescape perform: ObservableCollection -> Void) { - let collection = ObservableCollection(self.collection) - perform(collection) - _collection = collection.collection - } - - // MARK: CollectionType conformance - - public func generate() -> Collection.Generator { - return collection.generate() - } - - public func underestimateCount() -> Int { - return collection.underestimateCount() - } - - public var startIndex: Collection.Index { - return collection.startIndex - } - - public var endIndex: Collection.Index { - return collection.endIndex - } - - public var isEmpty: Bool { - return collection.isEmpty - } - - public var count: Collection.Index.Distance { - return collection.count - } - - public subscript(index: Collection.Index) -> Collection.Generator.Element { - get { - return collection[index] - } - } -} - -@warn_unused_result -public func create(producer: (ObservableCollectionEvent -> ()) -> DisposableType?) -> ObservableCollection { - return ObservableCollection(producer: producer) -} - -public extension ObservableCollectionType { - - public func replace(newCollection: Collection) { - let deletes = Array(collection.indices) - let inserts = Array(newCollection.indices) - next(ObservableCollectionEvent(collection: newCollection, inserts: inserts, deletes: deletes, updates: [])) - } -} - -public extension ObservableCollectionType where Collection.Index == Int { - - /// Each event costs O(n) - @warn_unused_result - public func map(transform: Collection.Generator.Element -> U) -> ObservableCollection> { - return create { observer in - return self.observe(on: nil) { event in - observer(event.map(transform)) - } - } - } - - /// Each event costs O(1) - @warn_unused_result - public func lazyMap(transform: Collection.Generator.Element -> U) -> ObservableCollection> { - return create { observer in - return self.observe(on: nil) { event in - observer(event.lazyMap(transform)) - } - } - } -} - -public extension ObservableCollectionType where Collection.Index == Int { - - /// Each event costs O(n) - @warn_unused_result - public func filter(include: Collection.Generator.Element -> Bool) -> ObservableCollection> { - return create { observer in - return self.observe(on: nil) { event in - observer(event.filter(include)) - } - } - } -} - -public extension ObservableCollectionType where Collection.Index: Hashable { - - /// Each event costs O(n*logn) - @warn_unused_result - public func sort(isOrderedBefore: (Collection.Generator.Element, Collection.Generator.Element) -> Bool) -> ObservableCollection> { - return create { observer in - return self.observe(on: nil) { event in - observer(event.sort(isOrderedBefore)) - } - } - } -} - -public extension ObservableCollectionType where Collection.Index: Equatable { - - /// Each event costs O(n^2) - @warn_unused_result - public func sort(isOrderedBefore: (Collection.Generator.Element, Collection.Generator.Element) -> Bool) -> ObservableCollection> { - return create { observer in - return self.observe(on: nil) { event in - observer(event.sort(isOrderedBefore)) - } - } - } -} diff --git a/ReactiveKit/ObservableCollection/ObservableCollectionEvent.swift b/ReactiveKit/ObservableCollection/ObservableCollectionEvent.swift deleted file mode 100644 index d884a30..0000000 --- a/ReactiveKit/ObservableCollection/ObservableCollectionEvent.swift +++ /dev/null @@ -1,128 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public protocol ObservableCollectionEventType { - typealias Collection: CollectionType - - var collection: Collection { get } - - var inserts: [Collection.Index] { get } - var deletes: [Collection.Index] { get } - var updates: [Collection.Index] { get } -} - -public struct ObservableCollectionEvent: ObservableCollectionEventType { - public let collection: Collection - - public let inserts: [Collection.Index] - public let deletes: [Collection.Index] - public let updates: [Collection.Index] - - public static func initial(collection: Collection) -> ObservableCollectionEvent { - return ObservableCollectionEvent(collection: collection, inserts: [], deletes: [], updates: []) - } - - public init(collection: Collection, inserts: [Collection.Index], deletes: [Collection.Index], updates: [Collection.Index]) { - self.collection = collection - self.inserts = inserts - self.deletes = deletes - self.updates = updates - } -} - -public extension ObservableCollectionEvent { - public init(ObservableCollectionEvent: CE) { - collection = ObservableCollectionEvent.collection - inserts = ObservableCollectionEvent.inserts - deletes = ObservableCollectionEvent.deletes - updates = ObservableCollectionEvent.updates - } -} - -public extension ObservableCollectionEventType where Collection.Index == Int { - - /// O(n) - public func map(transform: Collection.Generator.Element -> U) -> ObservableCollectionEvent<[U]> { - return ObservableCollectionEvent(collection: collection.map(transform), inserts: inserts, deletes: deletes, updates: updates) - } - - /// O(1) - public func lazyMap(transform: Collection.Generator.Element -> U) -> ObservableCollectionEvent> { - return ObservableCollectionEvent(collection: collection.lazy.map(transform), inserts: inserts, deletes: deletes, updates: updates) - } -} - -public extension ObservableCollectionEventType where Collection.Index == Int { - - /// O(n) - public func filter(include: Collection.Generator.Element -> Bool) -> ObservableCollectionEvent> { - - let filteredPairs = zip(collection.indices, collection).filter { include($0.1) } - let includedIndices = Set(filteredPairs.map { $0.0 }) - - let filteredCollection = filteredPairs.map { $0.1 } - let filteredInserts = inserts.filter { includedIndices.contains($0) } - let filteredDeletes = deletes.filter { includedIndices.contains($0) } - let filteredUpdates = updates.filter { includedIndices.contains($0) } - - return ObservableCollectionEvent(collection: filteredCollection, inserts: filteredInserts, deletes: filteredDeletes, updates: filteredUpdates) - } -} - -public extension ObservableCollectionEventType where Collection.Index: Hashable { - - /// O(n*logn) - public func sort(isOrderedBefore: (Collection.Generator.Element, Collection.Generator.Element) -> Bool) -> ObservableCollectionEvent> { - let sortedPairs = zip(collection.indices, collection).sort { isOrderedBefore($0.1, $1.1) } - - var sortMap: [Collection.Index: Int] = [:] - for (index, pair) in sortedPairs.enumerate() { - sortMap[pair.0] = index - } - - let sortedCollection = sortedPairs.map { $0.1 } - let newInserts = inserts.map { sortMap[$0]! } - let newDeletes = deletes.map { sortMap[$0]! } - let newUpdates = updates.map { sortMap[$0]! } - - return ObservableCollectionEvent(collection: sortedCollection, inserts: newInserts, deletes: newDeletes, updates: newUpdates) - } -} - -public extension ObservableCollectionEventType where Collection.Index: Equatable { - - /// O(n^2) - public func sort(isOrderedBefore: (Collection.Generator.Element, Collection.Generator.Element) -> Bool) -> ObservableCollectionEvent> { - let sortedPairs = zip(collection.indices, collection).sort { isOrderedBefore($0.1, $1.1) } - let sortedIndices = sortedPairs.map { $0.0 } - - let newInserts = inserts.map { sortedIndices.indexOf($0)! } - let newDeletes = deletes.map { sortedIndices.indexOf($0)! } - let newUpdates = updates.map { sortedIndices.indexOf($0)! } - - let sortedCollection = sortedPairs.map { $0.1 } - - return ObservableCollectionEvent(collection: sortedCollection, inserts: newInserts, deletes: newDeletes, updates: newUpdates) - } -} diff --git a/ReactiveKit/Operation/Operation.swift b/ReactiveKit/Operation/Operation.swift deleted file mode 100644 index f502b65..0000000 --- a/ReactiveKit/Operation/Operation.swift +++ /dev/null @@ -1,801 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public protocol OperationType: StreamType { - typealias Value - typealias Error: ErrorType - - func lift(transform: Stream> -> Stream>) -> Operation - func observe(on context: ExecutionContext?, observer: OperationEvent -> ()) -> DisposableType -} - -public struct Operation: OperationType { - - private let stream: Stream> - - public init(producer: (OperationObserver -> DisposableType?)) { - stream = Stream { observer in - var completed: Bool = false - - return producer(OperationObserver { event in - if !completed { - observer(event) - completed = event._unbox.isTerminal - } - }) - } - } - - public func observe(on context: ExecutionContext? = ImmediateOnMainExecutionContext, observer: OperationEvent -> ()) -> DisposableType { - return stream.observe(on: context, observer: observer) - } - - public static func succeeded(with value: Value) -> Operation { - return create { observer in - observer.next(value) - observer.success() - return nil - } - } - - public static func failed(with error: Error) -> Operation { - return create { observer in - observer.failure(error) - return nil - } - } - - public func lift(transform: Stream> -> Stream>) -> Operation { - return create { observer in - return transform(self.stream).observe(on: nil, observer: observer.observer) - } - } -} - - -public func create(producer producer: OperationObserver -> DisposableType?) -> Operation { - return Operation { observer in - return producer(observer) - } -} - -public extension OperationType { - - public func on(next next: (Value -> ())? = nil, success: (() -> ())? = nil, failure: (Error -> ())? = nil, start: (() -> Void)? = nil, completed: (() -> Void)? = nil, context: ExecutionContext? = ImmediateOnMainExecutionContext) -> Operation { - return create { observer in - start?() - return self.observe(on: context) { event in - switch event { - case .Next(let value): - next?(value) - case .Failure(let error): - failure?(error) - completed?() - case .Success: - success?() - completed?() - } - - observer.observer(event) - } - } - } - - public func observeNext(on context: ExecutionContext? = ImmediateOnMainExecutionContext, observer: Value -> ()) -> DisposableType { - return self.observe(on: context) { event in - switch event { - case .Next(let event): - observer(event) - default: break - } - } - } - - public func observeError(on context: ExecutionContext? = ImmediateOnMainExecutionContext, observer: Error -> ()) -> DisposableType { - return self.observe(on: context) { event in - switch event { - case .Failure(let error): - observer(error) - default: break - } - } - } - - @warn_unused_result - public func shareNext(limit: Int = Int.max, context: ExecutionContext? = nil) -> ObservableBuffer { - return ObservableBuffer(limit: limit) { observer in - return self.observeNext(on: context, observer: observer) - } - } - - @warn_unused_result - public func map(transform: Value -> U) -> Operation { - return lift { $0.map { $0.map(transform) } } - } - - @warn_unused_result - public func tryMap(transform: Value -> Result) -> Operation { - return lift { $0.map { operationEvent in - switch operationEvent { - case .Next(let value): - switch transform(value) { - case let .Success(value): - return .Next(value) - case let .Failure(error): - return .Failure(error) - } - case .Failure(let error): - return .Failure(error) - case .Success: - return .Success - } - } - } - } - - @warn_unused_result - public func mapError(transform: Error -> F) -> Operation { - return lift { $0.map { $0.mapError(transform) } } - } - - @warn_unused_result - public func filter(include: Value -> Bool) -> Operation { - return lift { $0.filter { $0.filter(include) } } - } - - @warn_unused_result - public func switchTo(context: ExecutionContext) -> Operation { - return lift { $0.switchTo(context) } - } - - @warn_unused_result - public func throttle(seconds: Double, on queue: Queue) -> Operation { - return lift { $0.throttle(seconds, on: queue) } - } - - @warn_unused_result - public func skip(count: Int) -> Operation { - return lift { $0.skip(count) } - } - - @warn_unused_result - public func startWith(event: Value) -> Operation { - return lift { $0.startWith(.Next(event)) } - } - - @warn_unused_result - public func retry(var count: Int) -> Operation { - return create { observer in - let serialDisposable = SerialDisposable(otherDisposable: nil) - - var attempt: (() -> Void)? - - attempt = { - serialDisposable.otherDisposable?.dispose() - serialDisposable.otherDisposable = self.observe(on: nil) { event in - switch event { - case .Failure(let error): - if count > 0 { - count-- - attempt?() - } else { - observer.failure(error) - attempt = nil - } - default: - observer.observer(event._unbox) - attempt = nil - } - } - } - - attempt?() - return BlockDisposable { - serialDisposable.dispose() - attempt = nil - } - } - } - - @warn_unused_result - public func take(count: Int) -> Operation { - return create { observer in - - if count <= 0 { - observer.success() - return nil - } - - var taken = 0 - - let serialDisposable = SerialDisposable(otherDisposable: nil) - serialDisposable.otherDisposable = self.observe(on: nil) { event in - - switch event { - case .Next(let value): - if taken < count { - taken += 1 - observer.next(value) - } - if taken == count { - observer.success() - serialDisposable.otherDisposable?.dispose() - } - default: - observer.observer(event) - } - } - - return serialDisposable - } - } - - @warn_unused_result - public func first() -> Operation { - return take(1) - } - - @warn_unused_result - public func takeLast(count: Int = 1) -> Operation { - return create { observer in - - var values: [Value] = [] - values.reserveCapacity(count) - - return self.observe(on: nil) { event in - - switch event { - case .Next(let value): - while values.count + 1 > count { - values.removeFirst() - } - values.append(value) - case .Success: - values.forEach(observer.next) - observer.success() - default: - observer.observer(event) - } - } - } - } - - @warn_unused_result - public func last() -> Operation { - return takeLast(1) - } - - @warn_unused_result - public func pausable(by: S) -> Operation { - return create { observer in - - var allowed: Bool = true - - let compositeDisposable = CompositeDisposable() - compositeDisposable += by.observe(on: nil) { value in - allowed = value - } - - compositeDisposable += self.observe(on: nil) { event in - switch event { - case .Next(let value): - if allowed { - observer.next(value) - } - default: - observer.observer(event) - } - } - - return compositeDisposable - } - } - - @warn_unused_result - public func scan(initial: U, _ combine: (U, Value) -> U) -> Operation { - return create { observer in - - var scanned = initial - - return self.observe(on: nil) { event in - observer.observer(event.map { value in - scanned = combine(scanned, value) - return scanned - }) - } - } - } - - @warn_unused_result - public func reduce(initial: U, _ combine: (U, Value) -> U) -> Operation { - return Operation { observer in - observer.next(initial) - return self.scan(initial, combine).observe(on: nil, observer: observer.observer) - }.takeLast() - } - - @warn_unused_result - public func collect() -> Operation<[Value], Error> { - return reduce([], { memo, new in memo + [new] }) - } - - @warn_unused_result - public func combineLatestWith(other: S) -> Operation<(Value, S.Value), Error> { - return create { observer in - let queue = Queue(name: "com.ReactiveKit.ReactiveKit.Operation.CombineLatestWith") - - var latestSelfValue: Value! = nil - var latestOtherValue: S.Value! = nil - - var latestSelfEvent: OperationEvent! = nil - var latestOtherEvent: OperationEvent! = nil - - let dispatchNextIfPossible = { () -> () in - if let latestSelfValue = latestSelfValue, latestOtherValue = latestOtherValue { - observer.next(latestSelfValue, latestOtherValue) - } - } - - let onBoth = { () -> () in - if let latestSelfEvent = latestSelfEvent, let latestOtherEvent = latestOtherEvent { - switch (latestSelfEvent, latestOtherEvent) { - case (.Success, .Success): - observer.success() - case (.Next(let selfValue), .Next(let otherValue)): - latestSelfValue = selfValue - latestOtherValue = otherValue - dispatchNextIfPossible() - case (.Next(let selfValue), .Success): - latestSelfValue = selfValue - dispatchNextIfPossible() - case (.Success, .Next(let otherValue)): - latestOtherValue = otherValue - dispatchNextIfPossible() - default: - dispatchNextIfPossible() - } - } - } - - let selfDisposable = self.observe(on: nil) { event in - if case .Failure(let error) = event { - observer.failure(error) - } else { - queue.sync { - latestSelfEvent = event - onBoth() - } - } - } - - let otherDisposable = other.observe(on: nil) { event in - if case .Failure(let error) = event { - observer.failure(error) - } else { - queue.sync { - latestOtherEvent = event - onBoth() - } - } - } - - return CompositeDisposable([selfDisposable, otherDisposable]) - } - } - - @warn_unused_result - public func zipWith(other: S) -> Operation<(Value, S.Value), Error> { - return create { observer in - let queue = Queue(name: "com.ReactiveKit.ReactiveKit.ZipWith") - - var selfBuffer = Array() - var selfCompleted = false - var otherBuffer = Array() - var otherCompleted = false - - let dispatchIfPossible = { - while selfBuffer.count > 0 && otherBuffer.count > 0 { - observer.next((selfBuffer[0], otherBuffer[0])) - selfBuffer.removeAtIndex(0) - otherBuffer.removeAtIndex(0) - } - - if (selfCompleted && selfBuffer.isEmpty) || (otherCompleted && otherBuffer.isEmpty) { - observer.success() - } - } - - let selfDisposable = self.observe(on: nil) { event in - switch event { - case .Failure(let error): - observer.failure(error) - case .Success: - queue.sync { - selfCompleted = true - dispatchIfPossible() - } - case .Next(let value): - queue.sync { - selfBuffer.append(value) - dispatchIfPossible() - } - } - } - - let otherDisposable = other.observe(on: nil) { event in - switch event { - case .Failure(let error): - observer.failure(error) - case .Success: - queue.sync { - otherCompleted = true - dispatchIfPossible() - } - case .Next(let value): - queue.sync { - otherBuffer.append(value) - dispatchIfPossible() - } - } - } - - return CompositeDisposable([selfDisposable, otherDisposable]) - } - } -} - -public extension OperationType where Value: OptionalType { - - @warn_unused_result - public func ignoreNil() -> Operation { - return lift { $0.filter { $0.filter { $0._unbox != nil } }.map { $0.map { $0._unbox! } } } - } -} - -public extension OperationType where Value: OperationType, Value.Error == Error { - - @warn_unused_result - public func merge() -> Operation { - return create { observer in - let queue = Queue(name: "com.ReactiveKit.ReactiveKit.Operation.Merge") - - var numberOfOperations = 1 - let compositeDisposable = CompositeDisposable() - - let decrementNumberOfOperations = { () -> () in - queue.sync { - numberOfOperations -= 1 - if numberOfOperations == 0 { - observer.success() - } - } - } - - compositeDisposable += self.observe(on: nil) { taskEvent in - - switch taskEvent { - case .Failure(let error): - return observer.failure(error) - case .Success: - decrementNumberOfOperations() - case .Next(let task): - queue.sync { - numberOfOperations += 1 - } - compositeDisposable += task.observe(on: nil) { event in - switch event { - case .Next, .Failure: - observer.observer(event) - case .Success: - decrementNumberOfOperations() - } - } - } - } - return compositeDisposable - } - } - - @warn_unused_result - public func switchToLatest() -> Operation { - return create { observer in - let serialDisposable = SerialDisposable(otherDisposable: nil) - let compositeDisposable = CompositeDisposable([serialDisposable]) - - var outerCompleted: Bool = false - var innerCompleted: Bool = false - - compositeDisposable += self.observe(on: nil) { taskEvent in - - switch taskEvent { - case .Failure(let error): - observer.failure(error) - case .Success: - outerCompleted = true - if innerCompleted { - observer.success() - } - case .Next(let innerOperation): - innerCompleted = false - serialDisposable.otherDisposable?.dispose() - serialDisposable.otherDisposable = innerOperation.observe(on: nil) { event in - - switch event { - case .Failure(let error): - observer.failure(error) - case .Success: - innerCompleted = true - if outerCompleted { - observer.success() - } - case .Next(let value): - observer.next(value) - } - } - } - } - - return compositeDisposable - } - } - - @warn_unused_result - public func concat() -> Operation { - return create { observer in - let queue = Queue(name: "com.ReactiveKit.ReactiveKit.Operation.Concat") - - let serialDisposable = SerialDisposable(otherDisposable: nil) - let compositeDisposable = CompositeDisposable([serialDisposable]) - - var outerCompleted: Bool = false - var innerCompleted: Bool = true - - var taskQueue: [Value] = [] - - var startNextOperation: (() -> ())! = nil - startNextOperation = { - innerCompleted = false - - let task: Value = queue.sync { - return taskQueue.removeAtIndex(0) - } - - serialDisposable.otherDisposable?.dispose() - serialDisposable.otherDisposable = task.observe(on: nil) { event in - switch event { - case .Failure(let error): - observer.failure(error) - case .Success: - innerCompleted = true - if taskQueue.count > 0 { - startNextOperation() - } else if outerCompleted { - observer.success() - } - case .Next(let value): - observer.next(value) - } - } - } - - let addToQueue = { (task: Value) -> () in - queue.sync { - taskQueue.append(task) - } - - if innerCompleted { - startNextOperation() - } - } - - compositeDisposable += self.observe(on: nil) { taskEvent in - - switch taskEvent { - case .Failure(let error): - observer.failure(error) - case .Success: - outerCompleted = true - if innerCompleted { - observer.success() - } - case .Next(let innerOperation): - addToQueue(innerOperation) - } - } - - return compositeDisposable - } - } -} - -public enum OperationFlatMapStrategy { - case Latest - case Merge - case Concat -} - -public extension OperationType { - - @warn_unused_result - public func flatMap(strategy: OperationFlatMapStrategy, transform: Value -> T) -> Operation { - switch strategy { - case .Latest: - return map(transform).switchToLatest() - case .Merge: - return map(transform).merge() - case .Concat: - return map(transform).concat() - } - } - - @warn_unused_result - public func flatMapError(recover: Error -> T) -> Operation { - return create { observer in - let serialDisposable = SerialDisposable(otherDisposable: nil) - - serialDisposable.otherDisposable = self.observe(on: nil) { taskEvent in - switch taskEvent { - case .Next(let value): - observer.next(value) - case .Success: - observer.success() - case .Failure(let error): - serialDisposable.otherDisposable = recover(error).observe(on: nil) { event in - observer.observer(event) - } - } - } - - return serialDisposable - } - } -} - -@warn_unused_result -public func combineLatest(a: A, _ b: B) -> Operation<(A.Value, B.Value), A.Error> { - return a.combineLatestWith(b) -} - -@warn_unused_result -public func zip(a: A, _ b: B) -> Operation<(A.Value, B.Value), A.Error> { - return a.zipWith(b) -} - -@warn_unused_result -public func combineLatest(a: A, _ b: B, _ c: C) -> Operation<(A.Value, B.Value, C.Value), A.Error> { - return combineLatest(a, b).combineLatestWith(c).map { ($0.0, $0.1, $1) } -} - -@warn_unused_result -public func zip(a: A, _ b: B, _ c: C) -> Operation<(A.Value, B.Value, C.Value), A.Error> { - return zip(a, b).zipWith(c).map { ($0.0, $0.1, $1) } -} - -@warn_unused_result -public func combineLatest(a: A, _ b: B, _ c: C, _ d: D) -> Operation<(A.Value, B.Value, C.Value, D.Value), A.Error> { - return combineLatest(a, b, c).combineLatestWith(d).map { ($0.0, $0.1, $0.2, $1) } -} - -@warn_unused_result -public func zip(a: A, _ b: B, _ c: C, _ d: D) -> Operation<(A.Value, B.Value, C.Value, D.Value), A.Error> { - return zip(a, b, c).zipWith(d).map { ($0.0, $0.1, $0.2, $1) } -} - -@warn_unused_result -public func combineLatest - (a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value), A.Error> -{ - return combineLatest(a, b, c, d).combineLatestWith(e).map { ($0.0, $0.1, $0.2, $0.3, $1) } -} - -@warn_unused_result -public func zip - (a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value), A.Error> -{ - return zip(a, b, c, d).zipWith(e).map { ($0.0, $0.1, $0.2, $0.3, $1) } -} - -@warn_unused_result -public func combineLatest - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value), A.Error> -{ - return combineLatest(a, b, c, d, e).combineLatestWith(f).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $1) } -} - -@warn_unused_result -public func zip - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value), A.Error> -{ - return zip(a, b, c, d, e).zipWith(f).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $1) } -} - -@warn_unused_result -public func combineLatest - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value), A.Error> -{ - return combineLatest(a, b, c, d, e, f).combineLatestWith(g).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $1) } -} - -@warn_unused_result -public func zip - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value), A.Error> -{ - return zip(a, b, c, d, e, f).zipWith(g).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $1) } -} - -@warn_unused_result -public func combineLatest - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value), A.Error> -{ - return combineLatest(a, b, c, d, e, f, g).combineLatestWith(h).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $1) } -} - -@warn_unused_result -public func zip - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value), A.Error> -{ - return zip(a, b, c, d, e, f, g).zipWith(h).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $1) } -} - -@warn_unused_result -public func combineLatest - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value, I.Value), A.Error> -{ - return combineLatest(a, b, c, d, e, f, g, h).combineLatestWith(i).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $0.7, $1) } -} - -@warn_unused_result -public func zip - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value, I.Value), A.Error> -{ - return zip(a, b, c, d, e, f, g, h).zipWith(i).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $0.7, $1) } -} - -@warn_unused_result -public func combineLatest - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value, I.Value, J.Value), A.Error> -{ - return combineLatest(a, b, c, d, e, f, g, h, i).combineLatestWith(j).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $0.7, $0.8, $1) } -} - -@warn_unused_result -public func zip - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value, I.Value, J.Value), A.Error> -{ - return zip(a, b, c, d, e, f, g, h, i).zipWith(j).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $0.7, $0.8, $1) } -} - -@warn_unused_result -public func combineLatest - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J, _ k: K) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value, I.Value, J.Value, K.Value), A.Error> -{ - return combineLatest(a, b, c, d, e, f, g, h, i, j).combineLatestWith(k).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $0.7, $0.8, $0.9, $1) } -} - -@warn_unused_result -public func zip - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J, _ k: K) -> Operation<(A.Value, B.Value, C.Value, D.Value, E.Value, F.Value, G.Value, H.Value, I.Value, J.Value, K.Value), A.Error> -{ - return zip(a, b, c, d, e, f, g, h, i, j).zipWith(k).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $0.7, $0.8, $0.9, $1) } -} diff --git a/ReactiveKit/Operation/OperationEvent.swift b/ReactiveKit/Operation/OperationEvent.swift deleted file mode 100644 index 4ce374e..0000000 --- a/ReactiveKit/Operation/OperationEvent.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public protocol OperationEventType { - typealias Value - typealias Error: ErrorType - - var _unbox: OperationEvent { get } -} - -public enum OperationEvent: OperationEventType { - - case Next(Value) - case Failure(Error) - case Success - - public var _unbox: OperationEvent { - return self - } - - public var isTerminal: Bool { - switch self { - case .Next: return false - default: return true - } - } - - public func map(transform: Value -> U) -> OperationEvent { - switch self { - case .Next(let event): - return .Next(transform(event)) - case .Failure(let error): - return .Failure(error) - case .Success: - return .Success - } - } - - public func mapError(transform: Error -> F) -> OperationEvent { - switch self { - case .Next(let event): - return .Next(event) - case .Failure(let error): - return .Failure(transform(error)) - case .Success: - return .Success - } - } - - public func filter(include: Value -> Bool) -> Bool { - switch self { - case .Next(let value): - if include(value) { - return true - } else { - return false - } - default: - return true - } - } -} - -public func == (left: OperationEvent, right: OperationEvent) -> Bool { - switch (left, right) { - case (.Next(let valueL), .Next(let valueR)): - return valueL == valueR - case (.Failure(let errorL), .Failure(let errorR)): - return errorL == errorR - case (.Success, .Success): - return true - default: - return false - } -} diff --git a/ReactiveKit/Operation/OperationSink.swift b/ReactiveKit/Operation/OperationSink.swift deleted file mode 100644 index 0f2bb03..0000000 --- a/ReactiveKit/Operation/OperationSink.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public struct OperationObserver { - public let observer: OperationEvent -> () - - public func next(event: Value) { - observer(.Next(event)) - } - - public func success() { - observer(.Success) - } - - public func failure(error: Error) { - observer(.Failure(error)) - } -} diff --git a/ReactiveKit/Operation/Stream+Operation.swift b/ReactiveKit/Operation/Stream+Operation.swift deleted file mode 100644 index 8f31ed9..0000000 --- a/ReactiveKit/Operation/Stream+Operation.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public extension StreamType where Event: OperationType { - - @warn_unused_result - public func merge() -> Operation { - return create { observer in - let compositeDisposable = CompositeDisposable() - - compositeDisposable += self.observe(on: nil) { task in - compositeDisposable += task.observe(on: nil) { event in - switch event { - case .Next, .Failure: - observer.observer(event) - case .Success: - break - } - } - } - return compositeDisposable - } - } - - @warn_unused_result - public func switchToLatest() -> Operation { - return create { observer in - let serialDisposable = SerialDisposable(otherDisposable: nil) - let compositeDisposable = CompositeDisposable([serialDisposable]) - - compositeDisposable += self.observe(on: nil) { task in - - serialDisposable.otherDisposable?.dispose() - serialDisposable.otherDisposable = task.observe(on: nil) { event in - - switch event { - case .Failure(let error): - observer.failure(error) - case .Success: - break - case .Next(let value): - observer.next(value) - } - } - } - - return compositeDisposable - } - } - - @warn_unused_result - public func concat() -> Operation { - return create { observer in - let serialDisposable = SerialDisposable(otherDisposable: nil) - let compositeDisposable = CompositeDisposable([serialDisposable]) - - var innerCompleted: Bool = true - - var taskQueue: [Event] = [] - - var startNextOperation: (() -> ())! = nil - startNextOperation = { - innerCompleted = false - let task = taskQueue.removeAtIndex(0) - - serialDisposable.otherDisposable?.dispose() - serialDisposable.otherDisposable = task.observe(on: nil) { event in - switch event { - case .Failure(let error): - observer.failure(error) - case .Success: - innerCompleted = true - if taskQueue.count > 0 { - startNextOperation() - } - case .Next(let value): - observer.next(value) - } - } - } - - let addToQueue = { (task: Event) -> () in - taskQueue.append(task) - if innerCompleted { - startNextOperation() - } - } - - compositeDisposable += self.observe(on: nil) { task in - addToQueue(task) - } - - return compositeDisposable - } - } -} - -public extension StreamType { - - @warn_unused_result - public func flatMap(strategy: OperationFlatMapStrategy, transform: Event -> T) -> Operation { - switch strategy { - case .Latest: - return map(transform).switchToLatest() - case .Merge: - return map(transform).merge() - case .Concat: - return map(transform).concat() - } - } -} diff --git a/ReactiveKit/Other/Bindable.swift b/ReactiveKit/Other/Bindable.swift deleted file mode 100644 index 2b08949..0000000 --- a/ReactiveKit/Other/Bindable.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public protocol BindableType { - typealias Event - - /// Returns an observer that can be used to dispatch events to the receiver. - /// Can accept a disposable that will be disposed on receiver's deinit. - func observer(disconnectDisposable: DisposableType?) -> (Event -> ()) -} - -extension ActiveStream: BindableType { - - /// Creates a new observer that can be used to update the receiver. - /// Optionally accepts a disposable that will be disposed on receiver's deinit. - public func observer(disconnectDisposable: DisposableType?) -> Event -> () { - - if let disconnectDisposable = disconnectDisposable { - registerDisposable(disconnectDisposable) - } - - return { [weak self] value in - self?.next(value) - } - } -} - -extension StreamType { - - /// Establishes a one-way binding between the source and the bindable's observer - /// and returns a disposable that can cancel observing. - public func bindTo(bindable: B, context: ExecutionContext? = nil) -> DisposableType { - let disposable = SerialDisposable(otherDisposable: nil) - let observer = bindable.observer(disposable) - disposable.otherDisposable = observe(on: context) { value in - observer(value) - } - return disposable - } - - /// Establishes a one-way binding between the source and the bindable's observer - /// and returns a disposable that can cancel observing. - public func bindTo(bindable: B, context: ExecutionContext? = nil) -> DisposableType { - let disposable = SerialDisposable(otherDisposable: nil) - let observer = bindable.observer(disposable) - disposable.otherDisposable = observe(on: context) { value in - observer(value) - } - return disposable - } - - /// Establishes a one-way binding between the source and the bindable's observer - /// and returns a disposable that can cancel observing. - public func bindTo(bindable: S, context: ExecutionContext? = nil) -> DisposableType { - let disposable = SerialDisposable(otherDisposable: nil) - let observer = bindable.observer(disposable) - disposable.otherDisposable = observe(on: context) { value in - observer(value) - } - return disposable - } -} - -extension OperationType { - - public func bindNextTo(bindable: B, context: ExecutionContext? = nil) -> DisposableType { - let disposable = SerialDisposable(otherDisposable: nil) - let observer = bindable.observer(disposable) - disposable.otherDisposable = observeNext(on: context) { value in - observer(value) - } - return disposable - } - - public func bindNextTo(bindable: B, context: ExecutionContext? = nil) -> DisposableType { - let disposable = SerialDisposable(otherDisposable: nil) - let observer = bindable.observer(disposable) - disposable.otherDisposable = observeNext(on: context) { value in - observer(value) - } - return disposable - } -} diff --git a/ReactiveKit/Other/OptionalType.swift b/ReactiveKit/Other/OptionalType.swift deleted file mode 100644 index 56d7f21..0000000 --- a/ReactiveKit/Other/OptionalType.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public protocol OptionalType { - typealias Wrapped - var _unbox: Optional { get } - init(_ some: Wrapped) - init() -} - -extension Optional: OptionalType { - - public var _unbox: Optional { - return self - } -} diff --git a/ReactiveKit/Other/Result.swift b/ReactiveKit/Other/Result.swift deleted file mode 100644 index f9a9335..0000000 --- a/ReactiveKit/Other/Result.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// Thank you Rob Rix! https://github.com/antitypical/Result -// - -/// An enum representing either a failure with an explanatory error, or a success with a result value. -public enum Result: CustomStringConvertible { - - case Success(T) - case Failure(Error) - - public init(value: T) { - self = .Success(value) - } - - public init(error: Error) { - self = .Failure(error) - } - - public var description: String { - switch self { - case let .Success(value): - return ".Success(\(value))" - case let .Failure(error): - return ".Failure(\(error))" - } - } -} diff --git a/ReactiveKit/Streams/ActiveStream.swift b/ReactiveKit/Streams/ActiveStream.swift deleted file mode 100644 index cc9b31f..0000000 --- a/ReactiveKit/Streams/ActiveStream.swift +++ /dev/null @@ -1,127 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public class ActiveStream: StreamType { - public typealias Observer = Event -> () - - private typealias Token = Int64 - - private var nextToken: Token = 0 - public var observers: ContiguousArray = [] - private var observerStorage: [Token: Observer] = [:] { - didSet { - observers = ContiguousArray(observerStorage.values) - } - } - - private let lock = RecursiveLock(name: "com.ReactiveKit.ReactiveKit.ActiveStream") - - private var isDispatchInProgress: Bool = false - private let deinitDisposable = CompositeDisposable() - - private weak var selfReference: Reference>? = nil - - public init(@noescape producer: Observer -> DisposableType?) { - let tmpSelfReference = Reference(self) - tmpSelfReference.release() - - let disposable = producer { event in - tmpSelfReference.object?.next(event) - } - - if let disposable = disposable { - deinitDisposable.addDisposable(disposable) - } - - self.selfReference = tmpSelfReference - } - - public init() { - let tmpSelfReference = Reference(self) - tmpSelfReference.release() - self.selfReference = tmpSelfReference - } - - public func observe(on context: ExecutionContext? = ImmediateOnMainExecutionContext, observer: Observer) -> DisposableType { - selfReference?.retain() - - var contextedObserver = observer - if let context = context { - contextedObserver = { e in context { observer(e) } } - } - - let disposable = registerObserver(contextedObserver) - - let cleanupDisposable = BlockDisposable { [weak self] in - disposable.dispose() - - if let unwrappedSelf = self { - if unwrappedSelf.observers.count == 0 { - unwrappedSelf.selfReference?.release() - } - } - } - - deinitDisposable.addDisposable(cleanupDisposable) - return cleanupDisposable - } - - public func next(event: Event) { - guard !isDispatchInProgress else { return } - - lock.lock() - isDispatchInProgress = true - for observer in observers { - observer(event) - } - isDispatchInProgress = false - lock.unlock() - } - - internal func registerDisposable(disposable: DisposableType) { - deinitDisposable.addDisposable(disposable) - } - - private func registerObserver(observer: Observer) -> DisposableType { - lock.lock() - let token = nextToken - nextToken = nextToken + 1 - lock.unlock() - - observerStorage[token] = observer - - return BlockDisposable { [weak self] in - self?.observerStorage.removeValueForKey(token) - } - } - - deinit { - deinitDisposable.dispose() - } -} - -@warn_unused_result -public func create(producer: (Event -> ()) -> DisposableType?) -> ActiveStream { - return ActiveStream(producer: producer) -} diff --git a/ReactiveKit/Streams/Stream.swift b/ReactiveKit/Streams/Stream.swift deleted file mode 100644 index 092c8a1..0000000 --- a/ReactiveKit/Streams/Stream.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public struct Stream: StreamType { - - public typealias Observer = Event -> () - - public let producer: Observer -> DisposableType? - - public init(producer: Observer -> DisposableType?) { - self.producer = producer - } - - public func observe(on context: ExecutionContext? = ImmediateOnMainExecutionContext, observer: Observer) -> DisposableType { - let serialDisposable = SerialDisposable(otherDisposable: nil) - if let context = context { - serialDisposable.otherDisposable = producer { event in - if !serialDisposable.isDisposed { - context { - observer(event) - } - } - } - } else { - serialDisposable.otherDisposable = producer { event in - if !serialDisposable.isDisposed { - observer(event) - } - } - } - return serialDisposable - } -} - -@warn_unused_result -public func create(producer producer: (Event -> ()) -> DisposableType?) -> Stream { - return Stream(producer: producer) -} diff --git a/ReactiveKit/Streams/StreamType.swift b/ReactiveKit/Streams/StreamType.swift deleted file mode 100644 index d14a34d..0000000 --- a/ReactiveKit/Streams/StreamType.swift +++ /dev/null @@ -1,505 +0,0 @@ -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import Foundation - -public protocol StreamType { - typealias Event - func observe(on context: ExecutionContext?, observer: Event -> ()) -> DisposableType -} - -extension StreamType { - - @warn_unused_result - public func share(limit: Int = Int.max, context: ExecutionContext? = ImmediateOnMainExecutionContext) -> ObservableBuffer { - return ObservableBuffer(limit: limit) { observer in - return self.observe(on: context, observer: observer) - } - } -} - -extension StreamType { - - @warn_unused_result - public func map(transform: Event -> U) -> Stream { - return create { observer in - return self.observe(on: nil) { event in - observer(transform(event)) - } - } - } - - @warn_unused_result - public func filter(include: Event -> Bool) -> Stream { - return create { observer in - return self.observe(on: nil) { event in - if include(event) { - observer(event) - } - } - } - } - - @warn_unused_result - public func switchTo(context: ExecutionContext) -> Stream { - return create { observer in - return self.observe(on: context, observer: observer) - } - } - - @warn_unused_result - public func zipPrevious() -> Stream<(Event?, Event)> { - return create { observer in - var previous: Event? = nil - return self.observe(on: nil) { event in - observer(previous, event) - previous = event - } - } - } - - @warn_unused_result - public func throttle(seconds: Double, on queue: Queue) -> Stream { - return create { observer in - - var timerInFlight: Bool = false - var latestEvent: Event! = nil - var latestEventDate: NSDate! = nil - - var tryDispatch: (() -> Void)? - tryDispatch = { - if latestEventDate.dateByAddingTimeInterval(seconds).compare(NSDate()) == NSComparisonResult.OrderedAscending { - observer(latestEvent) - } else { - timerInFlight = true - queue.after(seconds) { - timerInFlight = false - tryDispatch?() - } - } - } - - let blockDisposable = BlockDisposable { tryDispatch = nil } - let compositeDisposable = CompositeDisposable([blockDisposable]) - compositeDisposable += self.observe(on: nil) { event in - latestEvent = event - latestEventDate = NSDate() - - guard timerInFlight == false else { return } - tryDispatch?() - } - return compositeDisposable - } - } - - @warn_unused_result - public func sample(interval: Double, on queue: Queue) -> Stream { - return create { observer in - - var shouldDispatch: Bool = true - var latestEvent: Event! = nil - - return self.observe(on: nil) { event in - latestEvent = event - guard shouldDispatch == true else { return } - - shouldDispatch = false - - queue.after(interval) { - let event = latestEvent! - latestEvent = nil - shouldDispatch = true - observer(event) - } - } - } - } - - @warn_unused_result - public func take(count:Int) -> Stream { - return create { observer in - - var remainder = count - let outerDisposable = CompositeDisposable() - - let disposable = self.observe(on: nil, observer: { event in - if remainder > 0{ - remainder-- - - observer(event) - } - if remainder == 0{ - outerDisposable.dispose() - } - }) - - outerDisposable.addDisposable(disposable) - return outerDisposable - } - } - - - - @warn_unused_result - public func skip(var count: Int) -> Stream { - return create { observer in - return self.observe(on: nil) { event in - if count > 0 { - count-- - } else { - observer(event) - } - } - } - } - - @warn_unused_result - public func pausable(by: S) -> Stream { - return create { observer in - - var allowed: Bool = false - - let compositeDisposable = CompositeDisposable() - compositeDisposable += by.observe(on: nil) { value in - allowed = value - } - - compositeDisposable += self.observe(on: nil) { event in - if allowed { - observer(event) - } - } - - return compositeDisposable - } - } - - @warn_unused_result - public func startWith(event: Event) -> Stream { - return create { observer in - observer(event) - return self.observe(on: nil) { event in - observer(event) - } - } - } - - @warn_unused_result - public func combineLatestWith(other: S) -> Stream<(Event, S.Event)> { - return create { observer in - let queue = Queue(name: "com.ReactiveKit.ReactiveKit.CombineLatestWith") - - var selfEvent: Event! = nil - var otherEvent: S.Event! = nil - - let dispatchIfPossible = { () -> () in - if let myEvent = selfEvent, let itsEvent = otherEvent { - observer((myEvent, itsEvent)) - } - } - - let selfDisposable = self.observe(on: nil) { event in - queue.sync { - selfEvent = event - dispatchIfPossible() - } - } - - let otherDisposable = other.observe(on: nil) { event in - queue.sync { - otherEvent = event - dispatchIfPossible() - } - } - - return CompositeDisposable([selfDisposable, otherDisposable]) - } - } - - @warn_unused_result - public func zipWith(other: S) -> Stream<(Event, S.Event)> { - return create { observer in - let queue = Queue(name: "com.ReactiveKit.ReactiveKit.ZipWith") - - var selfBuffer = Array() - var otherBuffer = Array() - - let dispatchIfPossible = { - while selfBuffer.count > 0 && otherBuffer.count > 0 { - observer(selfBuffer[0], otherBuffer[0]) - selfBuffer.removeAtIndex(0) - otherBuffer.removeAtIndex(0) - } - } - - let selfDisposable = self.observe(on: nil) { event in - queue.sync { - selfBuffer.append(event) - dispatchIfPossible() - } - } - - let otherDisposable = other.observe(on: nil) { event in - queue.sync { - otherBuffer.append(event) - dispatchIfPossible() - } - } - - return CompositeDisposable([selfDisposable, otherDisposable]) - } - } -} - -extension StreamType where Event: OptionalType { - - @warn_unused_result - public func ignoreNil() -> Stream { - return create { observer in - return self.observe(on: nil) { event in - if let event = event._unbox { - observer(event) - } - } - } - } -} - -extension StreamType where Event: Equatable { - - @warn_unused_result - public func distinct() -> Stream { - return create { observer in - var lastEvent: Event? = nil - return self.observe(on: nil) { event in - if lastEvent == nil || lastEvent! != event { - observer(event) - lastEvent = event - } - } - } - } -} - -public extension StreamType where Event: OptionalType, Event.Wrapped: Equatable { - - @warn_unused_result - public func distinctOptional() -> Stream { - return create { observer in - var lastEvent: Event.Wrapped? = nil - return self.observe(on: nil) { event in - - switch (lastEvent, event._unbox) { - case (.None, .Some(let new)): - observer(new) - case (.Some, .None): - observer(nil) - case (.Some(let old), .Some(let new)) where old != new: - observer(new) - default: - break - } - - lastEvent = event._unbox - } - } - } -} - -public extension StreamType where Event: StreamType { - - @warn_unused_result - public func merge() -> Stream { - return create { observer in - let compositeDisposable = CompositeDisposable() - compositeDisposable += self.observe(on: nil) { innerObserver in - compositeDisposable += innerObserver.observe(on: nil, observer: observer) - } - return compositeDisposable - } - } - - @warn_unused_result - public func switchToLatest() -> Stream { - return create { observer in - let serialDisposable = SerialDisposable(otherDisposable: nil) - let compositeDisposable = CompositeDisposable([serialDisposable]) - - compositeDisposable += self.observe(on: nil) { innerObserver in - serialDisposable.otherDisposable?.dispose() - serialDisposable.otherDisposable = innerObserver.observe(on: nil, observer: observer) - } - - return compositeDisposable - } - } -} - -public enum StreamFlatMapStrategy { - case Latest - case Merge -} - -public extension StreamType { - - @warn_unused_result - public func flatMap(strategy: StreamFlatMapStrategy, transform: Event -> S) -> Stream { - switch strategy { - case .Latest: - return map(transform).switchToLatest() - case .Merge: - return map(transform).merge() - } - } -} - -@warn_unused_result -public func combineLatest(a: A, _ b: B) -> Stream<(A.Event, B.Event)> { - return a.combineLatestWith(b) -} - -@warn_unused_result -public func zip(a: A, _ b: B) -> Stream<(A.Event, B.Event)> { - return a.zipWith(b) -} - -@warn_unused_result -public func combineLatest(a: A, _ b: B, _ c: C) -> Stream<(A.Event, B.Event, C.Event)> { - return combineLatest(a, b).combineLatestWith(c).map { ($0.0, $0.1, $1) } -} - -@warn_unused_result -public func zip(a: A, _ b: B, _ c: C) -> Stream<(A.Event, B.Event, C.Event)> { - return zip(a, b).zipWith(c).map { ($0.0, $0.1, $1) } -} - -@warn_unused_result -public func combineLatest(a: A, _ b: B, _ c: C, _ d: D) -> Stream<(A.Event, B.Event, C.Event, D.Event)> { - return combineLatest(a, b, c).combineLatestWith(d).map { ($0.0, $0.1, $0.2, $1) } -} - -@warn_unused_result -public func zip(a: A, _ b: B, _ c: C, _ d: D) -> Stream<(A.Event, B.Event, C.Event, D.Event)> { - return zip(a, b, c).zipWith(d).map { ($0.0, $0.1, $0.2, $1) } -} - -@warn_unused_result -public func combineLatest - (a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event)> -{ - return combineLatest(a, b, c, d).combineLatestWith(e).map { ($0.0, $0.1, $0.2, $0.3, $1) } -} - -@warn_unused_result -public func zip - (a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event)> -{ - return zip(a, b, c, d).zipWith(e).map { ($0.0, $0.1, $0.2, $0.3, $1) } -} - - -@warn_unused_result -public func combineLatest - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event, F.Event)> -{ - return combineLatest(a, b, c, d, e).combineLatestWith(f).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $1) } -} - -@warn_unused_result -public func zip - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event, F.Event)> -{ - return zip(a, b, c, d, e).zipWith(f).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $1) } -} - -@warn_unused_result -public func combineLatest - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event, F.Event, G.Event)> -{ - return combineLatest(a, b, c, d, e, f).combineLatestWith(g).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $1) } -} - -@warn_unused_result -public func zip - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event, F.Event, G.Event)> -{ - return zip(a, b, c, d, e, f).zipWith(g).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $1) } -} - -@warn_unused_result -public func combineLatest - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event, F.Event, G.Event, H.Event)> -{ - return combineLatest(a, b, c, d, e, f, g).combineLatestWith(h).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $1) } -} - -@warn_unused_result -public func zip - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event, F.Event, G.Event, H.Event)> -{ - return zip(a, b, c, d, e, f, g).zipWith(h).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $1) } -} - -@warn_unused_result -public func combineLatest - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event, F.Event, G.Event, H.Event, I.Event)> -{ - return combineLatest(a, b, c, d, e, f, g, h).combineLatestWith(i).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $0.7, $1) } -} - -@warn_unused_result -public func zip - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event, F.Event, G.Event, H.Event, I.Event)> -{ - return zip(a, b, c, d, e, f, g, h).zipWith(i).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $0.7, $1) } -} - -@warn_unused_result -public func combineLatest - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event, F.Event, G.Event, H.Event, I.Event, J.Event)> -{ - return combineLatest(a, b, c, d, e, f, g, h, i).combineLatestWith(j).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $0.7, $0.8, $1) } -} - -@warn_unused_result -public func zip - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event, F.Event, G.Event, H.Event, I.Event, J.Event)> -{ - return zip(a, b, c, d, e, f, g, h, i).zipWith(j).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $0.7, $0.8, $1) } -} - -@warn_unused_result -public func combineLatest - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J, _ k: K) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event, F.Event, G.Event, H.Event, I.Event, J.Event, K.Event)> -{ - return combineLatest(a, b, c, d, e, f, g, h, i, j).combineLatestWith(k).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $0.7, $0.8, $0.9, $1) } -} - - -@warn_unused_result -public func zip - ( a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G, _ h: H, _ i: I, _ j: J, _ k: K) -> Stream<(A.Event, B.Event, C.Event, D.Event, E.Event, F.Event, G.Event, H.Event, I.Event, J.Event, K.Event)> -{ - return zip(a, b, c, d, e, f, g, h, i, j).zipWith(k).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $0.5, $0.6, $0.7, $0.8, $0.9, $1) } -} diff --git a/ReactiveKitPlayground.playground/Contents.swift b/ReactiveKitPlayground.playground/Contents.swift index a492ba6..1f450c8 100644 --- a/ReactiveKitPlayground.playground/Contents.swift +++ b/ReactiveKitPlayground.playground/Contents.swift @@ -1,3 +1,16 @@ //: Playground - noun: a place where people can play import ReactiveKit + +let numbers = CollectionProperty([2, 3, 1]) + +let doubleNumbers = numbers.map { $0 * 2 } +let evenNumbers = numbers.filter { $0 % 2 == 0 } +let sortedNumbers = numbers.sort(<) + +sortedNumbers.observeNext { changeset in + print(changeset.collection) +} + +numbers.insert(9, atIndex: 1) +numbers.removeLast() diff --git a/ReactiveKitPlayground.playground/contents.xcplayground b/ReactiveKitPlayground.playground/contents.xcplayground index 3596865..fd676d5 100644 --- a/ReactiveKitPlayground.playground/contents.xcplayground +++ b/ReactiveKitPlayground.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/ReactiveKitPlayground.playground/timeline.xctimeline b/ReactiveKitPlayground.playground/timeline.xctimeline deleted file mode 100644 index bf468af..0000000 --- a/ReactiveKitPlayground.playground/timeline.xctimeline +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/ReactiveKitTests/ObservableCollectionSpec.swift b/ReactiveKitTests/ObservableCollectionSpec.swift deleted file mode 100644 index aad4247..0000000 --- a/ReactiveKitTests/ObservableCollectionSpec.swift +++ /dev/null @@ -1,242 +0,0 @@ -// -// ObservableCollectionSpec.swift -// ReactiveKit -// -// Created by Srdan Rasic on 22/11/15. -// Copyright © 2015 Srdan Rasic. All rights reserved. -// - -import Quick -import Nimble -@testable import ReactiveKit - -class ObservableCollectionSpec: QuickSpec { - - override func spec() { - - // MARK: - Array - - describe("ObservableCollection>") { - var observableCollection: ObservableCollection<[Int]>! - - beforeEach { - observableCollection = ObservableCollection([1, 2, 3]) - } - - context("when observed") { - var observedEvents: [ObservableCollectionEvent<[Int]>] = [] - - beforeEach { - observedEvents = [] - observableCollection.observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("generates initial event") { - expect(observedEvents[0]).to(equal(ObservableCollectionEvent(collection: [1, 2, 3], inserts: [], deletes: [], updates: []))) - } - - describe("append") { - beforeEach { - observableCollection.append(4) - } - - it("appends") { - expect(observedEvents[1]).to(equal(ObservableCollectionEvent(collection: [1, 2, 3, 4], inserts: [3], deletes: [], updates: []))) - } - } - - describe("insert") { - beforeEach { - observableCollection.insert(0, atIndex: 0) - } - - it("inserts") { - expect(observedEvents[1]).to(equal(ObservableCollectionEvent(collection: [0, 1, 2, 3], inserts: [0], deletes: [], updates: []))) - } - } - - describe("insertContentsOf") { - beforeEach { - observableCollection.insertContentsOf([10, 11], at: 1) - } - - it("insertContentsOf") { - expect(observedEvents[1]).to(equal(ObservableCollectionEvent(collection: [1, 10, 11, 2, 3], inserts: [1, 2], deletes: [], updates: []))) - } - } - - describe("moveItemAtIndex") { - describe("fromFirstToLast") { - beforeEach { - observableCollection.moveItemAtIndex(0, toIndex: 2) - } - - it("movesAndUpdatesAllIndexes") { - expect(observedEvents[1]).to(equal(ObservableCollectionEvent(collection: [2, 3, 1], inserts: [], deletes: [], updates: [0, 1, 2]))) - } - } - - describe("fromFirstToSecond") { - beforeEach { - observableCollection.moveItemAtIndex(0, toIndex: 1) - } - - it("movesAndUpdatesAffectedIndexes") { - expect(observedEvents[1]).to(equal(ObservableCollectionEvent(collection: [2, 1, 3], inserts: [], deletes: [], updates: [0, 1]))) - } - } - - describe("fromLastToFirst") { - beforeEach { - observableCollection.moveItemAtIndex(2, toIndex: 0) - } - - it("movesAndUpdatesAllIndexes") { - expect(observedEvents[1]).to(equal(ObservableCollectionEvent(collection: [3, 1, 2], inserts: [], deletes: [], updates: [0, 1, 2]))) - } - } - - describe("fromLastToSecond") { - beforeEach { - observableCollection.moveItemAtIndex(2, toIndex: 1) - } - - it("movesAndUpdatesAffectedIndexes") { - expect(observedEvents[1]).to(equal(ObservableCollectionEvent(collection: [1, 3, 2], inserts: [], deletes: [], updates: [1, 2]))) - } - } - } - - describe("removeAtIndex") { - beforeEach { - observableCollection.removeAtIndex(1) - } - - it("removeAtIndex") { - expect(observedEvents[1]).to(equal(ObservableCollectionEvent(collection: [1, 3], inserts: [], deletes: [1], updates: []))) - } - } - - describe("removeLast") { - beforeEach { - observableCollection.removeLast() - } - - it("removeLast") { - expect(observedEvents[1]).to(equal(ObservableCollectionEvent(collection: [1, 2], inserts: [], deletes: [2], updates: []))) - } - } - - describe("removeAll") { - beforeEach { - observableCollection.removeAll() - } - - it("removeAll") { - expect(observedEvents[1]).to(equal(ObservableCollectionEvent(collection: [], inserts: [], deletes: [0, 1, 2], updates: []))) - } - } - - describe("subscript") { - beforeEach { - observableCollection[1] = 20 - } - - it("subscript") { - expect(observableCollection[1]) == 20 - expect(observedEvents[1]).to(equal(ObservableCollectionEvent(collection: [1, 20, 3], inserts: [], deletes: [], updates: [1]))) - } - } - - describe("replace-diff") { - beforeEach { - observableCollection.replace([0, 1, 3, 4], performDiff: true) - } - - it("sends right events") { - expect(observedEvents[1]).to(equal(ObservableCollectionEvent(collection: [0, 1, 3, 4], inserts: [0, 3], deletes: [1], updates: []))) - } - } - } - } - - // MARK: - Set - - describe("ObservableCollection>") { - var observableCollection: ObservableCollection>! - - beforeEach { - observableCollection = ObservableCollection([1, 2, 3]) - } - - context("when observed") { - var observedEvents: [ObservableCollectionEvent>] = [] - - beforeEach { - observedEvents = [] - observableCollection.observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("generates initial event") { - expect(observedEvents[0]).to(equal(ObservableCollectionEvent(collection: [1, 2, 3], inserts: [], deletes: [], updates: []))) - } - - describe("contains") { - it("contains") { - expect(observableCollection.contains(1)) == true - expect(observableCollection.contains(5)) == false - } - } - - describe("insert") { - beforeEach { - observableCollection.insert(0) - } - - it("inserts") { - expect(observedEvents[1].collection).to(contain(0)) - } - } - - describe("remove") { - beforeEach { - observableCollection.remove(1) - } - - it("remove") { - expect(observedEvents[1].collection).toNot(contain(0)) - } - } - } - } - } -} - -// MARK: - Helpers - -public func equal(expectedValue: ObservableCollectionEvent) -> MatcherFunc> { - return MatcherFunc { actualExpression, failureMessage in - failureMessage.postfixMessage = "equal <\(expectedValue)>" - return try! actualExpression.evaluate()! == expectedValue - } -} - - -func == (left: ObservableCollectionEvent, right: ObservableCollectionEvent) -> Bool { - return left.collection == right.collection && left.inserts == right.inserts && left.updates == right.updates && left.deletes == right.deletes -} - -func == (left: C, right: C) -> Bool { - guard left.count == right.count else { return false } - for (l, r) in zip(left, right) { - if l != r { - return false - } - } - return true -} - diff --git a/ReactiveKitTests/OperationSpec.swift b/ReactiveKitTests/OperationSpec.swift deleted file mode 100644 index 64bc0e4..0000000 --- a/ReactiveKitTests/OperationSpec.swift +++ /dev/null @@ -1,638 +0,0 @@ -// -// OperationSpec.swift -// ReactiveKit -// -// Created by Srdan Rasic on 06/11/15. -// Copyright © 2015 Srdan Rasic. All rights reserved. -// - -import Quick -import Nimble -@testable import ReactiveKit - -enum TestError: ErrorType { - case ErrorA - case ErrorB -} - -class OperationSpec: QuickSpec { - - override func spec() { - - describe("a successful operation") { - var operation: Operation! - var simpleDisposable: SimpleDisposable! - - beforeEach { - operation = create { observer in - observer.next(1) - observer.next(2) - observer.next(3) - observer.success() - simpleDisposable = SimpleDisposable() - return simpleDisposable - } - } - - context("observeNext") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - - beforeEach { - disposable = operation.observeNext(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("generates values") { - expect(observedEvents) == [1, 2, 3] - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("map") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - - beforeEach { - disposable = operation.map { $0 * 2 }.observeNext(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("generates mapped values") { - expect(observedEvents) == [2, 4, 6] - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("zipWith") { - var observedEvents: [(Int, Int)] = [] - var disposable: DisposableType! - let otherSimpleDisposable = SimpleDisposable() - - beforeEach { - let otherOperation: Operation = create { observer in - observer.next(10) - observer.next(20) - observer.next(30) - observer.next(40) - observer.success() - return otherSimpleDisposable - } - - disposable = operation.zipWith(otherOperation).observeNext(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("does zip") { - expect(observedEvents[0].0) == 1 - expect(observedEvents[0].1) == 10 - expect(observedEvents[1].0) == 2 - expect(observedEvents[1].1) == 20 - expect(observedEvents[2].0) == 3 - expect(observedEvents[2].1) == 30 - expect(observedEvents.count) == 3 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - - it("other is disposed") { - expect(otherSimpleDisposable.isDisposed).to(beTrue()) - } - } - } - } - - describe("merge") { - var outerDisposable: SimpleDisposable! - var innerDisposable1: SimpleDisposable! - var innerDisposable2: SimpleDisposable! - - var outerProducer: ActiveStream>! - var innerProducer1: ActiveStream>! - var innerProducer2: ActiveStream>! - - var operation: Operation! - - var observedValues: [Int]! - var observedErrors: [TestError]! - var observedSuccesses: Int! - var disposable: DisposableType! - - beforeEach { - observedValues = [] - observedErrors = [] - observedSuccesses = 0 - - outerDisposable = SimpleDisposable() - innerDisposable1 = SimpleDisposable() - innerDisposable2 = SimpleDisposable() - - outerProducer = ActiveStream>(producer: { s in outerDisposable }) - innerProducer1 = ActiveStream>(producer: { s in innerDisposable1 }) - innerProducer2 = ActiveStream>(producer: { s in innerDisposable2 }) - - operation = create { observer in - outerProducer.observe(on: ImmediateExecutionContext) { e in - observer.observer(e) - } - return outerDisposable - } - - disposable = operation - .flatMap(.Merge) { (v: Int) -> Operation in - if v == 1 { - return create { observer in - innerProducer1.observe(on: ImmediateExecutionContext) { e in - observer.observer(e) - } - return innerDisposable1 - } - } else { - return create { observer in - innerProducer2.observe(on: ImmediateExecutionContext) { e in - observer.observer(e) - } - return innerDisposable2 - } - } - } - .observe(on: ImmediateExecutionContext) { e in - switch e { - case .Next(let value): observedValues.append(value) - case .Failure(let error): observedErrors.append(error) - case .Success: observedSuccesses = observedSuccesses + 1 - } - } - } - - it("not disposed") { - expect(outerDisposable.isDisposed).to(beFalse()) - expect(innerDisposable1.isDisposed).to(beFalse()) - expect(innerDisposable2.isDisposed).to(beFalse()) - } - - context("generates events") { - - beforeEach { - outerProducer.next(.Next(1)) - innerProducer1.next(.Next(10)) - innerProducer1.next(.Next(20)) - outerProducer.next(.Next(2)) - innerProducer1.next(.Next(30)) - innerProducer2.next(.Next(100)) - innerProducer2.next(.Next(200)) - innerProducer1.next(.Next(40)) - innerProducer1.next(.Success) - innerProducer1.next(.Next(50)) // should not be sent - innerProducer2.next(.Next(300)) - outerProducer.next(.Success) - innerProducer2.next(.Next(400)) // inner should still be active - innerProducer2.next(.Success) - innerProducer2.next(.Next(500)) // should not be sent - } - - it("generates correct events") { - expect(observedValues) == [10, 20, 30, 100, 200, 40, 300, 400] - expect(observedErrors) == [] - expect(observedSuccesses) == 1 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(outerDisposable.isDisposed).to(beTrue()) - expect(innerDisposable1.isDisposed).to(beTrue()) - expect(innerDisposable2.isDisposed).to(beTrue()) - } - } - } - - context("inner failure") { - - beforeEach { - outerProducer.next(.Next(1)) - outerProducer.next(.Next(2)) - innerProducer1.next(.Failure(.ErrorA)) - innerProducer2.next(.Next(100)) // should not be sent - innerProducer2.next(.Success) - outerProducer.next(.Next(1)) - innerProducer1.next(.Next(10)) // should not be sent - } - - it("correct events") { - expect(observedValues) == [] - expect(observedErrors) == [.ErrorA] - expect(observedSuccesses) == 0 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(outerDisposable.isDisposed).to(beTrue()) - expect(innerDisposable1.isDisposed).to(beTrue()) - expect(innerDisposable2.isDisposed).to(beTrue()) - } - } - } - - context("outer failure") { - - beforeEach { - outerProducer.next(.Next(1)) - outerProducer.next(.Next(2)) - innerProducer2.next(.Next(100)) - outerProducer.next(.Failure(.ErrorA)) - innerProducer1.next(.Next(10)) // should not be sent - } - - it("correct events") { - expect(observedValues) == [100] - expect(observedErrors) == [.ErrorA] - expect(observedSuccesses) == 0 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(outerDisposable.isDisposed).to(beTrue()) - expect(innerDisposable1.isDisposed).to(beTrue()) - expect(innerDisposable2.isDisposed).to(beTrue()) - } - } - } - } - - describe("switchToLateset") { - var outerDisposable: SimpleDisposable! - var innerDisposable1: SimpleDisposable! - var innerDisposable2: SimpleDisposable! - - var outerProducer: ActiveStream>! - var innerProducer1: ActiveStream>! - var innerProducer2: ActiveStream>! - - var operation: Operation! - - var observedValues: [Int]! - var observedErrors: [TestError]! - var observedSuccesses: Int! - var disposable: DisposableType! - - beforeEach { - observedValues = [] - observedErrors = [] - observedSuccesses = 0 - - outerDisposable = SimpleDisposable() - innerDisposable1 = SimpleDisposable() - innerDisposable2 = SimpleDisposable() - - outerProducer = ActiveStream>(producer: { s in outerDisposable }) - innerProducer1 = ActiveStream>(producer: { s in innerDisposable1 }) - innerProducer2 = ActiveStream>(producer: { s in innerDisposable2 }) - - operation = create { observer in - outerProducer.observe(on: ImmediateExecutionContext) { e in - observer.observer(e) - } - return outerDisposable - } - - disposable = operation - .flatMap(.Latest) { (v: Int) -> Operation in - if v == 1 { - return create { observer in - innerProducer1.observe(on: ImmediateExecutionContext) { e in - observer.observer(e) - } - return innerDisposable1 - } - } else { - return create { observer in - innerProducer2.observe(on: ImmediateExecutionContext) { e in - observer.observer(e) - } - return innerDisposable2 - } - } - } - .observe(on: ImmediateExecutionContext) { e in - switch e { - case .Next(let value): observedValues.append(value) - case .Failure(let error): observedErrors.append(error) - case .Success: observedSuccesses = observedSuccesses + 1 - } - } - } - - it("not disposed") { - expect(outerDisposable.isDisposed).to(beFalse()) - expect(innerDisposable1.isDisposed).to(beFalse()) - expect(innerDisposable2.isDisposed).to(beFalse()) - } - - context("generates events") { - - beforeEach { - outerProducer.next(.Next(1)) - innerProducer1.next(.Next(10)) - outerProducer.next(.Next(2)) - innerProducer1.next(.Next(20)) - innerProducer2.next(.Next(100)) - innerProducer1.next(.Next(30)) - innerProducer1.next(.Success) - innerProducer1.next(.Next(40)) - innerProducer2.next(.Next(200)) - innerProducer2.next(.Success) - outerProducer.next(.Next(1)) - outerProducer.next(.Success) - innerProducer1.next(.Next(10)) - innerProducer1.next(.Success) - } - - it("generates correct events") { - expect(observedValues) == [10, 100, 200, 10] - expect(observedErrors) == [] - expect(observedSuccesses) == 1 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(outerDisposable.isDisposed).to(beTrue()) - expect(innerDisposable1.isDisposed).to(beTrue()) - expect(innerDisposable2.isDisposed).to(beTrue()) - } - } - } - - context("inner failure") { - - beforeEach { - outerProducer.next(.Next(1)) - outerProducer.next(.Next(2)) - innerProducer1.next(.Next(10)) - innerProducer1.next(.Success) - innerProducer2.next(.Next(100)) - innerProducer2.next(.Failure(.ErrorB)) - outerProducer.next(.Next(1)) - innerProducer1.next(.Next(10)) - } - - it("correct events") { - expect(observedValues) == [100] - expect(observedErrors) == [.ErrorB] - expect(observedSuccesses) == 0 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(outerDisposable.isDisposed).to(beTrue()) - expect(innerDisposable1.isDisposed).to(beTrue()) - expect(innerDisposable2.isDisposed).to(beTrue()) - } - } - } - - context("outer failure") { - - beforeEach { - outerProducer.next(.Next(1)) - outerProducer.next(.Next(2)) - innerProducer2.next(.Next(100)) - outerProducer.next(.Failure(.ErrorA)) - innerProducer1.next(.Next(10)) // should not be sent - } - - it("correct events") { - expect(observedValues) == [100] - expect(observedErrors) == [.ErrorA] - expect(observedSuccesses) == 0 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(outerDisposable.isDisposed).to(beTrue()) - expect(innerDisposable1.isDisposed).to(beTrue()) - expect(innerDisposable2.isDisposed).to(beTrue()) - } - } - } - } - - - describe("concat") { - var outerDisposable: SimpleDisposable! - var innerDisposable1: SimpleDisposable! - var innerDisposable2: SimpleDisposable! - - var outerProducer: ObservableBuffer>! - var innerProducer1: ObservableBuffer>! - var innerProducer2: ObservableBuffer>! - - var operation: Operation! - - var observedValues: [Int]! - var observedErrors: [TestError]! - var observedSuccesses: Int! - var disposable: DisposableType! - - beforeEach { - observedValues = [] - observedErrors = [] - observedSuccesses = 0 - - outerDisposable = SimpleDisposable() - innerDisposable1 = SimpleDisposable() - innerDisposable2 = SimpleDisposable() - - outerProducer = ObservableBuffer>(limit: 10, producer: { s in outerDisposable }) - innerProducer1 = ObservableBuffer>(limit: 10, producer: { s in innerDisposable1 }) - innerProducer2 = ObservableBuffer>(limit: 10, producer: { s in innerDisposable2 }) - - operation = create { observer in - outerProducer.observe(on: ImmediateExecutionContext) { e in - observer.observer(e) - } - return outerDisposable - } - - disposable = operation - .flatMap(.Concat) { (v: Int) -> Operation in - if v == 1 { - return create { observer in - innerProducer1.observe(on: ImmediateExecutionContext) { e in - observer.observer(e) - } - return innerDisposable1 - } - } else { - return create { observer in - innerProducer2.observe(on: ImmediateExecutionContext) { e in - observer.observer(e) - } - return innerDisposable2 - } - } - } - .observe(on: ImmediateExecutionContext) { e in - switch e { - case .Next(let value): observedValues.append(value) - case .Failure(let error): observedErrors.append(error) - case .Success: observedSuccesses = observedSuccesses + 1 - } - } - } - - it("not disposed") { - expect(outerDisposable.isDisposed).to(beFalse()) - expect(innerDisposable1.isDisposed).to(beFalse()) - expect(innerDisposable2.isDisposed).to(beFalse()) - } - - context("generates events") { - - beforeEach { - outerProducer.next(.Next(1)) - innerProducer1.next(.Next(10)) - outerProducer.next(.Next(2)) - innerProducer1.next(.Next(20)) - innerProducer2.next(.Next(100)) - innerProducer2.next(.Next(200)) - innerProducer1.next(.Next(30)) - innerProducer1.next(.Success) - outerProducer.next(.Success) - innerProducer2.next(.Next(300)) - innerProducer2.next(.Success) - } - - it("generates correct events") { - expect(observedValues) == [10, 20, 30, 100, 200, 300] - expect(observedErrors) == [] - expect(observedSuccesses) == 1 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(outerDisposable.isDisposed).to(beTrue()) - expect(innerDisposable1.isDisposed).to(beTrue()) - expect(innerDisposable2.isDisposed).to(beTrue()) - } - } - } - - context("inner failure") { - - beforeEach { - outerProducer.next(.Next(1)) - outerProducer.next(.Next(2)) - innerProducer2.next(.Next(100)) - innerProducer2.next(.Failure(.ErrorB)) - innerProducer1.next(.Next(10)) - innerProducer1.next(.Success) - outerProducer.next(.Success) - } - - it("correct events") { - expect(observedValues) == [10, 100] - expect(observedErrors) == [.ErrorB] - expect(observedSuccesses) == 0 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(outerDisposable.isDisposed).to(beTrue()) - expect(innerDisposable1.isDisposed).to(beTrue()) - expect(innerDisposable2.isDisposed).to(beTrue()) - } - } - } - - context("outer failure") { - - beforeEach { - outerProducer.next(.Next(1)) - outerProducer.next(.Next(2)) - innerProducer1.next(.Success) - innerProducer2.next(.Next(100)) - outerProducer.next(.Failure(.ErrorA)) - innerProducer1.next(.Next(10)) - } - - it("correct events") { - expect(observedValues) == [100] - expect(observedErrors) == [.ErrorA] - expect(observedSuccesses) == 0 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(outerDisposable.isDisposed).to(beTrue()) - expect(innerDisposable1.isDisposed).to(beTrue()) - expect(innerDisposable2.isDisposed).to(beTrue()) - } - } - } - } - } -} diff --git a/ReactiveKitTests/StreamSpec.swift b/ReactiveKitTests/StreamSpec.swift deleted file mode 100644 index 8bd49b9..0000000 --- a/ReactiveKitTests/StreamSpec.swift +++ /dev/null @@ -1,473 +0,0 @@ -// -// ReactiveKitTests.swift -// ReactiveKitTests -// -// Created by Srdan Rasic on 05/11/15. -// Copyright © 2015 Srdan Rasic. All rights reserved. -// - -import Quick -import Nimble -@testable import ReactiveKit - -class StreamSpec: QuickSpec { - - override func spec() { - - describe("a stream") { - var stream: Stream! - var simpleDisposable: SimpleDisposable! - - beforeEach { - stream = create { observer in - observer(1) - observer(2) - observer(3) - simpleDisposable = SimpleDisposable() - return simpleDisposable - } - } - - context("when observed") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - - beforeEach { - disposable = stream.observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("generates event") { - expect(observedEvents) == [1, 2, 3] - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when mapped") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - - beforeEach { - disposable = stream.map { $0 * 2 }.observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("generates mapped event") { - expect(observedEvents) == [2, 4, 6] - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when filtered") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - - beforeEach { - disposable = stream.filter { $0 % 2 == 0 }.observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("generates filtered event") { - expect(observedEvents) == [2] - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when switchTo another context") { - var contextUsed = false - var disposable: DisposableType! - - beforeEach { - disposable = stream.switchTo { body in - contextUsed = true - body() - }.observe(on: ImmediateExecutionContext) { _ in } - } - - it("uses that context") { - expect(contextUsed).to(beTrue()) - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when zipPrevious event") { - var observedEvents: [(Int?, Int)] = [] - var disposable: DisposableType! - - beforeEach { - disposable = stream.zipPrevious().observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("generates zipped event") { - expect(observedEvents[0].0).to(beNil()) - expect(observedEvents[0].1) == 1 - expect(observedEvents[1].0) == 1 - expect(observedEvents[1].1) == 2 - expect(observedEvents[2].0) == 2 - expect(observedEvents[2].1) == 3 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when throttle by 0.1") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - - beforeEach { - disposable = stream.throttle(0.1, on: Queue.main).observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("does throttle") { - expect(observedEvents) == [] - expect(observedEvents).toEventually(equal([3])) - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when taking 0") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - - beforeEach { - disposable = stream.take(0).observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("does takes nothing") { - expect(observedEvents) == [] - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when taking 1") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - - beforeEach { - disposable = stream.take(1).observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("does take only 1") { - expect(observedEvents) == [1] - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when taking 2") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - - beforeEach { - disposable = stream.take(2).observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("does takes 1 and 2") { - expect(observedEvents) == [1,2] - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when skip by 1") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - - beforeEach { - disposable = stream.skip(1).observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("does skip 1") { - expect(observedEvents) == [2, 3] - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when startWith 9") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - - beforeEach { - disposable = stream.startWith(9).observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("does startWith 9") { - expect(observedEvents) == [9, 1, 2, 3] - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when combineLatestWith") { - var observedEvents: [(Int, Int)] = [] - var disposable: DisposableType! - let otherSimpleDisposable = SimpleDisposable() - - beforeEach { - let otherStream: Stream = create { observer in - observer(10) - observer(20) - return otherSimpleDisposable - } - - disposable = stream.combineLatestWith(otherStream).observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("does combine") { - expect(observedEvents[0].0) == 3 - expect(observedEvents[0].1) == 10 - expect(observedEvents[1].0) == 3 - expect(observedEvents[1].1) == 20 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - - it("other is disposed") { - expect(otherSimpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when zipWith") { - var observedEvents: [(Int, Int)] = [] - var disposable: DisposableType! - let otherSimpleDisposable = SimpleDisposable() - - beforeEach { - let otherStream: Stream = create { observer in - observer(10) - observer(20) - observer(30) - observer(40) - return otherSimpleDisposable - } - - disposable = stream.zipWith(otherStream).observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("does zip") { - expect(observedEvents[0].0) == 1 - expect(observedEvents[0].1) == 10 - expect(observedEvents[1].0) == 2 - expect(observedEvents[1].1) == 20 - expect(observedEvents[2].0) == 3 - expect(observedEvents[2].1) == 30 - expect(observedEvents.count) == 3 - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - - it("other is disposed") { - expect(otherSimpleDisposable.isDisposed).to(beTrue()) - } - } - } - - context("when merge") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - let otherSimpleDisposables = [SimpleDisposable(), SimpleDisposable(), SimpleDisposable()] - - beforeEach { - disposable = stream.flatMap(.Merge) { n in - Stream { observer in - observer(n * 2) - return otherSimpleDisposables[n-1] - } - }.observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("does combine") { - expect(observedEvents) == [2, 4, 6] - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - - it("others are disposed") { - expect(otherSimpleDisposables[0].isDisposed).to(beTrue()) - expect(otherSimpleDisposables[1].isDisposed).to(beTrue()) - expect(otherSimpleDisposables[2].isDisposed).to(beTrue()) - } - } - } - - context("when switchToLatest") { - var observedEvents: [Int] = [] - var disposable: DisposableType! - let otherSimpleDisposables = [SimpleDisposable(), SimpleDisposable(), SimpleDisposable()] - - beforeEach { - disposable = stream.flatMap(.Latest) { n in - Stream { observer in - observer(n * 2) - return otherSimpleDisposables[n-1] - } - }.observe(on: ImmediateExecutionContext) { - observedEvents.append($0) - } - } - - it("does switch") { - expect(observedEvents) == [2, 4, 6] - } - - describe("can be disposed") { - beforeEach { - disposable.dispose() - } - - it("is disposed") { - expect(simpleDisposable.isDisposed).to(beTrue()) - } - - it("others are disposed") { - expect(otherSimpleDisposables[0].isDisposed).to(beTrue()) - expect(otherSimpleDisposables[1].isDisposed).to(beTrue()) - expect(otherSimpleDisposables[2].isDisposed).to(beTrue()) - } - } - } - } - } -} - - diff --git a/Sources/CollectionProperty.swift b/Sources/CollectionProperty.swift new file mode 100644 index 0000000..f49da8e --- /dev/null +++ b/Sources/CollectionProperty.swift @@ -0,0 +1,564 @@ +// +// The MIT License (MIT) +// +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public protocol CollectionChangesetType { + associatedtype Collection: CollectionType + var collection: Collection { get } + var inserts: [Collection.Index] { get } + var deletes: [Collection.Index] { get } + var updates: [Collection.Index] { get } +} + +public struct CollectionChangeset: CollectionChangesetType { + public let collection: Collection + public let inserts: [Collection.Index] + public let deletes: [Collection.Index] + public let updates: [Collection.Index] + + public static func initial(collection: Collection) -> CollectionChangeset { + return CollectionChangeset(collection: collection, inserts: [], deletes: [], updates: []) + } + + public init(collection: Collection, inserts: [Collection.Index], deletes: [Collection.Index], updates: [Collection.Index]) { + self.collection = collection + self.inserts = inserts + self.deletes = deletes + self.updates = updates + } +} + +public extension CollectionChangeset { + + public init(ObservableCollectionEvent: E) { + collection = ObservableCollectionEvent.collection + inserts = ObservableCollectionEvent.inserts + deletes = ObservableCollectionEvent.deletes + updates = ObservableCollectionEvent.updates + } +} + +public extension CollectionChangesetType where Collection.Index == Int { + + /// O(n) + public func map(transform: Collection.Generator.Element -> U) -> CollectionChangeset<[U]> { + return CollectionChangeset(collection: collection.map(transform), inserts: inserts, deletes: deletes, updates: updates) + } + + /// O(1) + public func lazyMap(transform: Collection.Generator.Element -> U) -> CollectionChangeset> { + return CollectionChangeset(collection: collection.lazy.map(transform), inserts: inserts, deletes: deletes, updates: updates) + } +} + +public extension CollectionChangesetType where Collection.Index == Int { + + /// O(n) + public func filter(include: Collection.Generator.Element -> Bool) -> CollectionChangeset> { + + let filteredPairs = zip(collection.indices, collection).filter { include($0.1) } + let includedIndices = Set(filteredPairs.map { $0.0 }) + + let filteredCollection = filteredPairs.map { $0.1 } + let filteredInserts = inserts.filter { includedIndices.contains($0) } + let filteredDeletes = deletes.filter { includedIndices.contains($0) } + let filteredUpdates = updates.filter { includedIndices.contains($0) } + + return CollectionChangeset(collection: filteredCollection, inserts: filteredInserts, deletes: filteredDeletes, updates: filteredUpdates) + } +} + +public extension CollectionChangesetType where Collection.Index: Hashable { + + typealias SortMap = [Collection.Index: Int] + typealias Changeset = CollectionChangeset> + typealias CollectionElement = Collection.Generator.Element + + /// O(n*logn) + public func sort(previousSortMap: SortMap?, isOrderedBefore: (CollectionElement, CollectionElement) -> Bool) -> (changeset: Changeset, sortMap: SortMap) { + let sortedPairs = zip(collection.indices, collection).sort { isOrderedBefore($0.1, $1.1) } + + var sortMap: [Collection.Index: Int] = [:] + for (index, pair) in sortedPairs.enumerate() { + sortMap[pair.0] = index + } + + let sortedCollection = sortedPairs.map { $0.1 } + let newDeletes = deletes.map { previousSortMap![$0]! } + let newInserts = inserts.map { sortMap[$0]! } + let newUpdates = updates.map { sortMap[$0]! } + let changeSet = CollectionChangeset(collection: sortedCollection, inserts: newInserts, deletes: newDeletes, updates: newUpdates) + + return (changeset: changeSet, sortMap: sortMap) + } +} + +public extension CollectionChangesetType where Collection.Index: Equatable { + + /// O(n^2) + public func sort(previousSortedIndices: [Collection.Index]?, isOrderedBefore: (Collection.Generator.Element, Collection.Generator.Element) -> Bool) -> (changeset: CollectionChangeset>, sortedIndices: [Collection.Index]) { + let sortedPairs = zip(collection.indices, collection).sort { isOrderedBefore($0.1, $1.1) } + let sortedIndices = sortedPairs.map { $0.0 } + + let sortedCollection = sortedPairs.map { $0.1 } + let newDeletes = deletes.map { previousSortedIndices!.indexOf($0)! } + let newInserts = inserts.map { sortedIndices.indexOf($0)! } + let newUpdates = updates.map { sortedIndices.indexOf($0)! } + let changeset = CollectionChangeset(collection: sortedCollection, inserts: newInserts, deletes: newDeletes, updates: newUpdates) + + return (changeset: changeset, sortedIndices: sortedIndices) + } +} + +public protocol CollectionPropertyType: CollectionType, StreamType { + associatedtype Collection: CollectionType + associatedtype Index = Collection.Index + associatedtype Member = Collection.Generator.Element + var collection: Collection { get } + func observe(observer: StreamEvent> -> Void) -> Disposable + func update(changeset: CollectionChangeset) +} + +public class CollectionProperty: CollectionPropertyType { + private let subject = PublishSubject>>() + private let lock = RecursiveLock(name: "ReactiveKit.CollectionProperty") + + public var rawStream: RawStream>> { + return subject.toRawStream().startWith(.Next(CollectionChangeset.initial(collection))) + } + + public private(set) var collection: C + + public init(_ collection: C) { + self.collection = collection + } + + deinit { + subject.completed() + } + + public func generate() -> C.Generator { + return collection.generate() + } + + public func underestimateCount() -> Int { + return collection.underestimateCount() + } + + public var startIndex: C.Index { + return collection.startIndex + } + + public var endIndex: C.Index { + return collection.endIndex + } + + public var isEmpty: Bool { + return collection.isEmpty + } + + public var count: C.Index.Distance { + return collection.count + } + + public subscript(index: C.Index) -> C.Generator.Element { + get { + return collection[index] + } + } + + public func silentUpdate(@noescape perform: CollectionProperty -> Void) { + let collection = CollectionProperty(self.collection) + perform(collection) + self.collection = collection.collection + } + + public func update(changeset: CollectionChangeset) { + collection = changeset.collection + subject.on(.Next(changeset)) + } +} + +public extension CollectionPropertyType { + + public func replace(newCollection: Collection) { + let deletes = Array(collection.indices) + let inserts = Array(newCollection.indices) + update(CollectionChangeset(collection: newCollection, inserts: inserts, deletes: deletes, updates: [])) + } +} + +public extension CollectionPropertyType where Collection.Index == Int { + + /// Each event costs O(n) + @warn_unused_result + public func map(transform: Collection.Generator.Element -> U) -> Stream>> { + return Stream { observer in + return self.observe { event in + observer.observer(event.map { $0.map(transform) }) + } + } + } + + /// Each event costs O(1) + @warn_unused_result + public func lazyMap(transform: Collection.Generator.Element -> U) -> Stream>> { + return Stream { observer in + return self.observe { event in + observer.observer(event.map { $0.lazyMap(transform) }) + } + } + } + + /// Each event costs O(n) + @warn_unused_result + public func filter(include: Collection.Generator.Element -> Bool) -> Stream>> { + return Stream { observer in + return self.observe { event in + observer.observer(event.map { $0.filter(include) }) + } + } + } +} + +public extension CollectionPropertyType where Collection.Index: Hashable { + + typealias CollectionElement = Collection.Generator.Element + + /// Each event costs O(n*logn) + @warn_unused_result + public func sort(isOrderedBefore: (CollectionElement, CollectionElement) -> Bool) -> Stream> { + return Stream { observer in + var previousSortMap: [Collection.Index: Int]? = nil + return self.observe { event in + observer.observer(event.map { changeset in + let (newChangeset, sortMap) = changeset.sort(previousSortMap, isOrderedBefore: isOrderedBefore) + previousSortMap = sortMap + return newChangeset + }) + } + } + } +} + +public extension CollectionPropertyType where Collection.Index: Equatable { + + /// Each event costs O(n^2) + @warn_unused_result + public func sort(isOrderedBefore: (Collection.Generator.Element, Collection.Generator.Element) -> Bool) -> Stream>> { + return Stream { observer in + var previousSortedIndices: [Collection.Index]? = nil + return self.observe { event in + observer.observer(event.map { changeset in + let (newChangeset, sortedIndices) = changeset.sort(previousSortedIndices, isOrderedBefore: isOrderedBefore) + previousSortedIndices = sortedIndices + return newChangeset + }) + } + } + } +} + +// MARK: - Set Extensions + +public protocol SetIndexType { + associatedtype Element: Hashable + func successor() -> SetIndex +} + +extension SetIndex: SetIndexType {} + +public extension CollectionPropertyType where Index: SetIndexType, Collection == Set { + + public func contains(member: Index.Element) -> Bool { + return collection.contains(member) + } + + public func indexOf(member: Index.Element) -> SetIndex? { + return collection.indexOf(member) + } + + public subscript (position: SetIndex) -> Index.Element { + get { + return collection[position] + } + } +} + +public extension CollectionPropertyType where Index: SetIndexType, Collection == Set { + + public func insert(member: Index.Element) { + var new = collection + new.insert(member) + + if let index = collection.indexOf(member) { + update(CollectionChangeset(collection: new, inserts: [], deletes: [], updates: [index])) + } else { + update(CollectionChangeset(collection: new, inserts: [new.indexOf(member)!], deletes: [], updates: [])) + } + } + + public func remove(member: Index.Element) -> Index.Element? { + var new = collection + + if let index = collection.indexOf(member) { + let old = new.removeAtIndex(index) + update(CollectionChangeset(collection: new, inserts: [], deletes: [index], updates: [])) + return old + } else { + return nil + } + } +} + +// MARK: - Dictionary Extensions + +public protocol DictionaryIndexType { + associatedtype Key: Hashable + associatedtype Value + func successor() -> DictionaryIndex +} + +extension DictionaryIndex: DictionaryIndexType {} + +public extension CollectionPropertyType where Index: DictionaryIndexType, Collection == Dictionary { + + public func indexForKey(key: Index.Key) -> Collection.Index? { + return collection.indexForKey(key) + } + + + public subscript (position: Collection.Index) -> Collection.Generator.Element { + get { + return collection[position] + } + } + + public subscript (key: Index.Key) -> Index.Value? { + get { + return collection[key] + } + set { + if let value = newValue { + updateValue(value, forKey: key) + } else { + removeValueForKey(key) + } + } + } + + public func updateValue(value: Index.Value, forKey key: Index.Key) -> Index.Value? { + var new = collection + if let index = new.indexForKey(key) { + let oldValue = new.updateValue(value, forKey: key) + update(CollectionChangeset(collection: new, inserts: [], deletes: [], updates: [index])) + return oldValue + } else { + new.updateValue(value, forKey: key) + let index = new.indexForKey(key)! + update(CollectionChangeset(collection: new, inserts: [index], deletes: [], updates: [])) + return nil + } + } + + public func removeValueForKey(key: Index.Key) -> Index.Value? { + if let index = collection.indexForKey(key) { + var new = collection + let oldValue = new.removeValueForKey(key) + update(CollectionChangeset(collection: new, inserts: [], deletes: [index], updates: [])) + return oldValue + } else { + return nil + } + } +} + +// MARK: Array Extensions + +public extension CollectionPropertyType where Collection == Array { + + /// Append `newElement` to the array. + public func append(newElement: Member) { + var new = collection + new.append(newElement) + update(CollectionChangeset(collection: new, inserts: [collection.count], deletes: [], updates: [])) + } + + /// Insert `newElement` at index `i`. + public func insert(newElement: Member, atIndex: Int) { + var new = collection + new.insert(newElement, atIndex: atIndex) + update(CollectionChangeset(collection: new, inserts: [atIndex], deletes: [], updates: [])) + } + + /// Insert elements `newElements` at index `i`. + public func insertContentsOf(newElements: [Member], at index: Collection.Index) { + var new = collection + new.insertContentsOf(newElements, at: index) + update(CollectionChangeset(collection: new, inserts: Array(index.. Member { + var new = collection + let element = new.removeAtIndex(index) + update(CollectionChangeset(collection: new, inserts: [], deletes: [index], updates: [])) + return element + } + + /// Remove an element from the end of the array in O(1). + public func removeLast() -> Member { + var new = collection + let element = new.removeLast() + update(CollectionChangeset(collection: new, inserts: [], deletes: [new.count], updates: [])) + return element + } + + /// Remove all elements from the array. + public func removeAll() { + let deletes = Array(0.. Member { + get { + return collection[index] + } + set { + var new = collection + new[index] = newValue + update(CollectionChangeset(collection: new, inserts: [], deletes: [], updates: [index])) + } + } +} + +extension CollectionPropertyType where Collection == Array, Member: Equatable { + + /** + Replace current array with the new array and send change events. + + - parameter newCollection: The array to replace current array with. + + - parameter performDiff: When `true`, difference between the current + array and the new array will be calculated and the sent event will + contain exact description of which elements were inserted and which + deleted. When `false`, the sent event contains current array indices + as `deletes` indices and new array indices as `insertes` indices. + + - complexity: O(1) if `performDiff == false`. Otherwise O(`collection.count * newCollection.count`). + */ + public func replace(newCollection: Collection, performDiff: Bool) { + if performDiff { + var inserts: [Int] = [] + var deletes: [Int] = [] + + inserts.reserveCapacity(collection.count) + deletes.reserveCapacity(collection.count) + + let diff = Collection.diff(collection, newCollection) + + for diffStep in diff { + switch diffStep { + case .Insert(_, let index): inserts.append(index) + case .Delete(_, let index): deletes.append(index) + } + } + + update(CollectionChangeset(collection: newCollection, inserts: inserts, deletes: deletes, updates: [])) + } else { + replace(newCollection) + } + } +} + +enum DiffStep { + case Insert(element: T, index: Int) + case Delete(element: T, index: Int) +} + +extension Array where Element: Equatable { + + // Created by Dapeng Gao on 20/10/15. + // The central idea of this algorithm is taken from https://github.com/jflinter/Dwifft + + static func diff(x: [Element], _ y: [Element]) -> [DiffStep] { + + if x.count == 0 { + return zip(y, y.indices).map(DiffStep.Insert) + } + + if y.count == 0 { + return zip(x, x.indices).map(DiffStep.Delete) + } + + // Use dynamic programming to generate a table such that `table[i][j]` represents + // the length of the longest common substring (LCS) between `x[0..] = [] + var i = xLen + var j = yLen + while i > 0 || j > 0 { + if i == 0 { + j -= 1 + backtrack.append(.Insert(element: y[j], index: j)) + } else if j == 0 { + i -= 1 + backtrack.append(.Delete(element: x[i], index: i)) + } else if table[i][j] == table[i][j - 1] { + j -= 1 + backtrack.append(.Insert(element: y[j], index: j)) + } else if table[i][j] == table[i - 1][j] { + i -= 1 + backtrack.append(.Delete(element: x[i], index: i)) + } else { + i -= 1 + j -= 1 + } + } + + // Reverse the result + return backtrack.reverse() + } +} diff --git a/Sources/Disposable.swift b/Sources/Disposable.swift new file mode 100644 index 0000000..7e62e9f --- /dev/null +++ b/Sources/Disposable.swift @@ -0,0 +1,212 @@ +// +// The MIT License (MIT) +// +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +/// Objects conforming to this protocol dispose (cancel) streams and operations. +public protocol Disposable { + + /// Dispose the stream or operation. + func dispose() + + /// Returns `true` is already disposed. + var isDisposed: Bool { get } +} + +/// A disposable that cannot be disposed. +private struct _NotDisposable: Disposable { + + private init() {} + + private func dispose() { + } + + private var isDisposed: Bool { + return false + } +} + +public let NotDisposable: Disposable = _NotDisposable() + +/// A disposable that just encapsulates disposed state. +public final class SimpleDisposable: Disposable { + public private(set) var isDisposed: Bool = false + + public func dispose() { + isDisposed = true + } + + public init() {} +} + +/// A disposable that executes the given block upon disposing. +public final class BlockDisposable: Disposable { + + public var isDisposed: Bool { + return handler == nil + } + + private var handler: (() -> ())? + private let lock = RecursiveLock(name: "ReactiveKit.BlockDisposable") + + public init(_ handler: () -> ()) { + self.handler = handler + } + + public func dispose() { + lock.lock() + handler?() + handler = nil + lock.unlock() + } +} + +/// A disposable that disposes itself upon deallocation. +public class DeinitDisposable: Disposable { + + public var otherDisposable: Disposable? = nil + + public var isDisposed: Bool { + return otherDisposable == nil + } + + public init(disposable: Disposable) { + otherDisposable = disposable + } + + public func dispose() { + otherDisposable?.dispose() + } + + deinit { + otherDisposable?.dispose() + } +} + +/// A disposable that disposes a collection of disposables upon disposing. +public final class CompositeDisposable: Disposable { + + public private(set) var isDisposed: Bool = false + private var disposables: [Disposable] = [] + private let lock = RecursiveLock(name: "ReactiveKit.CompositeDisposable") + + public convenience init() { + self.init([]) + } + + public init(_ disposables: [Disposable]) { + self.disposables = disposables + } + + public func addDisposable(disposable: Disposable) { + lock.lock() + if isDisposed { + disposable.dispose() + } else { + disposables.append(disposable) + self.disposables = disposables.filter { $0.isDisposed == false } + } + lock.unlock() + } + + public func dispose() { + lock.lock() + isDisposed = true + for disposable in disposables { + disposable.dispose() + } + disposables = [] + lock.unlock() + } +} + +public func += (left: CompositeDisposable, right: Disposable) { + left.addDisposable(right) +} + +/// A disposable container that will dispose a collection of disposables upon deinit. +public final class DisposeBag: Disposable { + private var disposables: [Disposable] = [] + + /// This will return true whenever the bag is empty. + public var isDisposed: Bool { + return disposables.count == 0 + } + + public init() { + } + + /// Adds the given disposable to the bag. + /// Disposable will be disposed when the bag is deinitialized. + public func addDisposable(disposable: Disposable) { + disposables.append(disposable) + } + + /// Disposes all disposables that are currenty in the bag. + public func dispose() { + for disposable in disposables { + disposable.dispose() + } + disposables = [] + } + + deinit { + dispose() + } +} + +public extension Disposable { + public func disposeIn(disposeBag: DisposeBag) { + disposeBag.addDisposable(self) + } +} + +/// A disposable that disposes other disposable. +public final class SerialDisposable: Disposable { + + public private(set) var isDisposed: Bool = false + private let lock = RecursiveLock(name: "ReactiveKit.SerialDisposable") + + /// Will dispose other disposable immediately if self is already disposed. + public var otherDisposable: Disposable? { + didSet { + lock.lock() + if isDisposed { + otherDisposable?.dispose() + } + lock.unlock() + } + } + + public init(otherDisposable: Disposable?) { + self.otherDisposable = otherDisposable + } + + public func dispose() { + lock.lock() + if !isDisposed { + isDisposed = true + otherDisposable?.dispose() + } + lock.unlock() + } +} diff --git a/ReactiveKit/Other/ExecutionContext.swift b/Sources/Event.swift similarity index 50% rename from ReactiveKit/Other/ExecutionContext.swift rename to Sources/Event.swift index 8256a35..5ccaf36 100644 --- a/ReactiveKit/Other/ExecutionContext.swift +++ b/Sources/Event.swift @@ -1,7 +1,7 @@ // // The MIT License (MIT) // -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,25 +21,48 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // -// Thank you Thomas Visser! https://github.com/Thomvis/BrightFutures/ -// -public typealias ExecutionContext = (() -> Void) -> Void +/// Represents a stream event. +public protocol EventType { + + /// The type of elements generated by the stream. + associatedtype Element + + /// Extract an element from a non-terminal event. + var element: Element? { get } + + /// Does the event mark completion of a stream? + var isCompletion: Bool { get } + + /// Does the event mark failure of a stream? + var isFailure: Bool { get } + + /// Create new `.Next` event given an element. + static func next(element: Element) -> Self -public let ImmediateExecutionContext: ExecutionContext = { task in - task() + /// Create new `.Completed` event. + static func completed() -> Self } -public let ImmediateOnMainExecutionContext: ExecutionContext = { task in - if NSThread.isMainThread() { - task() - } else { - Queue.main.async(task) - } +/// An event that represents a failure of a stream. +public protocol Errorable { + + /// The type of error generated by the stream. + associatedtype Error: ErrorType + + /// Extract an error from a failure event. + var error: Error? { get } + + /// Create new `.Failure` event given an error. + static func failure(error: Error) -> Self } -public extension Queue { - public var context: ExecutionContext { - return self.async +// MARK: - Extensions + +public extension EventType { + + /// `True` if event is either a completion or a failure. + public var isTermination: Bool { + return isCompletion || isFailure } } diff --git a/Sources/Foundation.swift b/Sources/Foundation.swift new file mode 100644 index 0000000..98286bb --- /dev/null +++ b/Sources/Foundation.swift @@ -0,0 +1,231 @@ +// +// The MIT License (MIT) +// +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Foundation + +extension NSNotificationCenter { + + /// Observe notifications using a stream. + public func rNotification(name: String, object: AnyObject?) -> Stream { + return Stream { observer in + let subscription = NSNotificationCenter.defaultCenter().addObserverForName(name, object: object, queue: nil, usingBlock: { notification in + observer.next(notification) + }) + return BlockDisposable { + NSNotificationCenter.defaultCenter().removeObserver(subscription) + } + } + } +} + +public extension NSObject { + + private struct AssociatedKeys { + static var DisposeBagKey = "r_DisposeBagKey" + static var AssociatedPropertiesKey = "r_AssociatedPropertiesKey" + } + + /// Use this bag to dispose disposables upon the deallocation of the receiver. + public var rBag: DisposeBag { + if let disposeBag: AnyObject = objc_getAssociatedObject(self, &AssociatedKeys.DisposeBagKey) { + return disposeBag as! DisposeBag + } else { + let disposeBag = DisposeBag() + objc_setAssociatedObject(self, &AssociatedKeys.DisposeBagKey, disposeBag, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return disposeBag + } + } + + /// Create a stream that observes given key path using KVO. + @warn_unused_result + public func rValueForKeyPath(keyPath: String, sendInitial: Bool = true, retainStrongly: Bool = true) -> Stream { + return RKKeyValueStream(keyPath: keyPath, ofObject: self, sendInitial: sendInitial, retainStrongly: retainStrongly) { (object: AnyObject?) -> T? in + return object as? T + }.toStream() + } + + /// Create a stream that observes given key path using KVO. + @warn_unused_result + public func rValueForKeyPath(keyPath: String, sendInitial: Bool = true, retainStrongly: Bool = true) -> Stream { + return RKKeyValueStream(keyPath: keyPath, ofObject: self, sendInitial: sendInitial, retainStrongly: retainStrongly) { (object: AnyObject?) -> T? in + if object == nil { + return T() + } else { + if let object = object as? T.Wrapped { + return T(object) + } else { + return T() + } + } + }.toStream() + } + + /// Bind `stream` to `bindable` and dispose in `rBag` of receiver. + func bind(stream: S, to bindable: B) { + stream.bindTo(bindable).disposeIn(rBag) + } + + /// Bind `stream` to `bindable` and dispose in `rBag` of receiver. + func bind(stream: S, to bindable: B) { + stream.bindTo(bindable).disposeIn(rBag) + } + + internal var r_associatedProperties: [String:AnyObject] { + get { + return objc_getAssociatedObject(self, &AssociatedKeys.AssociatedPropertiesKey) as? [String:AnyObject] ?? [:] + } + set(property) { + objc_setAssociatedObject(self, &AssociatedKeys.AssociatedPropertiesKey, property, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + + public func rAssociatedPropertyForValueForKey(key: String, initial: T? = nil, set: (T -> ())? = nil) -> Property { + if let property: AnyObject = r_associatedProperties[key] { + return property as! Property + } else { + let property = Property(initial ?? self.valueForKey(key) as! T) + r_associatedProperties[key] = property + + property.observeNext { [weak self] (value: T) in + if let set = set { + set(value) + } else { + if let value = value as? AnyObject { + self?.setValue(value, forKey: key) + } else { + self?.setValue(nil, forKey: key) + } + } + }.disposeIn(rBag) + + return property + } + } + + public func rAssociatedPropertyForValueForKey(key: String, initial: T? = nil, set: (T -> ())? = nil) -> Property { + if let property: AnyObject = r_associatedProperties[key] { + return property as! Property + } else { + let property: Property + if let initial = initial { + property = Property(initial) + } else if let value = self.valueForKey(key) as? T.Wrapped { + property = Property(T(value)) + } else { + property = Property(T()) + } + + r_associatedProperties[key] = property + + property.observeNext { [weak self] (value: T) in + if let set = set { + set(value) + } else { + self?.setValue(value._unbox as! AnyObject?, forKey: key) + } + }.disposeIn(rBag) + + return property + } + } +} + +// MARK: - Implementations + +public class RKKeyValueStream: NSObject, StreamType { + private var strongObject: NSObject? = nil + private weak var object: NSObject? = nil + private var context = 0 + private var keyPath: String + private var options: NSKeyValueObservingOptions + private let transform: AnyObject? -> T? + private let subject: AnySubject> + private var numberOfObservers: Int = 0 + + private init(keyPath: String, ofObject object: NSObject, sendInitial: Bool, retainStrongly: Bool, transform: AnyObject? -> T?) { + self.keyPath = keyPath + self.options = sendInitial ? NSKeyValueObservingOptions.New.union(.Initial) : .New + self.transform = transform + + if sendInitial { + subject = AnySubject(base: ReplaySubject(bufferSize: 1)) + } else { + subject = AnySubject(base: PublishSubject()) + } + + super.init() + + self.object = object + if retainStrongly { + self.strongObject = object + } + } + + deinit { + subject.completed() + print("deinit") + } + + public override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer) { + if context == &self.context { + if let newValue = change?[NSKeyValueChangeNewKey] { + if let newValue = transform(newValue) { + subject.next(newValue) + } else { + fatalError("Value [\(newValue)] not convertible to \(T.self) type!") + } + } else { + // no new value - ignore + } + } else { + super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context) + } + } + + private func increaseNumberOfObservers() { + numberOfObservers += 1 + if numberOfObservers == 1 { + object?.addObserver(self, forKeyPath: keyPath, options: options, context: &self.context) + } + } + + private func decreaseNumberOfObservers() { + numberOfObservers -= 1 + if numberOfObservers == 0 { + object?.removeObserver(self, forKeyPath: self.keyPath) + } + } + + public var rawStream: RawStream> { + return RawStream { observer in + self.increaseNumberOfObservers() + let disposable = self.subject.toRawStream().observe(observer.observer) + let cleanupDisposabe = BlockDisposable { + disposable.dispose() + self.decreaseNumberOfObservers() + } + return DeinitDisposable(disposable: cleanupDisposabe) + } + } +} diff --git a/Sources/Observer.swift b/Sources/Observer.swift new file mode 100644 index 0000000..e035f69 --- /dev/null +++ b/Sources/Observer.swift @@ -0,0 +1,73 @@ +// +// The MIT License (MIT) +// +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +/// Represents a type that receives events. +public protocol ObserverType { + + /// Type of events being received. + associatedtype Event: EventType + + /// Sends given event to the observer. + func on(event: Event) +} + +/// Represents a type that receives events. Observer is just a convenience +/// wrapper around a closure that accepts an event of EventType. +public struct Observer: ObserverType { + + internal let observer: Event -> Void + + /// Creates an observer that wraps given closure. + public init(observer: Event -> Void) { + self.observer = observer + } + + /// Calles wrapped closure with given element. + public func on(event: Event) { + observer(event) + } +} + +// MARK: - Extensions + +public extension ObserverType { + + /// Convenience method to send `.Next` event. + public func next(element: Event.Element) { + on(.next(element)) + } + + /// Convenience method to send `.Completed` event. + public func completed() { + on(.completed()) + } +} + +public extension ObserverType where Event: Errorable { + + /// Convenience method to send `.Failure` event. + public func failure(error: Event.Error) { + on(.failure(error)) + } +} diff --git a/Sources/Operation.swift b/Sources/Operation.swift new file mode 100644 index 0000000..2307d5b --- /dev/null +++ b/Sources/Operation.swift @@ -0,0 +1,1143 @@ +// +// The MIT License (MIT) +// +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// MARK: - OperationEventType + +/// Represents an operation event. +public protocol OperationEventType: EventType, Errorable { + associatedtype Error: ErrorType +} + +// MARK: - OperationEvent + +/// Represents an operation event. +public enum OperationEvent: OperationEventType { + + /// The type of elements generated by the stream. + public typealias Element = T + + /// The type of error generated by the stream. + public typealias Error = E + + /// Contains element. + case Next(T) + + /// Contains error. + case Failure(E) + + /// Stream is completed. + case Completed + + /// Create new `.Next` event. + public static func next(element: T) -> OperationEvent { + return .Next(element) + } + + /// Create new `.Completed` event. + public static func completed() -> OperationEvent { + return .Completed + } + + /// Create new `.Failure` event. + public static func failure(error: Error) -> OperationEvent { + return .Failure(error) + } + + /// Extract an element from a non-terminal (`.Next`) event. + public var element: Element? { + switch self { + case .Next(let element): + return element + default: + return nil + } + } + + /// Does the event mark failure of a stream? `True` if event is `.Failure`. + public var isFailure: Bool { + switch self { + case .Next: + return false + case .Failure: + return true + case .Completed: + return false + } + } + + /// Does the event mark completion of a stream? `True` if event is `.Completion`. + public var isCompletion: Bool { + switch self { + case .Next: + return false + case .Failure: + return false + case .Completed: + return true + } + } + + /// Extract an error from a failure (`.Failure`) event. + public var error: Error? { + switch self { + case .Failure(let error): + return error + default: + return nil + } + } +} + +// MARK: - OperationEventType Extensions + + +public extension OperationEventType { + + public var unbox: OperationEvent { + if let element = element { + return OperationEvent.Next(element) + } else if let error = error { + return OperationEvent.failure(error) + } else { + return OperationEvent.Completed + } + } + + public func map(transform: Element -> U) -> OperationEvent { + switch self.unbox { + case .Next(let element): + return .Next(transform(element)) + case .Failure(let error): + return .Failure(error) + case .Completed: + return .Completed + } + } + + public func tryMap(transform: Element -> MapResult) -> OperationEvent { + switch self.unbox { + case .Next(let element): + switch transform(element) { + case .Success(let element): + return .Next(element) + case .Failure(let error): + return .Failure(error) + } + case .Failure(let error): + return .Failure(error) + case .Completed: + return .Completed + } + } + + public func mapError(transform: Error -> F) -> OperationEvent { + switch self.unbox { + case .Next(let element): + return .Next(element) + case .Failure(let error): + return .Failure(transform(error)) + case .Completed: + return .Completed + } + } +} + +// MARK: - OperationType + +/// Represents a stream that can fail. +public protocol OperationType: _StreamType { + + /// The type of elements generated by the stream. + associatedtype Element + + /// The type of error generated by the stream. + associatedtype Error: ErrorType + + /// Underlying raw stream. Operation is just a wrapper over `RawStream` that + /// operates on events of `OperationEvent` type. + var rawStream: RawStream> { get } + + /// Register an observer that will receive events from the operation. Registering + /// an observer starts the operation. Disposing the returned disposable can + /// be used to cancel the operation. + @warn_unused_result + func observe(observer: OperationEvent -> Void) -> Disposable +} + +public extension OperationType { + + /// Transform the operation by transforming underlying raw stream. + public func lift(transform: RawStream> -> RawStream>) -> Operation { + return Operation { observer in + return transform(self.rawStream).observe(observer.observer) + } + } + + /// Register an observer that will receive events from a stream. Registering + /// an observer starts the operation. Disposing the returned disposable can + /// be used to cancel the operation. + @warn_unused_result + public func observe(observer: OperationEvent -> Void) -> Disposable { + return rawStream.observe(observer) + } +} + +// MARK: - Operation + +/// Represents a stream that can fail. +/// Well-formed operation conforms to the grammar: `Next* (Completed | Failure)`. +public struct Operation: OperationType { + + /// The type of elements generated by the operation. + public typealias Element = T + + /// The type of error generated by the operation. + public typealias Error = E + + /// Underlying raw stream. Operation is just a wrapper over `RawStream` that + /// operates on events of `OperationEvent` type. + public let rawStream: RawStream> + + /// Create a new operation from a raw stream. + public init(rawStream: RawStream>) { + self.rawStream = rawStream + } + + /// Create a new operation using a producer. + public init(producer: Observer> -> Disposable) { + rawStream = RawStream(producer: producer) + } +} + +// MARK: - Extensions +// MARK: Creating an operation + +public extension Operation { + + /// Create an operation that emits given element and then completes. + @warn_unused_result + public static func just(element: Element) -> Operation { + return Operation { observer in + observer.next(element) + observer.completed() + return NotDisposable + } + } + + /// Create an operation that emits given sequence of elements and then completes. + @warn_unused_result + public static func sequence(sequence: S) -> Operation { + return Operation { observer in + sequence.forEach(observer.next) + observer.completed() + return NotDisposable + } + } + + /// Create an operation that fails with given error without emitting any elements. + @warn_unused_result + public static func failure(error: Error) -> Operation { + return Operation { observer in + observer.failure(error) + observer.completed() + return NotDisposable + } + } + + /// Create an operation that completes without emitting any elements. + @warn_unused_result + public static func completed() -> Operation { + return Operation { observer in + observer.completed() + return NotDisposable + } + } + + /// Create an operation that never completes. + @warn_unused_result + public static func never() -> Operation { + return Operation { observer in + return NotDisposable + } + } + + /// Create an operation that emits an integer every `interval` time on a given queue. + @warn_unused_result + public static func interval(interval: TimeValue, queue: Queue) -> Operation { + return Operation(rawStream: RawStream.interval(interval, queue: queue)) + } + + /// Create an operation that emits given element after `time` time on a given queue. + @warn_unused_result + public static func timer(element: Element, time: TimeValue, queue: Queue) -> Operation { + return Operation(rawStream: RawStream.timer(element, time: time, queue: queue)) + } +} + +// MARK: Transforming operation + +public extension OperationType { + + /// Batch the elements into arrays of given size. + @warn_unused_result + public func buffer(size: Int) -> Operation<[Element], Error> { + return Operation { observer in + var buffer: [Element] = [] + return self.observe { event in + switch event { + case .Next(let element): + buffer.append(element) + if buffer.count == size { + observer.next(buffer) + buffer = [] + } + case .Completed: + observer.completed() + case .Failure(let error): + observer.failure(error) + } + } + } + } + + /// Map each event into an operation and then flatten those operations using + /// the given flattening strategy. + @warn_unused_result + public func flatMap(strategy: FlatMapStrategy, transform: Element -> U) -> Operation { + switch strategy { + case .Latest: + return map(transform).switchToLatest() + case .Merge: + return map(transform).merge() + case .Concat: + return map(transform).concat() + } + } + + /// Transform each element by applying `transform` on it. + @warn_unused_result + public func map(transform: Element -> U) -> Operation { + return lift { $0.map { $0.map(transform) } } + } + + /// Transform error by applying `transform` on it. + @warn_unused_result + public func mapError(transform: Error -> F) -> Operation { + return lift { $0.map { $0.mapError(transform) } } + } + + /// Apply `combine` to each element starting with `initial` and emit each + /// intermediate result. This differs from `reduce` which emits only final result. + @warn_unused_result + public func scan(initial: U, _ combine: (U, Element) -> U) -> Operation { + return lift { stream in + return stream.scan(.Next(initial)) { memo, new in + switch new { + case .Next(let element): + return .Next(combine(memo.element!, element)) + case .Completed: + return .Completed + case .Failure(let error): + return .Failure(error) + } + } + } + } + + /// Transform each element by applying `transform` on it. + @warn_unused_result + public func tryMap(transform: Element -> MapResult) -> Operation { + return lift { $0.map { $0.tryMap(transform) } } + } + + /// Convert the operation to a concrete operation. + @warn_unused_result + public func toOperation() -> Operation { + return Operation(rawStream: self.rawStream) + } + + /// Convert the operation to a stream by ignoring the error. + @warn_unused_result + public func toStream(logError logError: Bool, completeOnError: Bool = true) -> Stream { + return Stream { observer in + return self.observe { event in + switch event { + case .Next(let element): + observer.next(element) + case .Failure(let error): + if completeOnError { + observer.completed() + } + if logError { + print("Operation.toStream encountered an error: \(error)") + } + case .Completed: + observer.completed() + } + } + } + } + + /// Convert operation to a stream by propagating default element if error happens. + @warn_unused_result + public func toStream(recoverWith element: Element) -> Stream { + return Stream { observer in + return self.observe { event in + switch event { + case .Next(let element): + observer.next(element) + case .Failure: + observer.next(element) + observer.completed() + case .Completed: + observer.completed() + } + } + } + } + + /// Batch each `size` elements into another operations. + @warn_unused_result + public func window(size: Int) -> Operation, Error> { + return buffer(size).map { Operation.sequence($0) } + } +} + +// MARK: Filtration + +extension OperationType { + + /// Emit an element only if `interval` time passes without emitting another element. + @warn_unused_result + public func debounce(interval: TimeValue, on queue: Queue) -> Operation { + return lift { $0.debounce(interval, on: queue) } + } + + /// Emit first element and then all elements that are not equal to their predecessor(s). + @warn_unused_result + public func distinct(areDistinct: (Element, Element) -> Bool) -> Operation { + return lift { $0.distinct(areDistinct) } + } + + /// Emit only element at given index if such element is produced. + @warn_unused_result + public func elementAt(index: Int) -> Operation { + return lift { $0.elementAt(index) } + } + + /// Emit only elements that pass `include` test. + @warn_unused_result + public func filter(include: Element -> Bool) -> Operation { + return lift { $0.filter { $0.element.flatMap(include) ?? true } } + } + + /// Emit only the first element generated by the operation and then complete. + @warn_unused_result + public func first() -> Operation { + return lift { $0.first() } + } + + /// Ignore all elements (just propagate terminal events). + @warn_unused_result + public func ignoreElements() -> Operation { + return lift { $0.ignoreElements() } + } + + /// Emit only last element generated by the stream and then completes. + @warn_unused_result + public func last() -> Operation { + return lift { $0.last() } + } + + /// Periodically sample the stream and emit latest element from each interval. + @warn_unused_result + public func sample(interval: TimeValue, on queue: Queue) -> Operation { + return lift { $0.sample(interval, on: queue) } + } + + /// Suppress first `count` elements generated by the operation. + @warn_unused_result + public func skip(count: Int) -> Operation { + return lift { $0.skip(count) } + } + + /// Suppress last `count` elements generated by the operation. + @warn_unused_result + public func skipLast(count: Int) -> Operation { + return lift { $0.skipLast(count) } + } + + /// Emit only first `count` elements of the operation and then complete. + @warn_unused_result + public func take(count: Int) -> Operation { + return lift { $0.take(count) } + } + + /// Emit only last `count` elements of the operation and then complete. + @warn_unused_result + public func takeLast(count: Int) -> Operation { + return lift { $0.takeLast(count) } + } + + /// Throttle operation to emit at most one element per given `seconds` interval. + @warn_unused_result + public func throttle(seconds: TimeValue) -> Operation { + return lift { $0.throttle(seconds) } + } +} + +extension OperationType where Element: Equatable { + + /// Emit first element and then all elements that are not equal to their predecessor(s). + @warn_unused_result + public func distinct() -> Operation { + return lift { $0.distinct() } + } +} + +public extension OperationType where Element: OptionalType, Element.Wrapped: Equatable { + + /// Emit first element and then all elements that are not equal to their predecessor(s). + @warn_unused_result + public func distinct() -> Operation { + return lift { $0.distinct() } + } +} + +public extension OperationType where Element: OptionalType { + + /// Suppress all `nil`-elements. + @warn_unused_result + public func ignoreNil() -> Operation { + return Operation { observer in + return self.observe { event in + switch event { + case .Next(let element): + if let element = element._unbox { + observer.next(element) + } + case .Failure(let error): + observer.failure(error) + case .Completed: + observer.completed() + } + } + } + } +} + +// MARK: Combination + +extension OperationType { + + /// Emit a pair of latest elements from each operation. Starts when both operations + /// emit at least one element, and emits `.Next` when either operation generates an element. + @warn_unused_result + public func combineLatestWith(other: O) -> Operation<(Element, O.Element), Error> { + return lift { + return $0.combineLatestWith(other.toOperation()) { myLatestElement, my, theirLatestElement, their in + switch (my, their) { + case (.Completed, .Completed): + return OperationEvent.Completed + case (.Next(let myElement), .Next(let theirElement)): + return OperationEvent.Next(myElement, theirElement) + case (.Next(let myElement), .Completed): + if let theirLatestElement = theirLatestElement { + return OperationEvent.Next(myElement, theirLatestElement) + } else { + return nil + } + case (.Completed, .Next(let theirElement)): + if let myLatestElement = myLatestElement { + return OperationEvent.Next(myLatestElement, theirElement) + } else { + return nil + } + case (.Failure(let error), _): + return OperationEvent.failure(error) + case (_, .Failure(let error)): + return OperationEvent.failure(error) + default: + fatalError("This will never execute: Swift compiler cannot infer switch completeness.") + } + } + } + } + + /// Merge emissions from both source and `other` into one operation. + @warn_unused_result + public func mergeWith(other: O) -> Operation { + return lift { $0.mergeWith(other.rawStream) } + } + + /// Prepend given element to the operation emission. + @warn_unused_result + public func startWith(element: Element) -> Operation { + return lift { $0.startWith(.Next(element)) } + } + + /// Emit elements from source and `other` in combination. This differs from `combineLatestWith` in + /// that combinations are produced from elements at same positions. + @warn_unused_result + public func zipWith(other: O) -> Operation<(Element, O.Element), Error> { + return lift { + return $0.zipWith(other.toOperation()) { my, their in + switch (my, their) { + case (.Next(let myElement), .Next(let theirElement)): + return OperationEvent.Next(myElement, theirElement) + case (_, .Completed): + return OperationEvent.Completed + case (.Completed, _): + return OperationEvent.Completed + case (.Failure(let error), _): + return OperationEvent.failure(error) + case (_, .Failure(let error)): + return OperationEvent.failure(error) + default: + fatalError("This will never execute: Swift compiler cannot infer switch completeness.") + } + } + } + } +} + +// MARK: Error Handling + +extension OperationType { + + /// Map failure event into another operation and continue with that operation. Also called `catch`. + @warn_unused_result + public func flatMapError(recover: Error -> U) -> Operation { + return Operation { observer in + let serialDisposable = SerialDisposable(otherDisposable: nil) + + serialDisposable.otherDisposable = self.observe { taskEvent in + switch taskEvent { + case .Next(let value): + observer.next(value) + case .Completed: + observer.completed() + case .Failure(let error): + serialDisposable.otherDisposable = recover(error).observe { event in + observer.observer(event) + } + } + } + + return serialDisposable + } + } + + /// Map failure event into another operation and continue with that operation. Also called `catch`. + @warn_unused_result + public func flatMapError(recover: Error -> S) -> Stream { + return Stream { observer in + let serialDisposable = SerialDisposable(otherDisposable: nil) + + serialDisposable.otherDisposable = self.observe { taskEvent in + switch taskEvent { + case .Next(let value): + observer.next(value) + case .Completed: + observer.completed() + case .Failure(let error): + serialDisposable.otherDisposable = recover(error).observe { event in + observer.observer(event) + } + } + } + + return serialDisposable + } + } + + /// Restart the operation in case of failure at most `times` number of times. + @warn_unused_result + public func retry(times: Int) -> Operation { + return lift { $0.retry(times) } + } +} + +// MARK: Utilities + +extension OperationType { + + /// Set the execution context in which to execute the operation (i.e. in which to run + /// the operation's producer). + @warn_unused_result + public func executeIn(context: ExecutionContext) -> Operation { + return lift { $0.executeIn(context) } + } + + /// Delay stream events for `interval` time. + @warn_unused_result + public func delay(interval: TimeValue, on queue: Queue) -> Operation { + return lift { $0.delay(interval, on: queue) } + } + + /// Do side-effect upon various events. + @warn_unused_result + public func doOn(next next: (Element -> ())? = nil, + failure: (Error -> ())? = nil, + start: (() -> Void)? = nil, + completed: (() -> Void)? = nil, + disposed: (() -> ())? = nil, + termination: (() -> ())? = nil + ) -> Operation { + return Operation { observer in + start?() + let disposable = self.observe { event in + switch event { + case .Next(let value): + next?(value) + case .Failure(let error): + failure?(error) + termination?() + case .Completed: + completed?() + termination?() + } + observer.observer(event) + } + return BlockDisposable { + disposable.dispose() + disposed?() + termination?() + } + } + } + + /// Use `doOn` to log various events. + @warn_unused_result + public func debug(id: String = "Untitled Operation") -> Operation { + return doOn(next: { element in + print("\(id): Next(\(element))") + }, failure: { error in + print("\(id): Failure(\(error))") + }, start: { + print("\(id): Start") + }, completed: { + print("\(id): Completed") + }, disposed: { + print("\(id): Disposed") + }) + } + + /// Set the execution context in which to dispatch events (i.e. in which to run observers). + @warn_unused_result + public func observeIn(context: ExecutionContext) -> Operation { + return lift { $0.observeIn(context) } + } + + /// Supress non-terminal events while last event generated on other stream is `false`. + @warn_unused_result + public func pausable(by other: S) -> Operation { + return lift { $0.pausable(other) } + } + + /// Error-out if `interval` time passes with no emitted elements. + @warn_unused_result + public func timeout(interval: TimeValue, with error: Error, on queue: Queue) -> Operation { + return Operation { observer in + var completed = false + var lastSubscription: Disposable? = nil + return self.observe { event in + lastSubscription?.dispose() + observer.observer(event) + completed = event.isTermination + lastSubscription = queue.disposableAfter(interval) { + if !completed { + completed = true + observer.failure(error) + } + } + } + } + } +} + +// MARK: Conditional, Boolean and Aggregational + +extension OperationType { + + /// Propagate event only from an operation that starts emitting first. + @warn_unused_result + public func ambWith(other: O) -> Operation { + return lift { $0.ambWith(other.rawStream) } + } + + /// Collect all elements into an array and emit just that array. + @warn_unused_result + public func collect() -> Operation<[Element], Error> { + return reduce([], { memo, new in memo + [new] }) + } + + /// First emit events from source and then from `other` operation. + @warn_unused_result + public func concatWith(other: O) -> Operation { + return lift { stream in + stream.concatWith(other.rawStream) + } + } + + /// Emit default element is the operation completes without emitting any element. + @warn_unused_result + public func defaultIfEmpty(element: Element) -> Operation { + return lift { $0.defaultIfEmpty(element) } + } + + /// Reduce elements to a single element by applying given function on each emission. + @warn_unused_result + public func reduce(initial: U, _ combine: (U, Element) -> U) -> Operation { + return Operation { observer in + observer.next(initial) + return self.scan(initial, combine).observe(observer.observer) + }.last() + } + + /// Par each element with its predecessor. First element is paired with `nil`. + @warn_unused_result + public func zipPrevious() -> Operation<(Element?, Element), Error> { + return Operation { observer in + var previous: Element? = nil + return self.observe { event in + switch event { + case .Next(let element): + observer.next((previous, element)) + previous = element + case .Failure(let error): + observer.failure(error) + case .Completed: + observer.completed() + } + } + } + } +} + +// MARK: Operations that emit other operation + +public extension Operation where T: OperationType, T.Event: OperationEventType { + public typealias InnerElement = T.Event.Element + public typealias InnerError = T.Event.Error + + /// Flatten the operation by observing all inner operation and propagate elements from each one as they come. + @warn_unused_result + public func merge(mapError: Error -> InnerError) -> Operation { + return lift { + $0.merge({ $0.unbox }, propagateErrorEvent: { event, observer in observer.failure(mapError(event.error!)) }) + } + } + + /// Flatten the operation by observing and propagating emissions only from the latest inner operation. + @warn_unused_result + public func switchToLatest(mapError: Error -> InnerError) -> Operation { + return lift { + $0.switchToLatest({ $0.unbox }, propagateErrorEvent: { event, observer in observer.failure(mapError(event.error!)) }) + } + } + + /// Flatten the operation by sequentially observing inner operations in order in + /// which they arrive, starting next observation only after the previous one completes, cancelling previous one when new one starts. + @warn_unused_result + public func concat(mapError: Error -> InnerError) -> Operation { + return lift { + $0.concat({ $0.unbox }, propagateErrorEvent: { event, observer in observer.failure(mapError(event.error!)) }) + } + } +} + +public extension Operation where T: OperationType, T.Event: OperationEventType, T.Event.Error == E { + + /// Flatten the operation by observing all inner operation and propagate elements from each one as they come. + @warn_unused_result + public func merge() -> Operation { + return merge { $0 } + } + + /// Flatten the operation by observing and propagating emissions only from latest operation. + @warn_unused_result + public func switchToLatest() -> Operation { + return switchToLatest { $0 } + } + + /// Flatten the operation by sequentially observing inner operations in order in + /// which they arrive, starting next observation only after previous one completes. + @warn_unused_result + public func concat() -> Operation { + return concat { $0 } + } +} + +// MARK: Connectable + +extension OperationType { + + /// Ensure that all observers see the same sequence of elements. Connectable. + @warn_unused_result + public func replay(limit: Int = Int.max) -> ConnectableOperation { + return ConnectableOperation(rawConnectableStream: rawStream.replay(limit)) + } + + /// Convert the operation to a connectable operation. + @warn_unused_result + public func publish() -> ConnectableOperation { + return ConnectableOperation(rawConnectableStream: rawStream.publish()) + } + + /// Ensure that all observers see the same sequence of elements. + /// Shorthand for `replay(limit).refCount()`. + @warn_unused_result + public func shareReplay(limit: Int = Int.max) -> Operation { + return replay(limit).refCount() + } +} + +// MARK: Functions + +/// Combine multiple operations into one. See `mergeWith` for more info. +@warn_unused_result +public func combineLatest + + (a: A, _ b: B) -> Operation<(A.Element, B.Element), A.Error> { + return a.combineLatestWith(b) +} + +/// Combine multiple operations into one. See `mergeWith` for more info. +@warn_unused_result +public func combineLatest + + (a: A, _ b: B, _ c: C) -> Operation<(A.Element, B.Element, C.Element), A.Error> { + return combineLatest(a, b).combineLatestWith(c).map { ($0.0, $0.1, $1) } +} + +/// Combine multiple operations into one. See `mergeWith` for more info. +@warn_unused_result +public func combineLatest + + (a: A, _ b: B, _ c: C, _ d: D) -> Operation<(A.Element, B.Element, C.Element, D.Element), A.Error> { + return combineLatest(a, b, c).combineLatestWith(d).map { ($0.0, $0.1, $0.2, $1) } +} + +/// Combine multiple operations into one. See `mergeWith` for more info. +@warn_unused_result +public func combineLatest + + (a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> Operation<(A.Element, B.Element, C.Element, D.Element, E.Element), A.Error> { + return combineLatest(a, b, c, d).combineLatestWith(e).map { ($0.0, $0.1, $0.2, $0.3, $1) } +} + +/// Combine multiple operations into one. See `mergeWith` for more info. +@warn_unused_result +public func combineLatest + + (a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F) -> Operation<(A.Element, B.Element, C.Element, D.Element, E.Element, F.Element), A.Error> { + return combineLatest(a, b, c, d, e).combineLatestWith(f).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $1) } +} + +/// Zip multiple operations into one. See `zipWith` for more info. +@warn_unused_result +public func zip + + (a: A, _ b: B) -> Operation<(A.Element, B.Element), A.Error> { + return a.zipWith(b) +} + +/// Zip multiple operations into one. See `zipWith` for more info. +@warn_unused_result +public func zip + + (a: A, _ b: B, _ c: C) -> Operation<(A.Element, B.Element, C.Element), A.Error> { + return zip(a, b).zipWith(c).map { ($0.0, $0.1, $1) } +} + +/// Zip multiple operations into one. See `zipWith` for more info. +@warn_unused_result +public func zip + + (a: A, _ b: B, _ c: C, _ d: D) -> Operation<(A.Element, B.Element, C.Element, D.Element), A.Error> { + return zip(a, b, c).zipWith(d).map { ($0.0, $0.1, $0.2, $1) } +} + +/// Zip multiple operations into one. See `zipWith` for more info. +@warn_unused_result +public func zip + + (a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> Operation<(A.Element, B.Element, C.Element, D.Element, E.Element), A.Error> { + return zip(a, b, c, d).zipWith(e).map { ($0.0, $0.1, $0.2, $0.3, $1) } +} + +/// Zip multiple operations into one. See `zipWith` for more info. +@warn_unused_result +public func zip + + (a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F) -> Operation<(A.Element, B.Element, C.Element, D.Element, E.Element, F.Element), A.Error> { + return zip(a, b, c, d, e).zipWith(f).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $1) } +} + +// MARK: - ConnectableOperation + +/// Represents an operation that is started by calling `connect` on it. +public class ConnectableOperation: OperationType, ConnectableStreamType { + public typealias Event = OperationEvent + + private let rawConnectableStream: RawConnectableStream> + + public var rawStream: RawStream> { + return rawConnectableStream.toRawStream() + } + + private init(rawConnectableStream: RawConnectableStream>) { + self.rawConnectableStream = rawConnectableStream + } + + /// Register an observer that will receive events from the operation. + /// Note that the events will not be generated until `connect` is called. + @warn_unused_result + public func observe(observer: Event -> Void) -> Disposable { + return rawConnectableStream.observe(observer) + } + + /// Start the operation. + public func connect() -> Disposable { + return rawConnectableStream.connect() + } +} + +public extension ConnectableOperation { + + /// Convert connectable operation into the ordinary one by calling `connect` + /// on first subscription and calling dispose when number of observers goes down to zero. + @warn_unused_result + public func refCount() -> Operation { + return Operation(rawStream: self.rawConnectableStream.refCount()) + } +} + +// MARK: - PushOperation + +/// Represents an operation that can push events to registered observers at will. +public class PushOperation: OperationType, SubjectType { + private let subject = PublishSubject>() + + public var rawStream: RawStream> { + return subject.toRawStream() + } + + public init() { + } + + /// Send event to all registered observers. + public func on(event: OperationEvent) { + subject.on(event) + } +} + +extension PushOperation { + + /// Convert `PushOperation` to ordinary `Operation`. + @warn_unused_result + public func toStream() -> Operation { + return Operation(rawStream: rawStream) + } +} + +// MARK: - Other + +public enum FlatMapStrategy { + + /// Use `switchToLatest` flattening method. + case Latest + + /// Use `merge` flattening method. + case Merge + + /// Use `concat` flattening method. + case Concat +} + +public enum MapResult { + case Success(T) + case Failure(E) +} diff --git a/ReactiveKit/Observable/Observable.swift b/Sources/Property.swift similarity index 50% rename from ReactiveKit/Observable/Observable.swift rename to Sources/Property.swift index ffebf79..fda940e 100644 --- a/ReactiveKit/Observable/Observable.swift +++ b/Sources/Property.swift @@ -1,7 +1,7 @@ // // The MIT License (MIT) // -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,41 +22,51 @@ // THE SOFTWARE. // -public protocol ObservableType: StreamType { - typealias Value - var value: Value { get set } +/// Represents a state as a stream of events. +public protocol PropertyType: StreamType { + var value: Element { get set } } -public final class Observable: ActiveStream, ObservableType { +/// Represents a state as a stream of events. +public final class Property: PropertyType { - private var _value: Value + private var _value: T + private let subject = PublishSubject>() + private let lock = RecursiveLock(name: "ReactiveKit.Property") + private let disposeBag = DisposeBag() - public var value: Value { + public var rawStream: RawStream> { + return subject.toRawStream().startWith(.Next(value)) + } + + /// Underlying value. Changing it emits `.Next` event with new value. + public var value: T { get { + lock.lock(); defer { lock.unlock() } return _value } set { + lock.lock(); defer { lock.unlock() } _value = newValue - super.next(newValue) + subject.next(newValue) } } - public init(_ value: Value) { + public init(_ value: T) { _value = value - super.init() - } - - public override func next(event: Value) { - value = event } - public override func observe(on context: ExecutionContext? = ImmediateOnMainExecutionContext, observer: Observer) -> DisposableType { - let disposable = super.observe(on: context, observer: observer) - observer(value) - return disposable + deinit { + subject.completed() } +} - public func silentUpdate(value: Value) { - _value = value +extension Property: BindableType { + + /// Returns an observer that can be used to dispatch events to the receiver. + /// Can accept a disposable that will be disposed on receiver's deinit. + public func observer(disconnectDisposable: Disposable) -> StreamEvent -> () { + disposeBag.addDisposable(disconnectDisposable) + return { [weak self] in self?.subject.on($0) } } } diff --git a/Sources/RawStream.swift b/Sources/RawStream.swift new file mode 100644 index 0000000..ab6d42a --- /dev/null +++ b/Sources/RawStream.swift @@ -0,0 +1,1049 @@ +// +// The MIT License (MIT) +// +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// MARK: - _StreamType + +/// Represents a stream over generic EventType. +public protocol _StreamType { + + /// The type of events generated by the stream. + associatedtype Event: EventType + + /// Register an observer that will receive events from a stream. + /// + /// In case of pull-driven streams, e.g. `Stream` or `Operation`, + /// this actually triggers event generation. + @warn_unused_result + func observe(observer: Event -> Void) -> Disposable +} + +extension _StreamType { + + /// Register an observer that will receive elements from `.Next` events of the stream. + @warn_unused_result + public func observeNext(observer: Event.Element -> Void) -> Disposable { + return observe { event in + if let element = event.element { + observer(element) + } + } + } +} + +// MARK: - RawStreamType + +/// Represents an underlying stream generalized over EventType and used by +/// higher-level implementations like `Stream` or `Operation`. +public protocol RawStreamType: _StreamType { + + /// The type of events generated by the stream. + associatedtype Event: EventType + + /// Register an observer that will receive events from a stream. + @warn_unused_result + func observe(observer: Event -> Void) -> Disposable +} + +// MARK: - RawStream + +/// An underlying stream generalized over EventType and used by +/// higher-level implementations like `Stream` or `Operation`. +public struct RawStream: RawStreamType { + + private let producer: Observer -> Disposable + + /// Create new `RawStream` given a producer closure. + public init(producer: Observer -> Disposable) { + self.producer = producer + } + + /// Register an observer that will receive events from a stream. + @warn_unused_result + public func observe(observer: Event -> Void) -> Disposable { + var terminated = false + let observer = Observer { event in + guard !terminated else { return } + terminated = event.isTermination + observer(event) + } + return producer(observer) + } +} + +// MARK: - Extensions + +public extension _StreamType { + + /// Transform a stream type into concrete `RawStream`. + @warn_unused_result + public func toRawStream() -> RawStream { + return RawStream { observer in + return self.observe(observer.observer) + } + } +} + +// MARK: Creation + +public extension RawStream where Event.Element == Int { + + /// Create a stream that emits an integer every `interval` time on a given queue. + @warn_unused_result + public static func interval(interval: TimeValue, queue: Queue) -> RawStream { + return RawStream { observer in + var number = 0 + var dispatch: (() -> Void)! + let disposable = SimpleDisposable() + dispatch = { + queue.after(interval) { + if !disposable.isDisposed { + observer.next(number) + number += 1 + dispatch() + } else { + dispatch = nil + } + } + } + dispatch() + return disposable + } + } +} + +public extension RawStream { + + /// Create a stream that emits given element after `time` time on a given queue. + @warn_unused_result + public static func timer(element: Event.Element, time: TimeValue, queue: Queue) -> RawStream { + return RawStream { observer in + let disposable = SimpleDisposable() + queue.after(time) { + if !disposable.isDisposed { + observer.next(element) + observer.completed() + } + } + return disposable + } + } +} + +// MARK: Transformation + +public extension RawStreamType { + + // WONTDO: buffer + // WONTDO: flatMap + + /// Transform each element by applying `transform` on it. + @warn_unused_result + public func map(transform: Event -> U) -> RawStream { + return RawStream { observer in + return self.observe { event in + observer.observer(transform(event)) + } + } + } + + /// Apply `combine` to each element starting with `initial` and emit each + /// intermediate result. This differs from `reduce` which emits only final result. + @warn_unused_result + public func scan(initial: U, _ combine: (U, Event) -> U) -> RawStream { + return RawStream { observer in + var accumulator = initial + return self.observe { event in + accumulator = combine(accumulator, event) + observer.observer(accumulator) + } + } + } + + /// WONTDO: window +} + +// MARK: Filtration + +public extension RawStreamType { + + /// Emit an element only if `interval` time passes without emitting another element. + @warn_unused_result + public func debounce(interval: TimeValue, on queue: Queue) -> RawStream { + return RawStream { observer in + var timerSubscription: Disposable? = nil + var previousEvent: Event? = nil + return self.observe { event in + timerSubscription?.dispose() + if event.isTermination { + if let previousEvent = previousEvent { + observer.observer(previousEvent) + observer.observer(event) + } + } else { + previousEvent = event + timerSubscription = queue.disposableAfter(interval) { + if let _event = previousEvent { + observer.observer(_event) + previousEvent = nil + } + } + } + } + } + } + + /// Emit first element and then all elements that are not equal to their predecessor(s). + @warn_unused_result + public func distinct(areDistinct: (Event.Element, Event.Element) -> Bool) -> RawStream { + return RawStream { observer in + var lastElement: Event.Element? = nil + return self.observe { event in + if let element = event.element { + if lastElement == nil || areDistinct(lastElement!, element) { + observer.observer(event) + } + lastElement = element + } else { + observer.observer(event) + } + } + } + } + + /// Emit only element at given index if such element is produced. + @warn_unused_result + public func elementAt(index: Int) -> RawStream { + return RawStream { observer in + var currentIndex = 0 + return self.observe { event in + if !event.isTermination { + if currentIndex == index { + observer.observer(event) + observer.completed() + } else { + currentIndex += 1 + } + } else { + observer.observer(event) + } + } + } + } + + /// Emit only elements that pass `include` test. + @warn_unused_result + public func filter(include: Event -> Bool) -> RawStream { + return RawStream { observer in + return self.observe { event in + if include(event) { + observer.observer(event) + } + } + } + } + + /// Emit only the first element generated by the stream and then completes. + @warn_unused_result + public func first() -> RawStream { + return take(1) + } + + /// Ignore all events that are not terminal events. + @warn_unused_result + public func ignoreElements() -> RawStream { + return RawStream { observer in + return self.observe { event in + if event.isTermination { + observer.observer(event) + } + } + } + } + + /// Emit only last element generated by the stream and then completes. + @warn_unused_result + public func last() -> RawStream { + return takeLast(1) + } + + /// Periodically sample the stream and emit latest element from each interval. + @warn_unused_result + public func sample(interval: TimeValue, on queue: Queue) -> RawStream { + return RawStream { observer in + + let serialDisposable = SerialDisposable(otherDisposable: nil) + var latestEvent: Event? = nil + var dispatch: (() -> Void)! + dispatch = { + queue.after(interval) { + if !serialDisposable.isDisposed { + if let event = latestEvent { + observer.observer(event) + latestEvent = nil + } + dispatch() + } else { + dispatch = nil + } + } + } + + serialDisposable.otherDisposable = self.observe { event in + latestEvent = event + if event.isTermination { + observer.observer(event) + serialDisposable.dispose() + } + } + + dispatch() + return serialDisposable + } + } + + /// Suppress first `count` elements generated by the stream. + @warn_unused_result + public func skip(count: Int) -> RawStream { + return RawStream { observer in + var count = count + return self.observe { event in + if count > 0 { + count -= 1 + } else { + observer.observer(event) + } + } + } + } + + /// Suppress last `count` elements generated by the stream. + @warn_unused_result + public func skipLast(count: Int) -> RawStream { + guard count > 0 else { return self.toRawStream() } + return RawStream { observer in + var buffer: [Event] = [] + return self.observe { event in + if event.isTermination { + observer.observer(event) + } else { + buffer.append(event) + if buffer.count > count { + observer.observer(buffer.removeFirst()) + } + } + } + } + } + + /// Emit only first `count` elements of the stream and then complete. + @warn_unused_result + public func take(count: Int) -> RawStream { + return RawStream { observer in + guard count > 0 else { + observer.completed() + return NotDisposable + } + + var taken = 0 + + let serialDisposable = SerialDisposable(otherDisposable: nil) + serialDisposable.otherDisposable = self.observe { event in + + if let element = event.element { + if taken < count { + taken += 1 + observer.next(element) + } + + if taken == count { + observer.completed() + serialDisposable.otherDisposable?.dispose() + } + } else { + observer.observer(event) + } + } + + return serialDisposable + } + } + + /// Emit only last `count` elements of the stream and then complete. + @warn_unused_result + public func takeLast(count: Int = 1) -> RawStream { + return RawStream { observer in + + var values: [Event.Element] = [] + values.reserveCapacity(count) + + return self.observe { event in + + if let element = event.element { + while values.count + 1 > count { + values.removeFirst() + } + values.append(element) + } + + if event.isCompletion { + values.forEach(observer.next) + observer.completed() + } else if event.isFailure { + observer.observer(event) + } + } + } + } + + // TODO: fix + /// Throttle stream to emit at most one event per given `seconds` interval. + @warn_unused_result + public func throttle(seconds: TimeValue) -> RawStream { + return RawStream { observer in + var lastEventTime: TimeValue = SystemTime.distantPast + return self.observe { event in + if event.isTermination { + observer.observer(event) + } else { + let now = SystemTime.now + if now - lastEventTime > seconds { + lastEventTime = now + observer.observer(event) + } + } + } + } + } +} + +public extension RawStreamType where Event.Element: Equatable { + + /// Emit first element and then all elements that are not equal to their predecessor(s). + @warn_unused_result + public func distinct() -> RawStream { + return distinct(==) + } +} + +public extension RawStreamType where Event.Element: OptionalType, Event.Element.Wrapped: Equatable { + + /// Emit first element and then all elements that are not equal to their predecessor(s). + @warn_unused_result + public func distinct() -> RawStream { + return distinct { a, b in + switch (a._unbox, b._unbox) { + case (.None, .Some): + return true + case (.Some, .None): + return true + case (.Some(let old), .Some(let new)) where old == new: + return true + default: + return false + } + } + } +} + +// MARK: Combination + +extension RawStreamType { + + /// Emit a combination of latest elements from each stream. Starts when both streams emit at least one element, + /// and emits next when either stream generates an event. + @warn_unused_result + public func combineLatestWith(other: R, combine: (Event.Element?, Event, R.Event.Element?, R.Event) -> U?) -> RawStream { + return RawStream { observer in + let lock = SpinLock() + + var latestMyElement: Event.Element? + var latestTheirElement: R.Event.Element? + var latestMyEvent: Event? + var latestTheirEvent: R.Event? + + let dispatchNextIfPossible = { () -> () in + if let latestMyEvent = latestMyEvent, latestTheirEvent = latestTheirEvent { + if let event = combine(latestMyElement, latestMyEvent, latestTheirElement, latestTheirEvent) { + observer.observer(event) + } + } + } + + let selfDisposable = self.observe { event in + lock.atomic { + if let element = event.element { latestMyElement = element } + latestMyEvent = event + dispatchNextIfPossible() + } + } + + let otherDisposable = other.observe { event in + lock.atomic { + if let element = event.element { latestTheirElement = element } + latestTheirEvent = event + dispatchNextIfPossible() + } + } + + return CompositeDisposable([selfDisposable, otherDisposable]) + } + } + + /// Merge emissions from both source and `other` into one stream. + @warn_unused_result + public func mergeWith(other: R) -> RawStream { + return RawStream { observer in + let lock = SpinLock() + var numberOfOperations = 2 + let compositeDisposable = CompositeDisposable() + let onBoth: Event -> Void = { event in + if event.isCompletion { + lock.atomic { + numberOfOperations -= 1 + if numberOfOperations == 0 { + observer.completed() + } + } + return + } else if event.isFailure { + compositeDisposable.dispose() + } + observer.observer(event) + } + compositeDisposable += self.observe(onBoth) + compositeDisposable += other.observe(onBoth) + return compositeDisposable + } + } + + /// Prepend given event to the stream emission. + @warn_unused_result + public func startWith(event: Event) -> RawStream { + return RawStream { observer in + observer.observer(event) + return self.observe { event in + observer.observer(event) + } + } + } + + + /// Emit elements from source and `other` in combination. This differs from `combineLatestWith` in + /// that combinations are produced from elements at same positions. + @warn_unused_result + public func zipWith(other: R, zip: (Event, R.Event) -> U) -> RawStream { + return RawStream { observer in + let lock = SpinLock() + + var selfBuffer = Array() + var otherBuffer = Array() + let disposable = CompositeDisposable() + + let dispatchIfPossible = { + while selfBuffer.count > 0 && otherBuffer.count > 0 { + let event = zip(selfBuffer[0], otherBuffer[0]) + observer.observer(event) + selfBuffer.removeAtIndex(0) + otherBuffer.removeAtIndex(0) + if event.isCompletion || event.isFailure { + disposable.dispose() + } + } + } + + disposable += self.observe { event in + lock.atomic { + selfBuffer.append(event) + dispatchIfPossible() + } + } + + disposable += other.observe { event in + lock.atomic { + otherBuffer.append(event) + dispatchIfPossible() + } + } + + return disposable + } + } +} + +// MARK: Error Handling + +public extension RawStreamType where Event: Errorable { + + /// Restart the stream in case of failure at most `times` number of times. + @warn_unused_result + public func retry(times: Int) -> RawStream { + return RawStream { observer in + var times = times + let serialDisposable = SerialDisposable(otherDisposable: nil) + + var attempt: (() -> Void)? + + attempt = { + serialDisposable.otherDisposable?.dispose() + serialDisposable.otherDisposable = self.observe { event in + if event.error != nil { + if times > 0 { + times -= 1 + attempt?() + } else { + observer.observer(event) + attempt = nil + } + } else { + observer.observer(event) + attempt = nil + } + } + } + + attempt?() + return BlockDisposable { + serialDisposable.dispose() + attempt = nil + } + } + } +} + +// MARK: Utilities + +public extension RawStreamType { + + /// Set the execution context in which to execute the stream (i.e. in which to run + /// stream's producer). + @warn_unused_result + public func executeIn(context: ExecutionContext) -> RawStream { + return RawStream { observer in + let serialDisposable = SerialDisposable(otherDisposable: nil) + context { + if !serialDisposable.isDisposed { + serialDisposable.otherDisposable = self.observe(observer.observer) + } + } + return serialDisposable + } + } + + /// Delay stream events for `interval` time. + @warn_unused_result + public func delay(interval: TimeValue, on queue: Queue) -> RawStream { + return RawStream { observer in + return self.observe { event in + queue.after(interval) { + observer.observer(event) + } + } + } + } + + // WONTDO: do + + /// Set the execution context in which to dispatch events (i.e. in which to run + /// observers). + @warn_unused_result + public func observeIn(context: ExecutionContext) -> RawStream { + return RawStream { observer in + return self.observe { event in + context { + observer.observer(event) + } + } + } + } + + /// Supress events while last event generated on other stream is `false`. + @warn_unused_result + public func pausable(by: R) -> RawStream { + return RawStream { observer in + + var allowed: Bool = true + + let compositeDisposable = CompositeDisposable() + compositeDisposable += by.observe { value in + if let element = value.element { + allowed = element + } else { + // ignore? + } + } + + compositeDisposable += self.observe { event in + if event.isFailure || event.isCompletion { + observer.observer(event) + } else if allowed { + observer.observer(event) + } + } + + return compositeDisposable + } + } + + // WONTDO: timeout +} + +// MARK: Conditional, Boolean and Aggregational + +public extension RawStreamType { + + // WONTDO: all + + /// Propagate event only from a stream that starts emitting first. + @warn_unused_result + public func ambWith(other: RawStream) -> RawStream { + return RawStream { observer in + let lock = SpinLock() + var isOtherDispatching = false + var isSelfDispatching = false + let d1 = SerialDisposable(otherDisposable: nil) + let d2 = SerialDisposable(otherDisposable: nil) + + d1.otherDisposable = self.observe { event in + lock.lock(); defer { lock.unlock() } + guard !isOtherDispatching else { return } + isSelfDispatching = true + observer.observer(event) + if !d2.isDisposed { + d2.dispose() + } + } + + d2.otherDisposable = other.observe { event in + lock.lock(); defer { lock.unlock() } + guard !isSelfDispatching else { return } + isOtherDispatching = true + observer.observer(event) + if !d1.isDisposed { + d1.dispose() + } + } + + return CompositeDisposable([d1, d2]) + } + } + + /// First emit events from source and then from `other` stream. + @warn_unused_result + public func concatWith(other: RawStream) -> RawStream { + return RawStream { observer in + let serialDisposable = SerialDisposable(otherDisposable: nil) + serialDisposable.otherDisposable = self.observe { event in + if event.isCompletion { + serialDisposable.otherDisposable = other.observe(observer.observer) + } else { + observer.observer(event) + } + } + return serialDisposable + } + } + + // WONTDO: contains + + /// Emit default element is stream completes without emitting any element. + @warn_unused_result + public func defaultIfEmpty(element: Event.Element) -> RawStream { + return RawStream { observer in + var didEmitNonTerminal = false + return self.observe { event in + if event.isTermination { + if !didEmitNonTerminal { + observer.next(element) + observer.completed() + } else { + observer.observer(event) + } + } else { + didEmitNonTerminal = true + observer.observer(event) + } + } + } + } + + /// Reduce stream events to a single event by applying given function on each emission. + @warn_unused_result + public func reduce(initial: U, _ combine: (U, Event) -> U) -> RawStream { + return RawStream { observer in + observer.observer(initial) + return self.scan(initial, combine).observe(observer.observer) + }.takeLast() + } +} + +// MARK: Streams that emit other streams + +public extension RawStreamType where Event.Element: _StreamType { + + public typealias InnerEvent = Event.Element.Event + + /// Flatten the stream by observing all inner streams and propagate events from each one as they come. + @warn_unused_result + public func merge(unboxEvent: InnerEvent -> U, propagateErrorEvent: (Event, Observer) -> Void) -> RawStream { + return RawStream { observer in + let lock = SpinLock() + + var numberOfOperations = 1 + let compositeDisposable = CompositeDisposable() + + let decrementNumberOfOperations = { () -> () in + lock.atomic { + numberOfOperations -= 1 + if numberOfOperations == 0 { + observer.completed() + } + } + } + + compositeDisposable += self.observe { outerEvent in + + if let stream = outerEvent.element { + lock.atomic { + numberOfOperations += 1 + } + compositeDisposable += stream.observe { innerEvent in + if !innerEvent.isCompletion { + observer.observer(unboxEvent(innerEvent)) + } else { + decrementNumberOfOperations() + } + } + } else if outerEvent.isCompletion { + decrementNumberOfOperations() + } else if outerEvent.isFailure { + propagateErrorEvent(outerEvent, observer) + } + } + return compositeDisposable + } + } + + /// Flatten the stream by observing and propagating emissions only from latest stream. + @warn_unused_result + public func switchToLatest(unboxEvent: InnerEvent -> U, propagateErrorEvent: (Event, Observer) -> Void) -> RawStream { + return RawStream { observer in + let serialDisposable = SerialDisposable(otherDisposable: nil) + let compositeDisposable = CompositeDisposable([serialDisposable]) + + var outerCompleted: Bool = false + var innerCompleted: Bool = false + + compositeDisposable += self.observe { outerEvent in + if outerEvent.isFailure { + propagateErrorEvent(outerEvent, observer) + } else if outerEvent.isCompletion { + outerCompleted = true + if innerCompleted { + observer.completed() + } + } else if let stream = outerEvent.element { + innerCompleted = false + serialDisposable.otherDisposable?.dispose() + serialDisposable.otherDisposable = stream.observe { innerEvent in + + if !innerEvent.isCompletion { + observer.observer(unboxEvent(innerEvent)) + } else { + innerCompleted = true + if outerCompleted { + observer.completed() + } + } + } + } + } + + return compositeDisposable + } + } + + /// Flatten the stream by sequentially observing inner streams in order in which they + /// arrive, starting next observation only after previous one completes. + @warn_unused_result + public func concat(unboxEvent: InnerEvent -> U, propagateErrorEvent: (Event, Observer) -> Void) -> RawStream { + return RawStream { observer in + let lock = SpinLock() + + let serialDisposable = SerialDisposable(otherDisposable: nil) + let compositeDisposable = CompositeDisposable([serialDisposable]) + + var outerCompleted: Bool = false + var innerCompleted: Bool = true + + var taskQueue: [Event.Element] = [] + + var startNextOperation: (() -> ())! = nil + startNextOperation = { + innerCompleted = false + + lock.lock() + let task = taskQueue.removeAtIndex(0) + lock.unlock() + + serialDisposable.otherDisposable?.dispose() + serialDisposable.otherDisposable = task.observe { event in + + if !event.isCompletion { + observer.observer(unboxEvent(event)) + } else { + innerCompleted = true + if taskQueue.count > 0 { + startNextOperation() + } else if outerCompleted { + observer.completed() + } + } + } + } + + let addToQueue = { (task: Event.Element) -> () in + lock.lock() + taskQueue.append(task) + lock.unlock() + if innerCompleted { + startNextOperation() + } + } + + compositeDisposable += self.observe { myEvent in + if let stream = myEvent.element { + addToQueue(stream) + } else if myEvent.isFailure { + propagateErrorEvent(myEvent, observer) + } else if myEvent.isCompletion { + outerCompleted = true + if innerCompleted { + observer.completed() + } + } + } + + return compositeDisposable + } + } +} + +// MARK: Connectable + +extension RawStreamType { + + /// Ensure that all observers see the same sequence of elements. Connectable. + @warn_unused_result + public func replay(limit: Int = Int.max) -> RawConnectableStream { + return RawConnectableStream(source: self, subject: AnySubject(base: ReplaySubject(bufferSize: limit))) + } + + /// Convert stream to a connectable stream. + @warn_unused_result + public func publish() -> RawConnectableStream { + return RawConnectableStream(source: self, subject: AnySubject(base: PublishSubject())) + } +} + +// MARK: - ConnectableStreamType + +/// Represents a stream that is started by calling `connect` on it. +public protocol ConnectableStreamType: _StreamType { + + /// Start the stream. + func connect() -> Disposable +} + +// MARK: - RawConnectableStream + +/// Makes a stream connectable through the given subject. +public final class RawConnectableStream: ConnectableStreamType { + + private let lock = SpinLock() + private let source: R + private let subject: AnySubject + private var connectionDisposable: Disposable? = nil + + public init(source: R, subject: AnySubject) { + self.source = source + self.subject = subject + } + + /// Start the stream. + public func connect() -> Disposable { + lock.lock(); defer { lock.unlock() } + if let connectionDisposable = connectionDisposable { + return connectionDisposable + } else { + return source.observe(subject.on) + } + } + + /// Register an observer that will receive events from the stream. + /// Note that the events will not be generated until `connect` is called. + @warn_unused_result + public func observe(observer: R.Event -> Void) -> Disposable { + return subject.observe(observer) + } +} + +public extension ConnectableStreamType { + + /// Convert connectable stream into the ordinary stream by calling `connect` + /// on first subscription and calling dispose when number of observers goes down to zero. + @warn_unused_result + public func refCount() -> RawStream { + var count = 0 + var connectionDisposable: Disposable? = nil + return RawStream { observer in + count = count + 1 + let disposable = self.observe(observer.observer) + if connectionDisposable == nil { + connectionDisposable = self.connect() + } + return BlockDisposable { + disposable.dispose() + count = count - 1 + if count == 0 { + connectionDisposable?.dispose() + } + } + } + } +} + +// MARK: Helpers + +public protocol OptionalType { + associatedtype Wrapped + var _unbox: Optional { get } + init() + init(_ some: Wrapped) +} + +extension Optional: OptionalType { + public var _unbox: Optional { + return self + } +} diff --git a/Sources/Stream.swift b/Sources/Stream.swift new file mode 100644 index 0000000..9c2a93c --- /dev/null +++ b/Sources/Stream.swift @@ -0,0 +1,947 @@ +// +// The MIT License (MIT) +// +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// MARK: - StreamEventType + +/// Represents a stream event. +public protocol StreamEventType: EventType { +} + +// MARK: - StreamEvent + +/// Represents a stream event. +public enum StreamEvent: StreamEventType { + + /// The type of elements generated by the stream. + public typealias Element = T + + /// Contains element. + case Next(T) + + /// Stream is completed. + case Completed + + /// Create new `.Next` event. + public static func next(element: T) -> StreamEvent { + return .Next(element) + } + + /// Create new `.Completed` event. + public static func completed() -> StreamEvent { + return .Completed + } + + /// Extract an element from a non-terminal (`.Next`) event. + public var element: Element? { + switch self { + case .Next(let element): + return element + default: + return nil + } + } + + /// Does the event mark failure of a stream? Always `False` for `StreamEvent`. + public var isFailure: Bool { + return false + } + + /// Does the event mark completion of a stream? `True` if event is `.Completion`. + public var isCompletion: Bool { + switch self { + case .Next: + return false + case .Completed: + return true + } + } +} + +// MARK: - StreamEventType Extensions + +public extension StreamEventType { + + public var unbox: StreamEvent { + if let element = element { + return StreamEvent.Next(element) + } else { + return StreamEvent.Completed + } + } + + public func map(transform: Element -> U) -> StreamEvent { + switch self.unbox { + case .Next(let value): + return .Next(transform(value)) + case .Completed: + return .Completed + } + } +} + +// MARK: - StreamType + +/// Represents a stream over generic Element type. +public protocol StreamType: _StreamType { + + /// The type of elements generated by the stream. + associatedtype Element + + /// Underlying raw stream. Stream is just a wrapper over `RawStream` that + /// operates on events of `StreamEvent` type. + var rawStream: RawStream> { get } + + /// Register an observer that will receive events from the stream. Registering + /// an observer starts the stream. Disposing the returned disposable can + /// be used to cancel (stop) the stream. + @warn_unused_result + func observe(observer: StreamEvent -> Void) -> Disposable +} + +public extension StreamType { + + /// Transform the stream by transforming underlying raw stream. + public func lift(transform: RawStream> -> RawStream>) -> Stream { + return Stream { observer in + return transform(self.rawStream).observe(observer.observer) + } + } + + /// Register an observer that will receive events from the stream. Registering + /// an observer starts the stream. Disposing the returned disposable can + /// be used to cancel (stop) the stream. + @warn_unused_result + public func observe(observer: StreamEvent -> Void) -> Disposable { + return rawStream.observe(observer) + } +} + +// MARK: - Stream + +/// Represents a stream over generic Element type. +/// Well-formed streams conform to the grammar: `Next* Completed`. +public struct Stream: StreamType { + + /// The type of elements generated by the stream. + public typealias Element = T + + public typealias Event = StreamEvent + + /// Underlying raw stream. Stream is just a wrapper over `RawStream` that + /// operates on events of `StreamEvent` type. + public let rawStream: RawStream> + + /// Create a new stream from a raw stream. + public init(rawStream: RawStream>) { + self.rawStream = rawStream + } + + /// Create a new stream using a producer. + public init(producer: Observer -> Disposable) { + rawStream = RawStream(producer: producer) + } +} + +// MARK: - Extensions + +// MARK: Creating a stream + +public extension Stream { + + /// Create a stream that emits given element and then completes. + @warn_unused_result + public static func just(element: Element) -> Stream { + return Stream { observer in + observer.next(element) + observer.completed() + return SimpleDisposable() + } + } + + /// Create a stream that emits given sequence of elements and then completes. + @warn_unused_result + public static func sequence(sequence: S) -> Stream { + return Stream { observer in + sequence.forEach(observer.next) + observer.completed() + return SimpleDisposable() + } + } + + /// Create a stream that completes without emitting any elements. + @warn_unused_result + public static func completed() -> Stream { + return Stream { observer in + observer.completed() + return SimpleDisposable() + } + } + + /// Create an stream that never completes. + @warn_unused_result + public static func never() -> Stream { + return Stream { observer in + return SimpleDisposable() + } + } + + /// Create a stream that emits an integer every `interval` time on a given queue. + @warn_unused_result + public static func interval(interval: TimeValue, queue: Queue) -> Stream { + return Stream(rawStream: RawStream.interval(interval, queue: queue)) + } + + /// Create a stream that emits given element after `time` time on a given queue. + @warn_unused_result + public static func timer(element: Element, time: TimeValue, queue: Queue) -> Stream { + return Stream(rawStream: RawStream.timer(element, time: time, queue: queue)) + } +} + +// MARK: Transforming stream + +public extension StreamType { + + /// Batch the elements into arrays of given size. + @warn_unused_result + public func buffer(size: Int) -> Stream<[Element]> { + return Stream { observer in + var buffer: [Element] = [] + return self.observe { event in + switch event { + case .Next(let element): + buffer.append(element) + if buffer.count == size { + observer.next(buffer) + buffer = [] + } + case .Completed: + observer.completed() + } + } + } + } + + /// Map each event into a stream and then flatten those streams using + /// the given flattening strategy. + @warn_unused_result + public func flatMap(strategy: FlatMapStrategy, transform: Element -> U) -> Stream { + let transform: Element -> Stream = { transform($0).toStream() } + switch strategy { + case .Latest: + return map(transform).switchToLatest() + case .Merge: + return map(transform).merge() + case .Concat: + return map(transform).concat() + } + } + + /// Map each event into an operation and then flatten those operation using + /// the given flattening strategy. + @warn_unused_result + public func flatMap(strategy: FlatMapStrategy, transform: Element -> U) -> Operation { + let transform: Element -> Operation = { transform($0).toOperation() } + switch strategy { + case .Latest: + return map(transform).switchToLatest() + case .Merge: + return map(transform).merge() + case .Concat: + return map(transform).concat() + } + } + + /// Transform each element by applying `transform` on it. + @warn_unused_result + public func map(transform: Element -> U) -> Stream { + return lift { stream in + stream.map { event in + return event.map(transform) + } + } + } + + /// Apply `combine` to each element starting with `initial` and emit each + /// intermediate result. This differs from `reduce` which emits only final result. + @warn_unused_result + public func scan(initial: U, _ combine: (U, Element) -> U) -> Stream { + return lift { stream in + return stream.scan(.Next(initial)) { memo, new in + switch new { + case .Next(let element): + return .Next(combine(memo.element!, element)) + case .Completed: + return .Completed + } + } + } + } + + /// Convert the stream to an operation. + @warn_unused_result + public func toOperation() -> Operation { + return Operation { observer in + return self.observe { event in + switch event { + case .Next(let element): + observer.next(element) + case .Completed: + observer.completed() + } + } + } + } + + /// Convert the stream to a concrete stream. + @warn_unused_result + public func toStream() -> Stream { + return Stream(rawStream: self.rawStream) + } + + /// Batch each `size` elements into another stream. + @warn_unused_result + public func window(size: Int) -> Stream> { + return buffer(size).map { Stream.sequence($0) } + } +} + +// MARK: Filtration + +extension StreamType { + + /// Emit an element only if `interval` time passes without emitting another element. + @warn_unused_result + public func debounce(interval: TimeValue, on queue: Queue) -> Stream { + return lift { $0.debounce(interval, on: queue) } + } + + /// Emit first element and then all elements that are not equal to their predecessor(s). + @warn_unused_result + public func distinct(areDistinct: (Element, Element) -> Bool) -> Stream { + return lift { $0.distinct(areDistinct) } + } + + /// Emit only element at given index if such element is produced. + @warn_unused_result + public func elementAt(index: Int) -> Stream { + return lift { $0.elementAt(index) } + } + + /// Emit only elements that pass `include` test. + @warn_unused_result + public func filter(include: Element -> Bool) -> Stream { + return lift { $0.filter { $0.element.flatMap(include) ?? true } } + } + + /// Emit only the first element generated by the stream and then complete. + @warn_unused_result + public func first() -> Stream { + return lift { $0.first() } + } + + /// Ignore all elements (just propagate terminal events). + @warn_unused_result + public func ignoreElements() -> Stream { + return lift { $0.ignoreElements() } + } + + /// Emit only last element generated by the stream and then complete. + @warn_unused_result + public func last() -> Stream { + return lift { $0.last() } + } + + /// Periodically sample the stream and emit latest element from each interval. + @warn_unused_result + public func sample(interval: TimeValue, on queue: Queue) -> Stream { + return lift { $0.sample(interval, on: queue) } + } + + /// Suppress first `count` elements generated by the stream. + @warn_unused_result + public func skip(count: Int) -> Stream { + return lift { $0.skip(count) } + } + + /// Suppress last `count` elements generated by the stream. + @warn_unused_result + public func skipLast(count: Int) -> Stream { + return lift { $0.skipLast(count) } + } + + /// Emit only first `count` elements of the stream and then complete. + @warn_unused_result + public func take(count: Int) -> Stream { + return lift { $0.take(count) } + } + + /// Emit only last `count` elements of the stream and then complete. + @warn_unused_result + public func takeLast(count: Int) -> Stream { + return lift { $0.takeLast(count) } + } + + /// Throttle the stream to emit at most one element per given `seconds` interval. + @warn_unused_result + public func throttle(seconds: TimeValue) -> Stream { + return lift { $0.throttle(seconds) } + } +} + +extension StreamType where Element: Equatable { + + /// Emit first element and then all elements that are not equal to their predecessor(s). + @warn_unused_result + public func distinct() -> Stream { + return lift { $0.distinct() } + } +} + +public extension StreamType where Element: OptionalType, Element.Wrapped: Equatable { + + /// Emit first element and then all elements that are not equal to their predecessor(s). + @warn_unused_result + public func distinct() -> Stream { + return lift { $0.distinct() } + } +} + +public extension StreamType where Element: OptionalType { + + /// Suppress all `nil`-elements. + @warn_unused_result + public func ignoreNil() -> Stream { + return Stream { observer in + return self.observe { event in + switch event { + case .Next(let element): + if let element = element._unbox { + observer.next(element) + } + case .Completed: + observer.completed() + } + } + } + } +} + +// MARK: Combination + +extension StreamType { + + /// Emit a pair of latest elements from each stream. Starts when both streams + /// emit at least one element, and emits `.Next` when either stream generates an element. + @warn_unused_result + public func combineLatestWith(other: S) -> Stream<(Element, S.Element)> { + return lift { + return $0.combineLatestWith(other.toStream()) { myLatestElement, my, theirLatestElement, their in + switch (my, their) { + case (.Completed, .Completed): + return StreamEvent.Completed + case (.Next(let myElement), .Next(let theirElement)): + return StreamEvent.Next(myElement, theirElement) + case (.Next(let myElement), .Completed): + if let theirLatestElement = theirLatestElement { + return StreamEvent.Next(myElement, theirLatestElement) + } else { + return nil + } + case (.Completed, .Next(let theirElement)): + if let myLatestElement = myLatestElement { + return StreamEvent.Next(myLatestElement, theirElement) + } else { + return nil + } + } + } + } + } + + /// Merge emissions from both source and `other` into one stream. + @warn_unused_result + public func mergeWith(other: S) -> Stream { + return lift { $0.mergeWith(other.rawStream) } + } + + /// Prepend given element to the operation emission. + @warn_unused_result + public func startWith(element: Element) -> Stream { + return lift { $0.startWith(.Next(element)) } + } + + /// Emit elements from source and `other` in pairs. This differs from `combineLatestWith` in + /// that pairs are produced from elements at same positions. + @warn_unused_result + public func zipWith(other: S) -> Stream<(Element, S.Element)> { + return lift { + return $0.zipWith(other.toStream()) { my, their in + switch (my, their) { + case (.Next(let myElement), .Next(let theirElement)): + return StreamEvent.Next(myElement, theirElement) + case (_, .Completed): + return StreamEvent.Completed + case (.Completed, _): + return StreamEvent.Completed + default: + fatalError("This will never execute: Swift compiler cannot infer switch completeness.") + } + } + } + } +} + +// MARK: Utilities + +extension StreamType { + + /// Set the execution context in which to execute the stream (i.e. in which to run + /// the stream's producer). + @warn_unused_result + public func executeIn(context: ExecutionContext) -> Stream { + return lift { $0.executeIn(context) } + } + + /// Delay stream events for `interval` time. + @warn_unused_result + public func delay(interval: TimeValue, on queue: Queue) -> Stream { + return lift { $0.delay(interval, on: queue) } + } + + /// Do side-effect upon various events. + @warn_unused_result + public func doOn(next next: (Element -> ())? = nil, + start: (() -> Void)? = nil, + completed: (() -> Void)? = nil, + disposed: (() -> ())? = nil, + termination: (() -> ())? = nil) -> Stream { + return Stream { observer in + start?() + let disposable = self.observe { event in + switch event { + case .Next(let value): + next?(value) + case .Completed: + completed?() + termination?() + } + observer.observer(event) + } + return BlockDisposable { + disposable.dispose() + disposed?() + termination?() + } + } + } + + /// Use `doOn` to log various events. + @warn_unused_result + public func debug(id: String = "Untitled Stream") -> Stream { + return doOn(next: { element in + print("\(id): Next(\(element))") + }, start: { + print("\(id): Start") + }, completed: { + print("\(id): Completed") + }, disposed: { + print("\(id): Disposed") + }) + } + + /// Set the execution context in which to dispatch events (i.e. in which to run observers). + @warn_unused_result + public func observeIn(context: ExecutionContext) -> Stream { + return lift { $0.observeIn(context) } + } + + /// Supress non-terminal events while last event generated on other stream is `false`. + @warn_unused_result + public func pausable(by other: S) -> Stream { + return lift { $0.pausable(other) } + } +} + +// MARK: Conditional, Boolean and Aggregational + +extension StreamType { + + /// Propagate event only from an operation that starts emitting first. + @warn_unused_result + public func ambWith(other: S) -> Stream { + return lift { $0.ambWith(other.rawStream) } + } + + /// Collect all elements into an array and emit just that array. + @warn_unused_result + public func collect() -> Stream<[Element]> { + return reduce([], { memo, new in memo + [new] }) + } + + /// First emit events from source and then from `other` stream. + @warn_unused_result + public func concatWith(other: S) -> Stream { + return lift { stream in + stream.concatWith(other.rawStream) + } + } + + /// Emit default element is the stream completes without emitting any element. + @warn_unused_result + public func defaultIfEmpty(element: Element) -> Stream { + return lift { $0.defaultIfEmpty(element) } + } + + /// Reduce elements to a single element by applying given function on each emission. + @warn_unused_result + public func reduce(initial: U, _ combine: (U, Element) -> U) -> Stream { + return Stream { observer in + observer.next(initial) + return self.scan(initial, combine).observe(observer.observer) + }.last() + } + + /// Par each element with its predecessor. First element is paired with `nil`. + @warn_unused_result + public func zipPrevious() -> Stream<(Element?, Element)> { + return Stream { observer in + var previous: Element? = nil + return self.observe { event in + switch event { + case .Next(let element): + observer.next((previous, element)) + previous = element + case .Completed: + observer.completed() + } + } + } + } +} + +// MARK: Streams that emit other streams + +public extension StreamType where Element: StreamType, Element.Event: StreamEventType { + + public typealias InnerElement = Element.Event.Element + + /// Flatten the stream by observing all inner streams and propagate elements from each one as they come. + @warn_unused_result + public func merge() -> Stream { + return lift { stream in + return stream.merge({ $0.unbox }, propagateErrorEvent: { _, _ in }) + } + } + + /// Flatten the stream by observing and propagating emissions only from the latest inner stream. + @warn_unused_result + public func switchToLatest() -> Stream { + return lift { stream in + return stream.switchToLatest({ $0.unbox }, propagateErrorEvent: { _, _ in }) + } + } + + /// Flatten the stream by sequentially observing inner streams in order in + /// which they arrive, starting next observation only after the previous one completes. + @warn_unused_result + public func concat() -> Stream { + return lift { stream in + return stream.concat({ $0.unbox }, propagateErrorEvent: { _, _ in }) + } + } +} + +// MARK: Streams that emit operations + +public extension StreamType where Element: OperationType, Element.Event: OperationEventType { + public typealias InnerOperationElement = Element.Event.Element + public typealias InnerOperationError = Element.Event.Error + + /// Flatten the stream by observing all inner operation and propagate elements from each one as they come. + @warn_unused_result + public func merge() -> Operation { + return self.toOperation().merge() + } + + /// Flatten the stream by observing and propagating emissions only from the latest inner operation, cancelling previous one when new one starts. + @warn_unused_result + public func switchToLatest() -> Operation { + return self.toOperation().switchToLatest() + } + + /// Flatten the stream by sequentially observing inner operations in order in + /// which they arrive, starting next observation only after the previous one completes. + @warn_unused_result + public func concat() -> Operation { + return self.toOperation().switchToLatest() + } +} + +// MARK: Connectable + +extension StreamType { + + /// Ensure that all observers see the same sequence of elements. Connectable. + @warn_unused_result + public func replay(limit: Int = Int.max) -> ConnectableStream { + return ConnectableStream(rawConnectableStream: rawStream.replay(limit)) + } + + /// Convert the stream to a connectable stream. + @warn_unused_result + public func publish() -> ConnectableStream { + return ConnectableStream(rawConnectableStream: rawStream.publish()) + } + + /// Ensure that all observers see the same sequence of elements. + /// Shorthand for `replay(limit).refCount()`. + @warn_unused_result + public func shareReplay(limit: Int = Int.max) -> Stream { + return replay(limit).refCount() + } +} + +// MARK: Functions + +/// Combine multiple streams into one. See `mergeWith` for more info. +@warn_unused_result +public func combineLatest + + (a: A, _ b: B) -> Stream<(A.Element, B.Element)> { + return a.combineLatestWith(b) +} + +/// Combine multiple operations into one. See `mergeWith` for more info. +@warn_unused_result +public func combineLatest + + (a: A, _ b: B, _ c: C) -> Stream<(A.Element, B.Element, C.Element)> { + return combineLatest(a, b).combineLatestWith(c).map { ($0.0, $0.1, $1) } +} + +/// Combine multiple operations into one. See `mergeWith` for more info. +@warn_unused_result +public func combineLatest + + (a: A, _ b: B, _ c: C, _ d: D) -> Stream<(A.Element, B.Element, C.Element, D.Element)> { + return combineLatest(a, b, c).combineLatestWith(d).map { ($0.0, $0.1, $0.2, $1) } +} + +/// Combine multiple operations into one. See `mergeWith` for more info. +@warn_unused_result +public func combineLatest + + (a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> Stream<(A.Element, B.Element, C.Element, D.Element, E.Element)> { + return combineLatest(a, b, c, d).combineLatestWith(e).map { ($0.0, $0.1, $0.2, $0.3, $1) } +} + +/// Combine multiple operations into one. See `mergeWith` for more info. +@warn_unused_result +public func combineLatest + + (a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F) -> Stream<(A.Element, B.Element, C.Element, D.Element, E.Element, F.Element)> { + return combineLatest(a, b, c, d, e).combineLatestWith(f).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $1) } +} + +/// Zip multiple operations into one. See `zipWith` for more info. +@warn_unused_result +public func zip + + (a: A, _ b: B) -> Stream<(A.Element, B.Element)> { + return a.zipWith(b) +} + +/// Zip multiple operations into one. See `zipWith` for more info. +@warn_unused_result +public func zip + + (a: A, _ b: B, _ c: C) -> Stream<(A.Element, B.Element, C.Element)> { + return zip(a, b).zipWith(c).map { ($0.0, $0.1, $1) } +} + +/// Zip multiple operations into one. See `zipWith` for more info. +@warn_unused_result +public func zip + + (a: A, _ b: B, _ c: C, _ d: D) -> Stream<(A.Element, B.Element, C.Element, D.Element)> { + return zip(a, b, c).zipWith(d).map { ($0.0, $0.1, $0.2, $1) } +} + +/// Zip multiple operations into one. See `zipWith` for more info. +@warn_unused_result +public func zip + + (a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> Stream<(A.Element, B.Element, C.Element, D.Element, E.Element)> { + return zip(a, b, c, d).zipWith(e).map { ($0.0, $0.1, $0.2, $0.3, $1) } +} + +/// Zip multiple operations into one. See `zipWith` for more info. +@warn_unused_result +public func zip + + (a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F) -> Stream<(A.Element, B.Element, C.Element, D.Element, E.Element, F.Element)> { + return zip(a, b, c, d, e).zipWith(f).map { ($0.0, $0.1, $0.2, $0.3, $0.4, $1) } +} + +// MARK: - ConnectableStream + +/// Represents a stream that is started by calling `connect` on it. +public class ConnectableStream: StreamType { + public typealias Event = StreamEvent + + private let rawConnectableStream: RawConnectableStream> + + public var rawStream: RawStream> { + return rawConnectableStream.toRawStream() + } + + private init(rawConnectableStream: RawConnectableStream>) { + self.rawConnectableStream = rawConnectableStream + } + + /// Register an observer that will receive events from the stream. + /// Note that the events will not be generated until `connect` is called. + @warn_unused_result + public func observe(observer: Event -> Void) -> Disposable { + return rawConnectableStream.observe(observer) + } + + /// Start the stream. + public func connect() -> Disposable { + return rawConnectableStream.connect() + } +} + +// MARK: - ConnectableStream Extensions + +public extension ConnectableStream { + + /// Convert connectable stream into the ordinary one by calling `connect` + /// on first subscription and calling dispose when number of observers goes down to zero. + @warn_unused_result + public func refCount() -> Stream { + return Stream(rawStream: self.rawConnectableStream.refCount()) + } +} + +// MARK: - PushStream + +/// Represents a stream that can push events to registered observers at will. +public class PushStream: StreamType, SubjectType { + private let subject = PublishSubject>() + private let disposeBag = DisposeBag() + + public var rawStream: RawStream> { + return subject.toRawStream() + } + + public init() { + } + + /// Send event to all registered observers. + public func on(event: StreamEvent) { + subject.on(event) + } +} + +// MARK: - BindableType + +/// Bindable is like an observer, but knows to manage the subscription by itself. +public protocol BindableType { + associatedtype Element + + /// Returns an observer that can be used to dispatch events to the receiver. + /// Can accept a disposable that will be disposed on receiver's deinit. + func observer(disconnectDisposable: Disposable) -> (StreamEvent -> ()) +} + +extension StreamType { + + /// Establish a one-way binding between the source and the bindable's observer + /// and return a disposable that can cancel binding. + public func bindTo(bindable: B) -> Disposable { + let disposable = SerialDisposable(otherDisposable: nil) + let observer = bindable.observer(disposable) + disposable.otherDisposable = observe(observer) + return disposable + } + + /// Establish a one-way binding between the source and the bindable's observer + /// and return a disposable that can cancel binding. + public func bindTo(bindable: B) -> Disposable { + let disposable = SerialDisposable(otherDisposable: nil) + let observer = bindable.observer(disposable) + disposable.otherDisposable = observe { event in + switch event { + case .Next(let element): + observer(.Next(B.Element(element))) + case .Completed: + observer(.Completed) + disposable.dispose() + } + } + return disposable + } +} + +extension PushStream: BindableType { + + /// Returns an observer that can be used to dispatch events to the receiver. + /// Can accept a disposable that will be disposed on receiver's deinit. + public func observer(disconnectDisposable: Disposable) -> StreamEvent -> () { + disposeBag.addDisposable(disconnectDisposable) + return { [weak self] in self?.on($0) } + } +} diff --git a/Sources/Subjects.swift b/Sources/Subjects.swift new file mode 100644 index 0000000..9eba79e --- /dev/null +++ b/Sources/Subjects.swift @@ -0,0 +1,125 @@ +// +// The MIT License (MIT) +// +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +internal class ObserverRegister { + private typealias Token = Int64 + private var nextToken: Token = 0 + private(set) var observers: ContiguousArray Void> = [] + + private var observerStorage: [Token: E -> Void] = [:] { + didSet { + observers = ContiguousArray(observerStorage.values) + } + } + + private let tokenLock = SpinLock() + + func addObserver(observer: E -> Void) -> Disposable { + tokenLock.lock() + let token = nextToken + nextToken = nextToken + 1 + tokenLock.unlock() + + observerStorage[token] = observer + + return BlockDisposable { [weak self] in + self?.observerStorage.removeValueForKey(token) + } + } +} + +public protocol SubjectType: ObserverType, RawStreamType { +} + +public final class PublishSubject: ObserverRegister, SubjectType { + + private let lock = RecursiveLock(name: "ReactiveKit.PublishSubject") + private var completed = false + + public override init() { + } + + public func on(event: E) { + guard !completed else { return } + lock.lock(); defer { lock.unlock() } + completed = event.isTermination + observers.forEach { $0(event) } + } + + public func observe(observer: E -> Void) -> Disposable { + return addObserver(observer) + } +} + +public final class ReplaySubject: ObserverRegister, SubjectType { + + public let bufferSize: Int + private var buffer: ArraySlice = [] + private let lock = RecursiveLock(name: "ReactiveKit.ReplaySubject") + + public init(bufferSize: Int = Int.max) { + self.bufferSize = bufferSize + } + + public func on(event: E) { + guard !completed else { return } + lock.lock(); defer { lock.unlock() } + buffer.append(event) + buffer = buffer.suffix(bufferSize) + observers.forEach { $0(event) } + } + + public func observe(observer: E -> Void) -> Disposable { + lock.lock(); defer { lock.unlock() } + buffer.forEach(observer) + return addObserver(observer) + } + + private var completed: Bool { + if let lastEvent = buffer.last { + return lastEvent.isTermination + } else { + return false + } + } +} + +public final class AnySubject: SubjectType { + private let baseObserve: (E -> Void) -> Disposable + private let baseOn: E -> Void + + public init(base: S) { + baseObserve = base.observe + baseOn = base.on + } + + public func on(event: E) { + return baseOn(event) + } + + public func observe(observer: E -> Void) -> Disposable { + return baseObserve(observer) + } +} + diff --git a/ReactiveKit/Internals/Lock.swift b/Sources/System.swift similarity index 76% rename from ReactiveKit/Internals/Lock.swift rename to Sources/System.swift index ebc91d2..2c949b7 100644 --- a/ReactiveKit/Internals/Lock.swift +++ b/Sources/System.swift @@ -1,7 +1,7 @@ // // The MIT License (MIT) // -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -24,9 +24,19 @@ import Foundation -internal class RecursiveLock: NSRecursiveLock { - init(name: String) { - super.init() - self.name = name +public typealias TimeValue = Double + +internal struct SystemTime { + + internal static var now: TimeValue { + return CFAbsoluteTimeGetCurrent() + } + + internal static var distantPast: TimeValue { + return -Double.infinity + } + + internal static var distantFuture: TimeValue { + return Double.infinity } } diff --git a/ReactiveKit/Other/Queue.swift b/Sources/Threading.swift similarity index 54% rename from ReactiveKit/Other/Queue.swift rename to Sources/Threading.swift index 1ed587a..b40dbca 100644 --- a/ReactiveKit/Other/Queue.swift +++ b/Sources/Threading.swift @@ -1,7 +1,7 @@ // // The MIT License (MIT) // -// Copyright (c) 2015 Srdan Rasic (@srdanrasic) +// Copyright (c) 2016 Srdan Rasic (@srdanrasic) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -25,17 +25,34 @@ import Dispatch import Foundation +/// Represents a context that can execute given block. +public typealias ExecutionContext = (() -> Void) -> Void + +/// Execute block on current thread or queue. +public let ImmediateExecutionContext: ExecutionContext = { block in + block() +} + +/// If current thread is main thread, just execute block. Otherwise, do +/// async dispatch of the block to the main queue (thread). +public let ImmediateOnMainExecutionContext: ExecutionContext = { block in + if NSThread.isMainThread() { + block() + } else { + Queue.main.async(block) + } +} + +/// A simple wrapper over GCD queue. public struct Queue { - - public typealias TimeInterval = NSTimeInterval - + public static let main = Queue(queue: dispatch_get_main_queue()); public static let global = Queue(queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) public static let background = Queue(queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) public private(set) var queue: dispatch_queue_t - public init(queue: dispatch_queue_t = dispatch_queue_create("com.ReactiveKit.ReactiveKit.Queue", DISPATCH_QUEUE_SERIAL)) { + public init(queue: dispatch_queue_t = dispatch_queue_create("ReactiveKit.Queue", DISPATCH_QUEUE_SERIAL)) { self.queue = queue } @@ -43,11 +60,22 @@ public struct Queue { self.queue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL) } - public func after(interval: NSTimeInterval, block: () -> ()) { + public func after(interval: TimeValue, block: () -> ()) { let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * NSTimeInterval(NSEC_PER_SEC))) dispatch_after(dispatchTime, queue, block) } - + + public func disposableAfter(interval: TimeValue, block: () -> ()) -> Disposable { + let disposable = SimpleDisposable() + let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * NSTimeInterval(NSEC_PER_SEC))) + dispatch_after(dispatchTime, queue) { + if !disposable.isDisposed { + block() + } + } + return disposable + } + public func async(block: () -> ()) { dispatch_async(queue, block) } @@ -64,3 +92,40 @@ public struct Queue { return res } } + +public extension Queue { + + /// Returns context that executes blocks on this queue. + public var context: ExecutionContext { + return self.async + } +} + + +/// Spin Lock +internal final class SpinLock { + private var spinLock = OS_SPINLOCK_INIT + + internal func lock() { + OSSpinLockLock(&spinLock) + } + + internal func unlock() { + OSSpinLockUnlock(&spinLock) + } + + internal func atomic(body: () -> Void) { + lock() + body() + unlock() + } +} + +/// Recursive Lock +internal final class RecursiveLock: NSRecursiveLock { + + internal init(name: String) { + super.init() + self.name = name + } +} diff --git a/ReactiveKitTests/ArrayDiffTests.swift b/Tests/ArrayDiffTests.swift similarity index 100% rename from ReactiveKitTests/ArrayDiffTests.swift rename to Tests/ArrayDiffTests.swift diff --git a/ReactiveKitTests/PerformanceTests.swift b/Tests/PerformanceTests.swift similarity index 63% rename from ReactiveKitTests/PerformanceTests.swift rename to Tests/PerformanceTests.swift index 8be50fa..aa554fa 100644 --- a/ReactiveKitTests/PerformanceTests.swift +++ b/Tests/PerformanceTests.swift @@ -14,11 +14,11 @@ class PerformanceTests: XCTestCase { func test1() { measureBlock { var counter: Int = 0 - let observable = Observable(0) + let observable = Property(0) - observable.observe(on: nil) { counter += $0 } + let _ = observable.observeNext { counter += $0 } - for i in 1..<10000 { + for i in 1..<1000 { observable.value = i } } @@ -27,13 +27,13 @@ class PerformanceTests: XCTestCase { func test2() { measureBlock { var counter: Int = 0 - let observable = Observable(0) + let observable = Property(0) for _ in 1..<30 { - observable.observe(on: nil) { counter += $0 } + let _ = observable.observeNext { counter += $0 } } - for i in 1..<10000 { + for i in 1..<1000 { observable.value = i } } @@ -41,14 +41,14 @@ class PerformanceTests: XCTestCase { func test_measure_3() { measureBlock { - let observable = Observable(0) + let observable = Property(0) var counter : Int = 0 for _ in 1..<30 { - observable.filter{ $0 % 2 == 0 }.observe(on: nil) { counter += $0 } + let _ = observable.filter{ $0 % 2 == 0 }.observeNext { counter += $0 } } - for i in 1..<10000 { + for i in 1..<1000 { observable.value = i } }