Skip to content

Commit cd9d110

Browse files
committed
Merge pull request #31 from ReactKit/feature/removable-handler
Add Canceller for removing progress/then handlers.
2 parents b4b6eec + 18c4f7a commit cd9d110

File tree

5 files changed

+343
-27
lines changed

5 files changed

+343
-27
lines changed

SwiftTask.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
/* Begin PBXBuildFile section */
1010
1F20250219ADA8FD00DE0495 /* BasicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F20250119ADA8FD00DE0495 /* BasicTests.swift */; };
11+
1F218D5B1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */; };
12+
1F218D5C1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */; };
1113
1F46DEDA199EDF1000F97868 /* SwiftTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F46DED9199EDF1000F97868 /* SwiftTask.h */; settings = {ATTRIBUTES = (Public, ); }; };
1214
1F46DEFB199EDF8100F97868 /* SwiftTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEFA199EDF8100F97868 /* SwiftTask.swift */; };
1315
1F46DEFD199EE2C200F97868 /* _TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEFC199EE2C200F97868 /* _TestCase.swift */; };
@@ -18,6 +20,8 @@
1820
1FCF71141AD8CD2F007079C2 /* Async.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCF71131AD8CD2F007079C2 /* Async.framework */; };
1921
1FCF71161AD8CD38007079C2 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCF71151AD8CD38007079C2 /* Alamofire.framework */; };
2022
1FCF71181AD8CD3C007079C2 /* Async.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCF71171AD8CD3C007079C2 /* Async.framework */; };
23+
1FD7197B1AFE387C00BC38C4 /* Cancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD7197A1AFE387C00BC38C4 /* Cancellable.swift */; };
24+
1FD7197C1AFE387C00BC38C4 /* Cancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD7197A1AFE387C00BC38C4 /* Cancellable.swift */; };
2125
1FF52EB41A4C395A00B4BA28 /* _InterruptableTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF52EB31A4C395A00B4BA28 /* _InterruptableTask.swift */; };
2226
1FF52EB51A4C395A00B4BA28 /* _InterruptableTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF52EB31A4C395A00B4BA28 /* _InterruptableTask.swift */; };
2327
4822F0DC19D00B2300F5F572 /* _TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEFC199EE2C200F97868 /* _TestCase.swift */; };
@@ -37,6 +41,7 @@
3741

