├── .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 | [![Build Status](https://travis-ci.org/couchdeveloper/TaskQueue.svg?branch=master)](https://travis-ci.org/couchdeveloper/TaskQueue) [![GitHub license](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) [![Swift 4.2](https://img.shields.io/badge/Swift-4.2-orange.svg?style=flat)](https://developer.apple.com/swift/) ![Platforms MacOS | iOS | tvOS | watchOS](https://img.shields.io/badge/Platforms-OS%20X%20%7C%20iOS%20%7C%20tvOS%20%7C%20watchOS-brightgreen.svg) [![Carthage Compatible](https://img.shields.io/badge/Carthage-Compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![CocoaPods](https://img.shields.io/badge/CocoaPods-available-370301.svg)](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 | --------------------------------------------------------------------------------