Skip to content

Commit

Permalink
Merge pull request #31 from ReactKit/feature/removable-handler
Browse files Browse the repository at this point in the history
Add Canceller for removing progress/then handlers.
  • Loading branch information
inamiy committed May 10, 2015
2 parents b4b6eec + 18c4f7a commit cd9d110
Show file tree
Hide file tree
Showing 5 changed files with 343 additions and 27 deletions.
12 changes: 12 additions & 0 deletions SwiftTask.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

/* Begin PBXBuildFile section */
1F20250219ADA8FD00DE0495 /* BasicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F20250119ADA8FD00DE0495 /* BasicTests.swift */; };
1F218D5B1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */; };
1F218D5C1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */; };
1F46DEDA199EDF1000F97868 /* SwiftTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F46DED9199EDF1000F97868 /* SwiftTask.h */; settings = {ATTRIBUTES = (Public, ); }; };
1F46DEFB199EDF8100F97868 /* SwiftTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEFA199EDF8100F97868 /* SwiftTask.swift */; };
1F46DEFD199EE2C200F97868 /* _TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEFC199EE2C200F97868 /* _TestCase.swift */; };
Expand All @@ -18,6 +20,8 @@
1FCF71141AD8CD2F007079C2 /* Async.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCF71131AD8CD2F007079C2 /* Async.framework */; };
1FCF71161AD8CD38007079C2 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCF71151AD8CD38007079C2 /* Alamofire.framework */; };
1FCF71181AD8CD3C007079C2 /* Async.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCF71171AD8CD3C007079C2 /* Async.framework */; };
1FD7197B1AFE387C00BC38C4 /* Cancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD7197A1AFE387C00BC38C4 /* Cancellable.swift */; };
1FD7197C1AFE387C00BC38C4 /* Cancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD7197A1AFE387C00BC38C4 /* Cancellable.swift */; };
1FF52EB41A4C395A00B4BA28 /* _InterruptableTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF52EB31A4C395A00B4BA28 /* _InterruptableTask.swift */; };
1FF52EB51A4C395A00B4BA28 /* _InterruptableTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF52EB31A4C395A00B4BA28 /* _InterruptableTask.swift */; };
4822F0DC19D00B2300F5F572 /* _TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEFC199EE2C200F97868 /* _TestCase.swift */; };
Expand All @@ -37,6 +41,7 @@

