Skip to content

Commit b8473af

Browse files
author
Bell App Lab
committed
- Bump to 1.4.0
- Swift 5.3 - Implemented the `AsyncOperationUniquenessPolicy` to handle deduplicating operations - Implemented `onTimeoutCallback:` to notify users when an operation times out
1 parent f100505 commit b8473af

9 files changed

Lines changed: 813 additions & 38 deletions

File tree

Backgroundable.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Pod::Spec.new do |s|
22

33
s.name = "Backgroundable"
4-
s.version = "1.3.4"
4+
s.version = "1.4.0"
55
s.summary = "A collection of handy classes, extensions and global functions to handle being in the background using Swift."
66
s.screenshot = "https://github.com/BellAppLab/Backgroundable/raw/master/Images/backgroundable.png"
77

@@ -24,7 +24,7 @@ It's powerful because it's simple.
2424
s.osx.deployment_target = "10.12"
2525
s.tvos.deployment_target = "10.0"
2626

27-
s.swift_version = '5.1'
27+
s.swift_version = '5.3'
2828

2929
s.module_name = 'Backgroundable'
3030

Example/Example.xcodeproj/project.pbxproj

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
650BF6DC24D70B80001C531A /* Backgroundable+Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650BF6DB24D70B80001C531A /* Backgroundable+Operation.swift */; };
11+
650BF6DD24D70B80001C531A /* Backgroundable+Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650BF6DB24D70B80001C531A /* Backgroundable+Operation.swift */; };
12+
650BF6DE24D70B80001C531A /* Backgroundable+Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650BF6DB24D70B80001C531A /* Backgroundable+Operation.swift */; };
13+
650BF6E024D7D8E4001C531A /* UniquenessPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650BF6DF24D7D8E4001C531A /* UniquenessPolicyTests.swift */; };
14+
650BF6E124D7D8E4001C531A /* UniquenessPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650BF6DF24D7D8E4001C531A /* UniquenessPolicyTests.swift */; };
15+
650BF6E224D7D8E4001C531A /* UniquenessPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650BF6DF24D7D8E4001C531A /* UniquenessPolicyTests.swift */; };
1016
6553C35E20D944D800405D40 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6553C35D20D944D800405D40 /* AppDelegate.swift */; };
1117
6553C36020D944D900405D40 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6553C35F20D944D900405D40 /* Assets.xcassets */; };
1218
6553C36320D944D900405D40 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6553C36120D944D900405D40 /* MainMenu.xib */; };
@@ -134,6 +140,8 @@
134140

