Skip to content

Commit

Permalink
Merge pull request #23 from ra1028/1.4.0
Browse files Browse the repository at this point in the history
1.4.0
  • Loading branch information
ra1028 authored Mar 8, 2018
2 parents 0a81044 + 4d795f4 commit 5241958
Show file tree
Hide file tree
Showing 15 changed files with 259 additions and 209 deletions.
43 changes: 43 additions & 0 deletions CommonInternal/CancelableProcedure.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/// Encapsulate the function and make it cancelable.
/// A function will not be executed after canceling, except already in progress execution.
final class CancelableProcedure<Value> {
/// A Bool value indicating whether canceled.
var isCanceled: Bool {
return _isCanceled.value
}

private let _isCanceled: AtomicBool = false
private var _execute: ((Value) -> Void)?

/// Initialize with an arbitrary function.
///
/// - Parameters:
/// - execute: A function to be executed by calling `execute(with:)` until canceled.
init(_ execute: @escaping (Value) -> Void) {
_execute = execute
}

/// Synchronously execute the specified function.
///
/// - Parameters:
/// - value: A value to be pass to specified function.
func execute(with value: @autoclosure () -> Value) {
guard !isCanceled, let execute = _execute else { return }
execute(value())
}

/// Cancel the specified function.
/// Cancellation does not affect already in progress execution.
func cancel() {
guard _isCanceled.compareAndSwapBarrier(old: false, new: true) else { return }
_execute = nil
}
}

