Giter Club home page Giter Club logo

swifttask's Introduction

ReactKit Circle CI

Swift Reactive Programming.

How to install

See Wiki page.

Example

For UI Demo, please see ReactKit/ReactKitCatalog.

Key-Value Observing

// create stream via KVO
self.obj1Stream = KVO.stream(obj1, "value")

// bind stream via KVC (`<~` as binding operator)
(obj2, "value") <~ self.obj1Stream

XCTAssertEqual(obj1.value, "initial")
XCTAssertEqual(obj2.value, "initial")

obj1.value = "REACT"

XCTAssertEqual(obj1.value, "REACT")
XCTAssertEqual(obj2.value, "REACT")

To remove stream-bindings, just release stream itself (or call stream.cancel()).

self.obj1Stream = nil   // release stream & its bindings

obj1.value = "Done"

XCTAssertEqual(obj1.value, "Done")
XCTAssertEqual(obj2.value, "REACT")

If you want to observe changes in Swift.Array or NSMutableArray, use DynamicArray feature in Pull Request #23.

NSNotification

self.stream = Notification.stream("MyNotification", obj1)
    |> map { notification -> NSString? in
        return "hello" // convert NSNotification? to NSString?
    }

(obj2, "value") <~ self.stream

Normally, NSNotification itself is useless value for binding with other objects, so use Stream Operations e.g. map(f: T -> U) to convert it.

To understand more about |> pipelining operator, see Stream Pipelining.

Target-Action

// UIButton
self.buttonStream = self.button.buttonStream("OK")

// UITextField
self.textFieldStream = self.textField.textChangedStream()

^{ println($0) } <~ self.buttonStream     // prints "OK" on tap

// NOTE: ^{ ... } = closure-first operator, same as `stream ~> { ... }`
^{ println($0) } <~ self.textFieldStream  // prints textField.text on change

Complex example

The example below is taken from

where it describes 4 UITextFields which enables/disables UIButton at certain condition (demo available in ReactKit/ReactKitCatalog):

let usernameTextStream = self.usernameTextField.textChangedStream()
let emailTextStream = self.emailTextField.textChangedStream()
let passwordTextStream = self.passwordTextField.textChangedStream()
let password2TextStream = self.password2TextField.textChangedStream()

let allTextStreams = [usernameTextStream, emailTextStream, passwordTextStream, password2TextStream]

let combinedTextStream = allTextStreams |> merge2All

// create button-enabling stream via any textField change
self.buttonEnablingStream = combinedTextStream
    |> map { (values, changedValue) -> NSNumber? in

        let username: NSString? = values[0] ?? nil
        let email: NSString? = values[1] ?? nil
        let password: NSString? = values[2] ?? nil
        let password2: NSString? = values[3] ?? nil

        // validation
        let buttonEnabled = username?.length > 0 && email?.length > 0 && password?.length >= MIN_PASSWORD_LENGTH && password == password2

        // NOTE: use NSNumber because KVO does not understand Bool
        return NSNumber(bool: buttonEnabled)
    }

// REACT: enable/disable okButton
(self.okButton, "enabled") <~ self.buttonEnablingStream!

For more examples, please see XCTestCases.

How it works

ReactKit is based on powerful SwiftTask (JavaScript Promise-like) library, allowing to start & deliver multiple events (KVO, NSNotification, Target-Action, etc) continuously over time using its resume & progress feature (react() or <~ operator in ReactKit).

Unlike Reactive Extensions (Rx) libraries which has a basic concept of "hot" and "cold" observables, ReactKit gracefully integrated them into one hot + paused (lazy) stream Stream<T> class. Lazy streams will be auto-resumed via react() & <~ operator.

Here are some differences in architecture:

Reactive Extensions (Rx) ReactKit
Basic Classes Hot Observable (broadcasting)
Cold Observable (laziness)
Stream<T>
Generating Cold Observable (cloneability) Void -> Stream<T>
(= Stream<T>.Producer)
Subscribing observable.subscribe(onNext, onError, onComplete) stream.react {...}.then {...}
(method-chainable)
Pausing pausableObservable.pause() stream.pause()
Disposing disposable.dispose() stream.cancel()

