├── .swift-version
├── Gemfile
├── Package.swift
├── Example.playground
├── Contents.o
├── contents.xcplayground
└── Pages
│ ├── URL Session.xcplaygroundpage
│ └── Contents.swift
│ └── Authentication Simulation.xcplaygroundpage
│ └── Contents.swift
├── TaskQueue.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── WorkspaceSettings.xcsettings
│ └── IDEWorkspaceChecks.plist
├── TaskQueue.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcuserdata
│ └── agrosam.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── xcshareddata
│ └── xcschemes
│ │ └── TaskQueue.xcscheme
└── project.pbxproj
├── Sources
├── TaskQueue.h
├── Info.plist
└── TaskQueue.swift
├── .gitignore
├── LICENSE.md
├── Tests
├── Info.plist
└── TaskQueueTests.swift
├── cdTaskQueue.podspec
├── .travis.yml
└── README.md
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.2
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org' do
2 | gem 'xcpretty'
3 | end
4 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | import PackageDescription
2 |
3 | let package = Package(
4 | name: "TaskQueue"
5 | )
6 |
--------------------------------------------------------------------------------
/Example.playground/Contents.o:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/couchdeveloper/TaskQueue/HEAD/Example.playground/Contents.o
--------------------------------------------------------------------------------
/TaskQueue.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TaskQueue.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TaskQueue.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Example.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/TaskQueue.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Sources/TaskQueue.h:
--------------------------------------------------------------------------------
1 | //
2 | // TaskQueue.h
3 | // TaskQueue
4 | //
5 | // Created by Andreas Grosam on 20.04.18.
6 | // Copyright © 2018 Andreas Grosam. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for TaskQueue.
12 | FOUNDATION_EXPORT double TaskQueueVersionNumber;
13 |
14 | //! Project version string for TaskQueue.
15 | FOUNDATION_EXPORT const unsigned char TaskQueueVersionString[];
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X folder attributes
2 | .DS_Store
3 |
4 | # Xcode's folder for intermediate data:
5 | Build
6 | build
7 | DerivedData
8 |
9 | # temp nibs and swap files
10 | *~.nib
11 | *.swp
12 |
13 |
14 | #exclude generated documentation files in folder "doc" and ""docs":
15 | doc/
16 | docs/
17 |
18 | ignored
19 |
20 | # Xcode's user settings
21 | *.mode1v3
22 | *.mode2v3
23 | *.pbxuser
24 | *.perspectivev3
25 | xcuserdata
26 | project.xcworkspace
27 |
28 | #fastlane
29 | test_output
30 |
31 | # Automatic backup files
32 | *~.nib/
33 | *.swp
34 | *~
35 | *.dat
36 | *.dep
37 |
38 | # Cocoapods
39 | Pods
40 | Carthage
41 |
42 | # AppCode specific files
43 | .idea/
44 | *.iml
45 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | If not otherwise noted, the content in this package is licensed
2 | under the following license:
3 |
4 | Copyright 2017 Andreas Grosam
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 |
--------------------------------------------------------------------------------
/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 0.13.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/cdTaskQueue.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = 'cdTaskQueue'
3 | spec.version = '0.13.0'
4 | spec.summary = 'A TaskQueue controls the execution of asynchronous functions.'
5 | spec.license = 'Apache License, Version 2.0'
6 | spec.homepage = 'https://github.com/couchdeveloper/TaskQueue'
7 | spec.authors = { 'Andreas Grosam' => 'couchdeveloper@gmail.com' }
8 | spec.source = { :git => 'https://github.com/couchdeveloper/TaskQueue.git', :tag => "#{spec.version}" }
9 |
10 | spec.osx.deployment_target = '10.10'
11 | spec.ios.deployment_target = '9.0'
12 | spec.tvos.deployment_target = '10.0'
13 | spec.watchos.deployment_target = '3.0'
14 |
15 | spec.module_name = 'TaskQueue'
16 | spec.source_files = "Sources/*.swift"
17 |
18 | spec.requires_arc = true
19 | end
20 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 |
3 | branches:
4 | only:
5 | - master
6 |
7 | xcode_project: TaskQueue.xcodeproj
8 | xcode_scheme: TaskQueue
9 | osx_image: xcode10.1
10 |
11 | script:
12 | - xcrun xcodebuild test -project TaskQueue.xcodeproj -scheme TaskQueue -destination 'arch=x86_64' | xcpretty
13 | - xcrun xcodebuild test -project TaskQueue.xcodeproj -scheme TaskQueue -destination 'platform=iOS Simulator,name=iPhone 6' | xcpretty
14 | - xcrun xcodebuild test -project TaskQueue.xcodeproj -scheme TaskQueue -destination 'platform=iOS Simulator,name=iPhone 6,OS=9.3' | xcpretty
15 | - xcrun xcodebuild test -project TaskQueue.xcodeproj -scheme TaskQueue -destination 'platform=tvOS Simulator,name=Apple TV 1080p' | xcpretty
16 | - xcrun xcodebuild build -quiet -project TaskQueue.xcodeproj -scheme TaskQueue -destination 'platform=watchOS Simulator,name=Apple Watch - 38mm' | xcpretty
17 |
--------------------------------------------------------------------------------
/TaskQueue.xcodeproj/xcuserdata/agrosam.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | TaskQueue.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | A1B4E5C31E9EA2AD008B5969
16 |
17 | primary
18 |
19 |
20 | A1B4E5D81EA0DE67008B5969
21 |
22 | primary
23 |
24 |
25 | A1B4E5E01EA0DE67008B5969
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Sources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.13.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2017 Andreas Grosam. All rights reserved.
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example.playground/Pages/URL Session.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: Playground - noun: a place where people can play
2 |
3 | import TaskQueue
4 | import Foundation
5 | import PlaygroundSupport
6 | PlaygroundPage.current.needsIndefiniteExecution = true
7 |
8 |
9 | extension String: Swift.Error {}
10 |
11 | func get(_ url: URL) -> (_ completion: @escaping ((Data?, URLResponse?, Error?)) -> ()) -> () {
12 | return { completion in
13 | URLSession.shared.dataTask(with: url) { data, response, error in
14 | completion((data, response, error))
15 | }.resume()
16 | }
17 | }
18 |
19 | let taskQueue = TaskQueue(maxConcurrentTasks: 4)
20 | let urlString = "https://www.example.com"
21 | guard let url = URL(string: urlString) else {
22 | fatalError("URL not valid")
23 | }
24 | taskQueue.enqueue(task: get(url)) { (data, response, error) in
25 | guard error == nil, let data = data else {
26 | print(String(describing: error ?? "data is nil"))
27 | return
28 | }
29 | let html = String(data: data, encoding: .utf8) ?? ""
30 | print(html)
31 | }
32 |
--------------------------------------------------------------------------------
/TaskQueue.xcodeproj/xcshareddata/xcschemes/TaskQueue.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
65 |
71 |
72 |
73 |
74 |
75 |
76 |
82 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/Example.playground/Pages/Authentication Simulation.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: [Previous](@previous)
2 |
3 | import TaskQueue
4 | import Foundation
5 | import PlaygroundSupport
6 | PlaygroundPage.current.needsIndefiniteExecution = true
7 |
8 | // Clientside representation of an acccess token
9 | enum AccessToken {
10 | case unknown, expired
11 | }
12 | var _accessToken = AccessToken.unknown
13 | func getAccessToken() -> AccessToken {
14 | return syncQueue.sync {
15 | return _accessToken
16 | }
17 | }
18 | func setAccessToken(_ accessToken: AccessToken) {
19 | syncQueue.sync {
20 | _accessToken = accessToken
21 | }
22 | }
23 |
24 | // Serveride algorithm to check if access token is expired
25 | var _expirationDate = Date().addingTimeInterval(0.8)
26 | func getServerSideAccessTokenIsValid() -> Bool {
27 | return syncQueue.sync {
28 | let valid = Date().compare(_expirationDate) == .orderedAscending
29 | return valid
30 | }
31 | }
32 | func refreshServerSideAccessToken() {
33 | syncQueue.sync {
34 | _expirationDate = Date().addingTimeInterval(30)
35 | }
36 | }
37 |
38 |
39 | let syncQueue = DispatchQueue(label: "sync_queue")
40 |
41 |
42 |
43 | // Request the client must perform to get a new access token
44 | func refreshTokenRequest(completion: @escaping ((AccessToken?, Swift.Error?)) -> ()) {
45 | let accessToken = getAccessToken()
46 | if accessToken != .expired {
47 | //print("refresh token request not started due to access token is not expired")
48 | DispatchQueue.global().async {
49 | completion((.unknown, nil))
50 | }
51 | return
52 | }
53 | print("start refresh token request")
54 | DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
55 | print("completed refresh token request")
56 | refreshServerSideAccessToken()
57 | completion((.unknown, nil))
58 | }
59 | }
60 |
61 | enum RequestError: Swift.Error {
62 | case accessTokenExpired
63 | }
64 |
65 | // Fetch a protected resource which require authentication
66 | func resourceRequest(_ url: String) -> (_ completion: @escaping ((String?, Error?)) -> ()) -> () {
67 | return { completion in
68 | if getAccessToken() == .expired {
69 | //print("[\(url)] resource request not started due to access token expired")
70 | completion((nil, RequestError.accessTokenExpired))
71 | return
72 | }
73 | //print("[\(url)] start resource request")
74 | DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
75 | if getServerSideAccessTokenIsValid() {
76 | //print("[\(url)] completed resource request (succeeded)")
77 | completion(("[\(url)] Success", nil))
78 | } else {
79 | //print("[\(url)] completed resource request (failed due to access token expired)")
80 | completion((nil, RequestError.accessTokenExpired))
81 | }
82 | }
83 | }
84 | }
85 |
86 |
87 |
88 | // A TaskQueue where all network request will be enqueued. It's the target queue of all other queues.
89 | let sessionQueue = TaskQueue(maxConcurrentTasks: 4)
90 |
91 | // A TaskQueue where resource request will be enqueued.
92 | let requestQueue = TaskQueue(maxConcurrentTasks: 4, targetQueue: sessionQueue)
93 |
94 | // A TaskQueue where resource requests will be enqueued which previously failed
95 | // do to an expired access token.
96 | let retryQueue = TaskQueue(maxConcurrentTasks: 4, targetQueue: sessionQueue)
97 |
98 | // A TaskQueue where the refresh token request will be enqueued.
99 | let refreshTokenRequestQueue = TaskQueue(maxConcurrentTasks: 1)
100 |
101 | // Enqueues a number of resource requests. If a request fails due to an expired
102 | // access token, the request will be put into the suspended retry queue, the request
103 | // queue will be suspended and a refresh token request will be performed. When this
104 | // finished, the retry queue and the resource queue will be resumed again which
105 | // continues to start the enqueued requests.
106 | func enqueueResourceRequests(urls: [String]) {
107 | for i in urls {
108 | print("Enqueue resource request \(i)")
109 | let task = resourceRequest(String(describing: i))
110 | let completion: ((String?, Error?)) -> () = { result in
111 | print("[\(i)] completed resource request with result: \(result)")
112 | }
113 | requestQueue.enqueue(task: task) { result in
114 | switch result.1 {
115 | case let error as RequestError where error == .accessTokenExpired:
116 | setAccessToken(AccessToken.expired)
117 | retryQueue.suspend()
118 | requestQueue.suspend()
119 | //print("Enqueue resource retry request \(i)")
120 | retryQueue.enqueue(task: task, completion: completion)
121 | refreshTokenRequestQueue.enqueue(task: refreshTokenRequest) { result in
122 | defer {
123 | retryQueue.enqueue(task: { (Void) -> () in
124 | requestQueue.resume()
125 | }, completion: {})
126 | retryQueue.resume()
127 | }
128 | guard let accessToken = result.0 else {
129 | return
130 | }
131 | setAccessToken(accessToken)
132 | }
133 | default:
134 | completion(result)
135 | }
136 | }
137 | }
138 | }
139 |
140 |
141 |
142 | // Define some "urls" and enqueue them:
143 | let urls = (0..<32).map { "\($0)" }
144 | enqueueResourceRequests(urls: urls)
145 |
146 | //: [Next](@next)
147 |
--------------------------------------------------------------------------------
/Sources/TaskQueue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TaskQueue.swift
3 | //
4 | // Copyright © 2017 Andreas Grosam. All rights reserved.
5 | //
6 |
7 | import Dispatch
8 |
9 |
10 | /**
11 | A `TaskQueue` is a FIFO queue where _tasks_ can be enqueued for execution. The
12 | tasks will be executed in order up to `maxConcurrentTasks` concurrently. The start
13 | of a task will be delayed up until the current number of running tasks is below
14 | the allowed maximum number of concurrent tasks.
15 |
16 | When a task completes it will be automatically dequeued and its completion handler
17 | will be called.
18 |
19 | The allowed maximum number of concurrent tasks can be changed while tasks are
20 | executing.
21 | `
22 | A _task_ is simply a non-throwing asynchronous function with a single parameter
23 | `completion` which is a function type. The completion function has a single parameter
24 | which desginates the result type of the task. On completion, the task MUST call
25 | the completion handler passing the result. Usually, the result type is a discriminated
26 | union (aka Enum) which contains either a value or an error.
27 | */
28 | public class TaskQueue {
29 |
30 | private let _queue: DispatchQueue
31 | private var _maxConcurrentTasks: UInt = 1
32 | private var _concurrentTasks: UInt = 0
33 | private let _group = DispatchGroup()
34 | private let _syncQueue = DispatchQueue(label: "task_queue.sync_queue")
35 | public private(set) var targetQueue: TaskQueue?
36 |
37 |
38 | /// Designated initializer.
39 | ///
40 | /// - Parameter maxConcurrentTasks: The number of tasks which can be executed concurrently.
41 | public init(maxConcurrentTasks: UInt = 1, targetQueue: TaskQueue? = nil) {
42 | self._queue = DispatchQueue(label: "task_queue.queue", target: _syncQueue)
43 | _maxConcurrentTasks = maxConcurrentTasks
44 | self.targetQueue = targetQueue
45 | }
46 |
47 |
48 | /// Enqueues the given task and returns immediately.
49 | ///
50 | /// The task will be executed when the current number of active tasks is
51 | /// smaller than `maxConcurrentTasks`.
52 | /// - Parameters:
53 | /// - task: The task which will be enqueued.
54 | /// - queue: The dispatch queue where the task should be started.
55 | /// - completion: The completion handler which will be executed when the task completes. It will be executed on whatever execution context the task has been choosen.
56 | /// - Parameters:
57 | /// - result: The task's result.
58 | /// - Attention: There's no upper limit for the number of enqueued tasks. An enqueued task may reference resources and other objects which will be only released when the task has been completed.
59 | public final func enqueue(task: @escaping (@escaping (T)->())->(), queue q: DispatchQueue = DispatchQueue.global(), completion: @escaping (_ result: T)->()) {
60 | self._queue.async {
61 | self.execute(task: { c in q.async {task(c)} } ) { result in
62 | completion(result)
63 | }
64 | }
65 | }
66 |
67 |
68 | /// Executes the given task and returns immediately.
69 | ///
70 | /// - Parameters:
71 | /// - task: The task which will be enqueued.
72 | /// - queue: The dispatch queue where the task should be started.
73 | /// - completion: The completion handler that will be executed when the task completes.
74 | private final func execute(task: @escaping (@escaping (T)->())->(), completion: @escaping (T)->()) {
75 | assert(_concurrentTasks < _maxConcurrentTasks)
76 | _concurrentTasks += 1
77 | if _concurrentTasks == _maxConcurrentTasks {
78 | self._queue.suspend()
79 | }
80 | self._group.enter()
81 | let _completion: (T)->() = { result in
82 | self._syncQueue.async {
83 | if self._concurrentTasks == self._maxConcurrentTasks {
84 | self._queue.resume()
85 | }
86 | self._concurrentTasks -= 1
87 | self._group.leave()
88 | }
89 | completion(result)
90 | }
91 | if let targetQueue = self.targetQueue {
92 | targetQueue.enqueue(task: task, completion: _completion)
93 | } else {
94 | task(_completion)
95 | }
96 | }
97 |
98 |
99 | /// Enqueues the given function for barrier execution and returns immediately.
100 | ///
101 | /// A barrier function allows you to create a synchronization point within the
102 | /// `TaskQueue`. When the `TaskQueue` encounters a barrier function, it delays
103 | /// the execution of the barrier function and any further tasks until all tasks
104 | /// enqueued before the barrier finish executing. At that point, the barrier
105 | /// function executes exclusively. Upon completion, the `TaskQueue` resumes
106 | /// its normal execution behavior.
107 | ///
108 | /// - Parameters:
109 | /// - queue: The dispatch queue where the barrier should be executed.
110 | /// - f: The barrier function.
111 | public final func enqueueBarrier(queue q: DispatchQueue = DispatchQueue.global(), f: @escaping ()->()) {
112 | //print("enqueue barrier")
113 | func barrier(_ completion: (())->()) {
114 | self._queue.suspend()
115 | self._group.notify(queue: q) {
116 | f()
117 | self._syncQueue.async {
118 | self._queue.resume()
119 | }
120 | }
121 | completion(())
122 | }
123 | self._queue.async {
124 | self.execute(task: barrier, completion: {})
125 | }
126 | }
127 |
128 |
129 | /// Sets or returns the number of concurrently executing tasks.
130 | public final var maxConcurrentTasks: UInt {
131 | get {
132 | var result: UInt = 0
133 | _syncQueue.sync {
134 | result = self._maxConcurrentTasks
135 | }
136 | return result
137 | }
138 | set (value) {
139 | _syncQueue.async {
140 | self._maxConcurrentTasks = value
141 | }
142 | }
143 | }
144 |
145 |
146 | /// Returns the number of tasks currently running.
147 | public final var countRunningTasks: UInt {
148 | return _syncQueue.sync {
149 | return self._concurrentTasks
150 | }
151 | }
152 |
153 |
154 | /// Suspends the invokation of pending tasks.
155 | ///
156 | /// When suspending a TaskQueue, pending tasks can be temporarily delayed for
157 | /// execution. Tasks already running will not be affected.
158 | /// Calling this function will increment an internal suspension counter, while
159 | /// calling `resume()` will decrement it. While this counter is greater than
160 | /// zero the task queue remains suspended.
161 | public final func suspend() {
162 | self._queue.suspend()
163 | }
164 |
165 | /// Resumes the invokation of pending tasks.
166 | ///
167 | /// Calling this function will decrement an internal suspension counter, while
168 | /// calling `suspend()` will increment it. While this counter is greater than
169 | /// zero the task queue remains suspended. Only when this counter will become
170 | /// zero the task queue will resume its operation and start pending tasks.
171 | public final func resume() {
172 | self._queue.resume()
173 | }
174 |
175 | }
176 |
177 |
178 |
--------------------------------------------------------------------------------
/Tests/TaskQueueTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TaskQueueTests.swift
3 | // TaskQueueTests
4 | //
5 | // Created by Andreas Grosam on 14.04.17.
6 | // Copyright © 2017 Andreas Grosam. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import Dispatch
11 | import Foundation
12 | @testable import TaskQueue
13 |
14 |
15 | func task(id: Int, delay: Double = 1.0) -> (_ completion: @escaping (Int) -> ()) -> () {
16 | return { completion in
17 | DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
18 | completion(id)
19 | }
20 | }
21 | }
22 |
23 | class TaskQueueTests: XCTestCase {
24 |
25 | func testEnqueuedTaskWillComplete1() {
26 | let taskQueue = TaskQueue()
27 | let expect = expectation(description: "completed")
28 | taskQueue.enqueue(task: task(id: 1)) {id in
29 | XCTAssertEqual(1, id)
30 | expect.fulfill()
31 | }
32 | waitForExpectations(timeout: 1)
33 | }
34 |
35 |
36 | func testEnqueuedTaskWillComplete2() {
37 | let taskQueue = TaskQueue()
38 | let expect1 = expectation(description: "completed 1")
39 | let expect2 = expectation(description: "completed 2")
40 | taskQueue.enqueue(task: task(id: 1)) { id in
41 | XCTAssertEqual(1, id)
42 | expect1.fulfill()
43 | }
44 | taskQueue.enqueue(task: task(id: 2)) { id in
45 | XCTAssertEqual(2, id)
46 | expect2.fulfill()
47 | }
48 | waitForExpectations(timeout: 10)
49 | }
50 |
51 |
52 | func ensureMaxConcurrentTasks(taskQueue: TaskQueue, numberTasks: Int, maxConcurrentTasks: UInt) {
53 | let sem = DispatchSemaphore(value: Int(maxConcurrentTasks))
54 | let syncQueue = DispatchQueue(label: "sync_queue")
55 | var actualMaxConcurrentTasks: Int = 0
56 |
57 | func task(id: Int) -> (_ completion: @escaping (Int) -> ()) -> () {
58 | return { completion in
59 | if case .timedOut = sem.wait(timeout: .now()) {
60 | XCTFail("semaphore overuse")
61 | }
62 | syncQueue.sync {
63 | actualMaxConcurrentTasks = max(actualMaxConcurrentTasks, Int(taskQueue.countRunningTasks))
64 | }
65 | DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
66 | sem.signal()
67 | completion(id)
68 | }
69 | }
70 | }
71 |
72 | (1...numberTasks).forEach { id in
73 | let expect = expectation(description: "completed")
74 | taskQueue.enqueue(task: task(id: id), completion: { _ in
75 | expect.fulfill()
76 | })
77 | }
78 | waitForExpectations(timeout: 10)
79 | XCTAssertEqual(maxConcurrentTasks, UInt(actualMaxConcurrentTasks))
80 | }
81 |
82 |
83 | func testMaxConcurrentTasks1() {
84 | let maxConcurrentTasks: UInt = 1
85 | let taskQueue = TaskQueue(maxConcurrentTasks: maxConcurrentTasks)
86 | ensureMaxConcurrentTasks(taskQueue: taskQueue, numberTasks: 8, maxConcurrentTasks: maxConcurrentTasks)
87 | }
88 |
89 |
90 | func testMaxConcurrentTasks2() {
91 | let maxConcurrentTasks: UInt = 2
92 | let taskQueue = TaskQueue(maxConcurrentTasks: maxConcurrentTasks)
93 | ensureMaxConcurrentTasks(taskQueue: taskQueue, numberTasks: 16, maxConcurrentTasks: maxConcurrentTasks)
94 | }
95 |
96 |
97 | func testMaxConcurrentTasks3() {
98 | let maxConcurrentTasks: UInt = 3
99 | let taskQueue = TaskQueue(maxConcurrentTasks: maxConcurrentTasks)
100 | ensureMaxConcurrentTasks(taskQueue: taskQueue, numberTasks: 24, maxConcurrentTasks: maxConcurrentTasks)
101 | }
102 |
103 |
104 | func ensureBarrier(maxConcurrentTasks: UInt , before: Int, after: Int) {
105 | let syncQueue = DispatchQueue(label: "snyc_queue")
106 | var expectedSequence: [Int] = (0.. (_ completion: @escaping (Int) -> ()) -> () {
110 | return { completion in
111 | syncQueue.asyncAfter(deadline: .now() + 0.1) {
112 | guard let x = expectedSequence.first else {
113 | XCTFail("out of sequence")
114 | completion(-1)
115 | return
116 | }
117 | expectedSequence = Array(expectedSequence.dropFirst())
118 | XCTAssertEqual(tag, x)
119 | completion(x)
120 | }
121 | }
122 | }
123 |
124 | (1...before).forEach { id in
125 | let expect = expectation(description: "completed")
126 | taskQueue.enqueue(task: task(tag: 0), queue: syncQueue, completion: { _ in
127 | expect.fulfill()
128 | })
129 | }
130 | // When all "before" tasks finished, signal the sem:
131 | taskQueue.enqueueBarrier(queue: syncQueue) {
132 | guard let x = expectedSequence.first else {
133 | XCTFail("out of sequence")
134 | return
135 | }
136 | expectedSequence = Array(expectedSequence.dropFirst())
137 | XCTAssertEqual(1, x)
138 | sleep(1)
139 | expect.fulfill()
140 | }
141 | (1...after).forEach { id in
142 | let expect = expectation(description: "completed")
143 | taskQueue.enqueue(task: task(tag: 2), completion: { _ in
144 | expect.fulfill()
145 | })
146 | }
147 | waitForExpectations(timeout: 10)
148 | }
149 |
150 |
151 | func testBarrier1() {
152 | ensureBarrier(maxConcurrentTasks: 1, before: 4, after: 4)
153 | }
154 |
155 |
156 | func testBarrier2() {
157 | ensureBarrier(maxConcurrentTasks: 2, before: 8, after: 8)
158 | }
159 |
160 |
161 | func testBarrier3() {
162 | ensureBarrier(maxConcurrentTasks: 3, before: 12, after: 12)
163 | }
164 |
165 |
166 | func testBarrier4() {
167 | ensureBarrier(maxConcurrentTasks: 4, before: 16, after: 16)
168 | }
169 |
170 |
171 | func testChangeMaxConcurrentTasks() {
172 | let taskQueue = TaskQueue(maxConcurrentTasks: 1)
173 | ensureMaxConcurrentTasks(taskQueue: taskQueue, numberTasks: 4, maxConcurrentTasks: 1)
174 | taskQueue.enqueueBarrier {
175 | taskQueue.maxConcurrentTasks = 8
176 | }
177 | ensureMaxConcurrentTasks(taskQueue: taskQueue, numberTasks: 32, maxConcurrentTasks: 8)
178 | }
179 |
180 | func testSuspendedTaskQueueDelaysExecutionOfTasks1() {
181 | let taskQueue = TaskQueue(maxConcurrentTasks: 1)
182 | taskQueue.suspend()
183 | let sem = DispatchSemaphore(value: 0)
184 | taskQueue.enqueue(task: task(id: 0, delay: 0.1)) { id in
185 | sem.signal()
186 | }
187 | XCTAssertTrue(sem.wait(timeout: .now() + 0.5) == .timedOut)
188 | taskQueue.resume()
189 | XCTAssertFalse(sem.wait(timeout: .now() + 0.5) == .timedOut)
190 | }
191 |
192 | func testSuspendedTaskQueueDelaysExecutionOfTasks2() {
193 | let taskQueue = TaskQueue(maxConcurrentTasks: 8)
194 | taskQueue.suspend()
195 | let sem = DispatchSemaphore(value: 0)
196 | for i in 0..<8 {
197 | taskQueue.enqueue(task: task(id: i, delay: 0)) { _ in }
198 | }
199 | taskQueue.enqueueBarrier {
200 | sem.signal()
201 | }
202 | XCTAssertTrue(sem.wait(timeout: .now() + 0.5) == .timedOut)
203 | taskQueue.resume()
204 | XCTAssertFalse(sem.wait(timeout: .now() + 0.5) == .timedOut)
205 | }
206 |
207 | func testSuspendedTaskQueueDelaysExecutionOfTasks3() {
208 | let taskQueue = TaskQueue(maxConcurrentTasks: 8)
209 | taskQueue.suspend()
210 | taskQueue.suspend()
211 | taskQueue.suspend()
212 | let sem = DispatchSemaphore(value: 0)
213 | for i in 0..<8 {
214 | taskQueue.enqueue(task: task(id: i, delay: 0)) { _ in }
215 | }
216 | taskQueue.enqueueBarrier {
217 | sem.signal()
218 | }
219 | XCTAssertTrue(sem.wait(timeout: .now() + 0.5) == .timedOut)
220 | taskQueue.resume()
221 | XCTAssertTrue(sem.wait(timeout: .now() + 0.5) == .timedOut)
222 | taskQueue.resume()
223 | XCTAssertTrue(sem.wait(timeout: .now() + 0.5) == .timedOut)
224 | taskQueue.resume()
225 | XCTAssertFalse(sem.wait(timeout: .now() + 0.5) == .timedOut)
226 | }
227 |
228 |
229 | func testSuspendedTaskQueueDelaysExecutionOfTasks4() {
230 | let taskQueue = TaskQueue(maxConcurrentTasks: 8)
231 | let sem = DispatchSemaphore(value: 0)
232 | taskQueue.enqueueBarrier {
233 | taskQueue.suspend()
234 | }
235 | for i in 0..<8 {
236 | taskQueue.enqueue(task: task(id: i, delay: 0)) { _ in }
237 | }
238 | taskQueue.enqueueBarrier {
239 | sem.signal()
240 | }
241 | XCTAssertTrue(sem.wait(timeout: .now() + 0.5) == .timedOut)
242 | taskQueue.resume()
243 | XCTAssertFalse(sem.wait(timeout: .now() + 0.5) == .timedOut)
244 | }
245 |
246 | }
247 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TaskQueue
2 |
3 | [](https://travis-ci.org/couchdeveloper/TaskQueue) [](http://www.apache.org/licenses/LICENSE-2.0) [](https://developer.apple.com/swift/)  [](https://github.com/Carthage/Carthage) [](https://cocoapods.org/?q=cdTaskQueue)
4 |
5 | A `TaskQueue` is basically a FIFO queue where _tasks_ can be enqueued for execution. The tasks will be executed concurrently up to an allowed maximum number.
6 |
7 | A _task_ is simply a non-throwing asynchronous function with a single parameter
8 | which is a completion handler called when the task finished.
9 |
10 | ## Features
11 | - Employs the execution of asynchronous "non-blocking" tasks.
12 | - The maximum number of concurrently executing tasks can be set, even during the execution of tasks.
13 | - Employs a "barrier" task which serves as a synchronisation point which allows us to "join" all previous enqueued tasks.
14 | - A task queue can be suspended and resumed.
15 | - A task queue can have a _target_ task queue where tasks which are ready for execution will be enqueued and the target will then become responsible for execution of the task (which again may be actually performed by another target task queue).
16 | - Task and TaskQueue can be a used as a replacement for `NSOperation` and
17 | `NSOperationQueue`.
18 |
19 | With barriers, suspend and resume functionality, target relationships and the control of the concurrency level allows us to design complex systems where the execution of asynchronous tasks can be controlled by external conditions, interdependencies and by the restrictions of system resources.
20 |
21 |
22 | ----------------------------------------
23 |
24 | ## Description
25 |
26 | With a _TaskQueue_ we can control the maximum number of concurrent tasks that run "within" the task queue. In order to accomplish this, we _enqueue_ tasks into the task queue. If the actual number of running tasks is less than the maximum, the enqueued task will be immediately executed. Otherwise it will be delayed up until enough previously enqueued tasks have been completed.
27 |
28 | At any time, we can enqueue further tasks, while the maximum number of running tasks is continuously guaranteed. Furthermore, at any time, we can change the number of maximum concurrent tasks and the task queue will adapt until the constraints are fulfilled.
29 |
30 |
31 | ## Installation
32 |
33 | > **Note:**
34 | > Swift 4.0, 3.2 and 3.1 requires slightly different syntax:
35 | For Swift 4 use version >= 0.9.0.
36 | For Swift 3.2 compatibility use version 0.8.0 and for Swift 3.1 use version 0.7.0.
37 |
38 | ### [Carthage](https://github.com/Carthage/Carthage)
39 |
40 | Add
41 | ```Ruby
42 | github "couchdeveloper/TaskQueue"
43 | ```
44 | to your Cartfile. This is appropriate for use with Swift 4, otherwise specify version constraints as noted above.
45 |
46 | In your source files, import the library as follows
47 | ```Swift
48 | import TaskQueue
49 | ```
50 |
51 |
52 |
53 | ### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
54 |
55 | Add the following line to your [Podfile](http://guides.cocoapods.org/using/the-podfile.html):
56 |
57 | ```ruby
58 | pod 'cdTaskQueue'
59 | ```
60 | This is appropriate for use with Swift 4, otherwise specify version constraints as noted above.
61 |
62 | In your source files, import the library as follows
63 | ```Swift
64 | import TaskQueue
65 | ```
66 |
67 | Note that the module name (`TaskQueue`) differs from the Podspec name (`cdTaskQueue`).
68 |
69 |
70 | ### [SwiftPM](https://github.com/apple/swift-package-manager/tree/master/Documentation)
71 |
72 | To use SwiftPM, add this to your Package.swift:
73 |
74 | ```Swift
75 | .Package(url: "https://github.com/couchdeveloper/TaskQueue.git")
76 | ```
77 |
78 |
79 | ## Usage
80 |
81 | Suppose, there one or more asynchronous _tasks_ and we want to execute them in
82 | some controlled manner. In particular, we want to make guarantees that no more
83 | than a set limit of those tasks execute concurrently. For example, many times,
84 | we just want to ensure, that only one task is running at a time.
85 |
86 | Furthermore, we want to be notified when _all_ tasks of a certain set have been
87 | completed and then take further actions, for example, based on the results,
88 | enqueue further tasks.
89 |
90 | ### So, what's a _task_ anyway?
91 |
92 | A _task_ is a Swift function or closure, which executes _asynchronously_ returns
93 | `Void` and has a _single_ parameter, the completion handler. The completion handler has a single parameter where the eventual `Result` - which is computed by the underlying operation - will be passed when the task completes.
94 |
95 | We can use any type of "Result", for example a tuple `(Value?, Error?)` or more
96 | handy types like `Result` or `Try`.
97 |
98 | **Canonical task function:**
99 |
100 | ```Swift
101 | func task(completion: @escaping (R)->()) {
102 | ...
103 | }
104 | ```
105 | where `R` is for example: `(T?, Error?)` or `Result` or `(Data?, Response?, Error?)` etc.
106 |
107 | Note, that the type `R` may represent a _Swift Tuple_, for example `(T?, Error?)`, and please not that there are syntax changes in Swift 4:
108 |
109 | > **Caution:**
110 | > In Swift 4 please consider the following changes regarding tuple parameters:
111 | If a function type has only one parameter and that parameter’s type is a tuple type, then the tuple type must be parenthesized when writing the function’s type. For example, `((Int, Int)) -> Void` is the type of a function that takes a single parameter of the tuple type `(Int, Int)` and doesn’t return any value. In contrast, without parentheses, `(Int, Int) -> Void` is the type of a function that takes two Int parameters and doesn’t return any value. Likewise, because `Void` is a type alias for `()`, the function type `(Void) -> Void` is the same as `(()) -> ()` — a function that takes a single argument that is an empty tuple. These types are not the same as `() -> ()` — a function that takes no arguments.
112 |
113 | So, this means, if the result type of the task´s completion handler is a Swift Tuple, for example `(String?, Error?)`, that task must have the following signature:
114 |
115 | ```Swift
116 | func myTask(completion: @escaping ((String?, Error?))->()) {
117 | ...
118 | }
119 | ```
120 |
121 |
122 | Now, create a task queue where we can _enqueue_ a number of those tasks. We
123 | can control the number of maximum concurrently executing tasks in the initialiser:
124 |
125 | ```Swift
126 | let taskQueue = TaskQueue(maxConcurrentTasks: 1)
127 | // Create 8 tasks and let them run:
128 | (0...8).forEach { _ in
129 | taskQueue.enqueue(task: myTask) { (String?, Error?) in
130 | ...
131 | }
132 | }
133 | ```
134 |
135 |
136 | Note, that the start of a task will be delayed up until the current number of
137 | running tasks is below the allowed maximum number of concurrent tasks.
138 |
139 | In the above code, the asynchronous tasks are effectively serialised, since the
140 | maximum number of concurrent tasks is set to `1`.
141 |
142 |
143 | ### Using a barrier
144 |
145 | A _barrier function_ allows us to create a synchronisation point within the `TaskQueue`. When the `TaskQueue` encounters a barrier function, it delays the execution of the barrier function and any further tasks until all tasks enqueued before the barrier have been completed. At that point, the barrier function executes exclusively. Upon completion, the `TaskQueue` resumes its normal execution behaviour.
146 |
147 | ```Swift
148 | let taskQueue = TaskQueue(maxConcurrentTasks: 4)
149 | // Create 8 tasks and let them run (max 4 will run concurrently):
150 | (0...8).forEach { _ in
151 | taskQueue.enqueue(task: myTask) { (String?, Error?) in
152 | ...
153 | }
154 | }
155 | taskQueue.enqueueBarrier {
156 | // This will execute exclusively on the task queue after all previously
157 | // enqueued tasks have been completed.
158 | print("All tasks finished")
159 | }
160 |
161 | // enqueue further tasks as you like
162 | ```
163 |
164 |
165 | ### Specify a Dispatch Queue Where to Start the Task
166 |
167 | Even though, a task _should_ always be designed such, that it is irrelevant on
168 | which thread it will be called, the practice is often different. Fortunately, we
169 | can specify a dispatch queue in function `enqueue` where the task will be eventually started by the task queue, if there should be such a limitation.
170 |
171 | If a queue is not specified, the task will be started on the global queue (`DispatchQueue.global()`).
172 |
173 | ```Swift
174 | taskQueue.enqueue(task: myTask, queue: DispatchQueue.main) { Result in
175 | ...
176 | }
177 | ```
178 |
179 | Note, that this affects only where the task will be _started_. The task's completion handler will be executed on whatever thread or dispatch queue the task is choosing when it completes. There's no way in `TaskQueue` to specify the execution context for the completion handler.
180 |
181 |
182 | ### Constructing a Suitable Task Function from Any Other Asynchronous Function
183 |
184 | The function signature for `enqueue` requires that we pass a _task function_ which
185 | has a single parameter `completion` and returns `Void`. The single parameter is
186 | the completion handler, that is a function, taking a single parameter or a tuple
187 | `result` and returning `Void`.
188 |
189 | So, what if our asynchronous function does not have this signature, for example,
190 | has additional parameters and even returns a result?
191 |
192 | Take a look at this asynchronous function from `URLSession`:
193 | ```Swift
194 | dataTask(with url: URL,
195 | completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void)
196 | -> URLSessionDataTask
197 | ```
198 |
199 | Here, besides the completion handler we have an additional parameter `url` which is used to _configure_ the task. It also has a return value, the created `URLSessionTask` object.
200 |
201 | In order to use this function with `TaskQueue`, we need to ensure that the task is
202 | configured at the time we enqueue it, and that it has the right signature. We can
203 | accomplish both requirements by applying _currying_ to the given function.
204 |
205 | The basic steps are as follows:
206 |
207 | Given any asynchronous function with one or more additional parameters and possibly a return value:
208 |
209 | ```Swift
210 | func asyncFoo(param: T, completion: @escaping (Result)->()) -> U {
211 | ...
212 | }
213 | ```
214 |
215 | we transform it to:
216 |
217 | ```Swift
218 | func task(param: T) -> (_ completion: @escaping (Result) -> ()) -> () {
219 | return { completion in
220 | let u = asyncFoo(param: param) { result in
221 | completion(result)
222 | }
223 | // handle return value from asyncFoo, if any.
224 | }
225 | }
226 | ```
227 |
228 | That is, we transform the above function `asyncFoo` into another, whose parameters consist only of the configuring parameters, and returning a _function_ having the single remaining parameter, the completion handler, e.g.:
229 |
230 | `((Result) -> ()) -> ()`.
231 |
232 | The signature of this returned function must be valid for the task function
233 | required by `TaskQueue`. "Result" can be a single parameter, e.g. `Result` or
234 | any tuple, e.g. `(T?, Error?)` or `(T?, U?, Error?)`, etc.
235 |
236 | Note, that any return value from the original function (here `asyncFoo`), if any,
237 | will be ignored by the task queue. It should be handled by the implementation of
238 | the task function, though.
239 |
240 | You might want to examine this snippet a couple of times to get used to it ;)
241 |
242 | Then use it as follows:
243 |
244 | ```Swift
245 | taskQueue.enqueue(task: task(param: "Param")) { result in
246 | // handle result
247 | ...
248 | }
249 | ```
250 |
251 | This ensures, that the task will be "configured" with the given parameters at the
252 | time it will be enqueued. The execution, though, will be delayed up until the task
253 | queue is ready to execute it.
254 |
255 |
256 |
257 | ### Example
258 |
259 | Here, we wrap a `URLSessionTask` executing a "GET" into a _task_ function:
260 |
261 | ```Swift
262 | func get(_ url: URL) -> (_ completion: @escaping ((Data?, URLResponse?, Error?)) -> ()) -> () {
263 | return { completion in
264 | URLSession.shared.dataTask(with: url) { data, response, error in
265 | completion((data, response, error))
266 | }.resume()
267 | }
268 | }
269 | ```
270 | Then use it as follows:
271 |
272 | ```Swift
273 | let taskQueue = TaskQueue(maxConcurrentTasks: 4)
274 | taskQueue.enqueue(task: get(url)) { (data, response, error) in
275 | // handle (data, response, error)
276 | ...
277 | }
278 | ```
279 |
280 | Having a list of urls, enqueue them all at once and execute them with the
281 | constraints set in the task queue:
282 |
283 | ```Swift
284 | let urls = [ ... ]
285 | let taskQueue = TaskQueue(maxConcurrentTasks: 1) // serialise the tasks
286 | urls.forEach {
287 | taskQueue.enqueue(task: get($0)) { (data, response, error) in
288 | // handle (data, response, error)
289 | ...
290 | }
291 | }
292 | ```
293 |
--------------------------------------------------------------------------------
/TaskQueue.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | A121391B2089F584008E327F /* TaskQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = A121391A2089F584008E327F /* TaskQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
11 | A162A9E11EA0E91600B30D72 /* TaskQueue.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1B4E5D91EA0DE67008B5969 /* TaskQueue.framework */; };
12 | A1B4E5E71EA0DE67008B5969 /* TaskQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B4E5E61EA0DE67008B5969 /* TaskQueueTests.swift */; };
13 | A1B4E5F01EA0E19A008B5969 /* TaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B4E5CE1E9EA34C008B5969 /* TaskQueue.swift */; };
14 | /* End PBXBuildFile section */
15 |
16 | /* Begin PBXContainerItemProxy section */
17 | A1B4E5E31EA0DE67008B5969 /* PBXContainerItemProxy */ = {
18 | isa = PBXContainerItemProxy;
19 | containerPortal = A1B4E5BC1E9EA2AD008B5969 /* Project object */;
20 | proxyType = 1;
21 | remoteGlobalIDString = A1B4E5D81EA0DE67008B5969;
22 | remoteInfo = TaskQueue;
23 | };
24 | /* End PBXContainerItemProxy section */
25 |
26 | /* Begin PBXCopyFilesBuildPhase section */
27 | A162A9DF1EA0E8A300B30D72 /* CopyFiles */ = {
28 | isa = PBXCopyFilesBuildPhase;
29 | buildActionMask = 2147483647;
30 | dstPath = "";
31 | dstSubfolderSpec = 10;
32 | files = (
33 | );
34 | runOnlyForDeploymentPostprocessing = 0;
35 | };
36 | /* End PBXCopyFilesBuildPhase section */
37 |
38 | /* Begin PBXFileReference section */
39 | A121391A2089F584008E327F /* TaskQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TaskQueue.h; sourceTree = ""; };
40 | A18885DA1F795C140025FF65 /* Example.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Example.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
41 | A1B4E5CE1E9EA34C008B5969 /* TaskQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskQueue.swift; sourceTree = ""; };
42 | A1B4E5D91EA0DE67008B5969 /* TaskQueue.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TaskQueue.framework; sourceTree = BUILT_PRODUCTS_DIR; };
43 | A1B4E5DC1EA0DE67008B5969 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
44 | A1B4E5E11EA0DE67008B5969 /* TaskQueueTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TaskQueueTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
45 | A1B4E5E61EA0DE67008B5969 /* TaskQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskQueueTests.swift; sourceTree = ""; };
46 | A1B4E5E81EA0DE67008B5969 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
47 | /* End PBXFileReference section */
48 |
49 | /* Begin PBXFrameworksBuildPhase section */
50 | A1B4E5D51EA0DE67008B5969 /* Frameworks */ = {
51 | isa = PBXFrameworksBuildPhase;
52 | buildActionMask = 2147483647;
53 | files = (
54 | );
55 | runOnlyForDeploymentPostprocessing = 0;
56 | };
57 | A1B4E5DE1EA0DE67008B5969 /* Frameworks */ = {
58 | isa = PBXFrameworksBuildPhase;
59 | buildActionMask = 2147483647;
60 | files = (
61 | A162A9E11EA0E91600B30D72 /* TaskQueue.framework in Frameworks */,
62 | );
63 | runOnlyForDeploymentPostprocessing = 0;
64 | };
65 | /* End PBXFrameworksBuildPhase section */
66 |
67 | /* Begin PBXGroup section */
68 | A1B4E5BB1E9EA2AD008B5969 = {
69 | isa = PBXGroup;
70 | children = (
71 | A18885DA1F795C140025FF65 /* Example.playground */,
72 | A1B4E5DA1EA0DE67008B5969 /* Sources */,
73 | A1B4E5E51EA0DE67008B5969 /* Tests */,
74 | A1B4E5C51E9EA2AD008B5969 /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | A1B4E5C51E9EA2AD008B5969 /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | A1B4E5D91EA0DE67008B5969 /* TaskQueue.framework */,
82 | A1B4E5E11EA0DE67008B5969 /* TaskQueueTests.xctest */,
83 | );
84 | name = Products;
85 | sourceTree = "";
86 | };
87 | A1B4E5DA1EA0DE67008B5969 /* Sources */ = {
88 | isa = PBXGroup;
89 | children = (
90 | A1B4E5DC1EA0DE67008B5969 /* Info.plist */,
91 | A121391A2089F584008E327F /* TaskQueue.h */,
92 | A1B4E5CE1E9EA34C008B5969 /* TaskQueue.swift */,
93 | );
94 | path = Sources;
95 | sourceTree = "";
96 | };
97 | A1B4E5E51EA0DE67008B5969 /* Tests */ = {
98 | isa = PBXGroup;
99 | children = (
100 | A1B4E5E81EA0DE67008B5969 /* Info.plist */,
101 | A1B4E5E61EA0DE67008B5969 /* TaskQueueTests.swift */,
102 | );
103 | path = Tests;
104 | sourceTree = "";
105 | };
106 | /* End PBXGroup section */
107 |
108 | /* Begin PBXHeadersBuildPhase section */
109 | A1B4E5D61EA0DE67008B5969 /* Headers */ = {
110 | isa = PBXHeadersBuildPhase;
111 | buildActionMask = 2147483647;
112 | files = (
113 | A121391B2089F584008E327F /* TaskQueue.h in Headers */,
114 | );
115 | runOnlyForDeploymentPostprocessing = 0;
116 | };
117 | /* End PBXHeadersBuildPhase section */
118 |
119 | /* Begin PBXNativeTarget section */
120 | A1B4E5D81EA0DE67008B5969 /* TaskQueue */ = {
121 | isa = PBXNativeTarget;
122 | buildConfigurationList = A1B4E5EA1EA0DE67008B5969 /* Build configuration list for PBXNativeTarget "TaskQueue" */;
123 | buildPhases = (
124 | A1B4E5D41EA0DE67008B5969 /* Sources */,
125 | A1B4E5D51EA0DE67008B5969 /* Frameworks */,
126 | A1B4E5D61EA0DE67008B5969 /* Headers */,
127 | A1B4E5D71EA0DE67008B5969 /* Resources */,
128 | );
129 | buildRules = (
130 | );
131 | dependencies = (
132 | );
133 | name = TaskQueue;
134 | productName = TaskQueue;
135 | productReference = A1B4E5D91EA0DE67008B5969 /* TaskQueue.framework */;
136 | productType = "com.apple.product-type.framework";
137 | };
138 | A1B4E5E01EA0DE67008B5969 /* TaskQueueTests */ = {
139 | isa = PBXNativeTarget;
140 | buildConfigurationList = A1B4E5ED1EA0DE67008B5969 /* Build configuration list for PBXNativeTarget "TaskQueueTests" */;
141 | buildPhases = (
142 | A162A9DF1EA0E8A300B30D72 /* CopyFiles */,
143 | A1B4E5DD1EA0DE67008B5969 /* Sources */,
144 | A1B4E5DE1EA0DE67008B5969 /* Frameworks */,
145 | A1B4E5DF1EA0DE67008B5969 /* Resources */,
146 | );
147 | buildRules = (
148 | );
149 | dependencies = (
150 | A1B4E5E41EA0DE67008B5969 /* PBXTargetDependency */,
151 | );
152 | name = TaskQueueTests;
153 | productName = TaskQueueTests;
154 | productReference = A1B4E5E11EA0DE67008B5969 /* TaskQueueTests.xctest */;
155 | productType = "com.apple.product-type.bundle.unit-test";
156 | };
157 | /* End PBXNativeTarget section */
158 |
159 | /* Begin PBXProject section */
160 | A1B4E5BC1E9EA2AD008B5969 /* Project object */ = {
161 | isa = PBXProject;
162 | attributes = {
163 | LastSwiftUpdateCheck = 0830;
164 | LastUpgradeCheck = 0930;
165 | ORGANIZATIONNAME = "Andreas Grosam";
166 | TargetAttributes = {
167 | A1B4E5D81EA0DE67008B5969 = {
168 | CreatedOnToolsVersion = 8.3;
169 | LastSwiftMigration = "";
170 | ProvisioningStyle = Manual;
171 | };
172 | A1B4E5E01EA0DE67008B5969 = {
173 | CreatedOnToolsVersion = 8.3;
174 | LastSwiftMigration = "";
175 | ProvisioningStyle = Manual;
176 | };
177 | };
178 | };
179 | buildConfigurationList = A1B4E5BF1E9EA2AD008B5969 /* Build configuration list for PBXProject "TaskQueue" */;
180 | compatibilityVersion = "Xcode 3.2";
181 | developmentRegion = English;
182 | hasScannedForEncodings = 0;
183 | knownRegions = (
184 | en,
185 | );
186 | mainGroup = A1B4E5BB1E9EA2AD008B5969;
187 | productRefGroup = A1B4E5C51E9EA2AD008B5969 /* Products */;
188 | projectDirPath = "";
189 | projectRoot = "";
190 | targets = (
191 | A1B4E5D81EA0DE67008B5969 /* TaskQueue */,
192 | A1B4E5E01EA0DE67008B5969 /* TaskQueueTests */,
193 | );
194 | };
195 | /* End PBXProject section */
196 |
197 | /* Begin PBXResourcesBuildPhase section */
198 | A1B4E5D71EA0DE67008B5969 /* Resources */ = {
199 | isa = PBXResourcesBuildPhase;
200 | buildActionMask = 2147483647;
201 | files = (
202 | );
203 | runOnlyForDeploymentPostprocessing = 0;
204 | };
205 | A1B4E5DF1EA0DE67008B5969 /* Resources */ = {
206 | isa = PBXResourcesBuildPhase;
207 | buildActionMask = 2147483647;
208 | files = (
209 | );
210 | runOnlyForDeploymentPostprocessing = 0;
211 | };
212 | /* End PBXResourcesBuildPhase section */
213 |
214 | /* Begin PBXSourcesBuildPhase section */
215 | A1B4E5D41EA0DE67008B5969 /* Sources */ = {
216 | isa = PBXSourcesBuildPhase;
217 | buildActionMask = 2147483647;
218 | files = (
219 | A1B4E5F01EA0E19A008B5969 /* TaskQueue.swift in Sources */,
220 | );
221 | runOnlyForDeploymentPostprocessing = 0;
222 | };
223 | A1B4E5DD1EA0DE67008B5969 /* Sources */ = {
224 | isa = PBXSourcesBuildPhase;
225 | buildActionMask = 2147483647;
226 | files = (
227 | A1B4E5E71EA0DE67008B5969 /* TaskQueueTests.swift in Sources */,
228 | );
229 | runOnlyForDeploymentPostprocessing = 0;
230 | };
231 | /* End PBXSourcesBuildPhase section */
232 |
233 | /* Begin PBXTargetDependency section */
234 | A1B4E5E41EA0DE67008B5969 /* PBXTargetDependency */ = {
235 | isa = PBXTargetDependency;
236 | target = A1B4E5D81EA0DE67008B5969 /* TaskQueue */;
237 | targetProxy = A1B4E5E31EA0DE67008B5969 /* PBXContainerItemProxy */;
238 | };
239 | /* End PBXTargetDependency section */
240 |
241 | /* Begin XCBuildConfiguration section */
242 | A1B4E5C91E9EA2AD008B5969 /* Debug */ = {
243 | isa = XCBuildConfiguration;
244 | buildSettings = {
245 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
246 | ALWAYS_SEARCH_USER_PATHS = NO;
247 | CLANG_ANALYZER_NONNULL = YES;
248 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
249 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
250 | CLANG_CXX_LIBRARY = "libc++";
251 | CLANG_ENABLE_MODULES = YES;
252 | CLANG_ENABLE_OBJC_ARC = YES;
253 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
254 | CLANG_WARN_BOOL_CONVERSION = YES;
255 | CLANG_WARN_COMMA = YES;
256 | CLANG_WARN_CONSTANT_CONVERSION = YES;
257 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
258 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
259 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
260 | CLANG_WARN_EMPTY_BODY = YES;
261 | CLANG_WARN_ENUM_CONVERSION = YES;
262 | CLANG_WARN_INFINITE_RECURSION = YES;
263 | CLANG_WARN_INT_CONVERSION = YES;
264 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
265 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
266 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
267 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
268 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
269 | CLANG_WARN_STRICT_PROTOTYPES = YES;
270 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
271 | CLANG_WARN_UNREACHABLE_CODE = YES;
272 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
273 | CODE_SIGN_IDENTITY = "-";
274 | COPY_PHASE_STRIP = NO;
275 | DEBUG_INFORMATION_FORMAT = dwarf;
276 | ENABLE_STRICT_OBJC_MSGSEND = YES;
277 | ENABLE_TESTABILITY = YES;
278 | GCC_C_LANGUAGE_STANDARD = gnu99;
279 | GCC_DYNAMIC_NO_PIC = NO;
280 | GCC_NO_COMMON_BLOCKS = YES;
281 | GCC_OPTIMIZATION_LEVEL = 0;
282 | GCC_PREPROCESSOR_DEFINITIONS = (
283 | "DEBUG=1",
284 | "$(inherited)",
285 | );
286 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
287 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
288 | GCC_WARN_UNDECLARED_SELECTOR = YES;
289 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
290 | GCC_WARN_UNUSED_FUNCTION = YES;
291 | GCC_WARN_UNUSED_VARIABLE = YES;
292 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
293 | MACOSX_DEPLOYMENT_TARGET = 10.10;
294 | MTL_ENABLE_DEBUG_INFO = YES;
295 | ONLY_ACTIVE_ARCH = YES;
296 | SDKROOT = "";
297 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
298 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
299 | TVOS_DEPLOYMENT_TARGET = 10.0;
300 | WATCHOS_DEPLOYMENT_TARGET = 3.0;
301 | };
302 | name = Debug;
303 | };
304 | A1B4E5CA1E9EA2AD008B5969 /* Release */ = {
305 | isa = XCBuildConfiguration;
306 | buildSettings = {
307 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
308 | ALWAYS_SEARCH_USER_PATHS = NO;
309 | CLANG_ANALYZER_NONNULL = YES;
310 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
312 | CLANG_CXX_LIBRARY = "libc++";
313 | CLANG_ENABLE_MODULES = YES;
314 | CLANG_ENABLE_OBJC_ARC = YES;
315 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
316 | CLANG_WARN_BOOL_CONVERSION = YES;
317 | CLANG_WARN_COMMA = YES;
318 | CLANG_WARN_CONSTANT_CONVERSION = YES;
319 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
320 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
321 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
322 | CLANG_WARN_EMPTY_BODY = YES;
323 | CLANG_WARN_ENUM_CONVERSION = YES;
324 | CLANG_WARN_INFINITE_RECURSION = YES;
325 | CLANG_WARN_INT_CONVERSION = YES;
326 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
327 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
331 | CLANG_WARN_STRICT_PROTOTYPES = YES;
332 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
333 | CLANG_WARN_UNREACHABLE_CODE = YES;
334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
335 | CODE_SIGN_IDENTITY = "-";
336 | COPY_PHASE_STRIP = NO;
337 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
338 | ENABLE_NS_ASSERTIONS = NO;
339 | ENABLE_STRICT_OBJC_MSGSEND = YES;
340 | GCC_C_LANGUAGE_STANDARD = gnu99;
341 | GCC_NO_COMMON_BLOCKS = YES;
342 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
343 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
344 | GCC_WARN_UNDECLARED_SELECTOR = YES;
345 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
346 | GCC_WARN_UNUSED_FUNCTION = YES;
347 | GCC_WARN_UNUSED_VARIABLE = YES;
348 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
349 | MACOSX_DEPLOYMENT_TARGET = 10.10;
350 | MTL_ENABLE_DEBUG_INFO = NO;
351 | SDKROOT = "";
352 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
353 | TVOS_DEPLOYMENT_TARGET = 10.0;
354 | WATCHOS_DEPLOYMENT_TARGET = 3.0;
355 | };
356 | name = Release;
357 | };
358 | A1B4E5EB1EA0DE67008B5969 /* Debug */ = {
359 | isa = XCBuildConfiguration;
360 | buildSettings = {
361 | CODE_SIGN_IDENTITY = "";
362 | CODE_SIGN_STYLE = Manual;
363 | COMBINE_HIDPI_IMAGES = YES;
364 | CURRENT_PROJECT_VERSION = 1;
365 | DEFINES_MODULE = YES;
366 | DEVELOPMENT_TEAM = "";
367 | DYLIB_COMPATIBILITY_VERSION = 1;
368 | DYLIB_CURRENT_VERSION = 1;
369 | DYLIB_INSTALL_NAME_BASE = "@rpath";
370 | FRAMEWORK_VERSION = A;
371 | INFOPLIST_FILE = Sources/Info.plist;
372 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
373 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks @loader_path/../Frameworks";
374 | PRODUCT_BUNDLE_IDENTIFIER = ag.com.TaskQueue;
375 | PRODUCT_NAME = "$(TARGET_NAME)";
376 | PROVISIONING_PROFILE_SPECIFIER = "";
377 | SKIP_INSTALL = YES;
378 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
379 | SWIFT_VERSION = 4.2;
380 | VERSIONING_SYSTEM = "apple-generic";
381 | VERSION_INFO_PREFIX = "";
382 | };
383 | name = Debug;
384 | };
385 | A1B4E5EC1EA0DE67008B5969 /* Release */ = {
386 | isa = XCBuildConfiguration;
387 | buildSettings = {
388 | CODE_SIGN_IDENTITY = "";
389 | CODE_SIGN_STYLE = Manual;
390 | COMBINE_HIDPI_IMAGES = YES;
391 | CURRENT_PROJECT_VERSION = 1;
392 | DEFINES_MODULE = YES;
393 | DEVELOPMENT_TEAM = "";
394 | DYLIB_COMPATIBILITY_VERSION = 1;
395 | DYLIB_CURRENT_VERSION = 1;
396 | DYLIB_INSTALL_NAME_BASE = "@rpath";
397 | FRAMEWORK_VERSION = A;
398 | INFOPLIST_FILE = Sources/Info.plist;
399 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
400 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks @loader_path/../Frameworks";
401 | PRODUCT_BUNDLE_IDENTIFIER = ag.com.TaskQueue;
402 | PRODUCT_NAME = "$(TARGET_NAME)";
403 | PROVISIONING_PROFILE_SPECIFIER = "";
404 | SKIP_INSTALL = YES;
405 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
406 | SWIFT_VERSION = 4.2;
407 | VERSIONING_SYSTEM = "apple-generic";
408 | VERSION_INFO_PREFIX = "";
409 | };
410 | name = Release;
411 | };
412 | A1B4E5EE1EA0DE67008B5969 /* Debug */ = {
413 | isa = XCBuildConfiguration;
414 | buildSettings = {
415 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
416 | CODE_SIGN_STYLE = Manual;
417 | COMBINE_HIDPI_IMAGES = YES;
418 | DEVELOPMENT_TEAM = "";
419 | INFOPLIST_FILE = Tests/Info.plist;
420 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks @executable_path/Frameworks @loader_path/Frameworks";
421 | PRODUCT_BUNDLE_IDENTIFIER = ag.com.TaskQueueTests;
422 | PRODUCT_NAME = "$(TARGET_NAME)";
423 | PROVISIONING_PROFILE_SPECIFIER = "";
424 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
425 | SWIFT_VERSION = 4.2;
426 | };
427 | name = Debug;
428 | };
429 | A1B4E5EF1EA0DE67008B5969 /* Release */ = {
430 | isa = XCBuildConfiguration;
431 | buildSettings = {
432 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
433 | CODE_SIGN_STYLE = Manual;
434 | COMBINE_HIDPI_IMAGES = YES;
435 | DEVELOPMENT_TEAM = "";
436 | INFOPLIST_FILE = Tests/Info.plist;
437 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks @executable_path/Frameworks @loader_path/Frameworks";
438 | PRODUCT_BUNDLE_IDENTIFIER = ag.com.TaskQueueTests;
439 | PRODUCT_NAME = "$(TARGET_NAME)";
440 | PROVISIONING_PROFILE_SPECIFIER = "";
441 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
442 | SWIFT_VERSION = 4.2;
443 | };
444 | name = Release;
445 | };
446 | /* End XCBuildConfiguration section */
447 |
448 | /* Begin XCConfigurationList section */
449 | A1B4E5BF1E9EA2AD008B5969 /* Build configuration list for PBXProject "TaskQueue" */ = {
450 | isa = XCConfigurationList;
451 | buildConfigurations = (
452 | A1B4E5C91E9EA2AD008B5969 /* Debug */,
453 | A1B4E5CA1E9EA2AD008B5969 /* Release */,
454 | );
455 | defaultConfigurationIsVisible = 0;
456 | defaultConfigurationName = Release;
457 | };
458 | A1B4E5EA1EA0DE67008B5969 /* Build configuration list for PBXNativeTarget "TaskQueue" */ = {
459 | isa = XCConfigurationList;
460 | buildConfigurations = (
461 | A1B4E5EB1EA0DE67008B5969 /* Debug */,
462 | A1B4E5EC1EA0DE67008B5969 /* Release */,
463 | );
464 | defaultConfigurationIsVisible = 0;
465 | defaultConfigurationName = Release;
466 | };
467 | A1B4E5ED1EA0DE67008B5969 /* Build configuration list for PBXNativeTarget "TaskQueueTests" */ = {
468 | isa = XCConfigurationList;
469 | buildConfigurations = (
470 | A1B4E5EE1EA0DE67008B5969 /* Debug */,
471 | A1B4E5EF1EA0DE67008B5969 /* Release */,
472 | );
473 | defaultConfigurationIsVisible = 0;
474 | defaultConfigurationName = Release;
475 | };
476 | /* End XCConfigurationList section */
477 | };
478 | rootObject = A1B4E5BC1E9EA2AD008B5969 /* Project object */;
479 | }
480 |
--------------------------------------------------------------------------------