135141
/* Begin PBXFileReference section */
136142
650B58BC1DA6B8380037CBD7 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
143+
650BF6DB24D70B80001C531A /* Backgroundable+Operation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Backgroundable+Operation.swift"; sourceTree = "<group>"; };
144+
650BF6DF24D7D8E4001C531A /* UniquenessPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniquenessPolicyTests.swift; sourceTree = "<group>"; };
137145
6553C34D20D922DF00405D40 /* Backgroundable+Functions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Backgroundable+Functions.swift"; sourceTree = "<group>"; };
138146
6553C35220D9233800405D40 /* Backgroundable+OperationQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Backgroundable+OperationQueue.swift"; sourceTree = "<group>"; };
139147
6553C35B20D944D800405D40 /* macOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "macOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -379,6 +387,7 @@
379387
6582320C20D8015E00FA8CC9 /* GlobalFunctionsTests.swift */,
380388
6582320D20D8015E00FA8CC9 /* AsyncOperationTests.swift */,
381389
6582320E20D8015E00FA8CC9 /* BackgroundQueueTests.swift */,
390+
650BF6DF24D7D8E4001C531A /* UniquenessPolicyTests.swift */,
382391
6582320F20D8015E00FA8CC9 /* Tests+Aux.swift */,
383392
);
384393
path = BackgroundableTests;
@@ -398,6 +407,7 @@
398407
children = (
399408
6582321820D8017200FA8CC9 /* Backgroundable.swift */,
400409
6553C35220D9233800405D40 /* Backgroundable+OperationQueue.swift */,
410+
650BF6DB24D70B80001C531A /* Backgroundable+Operation.swift */,
401411
6553C34D20D922DF00405D40 /* Backgroundable+Functions.swift */,
402412
);
403413
path = Backgroundable;
@@ -786,6 +796,7 @@
786796
isa = PBXSourcesBuildPhase;
787797
buildActionMask = 2147483647;
788798
files = (
799+
650BF6E224D7D8E4001C531A /* UniquenessPolicyTests.swift in Sources */,
789800
6553C39520D9481A00405D40 /* AsyncOperationTests.swift in Sources */,
790801
6553C39620D9481A00405D40 /* BackgroundQueueTests.swift in Sources */,
791802
6553C39720D9481A00405D40 /* Tests+Aux.swift in Sources */,
@@ -798,6 +809,7 @@
798809
buildActionMask = 2147483647;
799810
files = (
800811
6553C39120D9473800405D40 /* Backgroundable.swift in Sources */,
812+
650BF6DE24D70B80001C531A /* Backgroundable+Operation.swift in Sources */,
801813
6553C39220D9473800405D40 /* Backgroundable+OperationQueue.swift in Sources */,
802814
6553C39320D9473800405D40 /* Backgroundable+Functions.swift in Sources */,
803815
);
@@ -808,6 +820,7 @@
808820
buildActionMask = 2147483647;
809821
files = (
810822
6553C3AA20D948B900405D40 /* Backgroundable.swift in Sources */,
823+
650BF6DD24D70B80001C531A /* Backgroundable+Operation.swift in Sources */,
811824
6553C3AB20D948B900405D40 /* Backgroundable+OperationQueue.swift in Sources */,
812825
6553C3AC20D948B900405D40 /* Backgroundable+Functions.swift in Sources */,
813826
);
@@ -818,6 +831,7 @@
818831
buildActionMask = 2147483647;
819832
files = (
820833
6553C3BE20D94A2B00405D40 /* Backgroundable.swift in Sources */,
834+
650BF6DC24D70B80001C531A /* Backgroundable+Operation.swift in Sources */,
821835
6553C3BF20D94A2B00405D40 /* Backgroundable+OperationQueue.swift in Sources */,
822836
6553C3C020D94A2B00405D40 /* Backgroundable+Functions.swift in Sources */,
823837
);
@@ -845,6 +859,7 @@
845859
isa = PBXSourcesBuildPhase;
846860
buildActionMask = 2147483647;
847861
files = (
862+
650BF6E024D7D8E4001C531A /* UniquenessPolicyTests.swift in Sources */,
848863
6582325720D812CF00FA8CC9 /* AsyncOperationTests.swift in Sources */,
849864
6582325820D812CF00FA8CC9 /* BackgroundQueueTests.swift in Sources */,
850865
6582325920D812CF00FA8CC9 /* Tests+Aux.swift in Sources */,
@@ -856,6 +871,7 @@
856871
isa = PBXSourcesBuildPhase;
857872
buildActionMask = 2147483647;
858873
files = (
874+
650BF6E124D7D8E4001C531A /* UniquenessPolicyTests.swift in Sources */,
859875
6582326920D8136E00FA8CC9 /* AsyncOperationTests.swift in Sources */,
860876
6582326A20D8136E00FA8CC9 /* BackgroundQueueTests.swift in Sources */,
861877
6582326B20D8136E00FA8CC9 /* Tests+Aux.swift in Sources */,
@@ -971,6 +987,7 @@
971987
DEBUG_INFORMATION_FORMAT = dwarf;
972988
ENABLE_STRICT_OBJC_MSGSEND = YES;
973989
ENABLE_TESTABILITY = YES;
990+
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../Carthage/build/$(PLATFORM)";
974991
GCC_C_LANGUAGE_STANDARD = gnu99;
975992
GCC_DYNAMIC_NO_PIC = NO;
976993
GCC_NO_COMMON_BLOCKS = YES;
@@ -993,9 +1010,9 @@
9931010
SDKROOT = iphoneos;
9941011
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
9951012
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
996-
SWIFT_VERSION = 5.1;
1013+
SWIFT_VERSION = 5.3;
9971014
TVOS_DEPLOYMENT_TARGET = 10.0;
998-
VERSION = 1.3.4;
1015+
VERSION = 1.4.0;
9991016
};
10001017
name = Debug;
10011018
};
@@ -1036,6 +1053,7 @@
10361053
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
10371054
ENABLE_NS_ASSERTIONS = NO;
10381055
ENABLE_STRICT_OBJC_MSGSEND = YES;
1056+
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../Carthage/build/$(PLATFORM)";
10391057
GCC_C_LANGUAGE_STANDARD = gnu99;
10401058
GCC_NO_COMMON_BLOCKS = YES;
10411059
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -1050,10 +1068,10 @@
10501068
PRODUCT_NAME = Backgroundable;
10511069
SDKROOT = iphoneos;
10521070
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
1053-
SWIFT_VERSION = 5.1;
1071+
SWIFT_VERSION = 5.3;
10541072
TVOS_DEPLOYMENT_TARGET = 10.0;
10551073
VALIDATE_PRODUCT = YES;
1056-
VERSION = 1.3.4;
1074+
VERSION = 1.4.0;
10571075
};
10581076
name = Release;
10591077
};