Stream Pipelining

Streams can be composed by using |> stream-pipelining operator and Stream Operations.

For example, a very common incremental search technique using searchTextStream will look like this:

let searchResultsStream: Stream<[Result]> = searchTextStream
    |> debounce(0.3)
    |> distinctUntilChanged
    |> map { text -> Stream<[Result]> in
        return API.getSearchResultsStream(text)
    }
    |> switchLatestInner

There are some scenarios (e.g. repeat()) when you want to use a cloneable Stream<T>.Producer (Void -> Stream<T>) rather than plain Stream<T>. In this case, you can use |>> streamProducer-pipelining operator instead.

// first, wrap stream with closure
let timerProducer: Void -> Stream<Int> = {
    return createTimerStream(interval: 1)
        |> map { ... }
        |> filter { ... }
}

// then, use `|>>`  (streamProducer-pipelining operator)
let repeatTimerProducer = timerProducer |>> repeat(3)

But in the above case, wrapping with closure will always become cumbersome, so you can also use |>> operator for Stream & Stream Operations as well (thanks to @autoclosure).

let repeatTimerProducer = createTimerStream(interval: 1)
    |>> map { ... }
    |>> filter { ... }
    |>> repeat(3)

Functions

Stream Operations

  • For Single Stream

    • Transforming
      • asStream(ValueType)
      • map(f: T -> U)
      • flatMap(f: T -> Stream<U>)
      • map2(f: (old: T?, new: T) -> U)
      • mapAccumulate(initialValue, accumulator) (alias: scan)
      • buffer(count)
      • bufferBy(stream)
      • groupBy(classifier: T -> Key)
    • Filtering
      • filter(f: T -> Bool)
      • filter2(f: (old: T?, new: T) -> Bool)
      • take(count)
      • takeUntil(stream)
      • skip(count)
      • skipUntil(stream)
      • sample(stream)
      • distinct()
      • distinctUntilChanged()
    • Combining
      • merge(stream)
      • concat(stream)
      • startWith(initialValue)
      • combineLatest(stream)
      • zip(stream)
      • recover(stream)
    • Timing
      • delay(timeInterval)
      • interval(timeInterval)
      • throttle(timeInterval)
      • debounce(timeInterval)
    • Collecting
      • reduce(initialValue, accumulator)
    • Other Utilities
      • peek(f: T -> Void) (for injecting side effects e.g. debug-logging)
      • customize(...)
  • For Array Streams

    • mergeAll(streams)
    • merge2All(streams) (generalized method for mergeAll & combineLatestAll)
    • combineLatestAll(streams)
    • zipAll(streams)
  • For Nested Stream (Stream<Stream<T>>)

    • mergeInner(nestedStream)
    • concatInner(nestedStream)
    • switchLatestInner(nestedStream)
  • For Stream Producer (Void -> Stream<T>)

    • prestart(bufferCapacity) (alias: replay)
    • times(count)
    • retry(count)

Helpers

  • Creating

    • Stream.once(value) (alias: just)
    • Stream.never()
    • Stream.fulfilled() (alias: empty)
    • Stream.rejected() (alias: error)
    • Stream.sequence(values) (a.k.a Rx.fromArray)
    • Stream.infiniteSequence(initialValue, iterator) (a.k.a Rx.iterate)
  • Other Utilities

    • ownedBy(owner: NSObject) (easy strong referencing to keep streams alive)

Dependencies

References

Licence

MIT

swifttask's People

Contributors

akio0911 avatar danielasher avatar inamiy avatar jordanekay avatar kelvinjin avatar kevm avatar koutalou avatar mono0926 avatar munky69rock avatar r-plus avatar readmecritic avatar skywinder avatar tokorom avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swifttask's Issues

Some strange behaviour