extension CancelableProcedure where Value == Void {
/// Synchronously execute the specified function.
@inline(__always)
func execute() {
self.execute(with: ())
}
}
20 changes: 0 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,26 +281,6 @@ store.computed.valueSignal
}
```
**Executor.WorkItem**
Executor can also be executed with WorkItem.
The WorkItem can call `cancel` to prevent it from execute in future.
Cancellation does not affect any execution of the function that is already in progress.
```swift
let executor = Executor.immediate
let workItem = Executor.WorkItem<Int> { value in
print("Value is \(value)")
}
executor.execute(workItem: workItem, with: 100)
workItem.cancel()
executor.execute(workItem: workItem, with: 200)
// prints "Value is 100"
```
### Signal Operators
VueFluxReactive restricts functional approach AMAP.
However, includes minimum operators for convenience.
Expand Down
131 changes: 131 additions & 0 deletions Tests/CancelableProcedureTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import XCTest
@testable import VueFlux
@testable import VueFluxReactive

private protocol CancelableProcedureProtocol {
associatedtype Value

var isCanceled: Bool { get }

init(_ execute: @escaping (Value) -> Void)
func execute(with value: @autoclosure () -> Value)
func cancel()
}

extension VueFlux.CancelableProcedure: CancelableProcedureProtocol {}
extension VueFluxReactive.CancelableProcedure: CancelableProcedureProtocol {}

final class CancelableProcedureTests: XCTestCase {
func testVoidProcedure() {
func runTest<CancelableProcedure: CancelableProcedureProtocol>(for type: CancelableProcedure.Type) where CancelableProcedure.Value == Void {

var value = 0

let procedure = CancelableProcedure {
value += 1
}

XCTAssertEqual(value, 0)

procedure.execute(with: ())

XCTAssertEqual(value, 1)

procedure.cancel()
procedure.execute(with: ())

XCTAssertEqual(value, 1)

procedure.cancel()
procedure.execute(with: ())

XCTAssertEqual(value, 1)
}

runTest(for: VueFlux.CancelableProcedure<Void>.self)
runTest(for: VueFluxReactive.CancelableProcedure<Void>.self)
}

func testValueWorkItem() {
func runTest<CancelableProcedure: CancelableProcedureProtocol>(for type: CancelableProcedure.Type) where CancelableProcedure.Value == Int {
var value = 0

let procedure = CancelableProcedure { int in
value = int
}

XCTAssertEqual(value, 0)

procedure.execute(with: 1)

XCTAssertFalse(procedure.isCanceled)
XCTAssertEqual(value, 1)

procedure.cancel()
procedure.execute(with: 2)

XCTAssertTrue(procedure.isCanceled)
XCTAssertEqual(value, 1)

procedure.cancel()
procedure.execute(with: 3)

XCTAssertTrue(procedure.isCanceled)
XCTAssertEqual(value, 1)
}

runTest(for: VueFlux.CancelableProcedure<Int>.self)
runTest(for: VueFluxReactive.CancelableProcedure<Int>.self)
}

func testCancelProcedureAsync() {
func runTest<CancelableProcedure: CancelableProcedureProtocol>(for type: CancelableProcedure.Type) where CancelableProcedure.Value == Int {
let queue = DispatchQueue(label: "testCancelProcedureAsync")

var value = 0

let expectation = self.expectation(description: "testCancelProcedureAsync")

let procedure = CancelableProcedure { int in
value = int
}

XCTAssertFalse(procedure.isCanceled)

queue.suspend()

queue.async {
procedure.execute(with: 1)
}

procedure.cancel()
queue.resume()

queue.async(execute: expectation.fulfill)

waitForExpectations(timeout: 1) { _ in
XCTAssertTrue(procedure.isCanceled)
XCTAssertEqual(value, 0)
}
}

runTest(for: VueFlux.CancelableProcedure<Int>.self)
runTest(for: VueFluxReactive.CancelableProcedure<Int>.self)
}

func testExecuteVoidProcedure() {
var value = 0
let vueFluxProcedure = VueFlux.CancelableProcedure<Void> {
value = 1
}

let vueFluxReactiveProcedure = VueFluxReactive.CancelableProcedure<Void> {
value = 2
}

vueFluxProcedure.execute()
vueFluxReactiveProcedure.execute()

XCTAssertEqual(value, 2)
}
}
11 changes: 11 additions & 0 deletions Tests/DisposableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,15 @@ final class DisposableTests: XCTestCase {
XCTAssertEqual(value, 1)
XCTAssertEqual(disposable.isDisposed, true)
}

func testNopDisposable() {
let disposable = AnyDisposable()

XCTAssertEqual(disposable.isDisposed, false)

disposable.dispose()

XCTAssertEqual(disposable.isDisposed, true)

}
}
98 changes: 0 additions & 98 deletions Tests/ExecutorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,102 +84,4 @@ final class ExecutorTests: XCTestCase {
XCTAssertEqual(value, 1)
}
}

func testVoidWorkItem() {
var value = 0

let workItem = Executor.WorkItem<Void> {
value = 1
}

XCTAssertEqual(value, 0)

workItem.execute()

XCTAssertEqual(value, 1)
}

func testValueWorkItem() {
var value = 0

let workItem = Executor.WorkItem<Int> { int in
value = int
}

XCTAssertEqual(value, 0)

workItem.execute(with: 1)

XCTAssertEqual(value, 1)
}

func testVoidWorkItemWithExecutor() {
var value = 0

let expectation = self.expectation(description: "testVoidWorkItemWithExecutor")

let workItem = Executor.WorkItem<Void> {
value = 1
expectation.fulfill()
}

let executor = Executor.queue(.globalDefault())

executor.execute(workItem: workItem)

waitForExpectations(timeout: 1) { _ in
XCTAssertEqual(value, 1)
}
}

func testValueWorkItemWithExecutor() {
var value = 0

let expectation = self.expectation(description: "testValueWorkItemWithExecutor")

let workItem = Executor.WorkItem<Int> { int in
value = int
expectation.fulfill()
}

let executor = Executor.queue(.globalDefault())

executor.execute(workItem: workItem, with: 1)

waitForExpectations(timeout: 1) { _ in
XCTAssertEqual(value, 1)
}
}

func testCancelWorkItem() {
let queue = DispatchQueue(label: "testCancelWorkItem")

var value = 0

let expectation = self.expectation(description: "testCancelWorkItem")

let workItem = Executor.WorkItem<Int> { int in
value = int
}

let executor = Executor.queue(queue)

XCTAssertFalse(workItem.isCanceled)

queue.suspend()

executor.execute(workItem: workItem, with: 1)
workItem.cancel()

queue.resume()

workItem.cancel()

queue.async(execute: expectation.fulfill)

waitForExpectations(timeout: 1) { _ in
XCTAssertTrue(workItem.isCanceled)
XCTAssertEqual(value, 0)
}
}
}
27 changes: 27 additions & 0 deletions Tests/SinkSignalTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,33 @@ final class SinkSignalTests: XCTestCase {
XCTAssertEqual(value, 1)
}

func testImmediatelyDisposeSignal() {
let queue = DispatchQueue(label: "testImmediatelyDisposeSignal")

let signal = Signal<Void> { send in
queue.async {
send(())
}
return AnyDisposable {}
}

queue.suspend()

let disposable = signal.observe {
XCTFail()
}

disposable.dispose()

let expectation = self.expectation(description: "testImmediatelyDisposeSignal")

queue.resume()

queue.async(execute: expectation.fulfill)

waitForExpectations(timeout: 1) { _ in }
}

func testDisposeOnObserving() {
let sink = Sink<Void>()
let signal = sink.signal
Expand Down
2 changes: 1 addition & 1 deletion VueFlux.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'VueFlux'
spec.version = '1.3.1'
spec.version = '1.4.0'
spec.author = { 'ra1028' => '[email protected]' }
spec.homepage = 'https://github.com/ra1028/VueFlux'
spec.summary = 'Unidirectional State Management for Swift - Inspired by Vuex and Flux'
Expand Down
Loading

0 comments on commit 5241958

Please sign in to comment.