/* Begin PBXFileReference section */
1F20250119ADA8FD00DE0495 /* BasicTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicTests.swift; sourceTree = "<group>"; };
1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoveHandlerTests.swift; sourceTree = "<group>"; };
1F46DED4199EDF1000F97868 /* SwiftTask.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftTask.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1F46DED8199EDF1000F97868 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
1F46DED9199EDF1000F97868 /* SwiftTask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftTask.h; sourceTree = "<group>"; };
Expand All @@ -50,6 +55,7 @@
1FCF71131AD8CD2F007079C2 /* Async.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Async.framework; path = "../Carthage/Checkouts/Async/build/Debug-iphoneos/Async.framework"; sourceTree = "<group>"; };
1FCF71151AD8CD38007079C2 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = ../Carthage/Checkouts/Alamofire/build/Debug/Alamofire.framework; sourceTree = "<group>"; };
1FCF71171AD8CD3C007079C2 /* Async.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Async.framework; path = ../Carthage/Checkouts/Async/build/Debug/Async.framework; sourceTree = "<group>"; };
1FD7197A1AFE387C00BC38C4 /* Cancellable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cancellable.swift; sourceTree = "<group>"; };
1FF52EB31A4C395A00B4BA28 /* _InterruptableTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _InterruptableTask.swift; sourceTree = "<group>"; };
4822F0D019D00ABF00F5F572 /* SwiftTask-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftTask-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
48511C5A19C17563002FE03C /* RetainCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetainCycleTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -121,6 +127,7 @@
children = (
1F46DED9199EDF1000F97868 /* SwiftTask.h */,
1F46DEFA199EDF8100F97868 /* SwiftTask.swift */,
1FD7197A1AFE387C00BC38C4 /* Cancellable.swift */,
48B58D7A1A6F255E0068E18C /* _StateMachine.swift */,
1F46DED7199EDF1000F97868 /* Supporting Files */,
);
Expand All @@ -144,6 +151,7 @@
1F46DEE3199EDF1000F97868 /* SwiftTaskTests.swift */,
48511C5A19C17563002FE03C /* RetainCycleTests.swift */,
485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */,
1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */,
1F5FA35619A374E600975FB9 /* AlamofireTests.swift */,
1F46DEE1199EDF1000F97868 /* Supporting Files */,
);
Expand Down Expand Up @@ -335,6 +343,7 @@
buildActionMask = 2147483647;
files = (
1F46DEFB199EDF8100F97868 /* SwiftTask.swift in Sources */,
1FD7197B1AFE387C00BC38C4 /* Cancellable.swift in Sources */,
48B58D7B1A6F255E0068E18C /* _StateMachine.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -345,6 +354,7 @@
files = (
1F20250219ADA8FD00DE0495 /* BasicTests.swift in Sources */,
1FF52EB41A4C395A00B4BA28 /* _InterruptableTask.swift in Sources */,
1F218D5B1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */,
1F6A8CA319A4E4F200369A5D /* SwiftTaskTests.swift in Sources */,
1F4C76A41AD8CF40004E47C1 /* AlamofireTests.swift in Sources */,
485C31F11A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */,
Expand All @@ -359,6 +369,7 @@
files = (
4822F0DE19D00B2300F5F572 /* SwiftTaskTests.swift in Sources */,
4822F0DD19D00B2300F5F572 /* BasicTests.swift in Sources */,
1F218D5C1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */,
1FF52EB51A4C395A00B4BA28 /* _InterruptableTask.swift in Sources */,
1F4C76A51AD8CF41004E47C1 /* AlamofireTests.swift in Sources */,
485C31F21A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */,
Expand All @@ -372,6 +383,7 @@
buildActionMask = 2147483647;
files = (
48CD5A3C19AEEBDF0042B9F1 /* SwiftTask.swift in Sources */,
1FD7197C1AFE387C00BC38C4 /* Cancellable.swift in Sources */,
48B58D7C1A6F255E0068E18C /* _StateMachine.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
56 changes: 56 additions & 0 deletions SwiftTask/Cancellable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// Cancellable.swift
// SwiftTask
//
// Created by Yasuhiro Inami on 2015/05/09.
// Copyright (c) 2015年 Yasuhiro Inami. All rights reserved.
//

import Foundation

public protocol Cancellable
{
typealias Error

//
// NOTE:
// Single `func cancel(error: Error) -> Bool` is preferred (as first implemented in 8a22ed5),
// but two overloaded methods are required for SwiftTask ver 3.x API compatibility.
//
func cancel() -> Bool
func cancel(#error: Error) -> Bool
}

public class Canceller: Cancellable
{
private var cancelHandler: (Void -> Void)?

public required init(cancelHandler: Void -> Void)
{
self.cancelHandler = cancelHandler
}

public func cancel() -> Bool
{
return self.cancel(error: ())
}

public func cancel(#error: Void) -> Bool
{
if let cancelHandler = self.cancelHandler {
self.cancelHandler = nil
cancelHandler()
return true
}

return false
}
}

public class AutoCanceller: Canceller
{
deinit
{
self.cancel()
}
}
116 changes: 95 additions & 21 deletions SwiftTask/SwiftTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public class TaskConfiguration
}
}

public class Task<Progress, Value, Error>: Printable
public class Task<Progress, Value, Error>: Cancellable, Printable
{
public typealias ProgressTuple = (oldProgress: Progress?, newProgress: Progress)
public typealias ErrorInfo = (error: Error?, isCancelled: Bool)
Expand Down Expand Up @@ -213,7 +213,8 @@ public class Task<Progress, Value, Error>: Printable
internal func setup(#weakified: Bool, paused: Bool, _initClosure: _InitClosure)
{
// #if DEBUG
// println("[init] \(self.name)")
// let addr = String(format: "%p", unsafeAddressOf(self))
// NSLog("[init] \(self.name) \(addr)")
// #endif

self._initClosure = _initClosure
Expand Down Expand Up @@ -287,7 +288,8 @@ public class Task<Progress, Value, Error>: Printable
deinit
{
// #if DEBUG
// println("[deinit] \(self.name)")
// let addr = String(format: "%p", unsafeAddressOf(self))
// NSLog("[deinit] \(self.name) \(addr)")
// #endif

// cancel in case machine is still running
Expand Down Expand Up @@ -355,31 +357,62 @@ public class Task<Progress, Value, Error>: Printable
///
public func progress(progressClosure: ProgressTuple -> Void) -> Task
{
self._machine.addProgressTupleHandler(progressClosure)
var dummyCanceller: Canceller? = nil
return self.progress(&dummyCanceller, progressClosure)
}

public func progress<C: Canceller>(inout canceller: C?, _ progressClosure: ProgressTuple -> Void) -> Task
{
var token: _HandlerToken? = nil
self._machine.addProgressTupleHandler(&token, progressClosure)

canceller = C { [weak self] in
self?._machine.removeProgressTupleHandler(token)
}

return self
}

///
/// then (fulfilled & rejected) + closure returning value
/// then (fulfilled & rejected) + closure returning **value**
/// (a.k.a. `map` in functional programming term)
///
/// - e.g. task.then { value, errorInfo -> NextValueType in ... }
///
public func then<Value2>(thenClosure: (Value?, ErrorInfo?) -> Value2) -> Task<Progress, Value2, Error>
{
return self.then { (value: Value?, errorInfo: ErrorInfo?) -> Task<Progress, Value2, Error> in
var dummyCanceller: Canceller? = nil
return self.then(&dummyCanceller, thenClosure)
}

public func then<Value2, C: Canceller>(inout canceller: C?, _ thenClosure: (Value?, ErrorInfo?) -> Value2) -> Task<Progress, Value2, Error>
{
return self.then(&canceller) { (value: Value?, errorInfo: ErrorInfo?) -> Task<Progress, Value2, Error> in
return Task<Progress, Value2, Error>(value: thenClosure(value, errorInfo))
}
}

///
/// then (fulfilled & rejected) + closure returning task
/// then (fulfilled & rejected) + closure returning **task**
/// (a.k.a. `flatMap` in functional programming term)
///
/// - e.g. task.then { value, errorInfo -> NextTaskType in ... }
///
public func then<Progress2, Value2>(thenClosure: (Value?, ErrorInfo?) -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
{
return Task<Progress2, Value2, Error> { [unowned self] newMachine, progress, fulfill, _reject, configure in
var dummyCanceller: Canceller? = nil
return self.then(&dummyCanceller, thenClosure)
}

//
// NOTE: then-canceller is a shorthand of `task.cancel(nil)`, i.e. these two are the same:
//
// - `let canceller = Canceller(); task1.then(&canceller) {...}; canceller.cancel();`
// - `let task2 = task1.then {...}; task2.cancel();`
//
public func then<Progress2, Value2, C: Canceller>(inout canceller: C?, _ thenClosure: (Value?, ErrorInfo?) -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
{
return Task<Progress2, Value2, Error> { [unowned self, weak canceller] newMachine, progress, fulfill, _reject, configure in

//
// NOTE:
Expand All @@ -389,8 +422,8 @@ public class Task<Progress, Value, Error>: Printable
// This is especially important for ReactKit's `deinitSignal` behavior.
//
let selfMachine = self._machine

self._then {
self._then(&canceller) {
let innerTask = thenClosure(selfMachine.value, selfMachine.errorInfo)
_bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure)
}
Expand All @@ -399,41 +432,58 @@ public class Task<Progress, Value, Error>: Printable
}

/// invokes `completionHandler` "now" or "in the future"
private func _then(completionHandler: Void -> Void)
private func _then<C: Canceller>(inout canceller: C?, _ completionHandler: Void -> Void)
{
switch self.state {
case .Fulfilled, .Rejected, .Cancelled:
completionHandler()
default:
self._machine.addCompletionHandler(completionHandler)
var token: _HandlerToken? = nil
self._machine.addCompletionHandler(&token, completionHandler)

canceller = C { [weak self] in
self?._machine.removeCompletionHandler(token)
}
}
}

///
/// success (fulfilled) + closure returning value
/// success (fulfilled) + closure returning **value**
///
/// - e.g. task.success { value -> NextValueType in ... }
///
public func success<Value2>(successClosure: Value -> Value2) -> Task<Progress, Value2, Error>
{
return self.success { (value: Value) -> Task<Progress, Value2, Error> in
var dummyCanceller: Canceller? = nil
return self.success(&dummyCanceller, successClosure)
}

public func success<Value2, C: Canceller>(inout canceller: C?, _ successClosure: Value -> Value2) -> Task<Progress, Value2, Error>
{
return self.success(&canceller) { (value: Value) -> Task<Progress, Value2, Error> in
return Task<Progress, Value2, Error>(value: successClosure(value))
}
}

///
/// success (fulfilled) + closure returning task
/// success (fulfilled) + closure returning **task**
///
/// - e.g. task.success { value -> NextTaskType in ... }
///
public func success<Progress2, Value2>(successClosure: Value -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
{
var dummyCanceller: Canceller? = nil
return self.success(&dummyCanceller, successClosure)
}

public func success<Progress2, Value2, C: Canceller>(inout canceller: C?, _ successClosure: Value -> Task<Progress2, Value2, Error>) -> 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 {
self._then(&canceller) {
if let value = selfMachine.value {
let innerTask = successClosure(value)
_bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure)
Expand All @@ -447,31 +497,43 @@ public class Task<Progress, Value, Error>: Printable
}

///
/// failure (rejected) + closure returning value
/// failure (rejected or cancelled) + closure returning **value**
///
/// - e.g. task.failure { errorInfo -> NextValueType in ... }
/// - e.g. task.failure { error, isCancelled -> NextValueType in ... }
///
public func failure(failureClosure: ErrorInfo -> Value) -> Task
{
return self.failure { (errorInfo: ErrorInfo) -> Task in
var dummyCanceller: Canceller? = nil
return self.failure(&dummyCanceller, failureClosure)
}

public func failure<C: Canceller>(inout canceller: C?, _ failureClosure: ErrorInfo -> Value) -> Task
{
return self.failure(&canceller) { (errorInfo: ErrorInfo) -> Task in
return Task(value: failureClosure(errorInfo))
}
}

///
/// failure (rejected) + closure returning task
/// failure (rejected or cancelled) + closure returning **task**
///
/// - e.g. task.failure { errorInfo -> NextTaskType in ... }
/// - e.g. task.failure { error, isCancelled -> NextTaskType in ... }
///
public func failure<Progress2>(failureClosure: ErrorInfo -> Task<Progress2, Value, Error>) -> Task<Progress2, Value, Error>
{
var dummyCanceller: Canceller? = nil
return self.failure(&dummyCanceller, failureClosure)
}

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

let selfMachine = self._machine

self._then {
self._then(&canceller) {
if let value = selfMachine.value {
fulfill(value)
}
Expand All @@ -494,7 +556,18 @@ public class Task<Progress, Value, Error>: Printable
return self._machine.handleResume()
}

public func cancel(error: Error? = nil) -> Bool
//
// NOTE:
// To conform to `Cancellable`, this method is needed in replace of:
// - `public func cancel(error: Error? = nil) -> Bool`
// - `public func cancel(_ error: Error? = nil) -> Bool` (segfault in Swift 1.2)
//
public func cancel() -> Bool
{
return self.cancel(error: nil)
}

public func cancel(#error: Error?) -> Bool
{
return self._cancel(error: error)
}
Expand All @@ -503,6 +576,7 @@ public class Task<Progress, Value, Error>: Printable
{
return self._machine.handleCancel(error: error)
}

}

// MARK: - Helper
Expand Down
Loading

0 comments on commit cd9d110

Please sign in to comment.