Can you help please? Found some case when failure block is called after task is fulfilled. I don't understand how could it be possible but from stacktrace of XCode cancel method is called from task deinit. This cause the task to be handled as canceled in my code, but I has fulfilled it. I don't understand what I made wrong.

screen shot 2016-10-03 at 10 16 08 pm

Run closure of promise/future on main ui thread

Is it possible to run the Promise-Closure on the main thread? Sometimes I want to perform a UI asynchronous operation like requesting permissions to user etc. If those are runned in separate threads, they are not shown to the user... I wasn't able to found any solution in documentation or sourcecode

Implement delay function.

Now retry will retry task immediately, but in some case, we want to have interval.
I propose you to make following two.

  • delay(interval: NSTimeInterval) -> Task
  • retry(maxRetryCount: Int, delayInterval: NSTimeInterval = default) -> Task

Failure in Task.all() make recursive calls

When calling Task.all() with many tasks and one of the task was failed, canceling all tasks process will start. But from the current algorithm, it makes recursive call. If the number of tasks is 10000 and the first task is failed, the depth of call is about 10000 and it causes stack overflow. So, the proposal is that prepare flag for limiting the depth of call to be 1.

            }.failure { (errorInfo: ErrorInfo) -> Void in

                    lock.lock()
                    _reject(errorInfo)

                    for task in tasks {
                        task.cancel()  // <- this call invokes ".failure" of another task in the same context
                    }
                    lock.unlock()
            }

Question: How to concatenate streams that terminate?

Hi!

Again thanks for you work on the lib :) I'm still learning Reactive Programming, and I wonder if you could assist me on how to do something I encounter all the time.

Let's say you have one stream, which does some asynchronous work, gives a successful value and fulfills.

And then somewhere else you want to continue doing something after this, with another stream.

If you connect both streams with flatMap, the second stream will terminate as well, which is not what I intended.

What do you recommend to use in this case?

Thanks!

Failure return `value`

Maybe I'm completely confused about the way this works, but shouldn't a failureClosure return Void? Currently, it's trying to return the Value type.

The README shows failure returning Void, which makes sense. Why is the failureClosure returning the value? If the task has failed, shouldn't there be no value?

I'm unable to use this, because, for the task I'm trying to create, if the task has failed, I can't return a value, since the value doesn't exist.

implement zip function

would be great to have a zip function that takes two tasks with different progress, value and error types and returns a task that returns a tuple of values.

I've implemented the function with the signature

public class func zip<P1, V1, R1, P2, V2, R2>(task1: Task<P1, V1, R1>, _ task2: Task<P2, V2, R2>) -> Task<(P1, P2), (V1, V2), (R1?, R2?)>

but unfortunately I'm unable to call it without the error:

Cannot invoke 'zip' with an argument list of type '(Task<Void, String, String>, Task<Void, String, String>)'

Publicly visible Swift 3 Support

I see that there is a branch for Swift 3 support open. What is the roadmap for having the Swift 3 branch merged and available publicly via cocoapods?

(Specifically, looking to consume the library in a podspec, where the branch cannot be specified)

Create tasks manager to retain tasks

Is it possible to retain task while it is executed in one scope? I have found that tasks deinit after creating )) even they are returned from success block of other task. Probably we need something that will retain task while it is executing like in next example:

public class Task<Progress, Value, Error> {

    //...

    class func createTask<Progress, Value, Error>(initClosure: InitClosure) -> Task<Progesss, Value, Error> {
        // Paused
        let task = Task<Progesss, Value, Error>(paused: true, initClosure: initClosure)
        task.onFinish = {
            TasksRegistry.unregister(task) // Release
        }
        TasksRegistry.register(task) // Retain
        return task
    }

    //...
}

Or is there any other ways to do so?

[Question]Task Manager

Is there easy way to make some manager around task? For example - i created about ten tasks with Alamofire.upload and some other stuff. I should put them in array and remove each value in success closure, so if array clear - all tasks complete successfully.
Problem, is that i can't to smth like this
let uploadTask = LoadImageTask(attachId: event.postId!, attachUrl: "events/(event.postId)/photos",
image: image)
.success({ (let imageId) -> Void in
self.tasksQueue.removeObject(uploadTask)
})
Variable use inside it's own initial value.
How i can deal with it?

