Skip to content

Add Canceller for removing progress/then handlers. #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 10, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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