README.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
# Backgroundable [![Version](https://img.shields.io/badge/Version-1.3.4-black.svg?style=flat)](#installation) [![License](https://img.shields.io/cocoapods/l/Backgroundable.svg?style=flat)](#license)
1+
# Backgroundable [![Version](https://img.shields.io/badge/Version-1.4.0-black.svg?style=flat)](#installation) [![License](https://img.shields.io/cocoapods/l/Backgroundable.svg?style=flat)](#license)
22

33
[![Platforms](https://img.shields.io/badge/Platforms-iOS|tvOS|macOS|Linux-brightgreen.svg?style=flat)](#installation)
4-
[![Swift support](https://img.shields.io/badge/Swift-3.3%20%7C%204.1%20%7C%204.2-red.svg?style=flat)](#swift-versions-support)
4+
[![Swift support](https://img.shields.io/badge/Swift-4.2%20%7C%205.3-red.svg?style=flat)](#swift-versions-support)
55
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Backgroundable.svg?style=flat&label=CocoaPods)](https://cocoapods.org/pods/Backgroundable)
66
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
77
[![Swift Package Manager compatible](https://img.shields.io/badge/SPM-compatible-orange.svg?style=flat)](https://github.com/apple/swift-package-manager)
@@ -154,6 +154,8 @@ AsyncOperation(timeout: 20, { (op) in
154154
})
155155
```
156156

157+
Optionally, you can set the `onTimeoutCallback:` when instantiating a new `AsyncOperation` to be notified when your operations times out.
158+
157159
### Cancelations
158160

159161
As per [Apple's documentation](https://developer.apple.com/documentation/foundation/operation/1408418-iscancelled), it's always a good idea to check if your operation has been cancelled during the execution of its closure and shortcircuit it prematurely if needed. For example:
@@ -168,20 +170,38 @@ AsyncOperation({ (op) in
168170
})
169171
```
170172

173+
### Uniqueness Policy
174+
175+
The uniqueness policy dictates whether `AsyncOperation`s with the same `name` should co-exist in a `BackgroundQueue`. This is great for deduplicating operations, for example:
176+
177+
```swift
178+
@IBAction func refresh(_ sender: UIRefreshControl) {
179+
let op = AsyncOperation(name: "Call to API endpoint /xyz", uniquenessPolicy: .drop) { op in
180+
//make the call to the API
181+
op.finish()
182+
}
183+
OperationQueue.background.addOperation(op)
184+
}
185+
```
186+
187+
This first time the user activates the refresh control, the operation will be added to the queue as normal, because there are no other operations with the name `"Call to API endpoint /xyz"` there yet. But if the user activates the control again before the first call to the API returns, then the `.drop` policy will make sure that a second operation is not added to the queue, since there's one operation with that name in there already. If `.replace` is set, then the previous operation is cancelled and the new one replaces it.
188+
189+
Neat!
190+
171191
## Installation
172192

173193
### Cocoapods
174194

175195
```ruby
176-
pod 'Backgroundable', '~> 1.3'
196+
pod 'Backgroundable', '~> 1.4'
177197
```
178198

179199
Then `import Backgroundable` where needed.
180200

181201
### Carthage
182202

183203
```swift
184-
github "BellAppLab/Backgroundable" ~> 1.3
204+
github "BellAppLab/Backgroundable" ~> 1.4
185205
```
186206

187207
Then `import Backgroundable` where needed.
@@ -190,7 +210,7 @@ Then `import Backgroundable` where needed.
190210

191211
```swift
192212
dependencies: [
193-
.package(url: "https://github.com/BellAppLab/Backgroundable", from: "1.3")
213+
.package(url: "https://github.com/BellAppLab/Backgroundable", from: "1.4")
194214
]
195215
```
196216

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// Backgroundable+Operation.swift
3+
// Example
4+
//
5+
// Created by André Campana on 02/08/2020.
6+
// Copyright © 2020 Bell App Lab. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
12+
@nonobjc
13+
extension Operation {
14+
/**
15+
Compares two `Operation`s and returns the one which should be cancelled.
16+
17+
- parameters:
18+
- operation: The second operation to be compared to the receiver.
19+
20+
- returns:
21+
- The operation that should be cancelled between the received and the other operation, according to the receiver's `uniquenessPolicy` flag. If the receiver is not an instance of `AsyncOperation`, `nil` is returned.
22+
23+
## See Also:
24+
- `AsyncOperationUniquenessPolicy`
25+
*/
26+
func operationToCancel(_ operation: Operation) -> Operation?
27+
{
28+
guard let policy = (self as? AsyncOperation)?.uniquenessPolicy else { return nil }
29+
guard let name = name, let otherName = operation.name, name == otherName else { return nil }
30+
guard isFinished == false, operation.isFinished == false else { return nil }
31+
guard isCancelled == false, operation.isCancelled == false else { return nil }
32+
guard isExecuting == false else { return nil }
33+
34+
switch policy {
35+
case .drop, .ignore: return nil
36+
case .replace: return operation
37+
}
38+
}
39+
40+
/**
41+
Compares two `Operation`s and returns the one which should be added to a queue.
42+
43+
- parameters:
44+
- operation: The second operation to be compared to the receiver.
45+
46+
- returns:
47+
- The operation that should be added between the received and the other operation, according to the receiver's `uniquenessPolicy` flag. If the receiver is not an instance of `AsyncOperation`, `self` is returned.
48+
49+
## See Also:
50+
- `AsyncOperationUniquenessPolicy`
51+
*/
52+
func operationToAdd(_ operation: Operation) -> Operation?
53+
{
54+
guard let policy = (self as? AsyncOperation)?.uniquenessPolicy else { return self }
55+
guard let name = name, let otherName = operation.name, name == otherName else { return self }
56+
guard isFinished == false else { return nil }
57+
guard operation.isFinished == false else { return self }
58+
guard isCancelled == false else { return nil }
59+
guard operation.isCancelled == false else { return self }
60+
guard isExecuting == false else { return nil }
61+
62+
63+
switch policy {
64+
case .drop, .ignore: return nil
65+
case .replace: return self
66+
}
67+
}
68+
}

Sources/Backgroundable/Backgroundable+OperationQueue.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,17 @@ public extension OperationQueue
7676
- timeout: The time in seconds after which this operation should be marked as finished and removed from the queue. Defaults to 10.
7777
- closure: The closure to be executed by the operation. The closure takes a `AsyncOperation` parameter. Call `finish()` on the object passed here.
7878
*/
79-
@nonobjc
79+
@objc(addAsyncOperationWithName:timeout:onTimeoutBlock:uniquenessPolicy:andBlock:)
8080
func addAsyncOperation(name: String? = nil,
8181
timeout: TimeInterval = 10,
82-
_ closure: @escaping (_ operation: AsyncOperation) -> Swift.Void)
82+
onTimeoutCallback: AsyncOperationClosure? = nil,
83+
uniquenessPolicy: AsyncOperationUniquenessPolicy = .default,
84+
_ closure: @escaping AsyncOperationClosure)
8385
{
84-
addOperation(AsyncOperation(name: name, timeout: timeout, closure))
86+
addOperation(AsyncOperation(name: name,
87+
timeout: timeout,
88+
onTimeoutCallback: onTimeoutCallback,
89+
uniquenessPolicy: uniquenessPolicy,
90+
closure))
8591
}
8692
}

0 commit comments

Comments
 (0)