Task chaining failure due to mismatched task-type and then-overload.

As discussed in https://gist.github.com/tokorom/19ae87acf9db5386c0e1 by @tokorom,

self.request1()
    .success { _ -> Task<Progress, Int, Bool> in
        // A
        return self.request2()
    }
    .success { _ in
        // B
    }

(B) gets called first before self.request2() completes.
This occurs when func request1() and func request2() returns Task type with different Error type,
so that type-inference will fail and uses "map" version of then rather than "flatMap" version of it.

We need more strict type constraint to avoid this issue, or add an alias method name to distinguish two methods.

Segmentation fault when archiving

I am able to build and run just fine on simulator and device, but get a segmentation fault only when I try to archive for App Store submission on this line:

@discardableResult public func progress(_ progressClosure: @escaping (ProgressTuple) -> Void) -> Self
{
    var dummyCanceller: Canceller? = nil
    return self.progress(&dummyCanceller, progressClosure)
}

This is the exact error:

@_TTSf4g_n___TTSg5CSo8Progress_T__Ps5Error____TFC9Concierge4Task8progressfFT11oldProgressGSqx_11newProgressx_T_DGS0_xq_q0__ for 'progress' at /Users/pritesh/sampleApp/Vendor/SwiftTask/SwiftTask/SwiftTask.swift:359:31

chain Task.all more than once, last `then` not called.

Under the following conditions, last then not called.

  • Task.all chain more than once
  • all tasks is empty

what would be a good way to do it?

Sample code

final class Image {
  let url: String

  var downloaded: Bool = false
  var converted: Bool = false

  typealias DownloadTask = Task<NSProgress, String, NSError?>
  typealias ConvertTask = Task<NSProgress, String, NSError?>

  init(url: String) {
    self.url = url
  }

  func download() -> DownloadTask {
    return DownloadTask { progress, fulfill, reject, configure in
      // download image and cache
      self.downloaded = true
      return fulfill("path to image")
    }
  }

  func convert() -> ConvertTask {
    return ConvertTask { progress, fulfill, reject, configure in
      // convert image
      self.converted = true
      return fulfill("path to converted image")
    }
  }
}

func downloadAndConvertAll() {
  let images = [
    Image(url: "http://example.com/1.jpg"),
    Image(url: "http://example.com/2.jpg"),
    Image(url: "http://example.com/3.jpg"),
  ]

  // I want to exclude downloaded image
  let downloadTasks = images.filter { !$0.downloaded }.map { $0.download() }

  // downloadTasks.count == 0 and convertTasks.count == 0 => doSomething not called
  Task.all(downloadTasks).then { _ -> Task<Image.ConvertTask.BulkProgress, [String], NSError?> in
    // I want to exclude converted image
    let convertTasks = images.filter { !$0.converted }.map { $0.convert() }
    return Task.all(convertTasks)
  }.then { _ in
    doSomething()
  }
}

func doSomething() {
  print("doSomething")
}

found workaround

Task.all(downloadTasks.count > 0 ? downloadTasks : [Image.DownloadTask(value: "dummy")])

and

Task.all(convertTasks.count > 0 ? convertTasks : [Image.ConvertTask(value: "dummy")])

Tuple conversion error & converting error

I'm using ReactKit (Swift/2.0 branch) through cocoapods in my xcode project.
I'm getting below error while trying to build. (_Xcode7.0 beta6, Swift2.0_)

Cannot express tuple conversion '(error: Error?, isCancelled: Bool)' (aka '(error: Optional, isCancelled: Bool)') to '(error: Error??, isCancelled: Bool)' (aka '(error: Optional<Optional>, isCancelled: Bool)')

2015-08-31 17 13 41

I've tried to fix this like below