3842
/* Begin PBXFileReference section */
3943
1F20250119ADA8FD00DE0495 /* BasicTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicTests.swift; sourceTree = "<group>"; };
44+
1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoveHandlerTests.swift; sourceTree = "<group>"; };
4045
1F46DED4199EDF1000F97868 /* SwiftTask.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftTask.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4146
1F46DED8199EDF1000F97868 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4247
1F46DED9199EDF1000F97868 /* SwiftTask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftTask.h; sourceTree = "<group>"; };
@@ -50,6 +55,7 @@
5055
1FCF71131AD8CD2F007079C2 /* Async.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Async.framework; path = "../Carthage/Checkouts/Async/build/Debug-iphoneos/Async.framework"; sourceTree = "<group>"; };
5156
1FCF71151AD8CD38007079C2 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = ../Carthage/Checkouts/Alamofire/build/Debug/Alamofire.framework; sourceTree = "<group>"; };
5257
1FCF71171AD8CD3C007079C2 /* Async.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Async.framework; path = ../Carthage/Checkouts/Async/build/Debug/Async.framework; sourceTree = "<group>"; };
58+
1FD7197A1AFE387C00BC38C4 /* Cancellable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cancellable.swift; sourceTree = "<group>"; };
5359
1FF52EB31A4C395A00B4BA28 /* _InterruptableTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _InterruptableTask.swift; sourceTree = "<group>"; };
5460
4822F0D019D00ABF00F5F572 /* SwiftTask-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftTask-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
5561
48511C5A19C17563002FE03C /* RetainCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetainCycleTests.swift; sourceTree = "<group>"; };
@@ -121,6 +127,7 @@
121127
children = (
122128
1F46DED9199EDF1000F97868 /* SwiftTask.h */,
123129
1F46DEFA199EDF8100F97868 /* SwiftTask.swift */,
130+
1FD7197A1AFE387C00BC38C4 /* Cancellable.swift */,
124131
48B58D7A1A6F255E0068E18C /* _StateMachine.swift */,
125132
1F46DED7199EDF1000F97868 /* Supporting Files */,
126133
);
@@ -144,6 +151,7 @@
144151
1F46DEE3199EDF1000F97868 /* SwiftTaskTests.swift */,
145152
48511C5A19C17563002FE03C /* RetainCycleTests.swift */,
146153
485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */,
154+
1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */,
147155
1F5FA35619A374E600975FB9 /* AlamofireTests.swift */,
148156
1F46DEE1199EDF1000F97868 /* Supporting Files */,
149157
);
@@ -335,6 +343,7 @@
335343
buildActionMask = 2147483647;
336344
files = (
337345
1F46DEFB199EDF8100F97868 /* SwiftTask.swift in Sources */,
346+
1FD7197B1AFE387C00BC38C4 /* Cancellable.swift in Sources */,
338347
48B58D7B1A6F255E0068E18C /* _StateMachine.swift in Sources */,
339348
);
340349
runOnlyForDeploymentPostprocessing = 0;
@@ -345,6 +354,7 @@
345354
files = (
346355
1F20250219ADA8FD00DE0495 /* BasicTests.swift in Sources */,
347356
1FF52EB41A4C395A00B4BA28 /* _InterruptableTask.swift in Sources */,
357+
1F218D5B1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */,
348358
1F6A8CA319A4E4F200369A5D /* SwiftTaskTests.swift in Sources */,
349359
1F4C76A41AD8CF40004E47C1 /* AlamofireTests.swift in Sources */,
350360
485C31F11A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */,
@@ -359,6 +369,7 @@
359369
files = (
360370
4822F0DE19D00B2300F5F572 /* SwiftTaskTests.swift in Sources */,
361371
4822F0DD19D00B2300F5F572 /* BasicTests.swift in Sources */,
372+
1F218D5C1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */,
362373
1FF52EB51A4C395A00B4BA28 /* _InterruptableTask.swift in Sources */,
363374
1F4C76A51AD8CF41004E47C1 /* AlamofireTests.swift in Sources */,
364375
485C31F21A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */,
@@ -372,6 +383,7 @@
372383
buildActionMask = 2147483647;
373384
files = (
374385
48CD5A3C19AEEBDF0042B9F1 /* SwiftTask.swift in Sources */,
386+
1FD7197C1AFE387C00BC38C4 /* Cancellable.swift in Sources */,
375387
48B58D7C1A6F255E0068E18C /* _StateMachine.swift in Sources */,
376388
);
377389
runOnlyForDeploymentPostprocessing = 0;

SwiftTask/Cancellable.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//
2+
// Cancellable.swift
3+
// SwiftTask
4+
//
5+
// Created by Yasuhiro Inami on 2015/05/09.
6+
// Copyright (c) 2015年 Yasuhiro Inami. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
public protocol Cancellable
12+
{
13+
typealias Error
14+
15+
//
16+
// NOTE:
17+
// Single `func cancel(error: Error) -> Bool` is preferred (as first implemented in 8a22ed5),
18+
// but two overloaded methods are required for SwiftTask ver 3.x API compatibility.
19+
//
20+
func cancel() -> Bool
21+
func cancel(#error: Error) -> Bool
22+
}
23+
24+
public class Canceller: Cancellable
25+
{
26+
private var cancelHandler: (Void -> Void)?
27+
28+
public required init(cancelHandler: Void -> Void)
29+
{
30+
self.cancelHandler = cancelHandler
31+
}
32+
33+
public func cancel() -> Bool
34+
{
35+
return self.cancel(error: ())
36+
}
37+
38+
public func cancel(#error: Void) -> Bool
39+
{
40+
if let cancelHandler = self.cancelHandler {
41+
self.cancelHandler = nil
42+
cancelHandler()
43+
return true
44+
}
45+
46+
return false
47+
}
48+
}
49+
50+
public class AutoCanceller: Canceller
51+
{
52+
deinit
53+
{
54+
self.cancel()
55+
}
56+
}

SwiftTask/SwiftTask.swift

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public class TaskConfiguration
5757
}
5858
}
5959

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

219220
self._initClosure = _initClosure
@@ -287,7 +288,8 @@ public class Task<Progress, Value, Error>: Printable
287288
deinit
288289
{
289290
// #if DEBUG
290-
// println("[deinit] \(self.name)")
291+
// let addr = String(format: "%p", unsafeAddressOf(self))
292+
// NSLog("[deinit] \(self.name) \(addr)")
291293
// #endif
292294

293295
// cancel in case machine is still running
@@ -355,31 +357,62 @@ public class Task<Progress, Value, Error>: Printable
355357
///
356358
public func progress(progressClosure: ProgressTuple -> Void) -> Task
357359
{
358-
self._machine.addProgressTupleHandler(progressClosure)
360+
var dummyCanceller: Canceller? = nil
361+
return self.progress(&dummyCanceller, progressClosure)
362+
}
363+
364+
public func progress<C: Canceller>(inout canceller: C?, _ progressClosure: ProgressTuple -> Void) -> Task
365+
{
366+
var token: _HandlerToken? = nil
367+
self._machine.addProgressTupleHandler(&token, progressClosure)
368+
369+
canceller = C { [weak self] in
370+
self?._machine.removeProgressTupleHandler(token)
371+
}
359372

360373
return self
361374
}
362375

363376
///
364-
/// then (fulfilled & rejected) + closure returning value
377+
/// then (fulfilled & rejected) + closure returning **value**
378+
/// (a.k.a. `map` in functional programming term)
365379
///
366380
/// - e.g. task.then { value, errorInfo -> NextValueType in ... }
367381
///
368382
public func then<Value2>(thenClosure: (Value?, ErrorInfo?) -> Value2) -> Task<Progress, Value2, Error>
369383
{
370-
return self.then { (value: Value?, errorInfo: ErrorInfo?) -> Task<Progress, Value2, Error> in
384+
var dummyCanceller: Canceller? = nil
385+
return self.then(&dummyCanceller, thenClosure)
386+
}
387+
388+
public func then<Value2, C: Canceller>(inout canceller: C?, _ thenClosure: (Value?, ErrorInfo?) -> Value2) -> Task<Progress, Value2, Error>
389+
{
390+
return self.then(&canceller) { (value: Value?, errorInfo: ErrorInfo?) -> Task<Progress, Value2, Error> in
371391
return Task<Progress, Value2, Error>(value: thenClosure(value, errorInfo))
372392
}
373393
}
374394

375395
///
376-
/// then (fulfilled & rejected) + closure returning task
396+
/// then (fulfilled & rejected) + closure returning **task**
397+
/// (a.k.a. `flatMap` in functional programming term)
377398
///
378399
/// - e.g. task.then { value, errorInfo -> NextTaskType in ... }
379400
///
380401
public func then<Progress2, Value2>(thenClosure: (Value?, ErrorInfo?) -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
381402
{
382-
return Task<Progress2, Value2, Error> { [unowned self] newMachine, progress, fulfill, _reject, configure in
403+
var dummyCanceller: Canceller? = nil
404+
return self.then(&dummyCanceller, thenClosure)
405+
}
406+
407+
//
408+
// NOTE: then-canceller is a shorthand of `task.cancel(nil)`, i.e. these two are the same:
409+
//
410+
// - `let canceller = Canceller(); task1.then(&canceller) {...}; canceller.cancel();`
411+
// - `let task2 = task1.then {...}; task2.cancel();`
412+
//
413+
public func then<Progress2, Value2, C: Canceller>(inout canceller: C?, _ thenClosure: (Value?, ErrorInfo?) -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
414+
{
415+
return Task<Progress2, Value2, Error> { [unowned self, weak canceller] newMachine, progress, fulfill, _reject, configure in
383416

384417
//
385418
// NOTE:
@@ -389,8 +422,8 @@ public class Task<Progress, Value, Error>: Printable
389422
// This is especially important for ReactKit's `deinitSignal` behavior.
390423
//
391424
let selfMachine = self._machine
392-
393-
self._then {
425+
426+
self._then(&canceller) {
394427
let innerTask = thenClosure(selfMachine.value, selfMachine.errorInfo)
395428
_bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure)
396429
}
@@ -399,41 +432,58 @@ public class Task<Progress, Value, Error>: Printable
399432
}
400433

401434
/// invokes `completionHandler` "now" or "in the future"
402-
private func _then(completionHandler: Void -> Void)
435+
private func _then<C: Canceller>(inout canceller: C?, _ completionHandler: Void -> Void)
403436
{
404437
switch self.state {
405438
case .Fulfilled, .Rejected, .Cancelled:
406439
completionHandler()
407440
default:
408-
self._machine.addCompletionHandler(completionHandler)
441+
var token: _HandlerToken? = nil
442+
self._machine.addCompletionHandler(&token, completionHandler)
443+
444+
canceller = C { [weak self] in
445+
self?._machine.removeCompletionHandler(token)
446+
}
409447
}
410448
}
411449

412450
///
413-
/// success (fulfilled) + closure returning value
451+
/// success (fulfilled) + closure returning **value**
414452
///
415453
/// - e.g. task.success { value -> NextValueType in ... }
416454
///
417455
public func success<Value2>(successClosure: Value -> Value2) -> Task<Progress, Value2, Error>
418456
{
419-
return self.success { (value: Value) -> Task<Progress, Value2, Error> in
457+
var dummyCanceller: Canceller? = nil
458+
return self.success(&dummyCanceller, successClosure)
459+
}
460+
461+
public func success<Value2, C: Canceller>(inout canceller: C?, _ successClosure: Value -> Value2) -> Task<Progress, Value2, Error>
462+
{
463+
return self.success(&canceller) { (value: Value) -> Task<Progress, Value2, Error> in
420464
return Task<Progress, Value2, Error>(value: successClosure(value))
421465
}
422466
}
423467

424468
///
425-
/// success (fulfilled) + closure returning task
469+
/// success (fulfilled) + closure returning **task**
426470
///
427471
/// - e.g. task.success { value -> NextTaskType in ... }
428472
///
429473
public func success<Progress2, Value2>(successClosure: Value -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
474+
{
475+
var dummyCanceller: Canceller? = nil
476+
return self.success(&dummyCanceller, successClosure)
477+
}
478+
479+
public func success<Progress2, Value2, C: Canceller>(inout canceller: C?, _ successClosure: Value -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
430480
{
431481
return Task<Progress2, Value2, Error> { [unowned self] newMachine, progress, fulfill, _reject, configure in
432482

433483
let selfMachine = self._machine
434484

435485
// NOTE: using `self._then()` + `selfMachine` instead of `self.then()` will reduce Task allocation
436-
self._then {
486+
self._then(&canceller) {
437487
if let value = selfMachine.value {
438488
let innerTask = successClosure(value)
439489
_bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure)
@@ -447,31 +497,43 @@ public class Task<Progress, Value, Error>: Printable
447497
}
448498

449499
///
450-
/// failure (rejected) + closure returning value
500+
/// failure (rejected or cancelled) + closure returning **value**
451501
///
452502
/// - e.g. task.failure { errorInfo -> NextValueType in ... }
453503
/// - e.g. task.failure { error, isCancelled -> NextValueType in ... }
454504
///
455505
public func failure(failureClosure: ErrorInfo -> Value) -> Task
456506
{
457-
return self.failure { (errorInfo: ErrorInfo) -> Task in
507+
var dummyCanceller: Canceller? = nil
508+
return self.failure(&dummyCanceller, failureClosure)
509+
}
510+
511+
public func failure<C: Canceller>(inout canceller: C?, _ failureClosure: ErrorInfo -> Value) -> Task
512+
{
513+
return self.failure(&canceller) { (errorInfo: ErrorInfo) -> Task in
458514
return Task(value: failureClosure(errorInfo))
459515
}
460516
}
461517

462518
///
463-
/// failure (rejected) + closure returning task
519+
/// failure (rejected or cancelled) + closure returning **task**
464520
///
465521
/// - e.g. task.failure { errorInfo -> NextTaskType in ... }
466522
/// - e.g. task.failure { error, isCancelled -> NextTaskType in ... }
467523
///
468524
public func failure<Progress2>(failureClosure: ErrorInfo -> Task<Progress2, Value, Error>) -> Task<Progress2, Value, Error>
525+
{
526+
var dummyCanceller: Canceller? = nil
527+
return self.failure(&dummyCanceller, failureClosure)
528+
}
529+
530+
public func failure<Progress2, C: Canceller>(inout canceller: C?, _ failureClosure: ErrorInfo -> Task<Progress2, Value, Error>) -> Task<Progress2, Value, Error>
469531
{
470532
return Task<Progress2, Value, Error> { [unowned self] newMachine, progress, fulfill, _reject, configure in
471533

472534
let selfMachine = self._machine
473535

474-
self._then {
536+
self._then(&canceller) {
475537
if let value = selfMachine.value {
476538
fulfill(value)
477539
}
@@ -494,7 +556,18 @@ public class Task<Progress, Value, Error>: Printable
494556
return self._machine.handleResume()
495557
}
496558

497-
public func cancel(error: Error? = nil) -> Bool
559+
//
560+
// NOTE:
561+
// To conform to `Cancellable`, this method is needed in replace of:
562+
// - `public func cancel(error: Error? = nil) -> Bool`
563+
// - `public func cancel(_ error: Error? = nil) -> Bool` (segfault in Swift 1.2)
564+
//
565+
public func cancel() -> Bool
566+
{
567+
return self.cancel(error: nil)
568+
}
569+
570+
public func cancel(#error: Error?) -> Bool
498571
{
499572
return self._cancel(error: error)
500573
}
@@ -503,6 +576,7 @@ public class Task<Progress, Value, Error>: Printable
503576
{
504577
return self._machine.handleCancel(error: error)
505578
}
579+
506580
}
507581

508582
// MARK: - Helper

0 commit comments

Comments
 (0)