public func success<Progress2, Value2, Error2, C: Canceller>(inout canceller: C?, _ successClosure: Value -> Task<Progress2, Value2, Error2>) -> Task<Progress2, Value2, Error>
    {
        return Task<Progress2, Value2, Error> { [unowned self] newMachine, progress, fulfill, _reject, configure in

            let selfMachine = self._machine

            // NOTE: using `self._then()` + `selfMachine` instead of `self.then()` will reduce Task allocation
            self._then(&canceller) {
                if let value = selfMachine.value.rawValue {
                    let innerTask = successClosure(value)
                    _bindInnerTask(innerTask, newMachine: newMachine, progress: progress, fulfill: fulfill, _reject: _reject, configure: configure)
                }
                else if let errorInfo = selfMachine.errorInfo.rawValue {
                    _reject(ErrorInfo(errorInfo.error, errorInfo.isCancelled))
                }
            }
        }.name("\(self.name)-success")
    }

however, I'm still have below error. I'm not sure how can I change return expression.

Cannot convert return expression of type 'Task<Progress2, Value2, Error?>' (aka 'Task<Progress2, Value2, Optional>') to return type 'Task<Progress2, Value2, Error>'

Thank you in beforehand.

Progress propagation

I am having problems getting progress events to propagate up the chain of tasks.

I have a lower level String task that will generate progress. Then, upon success I want to convert that String value to in Int if possible or an error otherwise. I expect progress handlers on both tasks to be called whenever progress changes on the lower level task. Is this a valid expectation?

To be clear, consider the following test (using Nimble) that always fails where indicated below:

class ProgressTest: QuickSpec {
    override func spec() {
        var stringTask: Task<Float, String, NSError>!
        var stringTaskProgressHandler: (Float ->Void)?
        var stringTaskProgressValue: Float?

        var intTask: Task<Float, Int, NSError>!
        var intTaskProgressValue: Float?

        beforeEach {
            stringTask = Task<Float, String, NSError> { progress, fulfill, reject, configure in
                stringTaskProgressHandler = progress
            }.progress { oldValue, newValue in
                stringTaskProgressValue = newValue
            }

            intTask = stringTask.success { stringValue -> Task<Float, Int, NSError> in
                if let intValue = Int(stringValue) {
                    return Task(value: intValue)
                } else {
                    return Task(error: NSError(domain: "test", code: 123, userInfo: nil))
                }
            }.progress { oldValue, newValue in
                intTaskProgressValue = newValue
            }
        }

        describe("when string task has progress") {
            beforeEach {
                stringTaskProgressHandler?(0.5)
            }

            it("string task progress should fire") {
                expect(stringTaskProgressValue).toEventually(equal(0.5))
            }
            it("should propagate progress to intTask") {
                // ************ This test fails! ************
                expect(intTaskProgressValue).toEventually(equal(0.5))
            }
        }
    }
}

Thoughts?

Update or remove Carthage/Checkouts

When compiling my project's main target, Xcode sometimes compiles the SwiftTask's Carthage/Checkouts directory, which includes outdated libraries. In my case, this breaks my build because the version of Alamofire doesn't compile under Swift 1.2. The only fix I've found is to delete these checkouts sources locally.

This may be a problem in my project configuration, but it seems like these sources should be kept up-to-date or removed.

Canceling/aborting a task that is running with task.try()

I'm sorry for asking in potentially the wrong place, but I haven't been able to completely cancel a task that I started with task.try().

I have tasks that belong to table view cells, and so I'm looking for the way to cancel/abort a task that is running on a cell that is about to scroll off of the screen and therefore no longer needs to be running (as it is now in the background).

Is there a way to cancel/abort/kill a task?

Cancel task from initClosure

Not sure is it clear how to cancel task from its initClosure block. I can manage progress, configure, reject or fullfill but it is absent or not so clear described in documentation how to cancel it. For example:

let task = Task<Float, Int, ErrorType>(paused: true) { (progress, fulfill, reject, configure) in
    progress.cancel() // should be something like this ?
}

Swift 4.1 support?

I tried both Carthage and Pod and both failed to compile in the end.

Is there support yet for Swift 4.1 or when is it planned?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.