├── .travis.yml ├── AsyncOp.swift ├── AsyncOpGroup.swift ├── AsyncOpKit-OSX ├── AsyncOpKit-OSX.h └── Info.plist ├── AsyncOpKit-OSXTests └── Info.plist ├── AsyncOpKit.h ├── AsyncOpKit.podspec ├── AsyncOpKit.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── AsyncOpKit-OSX.xcscheme │ └── AsyncOpKit.xcscheme ├── AsyncOpKit.xcworkspace └── contents.xcworkspacedata ├── AsyncOpKit └── Info.plist ├── AsyncOpTypes.swift ├── InstallingTests.md ├── LICENSE ├── Legacy ├── AsyncClosuresOperation.swift ├── AsyncOpKit.swift └── AsyncOperation.swift ├── Podfile ├── Podfile.lock ├── README.md └── Tests ├── AsyncOpKitTests ├── ClosureTestsFile.swift └── Tests.swift ├── AsyncOpTests ├── AsyncOpAutomaticInputTests.swift ├── AsyncOpFinishOverloadsTesting.swift ├── AsyncOpGroupTests.swift ├── AsyncOpManualInputTests.swift ├── AsyncOpPreconditionEvaluatorTests.swift ├── AsyncOpResultStatusTests.swift ├── AsyncOpSubclass.swift ├── AsyncOpTests.swift ├── AsyncOpThrowTests.swift └── AsyncOpWhenFinished.swift └── Info.plist /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | language: swift 6 | # cache: cocoapods 7 | # podfile: Example/Podfile 8 | # before_install: 9 | # - gem install cocoapods # Since Travis is not always on latest version 10 | # - pod install --project-directory=Example 11 | install: 12 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet 13 | script: 14 | - set -o pipefail && xcodebuild test -workspace Example/AsyncOpKit.xcworkspace -scheme AsyncOpKit-Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty -c 15 | - pod lib lint --quick 16 | -------------------------------------------------------------------------------- /AsyncOp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOp.swift 3 | // 4 | // Created by Jed Lewison 5 | // Copyright (c) 2015 Magic App Factory. MIT License. 6 | 7 | import Foundation 8 | 9 | /// AsyncOp is an NSOperation subclass that supports a generic output type and takes care of the boiler plate necessary for asynchronous execution of NSOperations. 10 | /// You can subclass AsyncOp, but because it's a generic subclass and provides convenient closures for performing work as well has handling cancellation, results, and errors, in many cases you may not need to. 11 | 12 | public class AsyncOp: NSOperation { 13 | 14 | @nonobjc public required override init() { 15 | super.init() 16 | } 17 | 18 | public var input: AsyncOpValue { 19 | get { 20 | return _input 21 | } 22 | set { 23 | guard state == .Initial else { debugPrint(WarnSetInput); return } 24 | _input = newValue 25 | } 26 | } 27 | 28 | public private(set) final var output: AsyncOpValue = .None(.NoValue) 29 | 30 | // Closures 31 | public typealias AsyncOpClosure = (asyncOp: AsyncOp) -> Void 32 | public typealias AsyncOpThrowingClosure = (asyncOp: AsyncOp) throws -> Void 33 | public typealias AsyncOpPreconditionEvaluator = () throws -> AsyncOpPreconditionInstruction 34 | 35 | // MARK: Implementation details 36 | override public final func start() { 37 | state = .Executing 38 | if !cancelled { 39 | main() 40 | } else { 41 | preconditionEvaluators.removeAll() 42 | implementationHandler = nil 43 | finish(with: .None(.Cancelled)) 44 | } 45 | } 46 | 47 | override public final func main() { 48 | // Helper functions to decompose the work 49 | func main_prepareInput() { 50 | if let handlerForAsyncOpInputRequest = handlerForAsyncOpInputRequest { 51 | _input = handlerForAsyncOpInputRequest() 52 | self.handlerForAsyncOpInputRequest = nil 53 | } 54 | } 55 | 56 | func main_evaluatePreconditions() -> AsyncOpPreconditionInstruction { 57 | 58 | var errors = [ErrorType]() 59 | var preconditionInstruction = AsyncOpPreconditionInstruction.Continue 60 | 61 | for evaluator in preconditionEvaluators { 62 | do { 63 | let evaluatorInstruction = try evaluator() 64 | switch evaluatorInstruction { 65 | case .Cancel where errors.count == 0: 66 | preconditionInstruction = .Cancel 67 | case .Fail(let error): 68 | errors.append(error) 69 | preconditionInstruction = AsyncOpPreconditionInstruction(errors: errors) 70 | case .Continue, .Cancel: 71 | break 72 | } 73 | } catch { 74 | errors.append(error) 75 | preconditionInstruction = AsyncOpPreconditionInstruction(errors: errors) 76 | } 77 | } 78 | 79 | preconditionEvaluators.removeAll() 80 | 81 | return preconditionInstruction 82 | } 83 | 84 | func main_performImplementation() { 85 | if let implementationHandler = self.implementationHandler { 86 | self.implementationHandler = nil 87 | do { 88 | try implementationHandler(asyncOp: self) 89 | } catch { 90 | finish(with: error) 91 | } 92 | } else { 93 | finish(with: AsyncOpError.UnimplementedOperation) 94 | } 95 | } 96 | 97 | // The actual implementation 98 | autoreleasepool { 99 | main_prepareInput() 100 | switch main_evaluatePreconditions() { 101 | case .Continue: 102 | main_performImplementation() // happy path 103 | case .Cancel: 104 | implementationHandler = nil 105 | cancel() 106 | finish(with: .Cancelled) 107 | case .Fail(let error): 108 | cancel() 109 | implementationHandler = nil 110 | finish(with: error) 111 | } 112 | } 113 | } 114 | 115 | override public final func cancel() { 116 | performOnce(onceAction: .cancel) { 117 | super.cancel() 118 | self.cancellationHandler?(asyncOp: self) 119 | self.cancellationHandler = nil 120 | } 121 | } 122 | 123 | public private(set) final var paused: Bool = false { 124 | willSet { 125 | guard state == .Initial else { return } 126 | if paused != newValue { 127 | willChangeValueForKey("isReady") 128 | } 129 | } 130 | didSet { 131 | guard state == .Initial else { return } 132 | if paused != oldValue { 133 | didChangeValueForKey("isReady") 134 | } 135 | } 136 | } 137 | 138 | private var state = AsyncOpState.Initial { 139 | willSet { 140 | if newValue != state { 141 | willChangeValueForState(newValue) 142 | willChangeValueForState(state) 143 | } 144 | } 145 | didSet { 146 | if oldValue != state { 147 | didChangeValueForState(oldValue) 148 | didChangeValueForState(state) 149 | } 150 | } 151 | } 152 | 153 | /// Overrides for required NSOperation properties 154 | override public final var asynchronous: Bool { return true } 155 | override public final var executing: Bool { return state == .Executing } 156 | override public final var finished: Bool { return state == .Finished } 157 | override public var ready: Bool { 158 | guard state == .Initial else { return true } 159 | guard super.ready else { return false } 160 | return !paused 161 | } 162 | 163 | // MARK: Private storage 164 | private typealias AsyncInputRequest = () -> AsyncOpValue 165 | private var handlerForAsyncOpInputRequest: AsyncInputRequest? 166 | private var preconditionEvaluators = [AsyncOpPreconditionEvaluator]() 167 | private var implementationHandler: AsyncOpThrowingClosure? 168 | private var completionHandler: AsyncOpClosure? 169 | private var completionHandlerQueue: NSOperationQueue? 170 | private var cancellationHandler: AsyncOpClosure? 171 | 172 | // Convenience for performing cancel and finish actions once 173 | private var onceGuards: [OnceAction : Bool] = Dictionary(minimumCapacity: OnceAction.count) 174 | private let performOnceGuardQ = NSQualityOfService.UserInitiated.createSerialDispatchQueue("asyncOpKit.performOnceGuardQ") 175 | private func performOnce(onceAction onceAction: OnceAction, @noescape action: () -> ()) { 176 | var canPerformAction: Bool? 177 | dispatch_sync(performOnceGuardQ) { 178 | canPerformAction = self.onceGuards[onceAction] ?? true 179 | self.onceGuards[onceAction] = false 180 | } 181 | 182 | if canPerformAction == true { 183 | action() 184 | } 185 | 186 | } 187 | private var _input: AsyncOpValue = AsyncOpValue.None(.NoValue) 188 | 189 | } 190 | 191 | extension AsyncOp { 192 | 193 | public func onStart(implementationHandler: AsyncOpThrowingClosure) { 194 | guard state == .Initial else { return } 195 | self.implementationHandler = implementationHandler 196 | } 197 | 198 | public func whenFinished(whenFinishedQueue completionHandlerQueue: NSOperationQueue = NSOperationQueue.mainQueue(), completionHandler: AsyncOpClosure) { 199 | 200 | performOnce(onceAction: .whenFinished) { 201 | guard self.completionHandler == nil else { return } 202 | if self.finished { 203 | completionHandlerQueue.addOperationWithBlock { 204 | completionHandler(asyncOp: self) 205 | } 206 | } else { 207 | self.completionHandlerQueue = completionHandlerQueue 208 | self.completionHandler = completionHandler 209 | } 210 | } 211 | } 212 | 213 | public func onCancel(cancellationHandler: AsyncOpClosure) { 214 | guard state == .Initial else { return } 215 | self.cancellationHandler = cancellationHandler 216 | } 217 | 218 | } 219 | 220 | extension AsyncOp where OutputType: AsyncVoidConvertible { 221 | 222 | public final func finishWithSuccess() { 223 | finish(with: .Some(OutputType(asyncVoid: .Void))) 224 | } 225 | 226 | } 227 | 228 | // MARK: Functions for finishing operation 229 | extension AsyncOp { 230 | 231 | public final func finish(with value: OutputType) { 232 | finish(with: .Some(value)) 233 | } 234 | 235 | public final func finish(with asyncOpValueError: AsyncOpValueErrorType) { 236 | finish(with: .None(asyncOpValueError)) 237 | } 238 | 239 | public final func finish(with failureError: ErrorType) { 240 | finish(with: .None(.Failed(failureError))) 241 | } 242 | 243 | public final func finish(with asyncOpValue: AsyncOpValue) { 244 | guard executing else { return } 245 | performOnce(onceAction: .finish) { 246 | 247 | self.output = asyncOpValue 248 | self.state = .Finished 249 | guard let completionHandler = self.completionHandler else { return } 250 | self.completionHandler = nil 251 | self.implementationHandler = nil 252 | self.cancellationHandler = nil 253 | self.handlerForAsyncOpInputRequest = nil 254 | self.preconditionEvaluators.removeAll() 255 | guard let completionHandlerQueue = self.completionHandlerQueue else { return } 256 | self.completionHandlerQueue = nil 257 | completionHandlerQueue.addOperationWithBlock { 258 | completionHandler(asyncOp: self) 259 | } 260 | } 261 | } 262 | 263 | } 264 | 265 | extension AsyncOp { 266 | 267 | /// Has no effect on operation readiness once it begins executing 268 | public final func pause() { 269 | paused = true 270 | } 271 | 272 | /// Has no effect on operation readiness once it begins executing 273 | public final func resume() { 274 | paused = false 275 | } 276 | 277 | } 278 | 279 | extension AsyncOp: AsyncOpInputProvider { 280 | 281 | public func addPreconditionEvaluator(evaluator: AsyncOpPreconditionEvaluator) { 282 | guard state == .Initial else { debugPrint(WarnSetInput); return } 283 | preconditionEvaluators.append(evaluator) 284 | } 285 | 286 | public func setInputProvider(inputProvider: T) { 287 | guard state == .Initial else { debugPrint(WarnSetInput); return } 288 | if let inputProvider = inputProvider as? NSOperation { 289 | addDependency(inputProvider) 290 | } 291 | handlerForAsyncOpInputRequest = inputProvider.provideAsyncOpInput 292 | } 293 | 294 | public typealias ProvidedInputValueType = OutputType 295 | public func provideAsyncOpInput() -> AsyncOpValue { 296 | return output 297 | } 298 | 299 | public func setInput(value: InputType, andResume resume: Bool = false) { 300 | setInput(AsyncOpValue.Some(value), andResume: resume) 301 | } 302 | 303 | public func setInput(input: AsyncOpValue, andResume resume: Bool = false) { 304 | guard state == .Initial else { debugPrint(WarnSetInput); return } 305 | self.input = input 306 | if resume { 307 | self.resume() 308 | } 309 | } 310 | 311 | } 312 | 313 | extension AsyncOp { 314 | 315 | public var resultStatus: AsyncOpResultStatus { 316 | guard state == .Finished else { return .Pending } 317 | guard !cancelled else { return .Cancelled } 318 | switch output { 319 | case .None: 320 | return .Failed 321 | case .Some: 322 | return .Succeeded 323 | } 324 | } 325 | 326 | } 327 | 328 | private extension AsyncOp { 329 | 330 | func willChangeValueForState(state: AsyncOpState) { 331 | guard let key = state.key else { return } 332 | willChangeValueForKey(key) 333 | } 334 | 335 | func didChangeValueForState(state: AsyncOpState) { 336 | guard let key = state.key else { return } 337 | didChangeValueForKey(key) 338 | } 339 | 340 | } 341 | 342 | private let WarnSetInput = "Setting input without manual mode automatic or when operation has started has no effect" 343 | 344 | private enum AsyncOpState { 345 | case Initial 346 | case Executing 347 | case Finished 348 | 349 | var key: String? { 350 | switch self { 351 | case .Executing: 352 | return "isExecuting" 353 | case .Finished: 354 | return "isFinished" 355 | case .Initial: 356 | return nil 357 | } 358 | } 359 | } 360 | 361 | private enum OnceAction: Int { 362 | case whenFinished 363 | case finish 364 | case cancel 365 | static let count = 3 366 | } -------------------------------------------------------------------------------- /AsyncOpGroup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOpGroup.swift 3 | // AsyncOpKit 4 | // 5 | // Created by Jed Lewison on 9/30/15. 6 | // Copyright © 2015 Magic App Factory. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct AsyncOpConnector { 12 | 13 | private let _asyncOpGroup: AsyncOpGroup 14 | private let _asyncOp: AsyncOp 15 | 16 | public func then(@noescape anOperationProvider: () -> AsyncOp) -> AsyncOpConnector { 17 | 18 | let op = anOperationProvider() 19 | op.setInputProvider(_asyncOp) 20 | op.addPreconditionEvaluator { [weak op] in 21 | guard let op = op else { return .Cancel } 22 | switch op.input { 23 | case .Some: 24 | return .Continue 25 | case .None(let asyncOpValueError): 26 | switch asyncOpValueError { 27 | case .NoValue, .Cancelled: 28 | return .Cancel 29 | case .Failed(let error): 30 | return .Fail(error) 31 | } 32 | } 33 | } 34 | return _asyncOpGroup.then(op) 35 | } 36 | 37 | public func finally(handler: (result: AsyncOpResult) -> ()) -> AsyncOpGroup { 38 | let op = operationToProvideResults() 39 | op.setInputProvider(_asyncOp) 40 | op.whenFinished { (asyncOp) -> Void in 41 | handler(result: op.result) 42 | } 43 | return _asyncOpGroup.finally(op) 44 | } 45 | 46 | private func operationToProvideResults() -> AsyncOp { 47 | let op = AsyncOp() 48 | op.onStart { asyncOp in 49 | asyncOp.finish(with: asyncOp.input) 50 | } 51 | return op 52 | } 53 | 54 | } 55 | 56 | public class AsyncOpGroup { 57 | 58 | public init() { 59 | 60 | } 61 | 62 | public func beginWith(@noescape anAsyncOpProvider: () -> AsyncOp) -> AsyncOpConnector { 63 | let op = anAsyncOpProvider() 64 | operations.append(op) 65 | return AsyncOpConnector(_asyncOpGroup: self, _asyncOp: op) 66 | } 67 | 68 | private var operations = [NSOperation]() 69 | 70 | 71 | public func cancelGroup() { 72 | operations.forEach { $0.cancel() } 73 | } 74 | 75 | private func then(operation: AsyncOp) -> AsyncOpConnector { 76 | operations.append(operation) 77 | return AsyncOpConnector(_asyncOpGroup: self, _asyncOp: operation) 78 | } 79 | 80 | private func finally(operation: AsyncOp) -> AsyncOpGroup { 81 | operations.append(operation) 82 | return self 83 | } 84 | 85 | } 86 | 87 | extension NSOperationQueue { 88 | public func addAsyncOpGroup(asyncOpGroup: AsyncOpGroup?, waitUntilFinished: Bool = false) { 89 | guard let asyncOpGroup = asyncOpGroup else { return } 90 | addOperations(asyncOpGroup.operations, waitUntilFinished: waitUntilFinished) 91 | } 92 | } 93 | 94 | 95 | -------------------------------------------------------------------------------- /AsyncOpKit-OSX/AsyncOpKit-OSX.h: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOpKit-OSX.h 3 | // AsyncOpKit-OSX 4 | // 5 | // Created by Jed Lewison on 12/28/15. 6 | // Copyright © 2015 Magic App Factory. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for AsyncOpKit-OSX. 12 | FOUNDATION_EXPORT double AsyncOpKit_OSXVersionNumber; 13 | 14 | //! Project version string for AsyncOpKit-OSX. 15 | FOUNDATION_EXPORT const unsigned char AsyncOpKit_OSXVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /AsyncOpKit-OSX/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 | 1.2.3 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 Magic App Factory. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /AsyncOpKit-OSXTests/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 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /AsyncOpKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOpKit.h 3 | // AsyncOpKit 4 | // 5 | // Created by Jed Lewison on 12/7/15. 6 | // Copyright © 2015 Magic App Factory. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | FOUNDATION_EXPORT double AsyncOpKitVersionNumber; 12 | FOUNDATION_EXPORT const unsigned char AsyncOpKitVersionString[]; 13 | -------------------------------------------------------------------------------- /AsyncOpKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "AsyncOpKit" 3 | s.version = "1.2.3" 4 | s.summary = "NSOperation for Swift with generic input/output, chaining, error handling, and closures" 5 | s.description = <<-DESC 6 | AsyncOpKit brings Swift generics, error handling, and closures to NSOperations with `AsyncOp`, a Swift-only generic NSOperation subclass for composing asynchronous code. 7 | `AsyncOp` supports: 8 | 9 | * Generic input and output 10 | * Closures for starting and cancelling work, handling results 11 | * Closures for evaluating preconditions 12 | * Making an AsyncOp dependent on input from another 13 | 14 | You can subclass AsyncOp, but because it provides built-in storage for generic input and output and allows you to customize behavior with closures, in many if not most cases you can just use AsyncOp as-is. 15 | DESC 16 | s.author = "Jed Lewison" 17 | s.homepage = "https://github.com/jedlewison/AsyncOpKit" 18 | s.license = 'MIT' 19 | s.source = { :git => "https://github.com/jedlewison/AsyncOpKit.git", :tag => s.version.to_s } 20 | s.ios.deployment_target = '8.0' 21 | s.osx.deployment_target = '10.10' 22 | s.requires_arc = true 23 | s.source_files = "{AsyncOp.swift,AsyncOpTypes.swift,AsyncOpGroup.swift,Legacy/*.swift}" 24 | end 25 | -------------------------------------------------------------------------------- /AsyncOpKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7D99E7FEB1B595E75F4F471E /* Pods_AsyncOpKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 10C5419355895C4278C0FD62 /* Pods_AsyncOpKitTests.framework */; }; 11 | 850B0F751BBCCB9C00553E91 /* AsyncOpGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850B0F741BBCCB9C00553E91 /* AsyncOpGroup.swift */; }; 12 | 850B0F781BBCCBD800553E91 /* AsyncOpGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850B0F761BBCCBD800553E91 /* AsyncOpGroupTests.swift */; }; 13 | 8516EFC71BA10B9B0079ADB8 /* AsyncOp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFB01BA10B9B0079ADB8 /* AsyncOp.swift */; }; 14 | 8516EFC81BA10B9B0079ADB8 /* AsyncOpTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFB11BA10B9B0079ADB8 /* AsyncOpTypes.swift */; }; 15 | 8516EFC91BA10B9B0079ADB8 /* AsyncClosuresOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFB31BA10B9B0079ADB8 /* AsyncClosuresOperation.swift */; }; 16 | 8516EFCA1BA10B9B0079ADB8 /* AsyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFB41BA10B9B0079ADB8 /* AsyncOperation.swift */; }; 17 | 8516EFCB1BA10B9B0079ADB8 /* AsyncOpKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFB51BA10B9B0079ADB8 /* AsyncOpKit.swift */; }; 18 | 8516EFDA1BA10BAE0079ADB8 /* ClosureTestsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFB91BA10B9B0079ADB8 /* ClosureTestsFile.swift */; }; 19 | 8516EFDB1BA10BAE0079ADB8 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFBB1BA10B9B0079ADB8 /* Tests.swift */; }; 20 | 8516EFDC1BA10BB30079ADB8 /* AsyncOpAutomaticInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFBD1BA10B9B0079ADB8 /* AsyncOpAutomaticInputTests.swift */; }; 21 | 8516EFDD1BA10BB30079ADB8 /* AsyncOpFinishOverloadsTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFBE1BA10B9B0079ADB8 /* AsyncOpFinishOverloadsTesting.swift */; }; 22 | 8516EFDE1BA10BB30079ADB8 /* AsyncOpManualInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFBF1BA10B9B0079ADB8 /* AsyncOpManualInputTests.swift */; }; 23 | 8516EFDF1BA10BB30079ADB8 /* AsyncOpPreconditionEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFC01BA10B9B0079ADB8 /* AsyncOpPreconditionEvaluatorTests.swift */; }; 24 | 8516EFE01BA10BB30079ADB8 /* AsyncOpResultStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFC11BA10B9B0079ADB8 /* AsyncOpResultStatusTests.swift */; }; 25 | 8516EFE11BA10BB30079ADB8 /* AsyncOpSubclass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFC21BA10B9B0079ADB8 /* AsyncOpSubclass.swift */; }; 26 | 8516EFE21BA10BB30079ADB8 /* AsyncOpTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFC31BA10B9B0079ADB8 /* AsyncOpTests.swift */; }; 27 | 8516EFE31BA10BB30079ADB8 /* AsyncOpThrowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFC41BA10B9B0079ADB8 /* AsyncOpThrowTests.swift */; }; 28 | 8516EFE41BA10BB30079ADB8 /* AsyncOpWhenFinished.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFC51BA10B9B0079ADB8 /* AsyncOpWhenFinished.swift */; }; 29 | 85A678A81C16625E00512283 /* AsyncOpKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 85A678A71C16625E00512283 /* AsyncOpKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 30 | 85C934AC1C324367009B2E3B /* AsyncOpKit-OSX.h in Headers */ = {isa = PBXBuildFile; fileRef = 85C934AB1C324367009B2E3B /* AsyncOpKit-OSX.h */; settings = {ATTRIBUTES = (Public, ); }; }; 31 | 85C934B31C324367009B2E3B /* AsyncOpKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 85C934A91C324367009B2E3B /* AsyncOpKit.framework */; }; 32 | 85C934C01C3243A6009B2E3B /* AsyncOp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFB01BA10B9B0079ADB8 /* AsyncOp.swift */; }; 33 | 85C934C11C3243A6009B2E3B /* AsyncOpGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850B0F741BBCCB9C00553E91 /* AsyncOpGroup.swift */; }; 34 | 85C934C21C3243A6009B2E3B /* AsyncOpTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFB11BA10B9B0079ADB8 /* AsyncOpTypes.swift */; }; 35 | 85C934C31C3243A6009B2E3B /* AsyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFB41BA10B9B0079ADB8 /* AsyncOperation.swift */; }; 36 | 85C934C41C3243A6009B2E3B /* AsyncClosuresOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFB31BA10B9B0079ADB8 /* AsyncClosuresOperation.swift */; }; 37 | 85C934C51C3243A6009B2E3B /* AsyncOpKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFB51BA10B9B0079ADB8 /* AsyncOpKit.swift */; }; 38 | 85C934C61C3243AF009B2E3B /* ClosureTestsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFB91BA10B9B0079ADB8 /* ClosureTestsFile.swift */; }; 39 | 85C934C71C3243AF009B2E3B /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFBB1BA10B9B0079ADB8 /* Tests.swift */; }; 40 | 85C934C81C3243AF009B2E3B /* AsyncOpAutomaticInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFBD1BA10B9B0079ADB8 /* AsyncOpAutomaticInputTests.swift */; }; 41 | 85C934C91C3243AF009B2E3B /* AsyncOpFinishOverloadsTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFBE1BA10B9B0079ADB8 /* AsyncOpFinishOverloadsTesting.swift */; }; 42 | 85C934CA1C3243AF009B2E3B /* AsyncOpManualInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFBF1BA10B9B0079ADB8 /* AsyncOpManualInputTests.swift */; }; 43 | 85C934CB1C3243AF009B2E3B /* AsyncOpPreconditionEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFC01BA10B9B0079ADB8 /* AsyncOpPreconditionEvaluatorTests.swift */; }; 44 | 85C934CC1C3243AF009B2E3B /* AsyncOpResultStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFC11BA10B9B0079ADB8 /* AsyncOpResultStatusTests.swift */; }; 45 | 85C934CD1C3243AF009B2E3B /* AsyncOpSubclass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFC21BA10B9B0079ADB8 /* AsyncOpSubclass.swift */; }; 46 | 85C934CE1C3243AF009B2E3B /* AsyncOpTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFC31BA10B9B0079ADB8 /* AsyncOpTests.swift */; }; 47 | 85C934CF1C3243AF009B2E3B /* AsyncOpThrowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFC41BA10B9B0079ADB8 /* AsyncOpThrowTests.swift */; }; 48 | 85C934D01C3243AF009B2E3B /* AsyncOpWhenFinished.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8516EFC51BA10B9B0079ADB8 /* AsyncOpWhenFinished.swift */; }; 49 | 85C934D11C3243AF009B2E3B /* AsyncOpGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850B0F761BBCCBD800553E91 /* AsyncOpGroupTests.swift */; }; 50 | /* End PBXBuildFile section */ 51 | 52 | /* Begin PBXContainerItemProxy section */ 53 | 85BE3E581B2CBDAE00558DEC /* PBXContainerItemProxy */ = { 54 | isa = PBXContainerItemProxy; 55 | containerPortal = 85BE3E421B2CBDAE00558DEC /* Project object */; 56 | proxyType = 1; 57 | remoteGlobalIDString = 85BE3E4A1B2CBDAE00558DEC; 58 | remoteInfo = AsyncOpKit; 59 | }; 60 | 85C934B41C324367009B2E3B /* PBXContainerItemProxy */ = { 61 | isa = PBXContainerItemProxy; 62 | containerPortal = 85BE3E421B2CBDAE00558DEC /* Project object */; 63 | proxyType = 1; 64 | remoteGlobalIDString = 85C934A81C324367009B2E3B; 65 | remoteInfo = "AsyncOpKit-OSX"; 66 | }; 67 | /* End PBXContainerItemProxy section */ 68 | 69 | /* Begin PBXFileReference section */ 70 | 10C5419355895C4278C0FD62 /* Pods_AsyncOpKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AsyncOpKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 71 | 502CF1E5F2EF34E207D671FF /* Pods-AsyncOpKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncOpKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncOpKitTests/Pods-AsyncOpKitTests.release.xcconfig"; sourceTree = ""; }; 72 | 61D167126F0ADC09A1707999 /* Pods-AsyncOpKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncOpKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncOpKitTests/Pods-AsyncOpKitTests.debug.xcconfig"; sourceTree = ""; }; 73 | 850B0F741BBCCB9C00553E91 /* AsyncOpGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpGroup.swift; sourceTree = ""; }; 74 | 850B0F761BBCCBD800553E91 /* AsyncOpGroupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpGroupTests.swift; sourceTree = ""; }; 75 | 850DD1D11B2D6CD40039EB71 /* AsyncOpKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = AsyncOpKit.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 76 | 850DD1D21B2D6CD40039EB71 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 77 | 850DD1D31B2D6CD40039EB71 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 78 | 8516EFB01BA10B9B0079ADB8 /* AsyncOp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOp.swift; sourceTree = ""; }; 79 | 8516EFB11BA10B9B0079ADB8 /* AsyncOpTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpTypes.swift; sourceTree = ""; }; 80 | 8516EFB31BA10B9B0079ADB8 /* AsyncClosuresOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncClosuresOperation.swift; sourceTree = ""; }; 81 | 8516EFB41BA10B9B0079ADB8 /* AsyncOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOperation.swift; sourceTree = ""; }; 82 | 8516EFB51BA10B9B0079ADB8 /* AsyncOpKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpKit.swift; sourceTree = ""; }; 83 | 8516EFB61BA10B9B0079ADB8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../AsyncOpKit/Info.plist; sourceTree = ""; }; 84 | 8516EFB91BA10B9B0079ADB8 /* ClosureTestsFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosureTestsFile.swift; sourceTree = ""; }; 85 | 8516EFBB1BA10B9B0079ADB8 /* Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 86 | 8516EFBD1BA10B9B0079ADB8 /* AsyncOpAutomaticInputTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpAutomaticInputTests.swift; sourceTree = ""; }; 87 | 8516EFBE1BA10B9B0079ADB8 /* AsyncOpFinishOverloadsTesting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpFinishOverloadsTesting.swift; sourceTree = ""; }; 88 | 8516EFBF1BA10B9B0079ADB8 /* AsyncOpManualInputTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpManualInputTests.swift; sourceTree = ""; }; 89 | 8516EFC01BA10B9B0079ADB8 /* AsyncOpPreconditionEvaluatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpPreconditionEvaluatorTests.swift; sourceTree = ""; }; 90 | 8516EFC11BA10B9B0079ADB8 /* AsyncOpResultStatusTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpResultStatusTests.swift; sourceTree = ""; }; 91 | 8516EFC21BA10B9B0079ADB8 /* AsyncOpSubclass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpSubclass.swift; sourceTree = ""; }; 92 | 8516EFC31BA10B9B0079ADB8 /* AsyncOpTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpTests.swift; sourceTree = ""; }; 93 | 8516EFC41BA10B9B0079ADB8 /* AsyncOpThrowTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpThrowTests.swift; sourceTree = ""; }; 94 | 8516EFC51BA10B9B0079ADB8 /* AsyncOpWhenFinished.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncOpWhenFinished.swift; sourceTree = ""; }; 95 | 8516EFE71BA10DFC0079ADB8 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 96 | 85A678A71C16625E00512283 /* AsyncOpKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncOpKit.h; sourceTree = ""; }; 97 | 85BE3E4B1B2CBDAE00558DEC /* AsyncOpKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncOpKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 98 | 85BE3E561B2CBDAE00558DEC /* AsyncOpKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AsyncOpKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 99 | 85C934A91C324367009B2E3B /* AsyncOpKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncOpKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 100 | 85C934AB1C324367009B2E3B /* AsyncOpKit-OSX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AsyncOpKit-OSX.h"; sourceTree = ""; }; 101 | 85C934AD1C324367009B2E3B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 102 | 85C934B21C324367009B2E3B /* AsyncOpKit-OSXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AsyncOpKit-OSXTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 103 | 85C934B91C324367009B2E3B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 104 | /* End PBXFileReference section */ 105 | 106 | /* Begin PBXFrameworksBuildPhase section */ 107 | 85BE3E471B2CBDAE00558DEC /* Frameworks */ = { 108 | isa = PBXFrameworksBuildPhase; 109 | buildActionMask = 2147483647; 110 | files = ( 111 | ); 112 | runOnlyForDeploymentPostprocessing = 0; 113 | }; 114 | 85BE3E531B2CBDAE00558DEC /* Frameworks */ = { 115 | isa = PBXFrameworksBuildPhase; 116 | buildActionMask = 2147483647; 117 | files = ( 118 | 7D99E7FEB1B595E75F4F471E /* Pods_AsyncOpKitTests.framework in Frameworks */, 119 | ); 120 | runOnlyForDeploymentPostprocessing = 0; 121 | }; 122 | 85C934A51C324367009B2E3B /* Frameworks */ = { 123 | isa = PBXFrameworksBuildPhase; 124 | buildActionMask = 2147483647; 125 | files = ( 126 | ); 127 | runOnlyForDeploymentPostprocessing = 0; 128 | }; 129 | 85C934AF1C324367009B2E3B /* Frameworks */ = { 130 | isa = PBXFrameworksBuildPhase; 131 | buildActionMask = 2147483647; 132 | files = ( 133 | 85C934B31C324367009B2E3B /* AsyncOpKit.framework in Frameworks */, 134 | ); 135 | runOnlyForDeploymentPostprocessing = 0; 136 | }; 137 | /* End PBXFrameworksBuildPhase section */ 138 | 139 | /* Begin PBXGroup section */ 140 | 850DD1D41B2D6CE20039EB71 /* Metadata */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 850DD1D11B2D6CD40039EB71 /* AsyncOpKit.podspec */, 144 | 850DD1D21B2D6CD40039EB71 /* LICENSE */, 145 | 850DD1D31B2D6CD40039EB71 /* README.md */, 146 | 85A678A71C16625E00512283 /* AsyncOpKit.h */, 147 | ); 148 | name = Metadata; 149 | sourceTree = ""; 150 | }; 151 | 8516EFB21BA10B9B0079ADB8 /* Legacy */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | 8516EFB41BA10B9B0079ADB8 /* AsyncOperation.swift */, 155 | 8516EFB31BA10B9B0079ADB8 /* AsyncClosuresOperation.swift */, 156 | 8516EFB51BA10B9B0079ADB8 /* AsyncOpKit.swift */, 157 | 8516EFB61BA10B9B0079ADB8 /* Info.plist */, 158 | ); 159 | path = Legacy; 160 | sourceTree = ""; 161 | }; 162 | 8516EFB71BA10B9B0079ADB8 /* Tests */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 8516EFE71BA10DFC0079ADB8 /* Info.plist */, 166 | 8516EFB81BA10B9B0079ADB8 /* AsyncOpKitTests */, 167 | 8516EFBC1BA10B9B0079ADB8 /* AsyncOpTests */, 168 | ); 169 | path = Tests; 170 | sourceTree = ""; 171 | }; 172 | 8516EFB81BA10B9B0079ADB8 /* AsyncOpKitTests */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | 8516EFB91BA10B9B0079ADB8 /* ClosureTestsFile.swift */, 176 | 8516EFBB1BA10B9B0079ADB8 /* Tests.swift */, 177 | ); 178 | path = AsyncOpKitTests; 179 | sourceTree = ""; 180 | }; 181 | 8516EFBC1BA10B9B0079ADB8 /* AsyncOpTests */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | 8516EFBD1BA10B9B0079ADB8 /* AsyncOpAutomaticInputTests.swift */, 185 | 8516EFBE1BA10B9B0079ADB8 /* AsyncOpFinishOverloadsTesting.swift */, 186 | 8516EFBF1BA10B9B0079ADB8 /* AsyncOpManualInputTests.swift */, 187 | 8516EFC01BA10B9B0079ADB8 /* AsyncOpPreconditionEvaluatorTests.swift */, 188 | 8516EFC11BA10B9B0079ADB8 /* AsyncOpResultStatusTests.swift */, 189 | 8516EFC21BA10B9B0079ADB8 /* AsyncOpSubclass.swift */, 190 | 8516EFC31BA10B9B0079ADB8 /* AsyncOpTests.swift */, 191 | 8516EFC41BA10B9B0079ADB8 /* AsyncOpThrowTests.swift */, 192 | 8516EFC51BA10B9B0079ADB8 /* AsyncOpWhenFinished.swift */, 193 | 850B0F761BBCCBD800553E91 /* AsyncOpGroupTests.swift */, 194 | ); 195 | path = AsyncOpTests; 196 | sourceTree = ""; 197 | }; 198 | 85A678A01C165F6E00512283 /* Frameworks */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 10C5419355895C4278C0FD62 /* Pods_AsyncOpKitTests.framework */, 202 | ); 203 | name = Frameworks; 204 | sourceTree = ""; 205 | }; 206 | 85BE3E411B2CBDAE00558DEC = { 207 | isa = PBXGroup; 208 | children = ( 209 | 850DD1D41B2D6CE20039EB71 /* Metadata */, 210 | 8516EFB01BA10B9B0079ADB8 /* AsyncOp.swift */, 211 | 850B0F741BBCCB9C00553E91 /* AsyncOpGroup.swift */, 212 | 8516EFB11BA10B9B0079ADB8 /* AsyncOpTypes.swift */, 213 | 8516EFB21BA10B9B0079ADB8 /* Legacy */, 214 | 8516EFB71BA10B9B0079ADB8 /* Tests */, 215 | 85C934AA1C324367009B2E3B /* AsyncOpKit-OSX */, 216 | 85C934B61C324367009B2E3B /* AsyncOpKit-OSXTests */, 217 | 85A678A01C165F6E00512283 /* Frameworks */, 218 | 85BE3E4C1B2CBDAE00558DEC /* Products */, 219 | D4CFBF0B0EC10F0407CE77E6 /* Pods */, 220 | ); 221 | sourceTree = ""; 222 | }; 223 | 85BE3E4C1B2CBDAE00558DEC /* Products */ = { 224 | isa = PBXGroup; 225 | children = ( 226 | 85BE3E4B1B2CBDAE00558DEC /* AsyncOpKit.framework */, 227 | 85BE3E561B2CBDAE00558DEC /* AsyncOpKitTests.xctest */, 228 | 85C934A91C324367009B2E3B /* AsyncOpKit.framework */, 229 | 85C934B21C324367009B2E3B /* AsyncOpKit-OSXTests.xctest */, 230 | ); 231 | name = Products; 232 | sourceTree = ""; 233 | }; 234 | 85C934AA1C324367009B2E3B /* AsyncOpKit-OSX */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | 85C934AB1C324367009B2E3B /* AsyncOpKit-OSX.h */, 238 | 85C934AD1C324367009B2E3B /* Info.plist */, 239 | ); 240 | path = "AsyncOpKit-OSX"; 241 | sourceTree = ""; 242 | }; 243 | 85C934B61C324367009B2E3B /* AsyncOpKit-OSXTests */ = { 244 | isa = PBXGroup; 245 | children = ( 246 | 85C934B91C324367009B2E3B /* Info.plist */, 247 | ); 248 | path = "AsyncOpKit-OSXTests"; 249 | sourceTree = ""; 250 | }; 251 | D4CFBF0B0EC10F0407CE77E6 /* Pods */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | 61D167126F0ADC09A1707999 /* Pods-AsyncOpKitTests.debug.xcconfig */, 255 | 502CF1E5F2EF34E207D671FF /* Pods-AsyncOpKitTests.release.xcconfig */, 256 | ); 257 | name = Pods; 258 | sourceTree = ""; 259 | }; 260 | /* End PBXGroup section */ 261 | 262 | /* Begin PBXHeadersBuildPhase section */ 263 | 85BE3E481B2CBDAE00558DEC /* Headers */ = { 264 | isa = PBXHeadersBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | 85A678A81C16625E00512283 /* AsyncOpKit.h in Headers */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | 85C934A61C324367009B2E3B /* Headers */ = { 272 | isa = PBXHeadersBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | 85C934AC1C324367009B2E3B /* AsyncOpKit-OSX.h in Headers */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | /* End PBXHeadersBuildPhase section */ 280 | 281 | /* Begin PBXNativeTarget section */ 282 | 85BE3E4A1B2CBDAE00558DEC /* AsyncOpKit */ = { 283 | isa = PBXNativeTarget; 284 | buildConfigurationList = 85BE3E611B2CBDAE00558DEC /* Build configuration list for PBXNativeTarget "AsyncOpKit" */; 285 | buildPhases = ( 286 | 85BE3E461B2CBDAE00558DEC /* Sources */, 287 | 85BE3E471B2CBDAE00558DEC /* Frameworks */, 288 | 85BE3E481B2CBDAE00558DEC /* Headers */, 289 | 85BE3E491B2CBDAE00558DEC /* Resources */, 290 | ); 291 | buildRules = ( 292 | ); 293 | dependencies = ( 294 | ); 295 | name = AsyncOpKit; 296 | productName = AsyncOpKit; 297 | productReference = 85BE3E4B1B2CBDAE00558DEC /* AsyncOpKit.framework */; 298 | productType = "com.apple.product-type.framework"; 299 | }; 300 | 85BE3E551B2CBDAE00558DEC /* AsyncOpKitTests */ = { 301 | isa = PBXNativeTarget; 302 | buildConfigurationList = 85BE3E641B2CBDAE00558DEC /* Build configuration list for PBXNativeTarget "AsyncOpKitTests" */; 303 | buildPhases = ( 304 | 2AACE34542AFEC943030726B /* [CP] Check Pods Manifest.lock */, 305 | 85BE3E521B2CBDAE00558DEC /* Sources */, 306 | 85BE3E531B2CBDAE00558DEC /* Frameworks */, 307 | 85BE3E541B2CBDAE00558DEC /* Resources */, 308 | 20DD9FD32239DA91E4EF1AF1 /* [CP] Embed Pods Frameworks */, 309 | EDA8091E7887A61979A988C2 /* [CP] Copy Pods Resources */, 310 | ); 311 | buildRules = ( 312 | ); 313 | dependencies = ( 314 | 85BE3E591B2CBDAE00558DEC /* PBXTargetDependency */, 315 | ); 316 | name = AsyncOpKitTests; 317 | productName = AsyncOpKitTests; 318 | productReference = 85BE3E561B2CBDAE00558DEC /* AsyncOpKitTests.xctest */; 319 | productType = "com.apple.product-type.bundle.unit-test"; 320 | }; 321 | 85C934A81C324367009B2E3B /* AsyncOpKit-OSX */ = { 322 | isa = PBXNativeTarget; 323 | buildConfigurationList = 85C934BE1C324367009B2E3B /* Build configuration list for PBXNativeTarget "AsyncOpKit-OSX" */; 324 | buildPhases = ( 325 | 85C934A41C324367009B2E3B /* Sources */, 326 | 85C934A51C324367009B2E3B /* Frameworks */, 327 | 85C934A61C324367009B2E3B /* Headers */, 328 | 85C934A71C324367009B2E3B /* Resources */, 329 | ); 330 | buildRules = ( 331 | ); 332 | dependencies = ( 333 | ); 334 | name = "AsyncOpKit-OSX"; 335 | productName = "AsyncOpKit-OSX"; 336 | productReference = 85C934A91C324367009B2E3B /* AsyncOpKit.framework */; 337 | productType = "com.apple.product-type.framework"; 338 | }; 339 | 85C934B11C324367009B2E3B /* AsyncOpKit-OSXTests */ = { 340 | isa = PBXNativeTarget; 341 | buildConfigurationList = 85C934BF1C324367009B2E3B /* Build configuration list for PBXNativeTarget "AsyncOpKit-OSXTests" */; 342 | buildPhases = ( 343 | 85C934AE1C324367009B2E3B /* Sources */, 344 | 85C934AF1C324367009B2E3B /* Frameworks */, 345 | 85C934B01C324367009B2E3B /* Resources */, 346 | ); 347 | buildRules = ( 348 | ); 349 | dependencies = ( 350 | 85C934B51C324367009B2E3B /* PBXTargetDependency */, 351 | ); 352 | name = "AsyncOpKit-OSXTests"; 353 | productName = "AsyncOpKit-OSXTests"; 354 | productReference = 85C934B21C324367009B2E3B /* AsyncOpKit-OSXTests.xctest */; 355 | productType = "com.apple.product-type.bundle.unit-test"; 356 | }; 357 | /* End PBXNativeTarget section */ 358 | 359 | /* Begin PBXProject section */ 360 | 85BE3E421B2CBDAE00558DEC /* Project object */ = { 361 | isa = PBXProject; 362 | attributes = { 363 | LastSwiftMigration = 0700; 364 | LastSwiftUpdateCheck = 0720; 365 | LastUpgradeCheck = 0700; 366 | ORGANIZATIONNAME = "Magic App Factory"; 367 | TargetAttributes = { 368 | 85BE3E4A1B2CBDAE00558DEC = { 369 | CreatedOnToolsVersion = 6.3.2; 370 | }; 371 | 85BE3E551B2CBDAE00558DEC = { 372 | CreatedOnToolsVersion = 6.3.2; 373 | }; 374 | 85C934A81C324367009B2E3B = { 375 | CreatedOnToolsVersion = 7.2; 376 | }; 377 | 85C934B11C324367009B2E3B = { 378 | CreatedOnToolsVersion = 7.2; 379 | }; 380 | }; 381 | }; 382 | buildConfigurationList = 85BE3E451B2CBDAE00558DEC /* Build configuration list for PBXProject "AsyncOpKit" */; 383 | compatibilityVersion = "Xcode 3.2"; 384 | developmentRegion = English; 385 | hasScannedForEncodings = 0; 386 | knownRegions = ( 387 | en, 388 | ); 389 | mainGroup = 85BE3E411B2CBDAE00558DEC; 390 | productRefGroup = 85BE3E4C1B2CBDAE00558DEC /* Products */; 391 | projectDirPath = ""; 392 | projectRoot = ""; 393 | targets = ( 394 | 85BE3E4A1B2CBDAE00558DEC /* AsyncOpKit */, 395 | 85BE3E551B2CBDAE00558DEC /* AsyncOpKitTests */, 396 | 85C934A81C324367009B2E3B /* AsyncOpKit-OSX */, 397 | 85C934B11C324367009B2E3B /* AsyncOpKit-OSXTests */, 398 | ); 399 | }; 400 | /* End PBXProject section */ 401 | 402 | /* Begin PBXResourcesBuildPhase section */ 403 | 85BE3E491B2CBDAE00558DEC /* Resources */ = { 404 | isa = PBXResourcesBuildPhase; 405 | buildActionMask = 2147483647; 406 | files = ( 407 | ); 408 | runOnlyForDeploymentPostprocessing = 0; 409 | }; 410 | 85BE3E541B2CBDAE00558DEC /* Resources */ = { 411 | isa = PBXResourcesBuildPhase; 412 | buildActionMask = 2147483647; 413 | files = ( 414 | ); 415 | runOnlyForDeploymentPostprocessing = 0; 416 | }; 417 | 85C934A71C324367009B2E3B /* Resources */ = { 418 | isa = PBXResourcesBuildPhase; 419 | buildActionMask = 2147483647; 420 | files = ( 421 | ); 422 | runOnlyForDeploymentPostprocessing = 0; 423 | }; 424 | 85C934B01C324367009B2E3B /* Resources */ = { 425 | isa = PBXResourcesBuildPhase; 426 | buildActionMask = 2147483647; 427 | files = ( 428 | ); 429 | runOnlyForDeploymentPostprocessing = 0; 430 | }; 431 | /* End PBXResourcesBuildPhase section */ 432 | 433 | /* Begin PBXShellScriptBuildPhase section */ 434 | 20DD9FD32239DA91E4EF1AF1 /* [CP] Embed Pods Frameworks */ = { 435 | isa = PBXShellScriptBuildPhase; 436 | buildActionMask = 2147483647; 437 | files = ( 438 | ); 439 | inputPaths = ( 440 | ); 441 | name = "[CP] Embed Pods Frameworks"; 442 | outputPaths = ( 443 | ); 444 | runOnlyForDeploymentPostprocessing = 0; 445 | shellPath = /bin/sh; 446 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncOpKitTests/Pods-AsyncOpKitTests-frameworks.sh\"\n"; 447 | showEnvVarsInLog = 0; 448 | }; 449 | 2AACE34542AFEC943030726B /* [CP] Check Pods Manifest.lock */ = { 450 | isa = PBXShellScriptBuildPhase; 451 | buildActionMask = 2147483647; 452 | files = ( 453 | ); 454 | inputPaths = ( 455 | ); 456 | name = "[CP] Check Pods Manifest.lock"; 457 | outputPaths = ( 458 | ); 459 | runOnlyForDeploymentPostprocessing = 0; 460 | shellPath = /bin/sh; 461 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 462 | showEnvVarsInLog = 0; 463 | }; 464 | EDA8091E7887A61979A988C2 /* [CP] Copy Pods Resources */ = { 465 | isa = PBXShellScriptBuildPhase; 466 | buildActionMask = 2147483647; 467 | files = ( 468 | ); 469 | inputPaths = ( 470 | ); 471 | name = "[CP] Copy Pods Resources"; 472 | outputPaths = ( 473 | ); 474 | runOnlyForDeploymentPostprocessing = 0; 475 | shellPath = /bin/sh; 476 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncOpKitTests/Pods-AsyncOpKitTests-resources.sh\"\n"; 477 | showEnvVarsInLog = 0; 478 | }; 479 | /* End PBXShellScriptBuildPhase section */ 480 | 481 | /* Begin PBXSourcesBuildPhase section */ 482 | 85BE3E461B2CBDAE00558DEC /* Sources */ = { 483 | isa = PBXSourcesBuildPhase; 484 | buildActionMask = 2147483647; 485 | files = ( 486 | 8516EFC71BA10B9B0079ADB8 /* AsyncOp.swift in Sources */, 487 | 850B0F751BBCCB9C00553E91 /* AsyncOpGroup.swift in Sources */, 488 | 8516EFCB1BA10B9B0079ADB8 /* AsyncOpKit.swift in Sources */, 489 | 8516EFC91BA10B9B0079ADB8 /* AsyncClosuresOperation.swift in Sources */, 490 | 8516EFCA1BA10B9B0079ADB8 /* AsyncOperation.swift in Sources */, 491 | 8516EFC81BA10B9B0079ADB8 /* AsyncOpTypes.swift in Sources */, 492 | ); 493 | runOnlyForDeploymentPostprocessing = 0; 494 | }; 495 | 85BE3E521B2CBDAE00558DEC /* Sources */ = { 496 | isa = PBXSourcesBuildPhase; 497 | buildActionMask = 2147483647; 498 | files = ( 499 | 8516EFE31BA10BB30079ADB8 /* AsyncOpThrowTests.swift in Sources */, 500 | 8516EFE01BA10BB30079ADB8 /* AsyncOpResultStatusTests.swift in Sources */, 501 | 8516EFDC1BA10BB30079ADB8 /* AsyncOpAutomaticInputTests.swift in Sources */, 502 | 8516EFDD1BA10BB30079ADB8 /* AsyncOpFinishOverloadsTesting.swift in Sources */, 503 | 8516EFDA1BA10BAE0079ADB8 /* ClosureTestsFile.swift in Sources */, 504 | 8516EFDE1BA10BB30079ADB8 /* AsyncOpManualInputTests.swift in Sources */, 505 | 8516EFE21BA10BB30079ADB8 /* AsyncOpTests.swift in Sources */, 506 | 8516EFE11BA10BB30079ADB8 /* AsyncOpSubclass.swift in Sources */, 507 | 8516EFDB1BA10BAE0079ADB8 /* Tests.swift in Sources */, 508 | 8516EFE41BA10BB30079ADB8 /* AsyncOpWhenFinished.swift in Sources */, 509 | 8516EFDF1BA10BB30079ADB8 /* AsyncOpPreconditionEvaluatorTests.swift in Sources */, 510 | 850B0F781BBCCBD800553E91 /* AsyncOpGroupTests.swift in Sources */, 511 | ); 512 | runOnlyForDeploymentPostprocessing = 0; 513 | }; 514 | 85C934A41C324367009B2E3B /* Sources */ = { 515 | isa = PBXSourcesBuildPhase; 516 | buildActionMask = 2147483647; 517 | files = ( 518 | 85C934C51C3243A6009B2E3B /* AsyncOpKit.swift in Sources */, 519 | 85C934C01C3243A6009B2E3B /* AsyncOp.swift in Sources */, 520 | 85C934C21C3243A6009B2E3B /* AsyncOpTypes.swift in Sources */, 521 | 85C934C41C3243A6009B2E3B /* AsyncClosuresOperation.swift in Sources */, 522 | 85C934C31C3243A6009B2E3B /* AsyncOperation.swift in Sources */, 523 | 85C934C11C3243A6009B2E3B /* AsyncOpGroup.swift in Sources */, 524 | ); 525 | runOnlyForDeploymentPostprocessing = 0; 526 | }; 527 | 85C934AE1C324367009B2E3B /* Sources */ = { 528 | isa = PBXSourcesBuildPhase; 529 | buildActionMask = 2147483647; 530 | files = ( 531 | 85C934C91C3243AF009B2E3B /* AsyncOpFinishOverloadsTesting.swift in Sources */, 532 | 85C934D01C3243AF009B2E3B /* AsyncOpWhenFinished.swift in Sources */, 533 | 85C934C81C3243AF009B2E3B /* AsyncOpAutomaticInputTests.swift in Sources */, 534 | 85C934CF1C3243AF009B2E3B /* AsyncOpThrowTests.swift in Sources */, 535 | 85C934C71C3243AF009B2E3B /* Tests.swift in Sources */, 536 | 85C934CC1C3243AF009B2E3B /* AsyncOpResultStatusTests.swift in Sources */, 537 | 85C934CB1C3243AF009B2E3B /* AsyncOpPreconditionEvaluatorTests.swift in Sources */, 538 | 85C934D11C3243AF009B2E3B /* AsyncOpGroupTests.swift in Sources */, 539 | 85C934CA1C3243AF009B2E3B /* AsyncOpManualInputTests.swift in Sources */, 540 | 85C934CE1C3243AF009B2E3B /* AsyncOpTests.swift in Sources */, 541 | 85C934CD1C3243AF009B2E3B /* AsyncOpSubclass.swift in Sources */, 542 | 85C934C61C3243AF009B2E3B /* ClosureTestsFile.swift in Sources */, 543 | ); 544 | runOnlyForDeploymentPostprocessing = 0; 545 | }; 546 | /* End PBXSourcesBuildPhase section */ 547 | 548 | /* Begin PBXTargetDependency section */ 549 | 85BE3E591B2CBDAE00558DEC /* PBXTargetDependency */ = { 550 | isa = PBXTargetDependency; 551 | target = 85BE3E4A1B2CBDAE00558DEC /* AsyncOpKit */; 552 | targetProxy = 85BE3E581B2CBDAE00558DEC /* PBXContainerItemProxy */; 553 | }; 554 | 85C934B51C324367009B2E3B /* PBXTargetDependency */ = { 555 | isa = PBXTargetDependency; 556 | target = 85C934A81C324367009B2E3B /* AsyncOpKit-OSX */; 557 | targetProxy = 85C934B41C324367009B2E3B /* PBXContainerItemProxy */; 558 | }; 559 | /* End PBXTargetDependency section */ 560 | 561 | /* Begin XCBuildConfiguration section */ 562 | 85BE3E5F1B2CBDAE00558DEC /* Debug */ = { 563 | isa = XCBuildConfiguration; 564 | buildSettings = { 565 | ALWAYS_SEARCH_USER_PATHS = NO; 566 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 567 | CLANG_CXX_LIBRARY = "libc++"; 568 | CLANG_ENABLE_MODULES = YES; 569 | CLANG_ENABLE_OBJC_ARC = YES; 570 | CLANG_WARN_BOOL_CONVERSION = YES; 571 | CLANG_WARN_CONSTANT_CONVERSION = YES; 572 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 573 | CLANG_WARN_EMPTY_BODY = YES; 574 | CLANG_WARN_ENUM_CONVERSION = YES; 575 | CLANG_WARN_INT_CONVERSION = YES; 576 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 577 | CLANG_WARN_UNREACHABLE_CODE = YES; 578 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 579 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 580 | COPY_PHASE_STRIP = NO; 581 | CURRENT_PROJECT_VERSION = 1; 582 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 583 | ENABLE_STRICT_OBJC_MSGSEND = YES; 584 | ENABLE_TESTABILITY = YES; 585 | GCC_C_LANGUAGE_STANDARD = gnu99; 586 | GCC_DYNAMIC_NO_PIC = NO; 587 | GCC_NO_COMMON_BLOCKS = YES; 588 | GCC_OPTIMIZATION_LEVEL = 0; 589 | GCC_PREPROCESSOR_DEFINITIONS = ( 590 | "DEBUG=1", 591 | "$(inherited)", 592 | ); 593 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 594 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 595 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 596 | GCC_WARN_UNDECLARED_SELECTOR = YES; 597 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 598 | GCC_WARN_UNUSED_FUNCTION = YES; 599 | GCC_WARN_UNUSED_VARIABLE = YES; 600 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 601 | MTL_ENABLE_DEBUG_INFO = YES; 602 | ONLY_ACTIVE_ARCH = YES; 603 | SDKROOT = iphoneos; 604 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 605 | TARGETED_DEVICE_FAMILY = "1,2"; 606 | VERSIONING_SYSTEM = "apple-generic"; 607 | VERSION_INFO_PREFIX = ""; 608 | }; 609 | name = Debug; 610 | }; 611 | 85BE3E601B2CBDAE00558DEC /* Release */ = { 612 | isa = XCBuildConfiguration; 613 | buildSettings = { 614 | ALWAYS_SEARCH_USER_PATHS = NO; 615 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 616 | CLANG_CXX_LIBRARY = "libc++"; 617 | CLANG_ENABLE_MODULES = YES; 618 | CLANG_ENABLE_OBJC_ARC = YES; 619 | CLANG_WARN_BOOL_CONVERSION = YES; 620 | CLANG_WARN_CONSTANT_CONVERSION = YES; 621 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 622 | CLANG_WARN_EMPTY_BODY = YES; 623 | CLANG_WARN_ENUM_CONVERSION = YES; 624 | CLANG_WARN_INT_CONVERSION = YES; 625 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 626 | CLANG_WARN_UNREACHABLE_CODE = YES; 627 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 628 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 629 | COPY_PHASE_STRIP = NO; 630 | CURRENT_PROJECT_VERSION = 1; 631 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 632 | ENABLE_NS_ASSERTIONS = NO; 633 | ENABLE_STRICT_OBJC_MSGSEND = YES; 634 | GCC_C_LANGUAGE_STANDARD = gnu99; 635 | GCC_NO_COMMON_BLOCKS = YES; 636 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 637 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 638 | GCC_WARN_UNDECLARED_SELECTOR = YES; 639 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 640 | GCC_WARN_UNUSED_FUNCTION = YES; 641 | GCC_WARN_UNUSED_VARIABLE = YES; 642 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 643 | MTL_ENABLE_DEBUG_INFO = NO; 644 | SDKROOT = iphoneos; 645 | TARGETED_DEVICE_FAMILY = "1,2"; 646 | VALIDATE_PRODUCT = YES; 647 | VERSIONING_SYSTEM = "apple-generic"; 648 | VERSION_INFO_PREFIX = ""; 649 | }; 650 | name = Release; 651 | }; 652 | 85BE3E621B2CBDAE00558DEC /* Debug */ = { 653 | isa = XCBuildConfiguration; 654 | buildSettings = { 655 | CLANG_ENABLE_MODULES = YES; 656 | DEFINES_MODULE = YES; 657 | DYLIB_COMPATIBILITY_VERSION = 1; 658 | DYLIB_CURRENT_VERSION = 1; 659 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 660 | ENABLE_TESTABILITY = YES; 661 | INFOPLIST_FILE = AsyncOpKit/Info.plist; 662 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 663 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 664 | PRODUCT_BUNDLE_IDENTIFIER = "com.magicappfactory.$(PRODUCT_NAME:rfc1034identifier)"; 665 | PRODUCT_NAME = "$(TARGET_NAME)"; 666 | SKIP_INSTALL = YES; 667 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 668 | }; 669 | name = Debug; 670 | }; 671 | 85BE3E631B2CBDAE00558DEC /* Release */ = { 672 | isa = XCBuildConfiguration; 673 | buildSettings = { 674 | CLANG_ENABLE_MODULES = YES; 675 | DEFINES_MODULE = YES; 676 | DYLIB_COMPATIBILITY_VERSION = 1; 677 | DYLIB_CURRENT_VERSION = 1; 678 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 679 | ENABLE_TESTABILITY = NO; 680 | INFOPLIST_FILE = AsyncOpKit/Info.plist; 681 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 682 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 683 | PRODUCT_BUNDLE_IDENTIFIER = "com.magicappfactory.$(PRODUCT_NAME:rfc1034identifier)"; 684 | PRODUCT_NAME = "$(TARGET_NAME)"; 685 | SKIP_INSTALL = YES; 686 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 687 | }; 688 | name = Release; 689 | }; 690 | 85BE3E651B2CBDAE00558DEC /* Debug */ = { 691 | isa = XCBuildConfiguration; 692 | baseConfigurationReference = 61D167126F0ADC09A1707999 /* Pods-AsyncOpKitTests.debug.xcconfig */; 693 | buildSettings = { 694 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 695 | GCC_PREPROCESSOR_DEFINITIONS = ( 696 | "DEBUG=1", 697 | "$(inherited)", 698 | ); 699 | INFOPLIST_FILE = Tests/Info.plist; 700 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 701 | PRODUCT_BUNDLE_IDENTIFIER = "com.magicappfactory.$(PRODUCT_NAME:rfc1034identifier)"; 702 | PRODUCT_NAME = "$(TARGET_NAME)"; 703 | }; 704 | name = Debug; 705 | }; 706 | 85BE3E661B2CBDAE00558DEC /* Release */ = { 707 | isa = XCBuildConfiguration; 708 | baseConfigurationReference = 502CF1E5F2EF34E207D671FF /* Pods-AsyncOpKitTests.release.xcconfig */; 709 | buildSettings = { 710 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 711 | INFOPLIST_FILE = Tests/Info.plist; 712 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 713 | PRODUCT_BUNDLE_IDENTIFIER = "com.magicappfactory.$(PRODUCT_NAME:rfc1034identifier)"; 714 | PRODUCT_NAME = "$(TARGET_NAME)"; 715 | }; 716 | name = Release; 717 | }; 718 | 85C934BA1C324367009B2E3B /* Debug */ = { 719 | isa = XCBuildConfiguration; 720 | buildSettings = { 721 | CODE_SIGN_IDENTITY = "-"; 722 | COMBINE_HIDPI_IMAGES = YES; 723 | DEBUG_INFORMATION_FORMAT = dwarf; 724 | DEFINES_MODULE = YES; 725 | DYLIB_COMPATIBILITY_VERSION = 1; 726 | DYLIB_CURRENT_VERSION = 1; 727 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 728 | FRAMEWORK_VERSION = A; 729 | INFOPLIST_FILE = "AsyncOpKit-OSX/Info.plist"; 730 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 731 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 732 | MACOSX_DEPLOYMENT_TARGET = 10.10; 733 | PRODUCT_BUNDLE_IDENTIFIER = "com.magicappfactory.AsyncOpKit-OSX"; 734 | PRODUCT_NAME = AsyncOpKit; 735 | SDKROOT = macosx; 736 | SKIP_INSTALL = YES; 737 | }; 738 | name = Debug; 739 | }; 740 | 85C934BB1C324367009B2E3B /* Release */ = { 741 | isa = XCBuildConfiguration; 742 | buildSettings = { 743 | CODE_SIGN_IDENTITY = "-"; 744 | COMBINE_HIDPI_IMAGES = YES; 745 | DEFINES_MODULE = YES; 746 | DYLIB_COMPATIBILITY_VERSION = 1; 747 | DYLIB_CURRENT_VERSION = 1; 748 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 749 | FRAMEWORK_VERSION = A; 750 | INFOPLIST_FILE = "AsyncOpKit-OSX/Info.plist"; 751 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 752 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 753 | MACOSX_DEPLOYMENT_TARGET = 10.10; 754 | PRODUCT_BUNDLE_IDENTIFIER = "com.magicappfactory.AsyncOpKit-OSX"; 755 | PRODUCT_NAME = AsyncOpKit; 756 | SDKROOT = macosx; 757 | SKIP_INSTALL = YES; 758 | }; 759 | name = Release; 760 | }; 761 | 85C934BC1C324367009B2E3B /* Debug */ = { 762 | isa = XCBuildConfiguration; 763 | buildSettings = { 764 | CODE_SIGN_IDENTITY = "-"; 765 | COMBINE_HIDPI_IMAGES = YES; 766 | DEBUG_INFORMATION_FORMAT = dwarf; 767 | INFOPLIST_FILE = "AsyncOpKit-OSXTests/Info.plist"; 768 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 769 | MACOSX_DEPLOYMENT_TARGET = 10.11; 770 | PRODUCT_BUNDLE_IDENTIFIER = "com.magicappfactory.AsyncOpKit-OSXTests"; 771 | PRODUCT_NAME = "$(TARGET_NAME)"; 772 | SDKROOT = macosx; 773 | }; 774 | name = Debug; 775 | }; 776 | 85C934BD1C324367009B2E3B /* Release */ = { 777 | isa = XCBuildConfiguration; 778 | buildSettings = { 779 | CODE_SIGN_IDENTITY = "-"; 780 | COMBINE_HIDPI_IMAGES = YES; 781 | INFOPLIST_FILE = "AsyncOpKit-OSXTests/Info.plist"; 782 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 783 | MACOSX_DEPLOYMENT_TARGET = 10.11; 784 | PRODUCT_BUNDLE_IDENTIFIER = "com.magicappfactory.AsyncOpKit-OSXTests"; 785 | PRODUCT_NAME = "$(TARGET_NAME)"; 786 | SDKROOT = macosx; 787 | }; 788 | name = Release; 789 | }; 790 | /* End XCBuildConfiguration section */ 791 | 792 | /* Begin XCConfigurationList section */ 793 | 85BE3E451B2CBDAE00558DEC /* Build configuration list for PBXProject "AsyncOpKit" */ = { 794 | isa = XCConfigurationList; 795 | buildConfigurations = ( 796 | 85BE3E5F1B2CBDAE00558DEC /* Debug */, 797 | 85BE3E601B2CBDAE00558DEC /* Release */, 798 | ); 799 | defaultConfigurationIsVisible = 0; 800 | defaultConfigurationName = Release; 801 | }; 802 | 85BE3E611B2CBDAE00558DEC /* Build configuration list for PBXNativeTarget "AsyncOpKit" */ = { 803 | isa = XCConfigurationList; 804 | buildConfigurations = ( 805 | 85BE3E621B2CBDAE00558DEC /* Debug */, 806 | 85BE3E631B2CBDAE00558DEC /* Release */, 807 | ); 808 | defaultConfigurationIsVisible = 0; 809 | defaultConfigurationName = Release; 810 | }; 811 | 85BE3E641B2CBDAE00558DEC /* Build configuration list for PBXNativeTarget "AsyncOpKitTests" */ = { 812 | isa = XCConfigurationList; 813 | buildConfigurations = ( 814 | 85BE3E651B2CBDAE00558DEC /* Debug */, 815 | 85BE3E661B2CBDAE00558DEC /* Release */, 816 | ); 817 | defaultConfigurationIsVisible = 0; 818 | defaultConfigurationName = Release; 819 | }; 820 | 85C934BE1C324367009B2E3B /* Build configuration list for PBXNativeTarget "AsyncOpKit-OSX" */ = { 821 | isa = XCConfigurationList; 822 | buildConfigurations = ( 823 | 85C934BA1C324367009B2E3B /* Debug */, 824 | 85C934BB1C324367009B2E3B /* Release */, 825 | ); 826 | defaultConfigurationIsVisible = 0; 827 | defaultConfigurationName = Release; 828 | }; 829 | 85C934BF1C324367009B2E3B /* Build configuration list for PBXNativeTarget "AsyncOpKit-OSXTests" */ = { 830 | isa = XCConfigurationList; 831 | buildConfigurations = ( 832 | 85C934BC1C324367009B2E3B /* Debug */, 833 | 85C934BD1C324367009B2E3B /* Release */, 834 | ); 835 | defaultConfigurationIsVisible = 0; 836 | defaultConfigurationName = Release; 837 | }; 838 | /* End XCConfigurationList section */ 839 | }; 840 | rootObject = 85BE3E421B2CBDAE00558DEC /* Project object */; 841 | } 842 | -------------------------------------------------------------------------------- /AsyncOpKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AsyncOpKit.xcodeproj/xcshareddata/xcschemes/AsyncOpKit-OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 16 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /AsyncOpKit.xcodeproj/xcshareddata/xcschemes/AsyncOpKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 16 | 22 | 23 | 24 | 31 | 37 | 38 | 39 | 40 | 41 | 46 | 47 | 49 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 80 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 98 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /AsyncOpKit.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AsyncOpKit/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 | 1.2.3 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /AsyncOpTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOpTypes.swift 3 | // 4 | // Created by Jed Lewison 5 | // Copyright (c) 2015 Magic App Factory. MIT License. 6 | 7 | import Foundation 8 | 9 | public enum AsyncOpResult { 10 | 11 | case Succeeded(ValueType) 12 | case Failed(ErrorType) 13 | case Cancelled 14 | 15 | init(asyncOpValue: AsyncOpValue) { 16 | switch asyncOpValue { 17 | case .Some(let value): 18 | self = .Succeeded(value) 19 | case .None(let asyncOpError): 20 | switch asyncOpError { 21 | case .NoValue: 22 | self = .Failed(AsyncOpError.NoResultBecauseOperationNotFinished) 23 | case .Cancelled: 24 | self = .Cancelled 25 | case .Failed(let error): 26 | self = .Failed(error) 27 | } 28 | } 29 | } 30 | 31 | var succeeded: Bool { 32 | switch self { 33 | case .Succeeded: 34 | return true 35 | default: 36 | return false 37 | } 38 | } 39 | } 40 | 41 | extension AsyncOp { 42 | 43 | public var result: AsyncOpResult { 44 | return AsyncOpResult(asyncOpValue: output) 45 | } 46 | 47 | } 48 | 49 | public protocol AsyncVoidConvertible: NilLiteralConvertible { 50 | init(asyncVoid: AsyncVoid) 51 | } 52 | 53 | extension AsyncVoidConvertible { 54 | public init(nilLiteral: ()) { 55 | self.init(asyncVoid: .Void) 56 | } 57 | } 58 | 59 | public enum AsyncVoid: AsyncVoidConvertible { 60 | case Void 61 | public init(asyncVoid: AsyncVoid) { 62 | self = .Void 63 | } 64 | } 65 | 66 | public protocol AsyncOpResultStatusProvider { 67 | var resultStatus: AsyncOpResultStatus { get } 68 | } 69 | 70 | public enum AsyncOpResultStatus { 71 | case Pending 72 | case Succeeded 73 | case Cancelled 74 | case Failed 75 | } 76 | 77 | public protocol AsyncOpInputProvider { 78 | associatedtype ProvidedInputValueType 79 | func provideAsyncOpInput() -> AsyncOpValue 80 | } 81 | 82 | public enum AsyncOpValue: AsyncOpInputProvider { 83 | case None(AsyncOpValueErrorType) 84 | case Some(ValueType) 85 | 86 | public typealias ProvidedInputValueType = ValueType 87 | public func provideAsyncOpInput() -> AsyncOpValue { 88 | return self 89 | } 90 | } 91 | 92 | public enum AsyncOpValueErrorType: ErrorType { 93 | case NoValue 94 | case Cancelled 95 | case Failed(ErrorType) 96 | } 97 | 98 | extension AsyncOpValue { 99 | 100 | public func getValue() throws -> ValueType { 101 | switch self { 102 | case .None: 103 | throw AsyncOpValueErrorType.NoValue 104 | case .Some(let value): 105 | return value 106 | } 107 | } 108 | 109 | public var value: ValueType? { 110 | switch self { 111 | case .None: 112 | return nil 113 | case .Some(let value): 114 | return value 115 | } 116 | } 117 | 118 | public var noneError: AsyncOpValueErrorType? { 119 | switch self { 120 | case .None(let error): 121 | return error 122 | case .Some: 123 | return nil 124 | } 125 | } 126 | 127 | } 128 | 129 | extension AsyncOpValueErrorType { 130 | 131 | public var cancelled: Bool { 132 | switch self { 133 | case .Cancelled: 134 | return true 135 | default: 136 | return false 137 | } 138 | } 139 | 140 | public var failed: Bool { 141 | switch self { 142 | case .Failed: 143 | return true 144 | default: 145 | return false 146 | } 147 | } 148 | 149 | public var failureError: ErrorType? { 150 | switch self { 151 | case .Failed(let error): 152 | return error 153 | default: 154 | return nil 155 | } 156 | } 157 | 158 | } 159 | 160 | public enum AsyncOpError: ErrorType { 161 | case Unspecified 162 | case NoResultBecauseOperationNotFinished 163 | case UnimplementedOperation 164 | case Multiple([ErrorType]) 165 | case PreconditionFailure 166 | } 167 | 168 | public enum AsyncOpPreconditionInstruction { 169 | case Continue 170 | case Cancel 171 | case Fail(ErrorType) 172 | 173 | init(errors: [ErrorType]) { 174 | if errors.count == 1 { 175 | self = .Fail(errors[0]) 176 | } else { 177 | self = .Fail(AsyncOpError.Multiple(errors)) 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /InstallingTests.md: -------------------------------------------------------------------------------- 1 | Install Carthage and `carthage update --no-build` or add Quick and Nimble projects to Carthage/Checkouts/. 2 | 3 | Note: DO NOT EDIT Cartfile.resolved under any scenario. Only edit Cartfile or Cartfile.private to change dependencies. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Jed Lewison 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Legacy/AsyncClosuresOperation.swift: -------------------------------------------------------------------------------- 1 | /// AsyncOperation is provided for compatability with objective c 2 | 3 | import Foundation 4 | 5 | /// Pass an AsyncClosure to a AsyncClosuresOperation to perform a potentially asynchronous work inside a closure, marking it as finished when done. 6 | /// 7 | /// - parameter closureController: Use the closureController to mark the closure finished, to cancel the parent closures operation, or to check operation status. 8 | public typealias AsyncClosure = (closureController: AsyncClosureObjectProtocol) -> Void 9 | 10 | /// AsyncClosureObjectProtocol defines the interface for the object passed into AsyncClosures by AsyncClosuresOperations 11 | @objc public protocol AsyncClosureObjectProtocol : NSObjectProtocol { 12 | /// Set a value on the AsyncClosuresOperation to pass among closures 13 | var value: AnyObject? {get set} 14 | 15 | /// Check if the operation is cancelled 16 | var isOperationCancelled: Bool { get } 17 | 18 | /// Mark the closure finished 19 | func finishClosure() -> Void 20 | 21 | /// Cancel the operation 22 | func cancelOperation() -> Void 23 | } 24 | 25 | /// The kind of serial operation queue the AsyncClosuresOperation should manage. 26 | @objc public enum AsyncClosuresQueueKind: Int { 27 | /// Use a mainQueue operation queue 28 | case Main 29 | /// Create a background queue 30 | case Background 31 | } 32 | 33 | /// AsyncClosuresOperation manages a queue of AsyncClosure closures. 34 | 35 | public class AsyncClosuresOperation : AsyncOperation { 36 | 37 | ///:queueKind: Whether the closures should execute on the mainQueue or a background queue. 38 | ///- returns: A new AsyncClosuresOperation 39 | @objc override public convenience init() { 40 | self.init(queueKind: .Main, qualityOfService: .Default) 41 | } 42 | 43 | @objc public convenience init(queueKind: AsyncClosuresQueueKind) { 44 | self.init(queueKind: queueKind, qualityOfService: .Default) 45 | } 46 | 47 | @objc public init(queueKind: AsyncClosuresQueueKind, qualityOfService: NSQualityOfService) { 48 | closureQueueKind = queueKind 49 | closureDispatchQueue = closureQueueKind.serialDispatchQueue(qualityOfService) 50 | 51 | super.init() 52 | self.qualityOfService = qualityOfService 53 | } 54 | 55 | /// Create a new AsyncClosuresOperation with a closure 56 | /// 57 | /// 1. Because the queue is performed serially, closures must be marked finished. (See addAsyncClosure). 58 | /// 2. Once started, an AsyncClosuresOperation will not finish until all its closures have been marked as finished, even if it has been cancelled. It is the programmer's responsibility to check for cancellation. 59 | /// 3. If an AsyncClosuresOperation is cancelled before it is started, its AsyncClosures will not be called. 60 | /// 4. For executing closures concurrently, use a concurrent operation queue with multiple AsyncClosuresOperations. You can add dependencies. 61 | /// 62 | /// :queueKind: Whether the closures should execute on the mainQueue or a background queue. 63 | /// :asyncClosure: The AsyncClosure. 64 | /// :see: addAsyncClosure. 65 | 66 | @objc public convenience init(queueKind: AsyncClosuresQueueKind, asyncClosure: AsyncClosure) { 67 | self.init(queueKind: queueKind, qualityOfService: .Default) 68 | addAsyncClosure(asyncClosure) 69 | } 70 | 71 | @objc class public func asyncClosuresOperation(queueKind: AsyncClosuresQueueKind, asyncClosure: AsyncClosure) -> AsyncClosuresOperation { 72 | return AsyncClosuresOperation(queueKind: queueKind, asyncClosure: asyncClosure) 73 | } 74 | 75 | /// Adds a new AsyncClosure to the AsyncClosuresOperation. Has no effect if the operation has begun executing or has already finished or been cancelled. 76 | /// Usage: 77 | /// closuresOp.addAsyncClosure { 78 | /// closureController in 79 | /// if closureController.isOperationCancelled { 80 | /// closureController.finishClosure() 81 | /// return 82 | /// } 83 | /// dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) { 84 | /// // do some async stuff and then finish 85 | /// closureController.finishClosure() 86 | /// } 87 | /// } 88 | /// 89 | /// - parameter asyncClosure: The AsyncClosure to add. For the operation to proceed to 90 | /// the next closure or to finish, you must use asyncClosure's closureController 91 | /// parameter mark it as finished. 92 | /// :see: AsyncClosureObjectProtocol 93 | 94 | public final func addAsyncClosure(asyncClosure: AsyncClosure) { 95 | if executing || finished || cancelled { 96 | return 97 | } 98 | 99 | let asyncClosureOp = AsyncClosureOperation(asyncClosure: asyncClosure, masterOperation: self) 100 | 101 | if let previousOp = closures.last { 102 | asyncClosureOp.addDependency(previousOp) 103 | } 104 | 105 | closures.append(asyncClosureOp) 106 | 107 | asyncClosureOp.completionBlock = { 108 | if let lastOp = self.closures.last { 109 | if lastOp.finished { 110 | self.closures.removeAll() 111 | self.finish() 112 | } 113 | } 114 | } 115 | } 116 | 117 | // MARK: Private methods/properties 118 | private let closureQueueKind: AsyncClosuresQueueKind 119 | private let closureDispatchQueue: dispatch_queue_t 120 | 121 | private var closures = [AsyncClosureOperation]() 122 | 123 | override public func main() { 124 | 125 | self.performNextClosureOperationOrFinish() 126 | } 127 | 128 | private final func performNextClosureOperationOrFinish() { 129 | 130 | if let nextClosureOp = closures.first { 131 | closures.removeAtIndex(0) 132 | nextClosureOp.completionBlock = { 133 | self.performNextClosureOperationOrFinish() 134 | } 135 | dispatch_async(closureDispatchQueue) { 136 | nextClosureOp.start() 137 | } 138 | } else { 139 | self.finish() 140 | } 141 | 142 | } 143 | 144 | internal class AsyncClosureOperation : AsyncOperation, AsyncClosureObjectProtocol { 145 | 146 | init(asyncClosure: AsyncClosure, masterOperation: AsyncClosuresOperation) { 147 | self.asyncClosure = asyncClosure 148 | self.masterOperation = masterOperation 149 | super.init() 150 | } 151 | 152 | unowned let masterOperation: AsyncClosuresOperation 153 | 154 | private var asyncClosure: AsyncClosure? 155 | 156 | override func main() { 157 | if let asyncClosure = asyncClosure { 158 | asyncClosure(closureController: self) 159 | self.asyncClosure = nil 160 | } else { 161 | self.finishClosure() 162 | } 163 | } 164 | 165 | var isOperationCancelled: Bool { 166 | return self.masterOperation.cancelled 167 | } 168 | 169 | func finishClosure() { 170 | self.finish() 171 | } 172 | 173 | func cancelOperation() { 174 | self.masterOperation.cancel() 175 | self.finish() 176 | } 177 | 178 | override var value: AnyObject? { 179 | get { 180 | return masterOperation.value 181 | } 182 | set { 183 | masterOperation.value = newValue 184 | } 185 | } 186 | } 187 | } 188 | 189 | 190 | 191 | 192 | extension AsyncClosuresQueueKind { 193 | public func serialOperationQueueForKind() -> NSOperationQueue { 194 | switch self { 195 | case .Main: 196 | return NSOperationQueue.mainQueue() 197 | case .Background: 198 | let serialOperationQueueForKind = NSOperationQueue() 199 | serialOperationQueueForKind.maxConcurrentOperationCount = 1 200 | return serialOperationQueueForKind 201 | } 202 | } 203 | public func serialDispatchQueue(qos: NSQualityOfService) -> dispatch_queue_t { 204 | switch self { 205 | case .Main: 206 | return dispatch_get_main_queue() 207 | case .Background: 208 | return qos.createSerialDispatchQueue("asyncClosuresOperation") 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /Legacy/AsyncOpKit.swift: -------------------------------------------------------------------------------- 1 | /// AsyncOperation is provided for compatability with objective c 2 | 3 | import Foundation 4 | 5 | extension NSQualityOfService { 6 | 7 | /// returns a global GCD queue for the corresponding QOS 8 | func getGlobalDispatchQueue() -> dispatch_queue_t { 9 | return dispatch_get_global_queue(dispatchQOS(), 0) 10 | } 11 | 12 | /// returns a GCD serial queue for the corresponding QOS 13 | func createSerialDispatchQueue(label: UnsafePointer) -> dispatch_queue_t { 14 | let attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, dispatchQOS(), 0) 15 | return dispatch_queue_create(label, attr) 16 | } 17 | 18 | /// returns a GCD concurrent queue for the corresponding QOS 19 | func createConcurrentDispatchQueue(label: UnsafePointer) -> dispatch_queue_t { 20 | let attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, dispatchQOS(), 0) 21 | return dispatch_queue_create(label, attr) 22 | } 23 | 24 | /// returns GCD's corresponding QOS class 25 | func dispatchQOS() -> qos_class_t { 26 | switch (self) { 27 | case .Background: 28 | return QOS_CLASS_BACKGROUND 29 | case .Default: 30 | return QOS_CLASS_DEFAULT 31 | case .UserInitiated: 32 | return QOS_CLASS_USER_INITIATED 33 | case .UserInteractive: 34 | return QOS_CLASS_USER_INTERACTIVE 35 | case .Utility: 36 | return QOS_CLASS_UTILITY 37 | } 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /Legacy/AsyncOperation.swift: -------------------------------------------------------------------------------- 1 | /// AsyncOperation is provided for compatability with objective c 2 | 3 | import Foundation 4 | 5 | 6 | enum AsyncOperationState: String { 7 | case Ready = "isReady" 8 | case Executing = "isExecuting" 9 | case Finished = "isFinished" 10 | } 11 | 12 | 13 | /// AsyncOperation takes care of the boilerplate you need for writing asynchronous NSOperations and adds a couple of useful features: An optional results handler that includes the operation, and properties to store results of the operation. 14 | 15 | public class AsyncOperation: NSOperation { 16 | 17 | var state = AsyncOperationState.Ready { 18 | willSet { 19 | if newValue != state { 20 | let oldValue = state 21 | willChangeValueForKey(newValue.rawValue) 22 | willChangeValueForKey(oldValue.rawValue) 23 | } 24 | } 25 | 26 | didSet { 27 | if oldValue != state { 28 | didChangeValueForKey(oldValue.rawValue) 29 | didChangeValueForKey(state.rawValue) 30 | if finished { 31 | if let completionHandler = completionHandler { 32 | self.completionHandler = nil 33 | dispatch_async(completionHandlerQueue) { completionHandler(finishedOp: self) } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | /// The completionHandler is fired once when the operation finishes on the queue specified by `completionHandlerQueue`. It passes in the finished operation which will indicate whethere the operation was cancelled, had an error, or has a value. 41 | /// :finishedOp: The finished operation. Downcast if needed inside the compleetion handler. 42 | public final var completionHandler: ((finishedOp: AsyncOperation) -> Void)? 43 | 44 | /// The operation queue on which the results handler will fire. Default is mainQueue. 45 | public final var completionHandlerQueue: dispatch_queue_t = dispatch_get_main_queue() 46 | 47 | /// Override main to start potentially asynchronous work. When the operation is complete, you must call finish(). Do not call super. 48 | /// This method will not be called it the operation was cancelled before it was started. 49 | override public func main() { 50 | finish() 51 | } 52 | 53 | // use this property to store the results of your operation. You can also declare new properties in subclasses 54 | public var value: AnyObject? 55 | 56 | // use this property to store any error about your operation 57 | public final var error: NSError? 58 | 59 | // MARK: Async Operation boilerplate. For more information, read the Concurrency Programming Guide for iOS or OS X. 60 | override public final var asynchronous: Bool { 61 | return true 62 | } 63 | 64 | override public final func start() { 65 | 66 | if state == .Finished { 67 | debugPrint("State was unexpectedly finished") 68 | } 69 | 70 | if state != .Ready { 71 | debugPrint("State was unexpectedly not ready") 72 | } 73 | 74 | state = .Executing 75 | 76 | if !cancelled { 77 | main() 78 | } else { 79 | finish() 80 | } 81 | } 82 | 83 | override public final var executing: Bool { 84 | get { return state == .Executing } 85 | } 86 | 87 | override public final var finished: Bool { 88 | get { return state == .Finished } 89 | } 90 | 91 | public final func finish() { 92 | dispatch_sync(lockQ) { 93 | self.state = .Finished 94 | } 95 | } 96 | 97 | private let lockQ = NSQualityOfService.UserInteractive.createSerialDispatchQueue("asyncOperation.lockQ") 98 | 99 | } 100 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | workspace 'AsyncOpKit' 2 | 3 | use_frameworks! 4 | inhibit_all_warnings! 5 | 6 | project 'AsyncOpKit' 7 | 8 | target "AsyncOpKitTests" do 9 | project 'AsyncOpKit' 10 | platform :ios, "9.0" 11 | pod 'Quick' 12 | pod 'Nimble' 13 | end 14 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Nimble (4.0.1) 3 | - Quick (0.9.2) 4 | 5 | DEPENDENCIES: 6 | - Nimble 7 | - Quick 8 | 9 | SPEC CHECKSUMS: 10 | Nimble: 0f3c8b8b084cda391209c3c5efbb48bedeeb920a 11 | Quick: 18d057bc66451eedd5d1c8dc99ba2a5db6e60226 12 | 13 | PODFILE CHECKSUM: c415f380faacf3e33b9ad082d390b8de42cf71e8 14 | 15 | COCOAPODS: 1.0.1 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AsyncOpKit 2 | 3 | AsyncOpKit brings Swift generics, error handling, and closures to NSOperations with `AsyncOp`, a Swift-only generic NSOperation subclass for composing asynchronous code. 4 | 5 | `AsyncOp` supports: 6 | 7 | * Generic input and output 8 | * Closures for starting and cancelling work, handling results 9 | * Closures for evaluating preconditions 10 | * Making an AsyncOp dependent on input from another 11 | 12 | You can subclass AsyncOp, but because it provides built-in storage for generic input and output and allows you to customize behavior with closures, in many if not most cases you can just use AsyncOp as-is. 13 | 14 | ## Requirements and installation 15 | 16 | * AsyncOp has been tested against iOS 8.0 and later. In theory, it should also work for OS X, tvOS, and WatchOS, but I haven't tested it. 17 | * Via CocoaPods: `pod AsyncOpKit` with `use_frameworks!` in your podfile and `import AsyncOpKit` in files where you use it. 18 | * Or just add the `AsyncOp.swift` and `AsyncOpTypes.swift` files to your project. 19 | 20 | ## License/Author 21 | 22 | `AsyncOp` is written by me (Jed Lewison) and has an MIT license. It's still a work in progress as is this documentation, so feedback is welcome. 23 | 24 | ## Sample usage 25 | 26 | Let's say you want to download an image. You could create a simple `AsyncOp` with a input type of NSURL and an output type of UIImage. Start with: 27 | 28 | ```swift 29 | let imageDownloadOp = AsyncOp() 30 | ``` 31 | 32 | Now since we already know our URL, we can simply provide it right away: 33 | 34 | ```swift 35 | imageDownloadOp.setInput(imageURL) 36 | ``` 37 | Next, we need specify how the image should be downloaded. We do that in the ``onStart`` closure, which begins like this: 38 | 39 | ```swift 40 | imageDownloadOp.onStart { asyncOp in 41 | ``` 42 | 43 | Note that in this example, `asyncOp` is identical to `imageDownloadOp`. That's not so useful here, but it can be useful if you're returning operations from a function. 44 | 45 | The first thign we have to do is get our input, which is stored in the input property which is an `AsyncOpValue`, an enum that stores the input value or if there was a problem providing the input, an associated error. `onStart` is a throwing closure, so to get our value we can call a throwing function on `AsyncOpValue` which will succeed if the value exists or throw if not. If it throws, the operation will finish with an error (more on that later). 46 | 47 | So here's what things should look like now: 48 | 49 | ```swift 50 | imageDownloadOp.onStart { asyncOp in 51 | let imageURL = try asyncOp.input.getValue() 52 | ``` 53 | 54 | Next we need to make a network request to get the data stored at the imageURL. For simplicity of this example, let's use a plain old NSURLSession for that: 55 | 56 | ```swift 57 | imageDownloadOp.onStart { asyncOp in 58 | let imageURL = try asyncOp.input.getValue() 59 | let dataTask = NSURLSession.sharedSession().dataTaskWithURL(imageURL) { data, response, error in 60 | // response handling here 61 | } 62 | dataTask.resume() 63 | } 64 | ``` 65 | 66 | Notice that I've cheated here by not handling the response. That's not only important for the obvious reasons, but it's also important because if we don't tell the operation when it's finished, it will never complete once it starts. 67 | 68 | ### Finish `AsyncOp`s with a `finish(with:)` function 69 | 70 | *Once an `AsyncOp` begins executing, **it must be manually finished***.* 71 | 72 | You can finish with an error by throwing. In our example, note that if `try asyncOp.input.getValue()` fails, that will finish the operation because it throws. Keep in mind that you can't throw from inside another closure unless that closure rethrows. 73 | 74 | Aside from throwing, how do you finish `AsyncOp`s? Here's a simple implementation extending the previous example: 75 | 76 | ```swift 77 | imageDownloadOp.onStart { asyncOp in 78 | let imageURL = try asyncOp.input.getValue() 79 | let dataTask = NSURLSession.sharedSession().dataTaskWithURL(imageURL) { data, _, error in 80 | if let data = data, image = UIImage(data: data) { 81 | asyncOp.finish(with: image) 82 | } else { 83 | asyncOp.finish(with: error ?? AsyncOpError.Unspecified) 84 | } 85 | } 86 | dataTask.resume() 87 | } 88 | ``` 89 | 90 | The key thing to take from that is that to finish an operation you call its `finish` function. `finish` has several convenient overloads that let you supply an error, the output value, or mark cancellation. **If you have an operation that does not product any output, you can use the `AsyncVoid` type and use `finishWithSuccess()` instead of doing something like `finish(with: Void())`.** 91 | 92 | ### Get `AsyncOp` results with `whenFinished` 93 | 94 | Once our operation finishes, how then do we get the image from the operation? We use the `whenFinished` closure. If we don't care about errors, the implementation might look like this: 95 | 96 | ```swift 97 | imageDownloadOp.whenFinished { asyncOp in 98 | guard let image = try? asyncOp.output.getValue() else { return } 99 | imageView.image = image 100 | } 101 | ``` 102 | 103 | Because AsyncOp uses generics and because we specified the output type as a UIImage, `image` is guaranteed to be a UIImage if it exists. If we wanted to handle errors, we could have switched on the output like this: 104 | 105 | ```swift 106 | imageDownloadOp.whenFinished { asyncOp in 107 | switch asyncOp.output { 108 | case .None(let asyncOpValueError): 109 | errorHandler.handleError(asyncOpValueError) 110 | case .Some(let image): 111 | imageView.image = image 112 | } 113 | } 114 | ``` 115 | 116 | The image is still guaranteed to be an image, but now we can inspect the error. Note that we just performed UI work in the `whenFinished` closure. That's because by default, the `whenFinished` closure fires on the mainQueue. To specify a different queue, simply don't accept the default parameter, for example: 117 | 118 | ```swift 119 | imageDownloadOp.whenFinished(whenFinishedQueue: notMainThreadQueue) { asyncOp in 120 | ``` 121 | 122 | Also keep in mind you can supply a `whenFinished` closure at any time, even after the operation has finished, but you can only do so once. 123 | 124 | ## Canceling `AsyncOp`s with `cancel()` 125 | 126 | Once an `AsyncOp` begins executing, it's up to you to handle cancelation. You can use the `onCancel` closure to specify actions to perform after `cancel()` is invoked, for example canceling the operations `dataTask`, but you must still check the operations `cancelled` property at appropriate times during execution to handle cancellation and finish the operation. If you choose to respect the cancel command, you should `finish(with: .Cancelled)`, usually in the `onStart` implementation after checking for cancellation. 127 | 128 | ## Chain AsyncOps with input dependencies via `AsyncOpInputProvider` 129 | 130 | Let's say we wanted to do something fancier with the data than simply attempting to convert it to a UIImage — perhaps we wanted to resize the image and mask it. We could add code to our operation's `onStart` closure to accomplish that, but that could quickly become very hard to read. Instead, what we'd want to do is to create two or more `AsyncOp`s and chain them together. 131 | 132 | Let's say what we want is this (of course catching errors along the way) 133 | 134 | 1. Get some image data from the network and provide a raw image 135 | 2. Process the raw image and provide a final output image 136 | 137 | Since CoreImage makes it easier to apply all sorts of filters to images, now we want our image download operation to provide a CIImage: 138 | 139 | ```swift 140 | let imageDownloadOp = AsyncOp() 141 | imageDownloadOp.setInput(imageURL) 142 | ``` 143 | 144 | And we want to create a new operation that takes in a CIImage and returns a UIImage: 145 | 146 | ```swift 147 | let imageFilteringOp = AsyncOp() 148 | ``` 149 | 150 | But there's a problem, right? How can we get the output of the `imageDownloadOp` to the input of the `imageFilteringOp` without a bunch of boilerplate? Fortunately, `AsyncOp` makes it simple: 151 | 152 | ```swift 153 | imageFilteringOp.setInputProvider(imageDownloadOp) 154 | ``` 155 | 156 | `setInputProvider` gives the target operation an object conforming to `AsyncOpInputProvider` from which to request its input just as it begins executing. Moreover, if the input provider is also an NSOperation, the target adds the provider as a dependency. What this means is that now that we've set our imageDownloadOp as the inputProvider for our imageFilteringOp, the only thing we need to do is get our input at the beginning of our `onStart` closure. For example: 157 | 158 | ```swift 159 | imageFilteringOp.onStart { asyncOp in 160 | let image = try asyncOp.input.getValue() 161 | ``` 162 | 163 | Remember, `getValue()` throws, and `onStart` is a throwing closure, so if the download operation errored out and we have no image, the operation will finish immediately at this point. Otherwise, we can continue on with our image filtering, making sure to `finish(with: outputImage)` when we are done. 164 | 165 | AsyncOp conforms to `AsyncOpInputProvider` so any `AsyncOp` can provide input to another `AsyncOp` as long as its output type matches the target's input type. Thanks to the dependency relationship provided by NSOperation, the input provider will neve be asked to provide input until it has completed. 166 | 167 | Remember that the input is an `AsyncOpValue` enum. Although it is strongly typed using generics, using an enum wrapper allows for propagating error messages, so you must unwrap input using the syntax above. 168 | 169 | ## Other features 170 | 171 | This documentation is still a work in progress, as is `AsyncOp` itself. Aside from reading theo code, you might want to peruse the tests for other features not yet covered here, including: 172 | 173 | * Using `pause()` and `resume()` to suspend the readiness of `AsyncOp`s before they begin executing 174 | * Using `AsyncOpPreconditionEvaluator` functions for evaluating preconditions. These allow you supply functions that are evaluated before `onStart` is called that can prevent an operation from executing if preconditions aren't met. 175 | 176 | 177 | ### AsyncOperation for Objective-C and Swift 1.2 compatibility 178 | 179 | AsyncOperation is provided for legacy compatibility with Objective-C. It doesn't provide all the features of AsyncOp, but it does take away the boilerplate involved in async operations and lets you specify a result and error value. AsyncOperation works with Obj-C. For Swift 1.2, either copy the AsyncOperation files only or use `pod AsyncOpKit, '0.0.8'`. 180 | -------------------------------------------------------------------------------- /Tests/AsyncOpKitTests/ClosureTestsFile.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Quick 3 | import Nimble 4 | @testable import AsyncOpKit 5 | 6 | class AsyncClosureOpKitConvenienceInitTests: AsyncOpKitTests { 7 | 8 | override internal func createTestInstance() -> AsyncOperation { 9 | 10 | // make sure the init closures object can pass all the current tests 11 | let dispatchQ = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT) 12 | let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(0.0005 * Double(NSEC_PER_SEC))) 13 | 14 | let closuresOp = AsyncClosuresOperation(queueKind: .Background) { 15 | closureController in 16 | dispatch_after(delayTime, dispatchQ) { 17 | closureController.finishClosure() 18 | } 19 | } 20 | 21 | return closuresOp 22 | } 23 | } 24 | 25 | class AsyncClosureOpKitClassFactoryTests: AsyncOpKitTests { 26 | 27 | override internal func createTestInstance() -> AsyncOperation { 28 | 29 | // make sure the factory created closures object can pass all the current tests 30 | let dispatchQ = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT) 31 | let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(0.0005 * Double(NSEC_PER_SEC))) 32 | 33 | let closuresOp = AsyncClosuresOperation.asyncClosuresOperation(.Main) { 34 | closureController in 35 | dispatch_after(delayTime, dispatchQ) { 36 | closureController.finishClosure() 37 | } 38 | } 39 | 40 | return closuresOp 41 | } 42 | } 43 | 44 | class AsyncClosureOpKitTests: QuickSpec { 45 | 46 | // Using a reference type to make it easier to cleanup 47 | internal class TestAssistant { 48 | var numberOfAsyncClosuresFinished = 0 49 | var resultValue = "Should change" 50 | var numberOfCancellations = 0 51 | } 52 | 53 | override func spec() { 54 | specWithQueueKind(.Background) 55 | specWithQueueKind(.Main) 56 | } 57 | 58 | func specWithQueueKind(queueKind: AsyncClosuresQueueKind) { 59 | 60 | describe("Handle Async Closures") { 61 | 62 | var subject: AsyncClosuresOperation! = nil 63 | var dispatchQ: dispatch_queue_t! 64 | var testAssistant: TestAssistant! = nil 65 | 66 | beforeEach { 67 | dispatchQ = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT) 68 | testAssistant = TestAssistant() 69 | 70 | subject = AsyncClosuresOperation() 71 | } 72 | 73 | afterEach { 74 | if let subject = subject { 75 | subject.cancel() 76 | subject.finish() 77 | subject.completionBlock = nil 78 | subject.completionHandler = nil 79 | 80 | } 81 | subject = nil 82 | dispatchQ = nil 83 | testAssistant = nil 84 | } 85 | 86 | context("when there is one closure that finishes synchronously") { 87 | 88 | let expectedValue = "AsyncClosuresOperation result value" 89 | 90 | beforeEach { 91 | subject.addAsyncClosure { 92 | closureController in 93 | testAssistant.numberOfAsyncClosuresFinished++ 94 | closureController.value = expectedValue 95 | closureController.finishClosure() 96 | 97 | } 98 | 99 | subject.completionHandler = { 100 | finishedOp in 101 | if let value = finishedOp.value as? String { 102 | testAssistant.resultValue = value 103 | } 104 | } 105 | 106 | subject.start() 107 | } 108 | 109 | it("should execute one closure") { 110 | expect(testAssistant.numberOfAsyncClosuresFinished).toEventually(equal(1)) 111 | } 112 | 113 | it("should eventually mark itself as finished") { 114 | expect(subject.finished).toEventually(beTrue()) 115 | } 116 | 117 | it("should have the same value that was assigned in the closure") { 118 | expect(testAssistant.resultValue).toEventually(equal(expectedValue)) 119 | } 120 | } 121 | 122 | context("when there are ten closures that finish synchronously") { 123 | 124 | beforeEach { 125 | for _ in 0...9 { 126 | subject.addAsyncClosure { 127 | closureController in 128 | testAssistant.numberOfAsyncClosuresFinished++ 129 | closureController.finishClosure() 130 | } 131 | } 132 | 133 | subject.start() 134 | } 135 | 136 | it("should execute ten closures") { 137 | expect(testAssistant.numberOfAsyncClosuresFinished).toEventually(equal(10)) 138 | } 139 | 140 | it("should eventually mark itself as finished") { 141 | expect(subject.finished).toEventually(beTrue()) 142 | } 143 | } 144 | 145 | context("when there are ten closures that finish asynchronously") { 146 | 147 | beforeEach { 148 | for _ in 0...9 { 149 | subject.addAsyncClosure { 150 | closureController in 151 | dispatch_async(dispatchQ) { 152 | testAssistant.numberOfAsyncClosuresFinished++ 153 | closureController.finishClosure() 154 | } 155 | } 156 | } 157 | 158 | subject.start() 159 | } 160 | 161 | it("should execute ten closures") { 162 | expect(testAssistant.numberOfAsyncClosuresFinished).toEventually(equal(10)) 163 | } 164 | 165 | it("should eventually mark itself as finished") { 166 | expect(subject.finished).toEventually(beTrue()) 167 | } 168 | } 169 | 170 | context("when there are ten closures that finish asynchronously with multiple finishAsyncClosure commands") { 171 | 172 | beforeEach { 173 | for _ in 0...9 { 174 | subject.addAsyncClosure { 175 | closureController in 176 | dispatch_async(dispatchQ) { 177 | testAssistant.numberOfAsyncClosuresFinished++ 178 | closureController.finishClosure() 179 | closureController.finishClosure() 180 | closureController.finishClosure() 181 | closureController.finishClosure() 182 | 183 | } 184 | } 185 | } 186 | 187 | subject.start() 188 | } 189 | 190 | it("should execute ten closures") { 191 | expect(testAssistant.numberOfAsyncClosuresFinished).toEventually(equal(10)) 192 | } 193 | 194 | it("should eventually mark itself as finished") { 195 | expect(subject.finished).toEventually(beTrue()) 196 | } 197 | } 198 | 199 | 200 | context("when the operation is cancelled after executing five closures but the closures simply opt-out by marking the closure finished") { 201 | 202 | beforeEach { 203 | for _ in 0...9 { 204 | subject.addAsyncClosure { 205 | closureController in 206 | if closureController.isOperationCancelled { 207 | testAssistant.numberOfCancellations++ 208 | closureController.finishClosure() 209 | return 210 | } 211 | dispatch_async(dispatchQ) { 212 | testAssistant.numberOfAsyncClosuresFinished++ 213 | if (testAssistant.numberOfAsyncClosuresFinished == 5) { 214 | closureController.cancelOperation() 215 | } 216 | closureController.finishClosure() 217 | 218 | } 219 | } 220 | } 221 | 222 | subject.start() 223 | } 224 | 225 | it("should execute five uncancelled closures") { 226 | expect(testAssistant.numberOfAsyncClosuresFinished).toEventually(equal(5)) 227 | } 228 | 229 | it("should eventually mark itself as finished") { 230 | expect(subject.finished).toEventually(beTrue()) 231 | } 232 | 233 | it("should eventually mark itself as canceled") { 234 | expect(subject.cancelled).toEventually(beTrue()) 235 | } 236 | 237 | it("should tell 5 of the closures that it was cancelled") { 238 | expect(testAssistant.numberOfCancellations).toEventually(equal(5)) 239 | } 240 | } 241 | 242 | context("when the operation is cancelled after executing five closures and the async block finishes after cancellation") { 243 | 244 | beforeEach { 245 | for _ in 0...9 { 246 | subject.addAsyncClosure { 247 | closureController in 248 | if closureController.isOperationCancelled { 249 | testAssistant.numberOfCancellations++ 250 | closureController.finishClosure() 251 | return 252 | } 253 | dispatch_async(dispatchQ) { 254 | testAssistant.numberOfAsyncClosuresFinished++ 255 | if (testAssistant.numberOfAsyncClosuresFinished == 5) { 256 | closureController.cancelOperation() 257 | } 258 | closureController.finishClosure() 259 | } 260 | } 261 | } 262 | 263 | subject.start() 264 | } 265 | 266 | it("should execute five uncancelled closures") { 267 | expect(testAssistant.numberOfAsyncClosuresFinished).toEventually(equal(5)) 268 | } 269 | 270 | it("should eventually mark itself as finished") { 271 | expect(subject.finished).toEventually(beTrue()) 272 | } 273 | 274 | it("should eventually mark itself as canceled") { 275 | expect(subject.cancelled).toEventually(beTrue()) 276 | } 277 | 278 | it("should tell 5 of the closures that it was cancelled and not execute the remaining 5") { 279 | expect(testAssistant.numberOfCancellations).toEventually(equal(5)) 280 | expect(testAssistant.numberOfAsyncClosuresFinished).toEventually(equal(5)) 281 | } 282 | } 283 | 284 | } 285 | 286 | } 287 | 288 | } 289 | -------------------------------------------------------------------------------- /Tests/AsyncOpKitTests/Tests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Quick 3 | import Nimble 4 | @testable import AsyncOpKit 5 | 6 | class AsyncOpKitTests: QuickSpec { 7 | 8 | // Create a simple subclass of the base class that does something asynchronously 9 | internal class TestAsyncOperation : AsyncOperation { 10 | let dispatchQ = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT) 11 | let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(0.05 * Double(NSEC_PER_SEC))) 12 | 13 | override final func main() { 14 | dispatch_after(delayTime, dispatchQ) { 15 | self.finish() 16 | } 17 | } 18 | } 19 | 20 | internal func createTestInstance() -> AsyncOperation { 21 | return TestAsyncOperation() 22 | } 23 | 24 | override func spec() { 25 | performTest() 26 | } 27 | 28 | func performTest() { 29 | describe("The behavior of an AsyncOperation") { 30 | 31 | var subject: AsyncOperation! = nil 32 | var finishedOperation: AsyncOperation? = nil 33 | var completionHandlerCompleted: Bool? = nil 34 | 35 | beforeEach { 36 | finishedOperation = nil 37 | completionHandlerCompleted = false 38 | 39 | subject = self.createTestInstance() 40 | subject.completionHandler = { 41 | finishedOp in 42 | finishedOperation = finishedOp 43 | completionHandlerCompleted = true 44 | } 45 | } 46 | 47 | afterEach { 48 | finishedOperation = nil 49 | completionHandlerCompleted = nil 50 | subject?.completionHandler = nil 51 | subject = nil 52 | } 53 | 54 | context("before it starts") { 55 | 56 | it("should be ready to start") { 57 | expect(subject.ready).to(beTrue()) 58 | } 59 | 60 | it("should not be executing") { 61 | expect(subject.executing).to(beFalse()) 62 | } 63 | 64 | it("should not be cancelled") { 65 | expect(subject.cancelled).to(beFalse()) 66 | } 67 | 68 | it("should not be finished") { 69 | expect(subject.finished).to(beFalse()) 70 | } 71 | 72 | } 73 | 74 | context("when it starts normally") { 75 | 76 | beforeEach { 77 | subject.start() 78 | } 79 | 80 | afterEach { 81 | subject = nil 82 | } 83 | 84 | describe("immediately after starting") { 85 | it("should be executing") { 86 | expect(subject.executing).to(beTrue()) 87 | } 88 | 89 | it("should not be finished") { 90 | expect(subject.finished).to(beFalse()) 91 | } 92 | 93 | it("should not be cancelled") { 94 | expect(subject.cancelled).to(beFalse()) 95 | } 96 | 97 | it("should not execute its results handler") { 98 | expect(completionHandlerCompleted).to(beFalse()) 99 | } 100 | 101 | } 102 | 103 | describe("asynchronously after starting") { 104 | it("should stop executing") { 105 | expect(subject.executing).toEventually(beFalse()) 106 | } 107 | 108 | it("should finish") { 109 | expect(subject.finished).toEventually(beTrue()) 110 | } 111 | 112 | it("should execute its results handler") { 113 | expect(completionHandlerCompleted).toEventually(beTrue()) 114 | } 115 | 116 | it("should nil out the results handler") { 117 | expect(subject.completionHandler).toEventually(beNil()) 118 | } 119 | 120 | it("should return itself in its results handler resultsObject") { 121 | expect(finishedOperation).toEventually(equal(subject)) 122 | } 123 | 124 | it("should return a results object that does not indicate it is canceled") { 125 | expect(finishedOperation?.cancelled).toEventually(beFalse()) 126 | } 127 | } 128 | 129 | context("when an operation finishes normally") { 130 | 131 | it("should not be cancelled") { 132 | expect(subject.cancelled).toEventuallyNot(beTrue()) 133 | } 134 | 135 | it("should eventually be finished") { 136 | expect(subject.finished).toEventually(beTrue()) 137 | } 138 | 139 | it("should not be executing") { 140 | expect(subject.executing).toEventually(beFalse()) 141 | } 142 | 143 | } 144 | 145 | context("When an executing operation is cancelled after being started") { 146 | 147 | beforeEach { 148 | subject.cancel() 149 | } 150 | 151 | it("should eventually be marked as cancelled") { 152 | expect(subject.cancelled).toEventually(beTrue()) 153 | } 154 | 155 | it("should eventually stop executing and finish") { 156 | expect(subject.executing).toEventually(beFalse()) 157 | expect(subject.finished).toEventually(beTrue()) 158 | } 159 | 160 | it("should invoke the results closure when finished") { 161 | expect(completionHandlerCompleted).toEventually(beTrue()) 162 | } 163 | 164 | it("the results closure results object should mark it as cancelled") { 165 | expect(finishedOperation?.cancelled).toEventually(beTrue()) 166 | } 167 | } 168 | 169 | } 170 | 171 | context("when it is canceled before starting") { 172 | 173 | beforeEach { 174 | subject.cancel() 175 | subject.start() 176 | } 177 | 178 | it("should immediately cancel") { 179 | expect(subject.cancelled).to(beTrue()) 180 | } 181 | 182 | it("should be finished") { 183 | expect(subject.finished).to(beTrue()) 184 | } 185 | 186 | it("should never start executing") { 187 | expect(subject.executing).to(beFalse()) 188 | } 189 | 190 | it("should never be executing") { 191 | expect(subject.executing).toEventuallyNot(beTrue()) 192 | } 193 | 194 | it ("the result handler's completion block should still fire") { 195 | expect(completionHandlerCompleted).toEventually(beTrue()) 196 | } 197 | 198 | it("the completionHandler's resultsObject should mark it as cancelled") { 199 | expect(finishedOperation?.cancelled).toEventually(beTrue()) 200 | } 201 | } 202 | 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /Tests/AsyncOpTests/AsyncOpAutomaticInputTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOpInferringInputTests.swift 3 | // AsyncOp 4 | // 5 | // Created by Jed Lewison on 9/3/15. 6 | // Copyright © 2015 Magic App Factory. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Quick 11 | import Nimble 12 | @testable import AsyncOpKit 13 | 14 | class AClassInputProvider: AsyncOpInputProvider { 15 | let asyncOpInput: AsyncOpValue 16 | init (asyncOpInput: AsyncOpValue) { 17 | self.asyncOpInput = asyncOpInput 18 | } 19 | 20 | func provideAsyncOpInput() -> AsyncOpValue { 21 | return asyncOpInput 22 | } 23 | } 24 | 25 | 26 | class AsyncOpAutomaticInputTests : QuickSpec, AsyncOpInputProvider { 27 | 28 | var randomOutputNumber = random() 29 | 30 | override func spec() { 31 | 32 | var randomOutputNumber: Int! 33 | 34 | beforeEach { 35 | self.randomOutputNumber = random() 36 | randomOutputNumber = self.randomOutputNumber 37 | } 38 | 39 | describe("Using another async operation to provide input") { 40 | 41 | var subject: AsyncOp! 42 | var inputProvider: AsyncOp! 43 | var inputValue: Int? 44 | var opQ: NSOperationQueue! 45 | 46 | beforeEach { 47 | opQ = NSOperationQueue() 48 | opQ.maxConcurrentOperationCount = 1 49 | subject = AsyncOp() 50 | inputProvider = AsyncOp() 51 | inputProvider.onStart { operation in 52 | operation.finish(with: .Some(randomOutputNumber)) 53 | } 54 | 55 | inputValue = nil 56 | subject.setInputProvider(inputProvider) 57 | 58 | subject.onStart { operation in 59 | guard let outValue = operation.input.value else { throw AsyncOpError.Unspecified } 60 | inputValue = outValue 61 | operation.finish(with: .Some(outValue)) 62 | } 63 | } 64 | 65 | context("The input provider is added to the operation queue before the subject") { 66 | 67 | beforeEach { 68 | opQ.addOperation(inputProvider) 69 | opQ.addOperation(subject) 70 | } 71 | 72 | it("should start with an input of randomOutputNumber") { 73 | expect(inputValue).toEventually(equal(randomOutputNumber)) 74 | } 75 | } 76 | 77 | context("The subject is added to the queue before the inputProvider") { 78 | 79 | beforeEach { 80 | opQ.addOperation(subject) 81 | opQ.addOperation(inputProvider) 82 | } 83 | 84 | it("should start with an input of randomOutputNumber") { 85 | expect(inputValue).toEventually(equal(randomOutputNumber)) 86 | } 87 | 88 | } 89 | 90 | context("The inputProvider is repeatedly added ") { 91 | 92 | beforeEach { 93 | opQ.addOperation(subject) 94 | subject.setInputProvider(inputProvider) 95 | subject.setInputProvider(inputProvider) 96 | subject.setInputProvider(inputProvider) 97 | subject.setInputProvider(inputProvider) 98 | opQ.addOperation(inputProvider) 99 | } 100 | 101 | it("should start with an input of randomOutputNumber") { 102 | expect(inputValue).toEventually(equal(randomOutputNumber)) 103 | } 104 | 105 | } 106 | 107 | } 108 | 109 | 110 | describe("Using an unrelated class to provide input") { 111 | 112 | var subject: AsyncOp! 113 | var inputProvider: AClassInputProvider! 114 | var inputValue: Int? 115 | var opQ: NSOperationQueue! 116 | 117 | beforeEach { 118 | opQ = NSOperationQueue() 119 | subject = AsyncOp() 120 | inputProvider = AClassInputProvider(asyncOpInput: .Some(randomOutputNumber)) 121 | inputValue = nil 122 | subject.onStart { operation in 123 | guard let outValue = operation.input.value else { throw AsyncOpError.Unspecified } 124 | inputValue = outValue 125 | operation.finish(with: .Some(outValue)) 126 | } 127 | } 128 | 129 | context("An Async operation with an input set before the operation is added to a queue") { 130 | 131 | beforeEach { 132 | subject.setInputProvider(inputProvider) 133 | opQ.addOperations([subject], waitUntilFinished: false) 134 | } 135 | 136 | it("should start with an input of randomOutputNumber") { 137 | expect(inputValue).toEventually(equal(randomOutputNumber)) 138 | } 139 | 140 | } 141 | } 142 | 143 | describe("Using an AsyncInput Enum to provide input") { 144 | 145 | var subject: AsyncOp! 146 | var inputProvider: AsyncOpValue! 147 | var inputValue: Int? 148 | var opQ: NSOperationQueue! 149 | 150 | beforeEach { 151 | opQ = NSOperationQueue() 152 | subject = AsyncOp() 153 | inputProvider = AsyncOpValue.Some(randomOutputNumber) 154 | inputValue = nil 155 | subject.onStart { operation in 156 | guard let outValue = operation.input.value else { throw AsyncOpError.Unspecified } 157 | inputValue = outValue 158 | operation.finish(with: .Some(outValue)) 159 | } 160 | } 161 | 162 | context("An Async operation with an input set before the operation is added to a queue") { 163 | 164 | beforeEach { 165 | subject.setInputProvider(inputProvider) 166 | opQ.addOperations([subject], waitUntilFinished: false) 167 | } 168 | 169 | it("should start with an input of randomOutputNumber") { 170 | expect(inputValue).toEventually(equal(randomOutputNumber)) 171 | } 172 | 173 | } 174 | } 175 | 176 | describe("Using a function on the test class to provide input") { 177 | 178 | var subject: AsyncOp! 179 | var inputProvider: AsyncOpAutomaticInputTests! 180 | var inputValue: Int? 181 | var opQ: NSOperationQueue! 182 | 183 | beforeEach { 184 | opQ = NSOperationQueue() 185 | subject = AsyncOp() 186 | inputProvider = self 187 | inputValue = nil 188 | subject.onStart { operation in 189 | guard let outValue = operation.input.value else { throw AsyncOpError.Unspecified } 190 | inputValue = outValue 191 | operation.finish(with: .Some(outValue)) 192 | } 193 | } 194 | 195 | context("An Async operation with an input set before the operation is added to a queue") { 196 | 197 | beforeEach { 198 | subject.setInputProvider(inputProvider) 199 | opQ.addOperations([subject], waitUntilFinished: false) 200 | } 201 | 202 | it("should start with an input of randomOutputNumber") { 203 | expect(inputValue).toEventually(equal(randomOutputNumber)) 204 | } 205 | 206 | } 207 | } 208 | 209 | } 210 | 211 | func provideAsyncOpInput() -> AsyncOpValue { 212 | return .Some(self.randomOutputNumber) 213 | } 214 | 215 | } 216 | -------------------------------------------------------------------------------- /Tests/AsyncOpTests/AsyncOpFinishOverloadsTesting.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Quick 3 | import Nimble 4 | @testable import AsyncOpKit 5 | 6 | 7 | class AsyncOpFinishOverloadsTesting : QuickSpec { 8 | 9 | override func spec() { 10 | universalOverloadSpec() 11 | voidOverloadSpec() 12 | } 13 | 14 | func universalOverloadSpec() { 15 | 16 | var randomOutputNumber = 0 17 | var subjectNoOverloads: AsyncOp? 18 | var subjectUsingOverloads: AsyncOp? 19 | var opQ: NSOperationQueue? 20 | 21 | describe("Finish Overloads") { 22 | 23 | beforeEach { 24 | randomOutputNumber = random() 25 | subjectNoOverloads = AsyncOp() 26 | subjectUsingOverloads = AsyncOp() 27 | opQ = NSOperationQueue() 28 | } 29 | 30 | afterEach { 31 | subjectNoOverloads = nil 32 | subjectUsingOverloads = nil 33 | opQ = nil 34 | } 35 | 36 | context("Success") { 37 | 38 | beforeEach { 39 | subjectNoOverloads?.onStart { op in 40 | op.finish(with: .Some(randomOutputNumber)) 41 | } 42 | subjectUsingOverloads?.onStart { op in 43 | op.finish(with: randomOutputNumber) 44 | } 45 | randomOutputNumber = random() 46 | opQ?.addOperations([subjectNoOverloads!, subjectUsingOverloads!], waitUntilFinished: true) 47 | } 48 | 49 | it("both subjects should have the same output") { 50 | expect(subjectNoOverloads?.output.value).to(equal(randomOutputNumber)) 51 | expect(subjectUsingOverloads?.output.value).to(equal(randomOutputNumber)) 52 | } 53 | 54 | } 55 | 56 | context("Cancelled") { 57 | 58 | beforeEach { 59 | subjectNoOverloads?.onStart { op in 60 | op.finish(with: .None(.Cancelled)) 61 | } 62 | subjectUsingOverloads?.onStart { op in 63 | op.finish(with: .Cancelled) 64 | } 65 | opQ?.addOperations([subjectNoOverloads!, subjectUsingOverloads!], waitUntilFinished: true) 66 | } 67 | 68 | it("both subjects should have the same output") { 69 | expect(subjectNoOverloads?.output.noneError?.cancelled).to(beTrue()) 70 | expect(subjectUsingOverloads?.output.noneError?.cancelled).to(beTrue()) 71 | } 72 | 73 | } 74 | 75 | context("Failed") { 76 | 77 | beforeEach { 78 | subjectNoOverloads?.onStart { op in 79 | op.finish(with: .None(.Failed(AsyncOpError.Unspecified))) 80 | } 81 | subjectUsingOverloads?.onStart { op in 82 | op.finish(with: .Failed(AsyncOpError.Unspecified)) 83 | } 84 | opQ?.addOperations([subjectNoOverloads!, subjectUsingOverloads!], waitUntilFinished: true) 85 | } 86 | 87 | it("both subjects should have the same output") { 88 | expect(subjectNoOverloads?.output.noneError?.failureError?._code).to(equal(AsyncOpError.Unspecified._code)) 89 | expect(subjectUsingOverloads?.output.noneError?.failureError?._code).to(equal(AsyncOpError.Unspecified._code)) 90 | } 91 | } 92 | } 93 | } 94 | 95 | func voidOverloadSpec() { 96 | 97 | var subject: AsyncOp? 98 | var opQ: NSOperationQueue? 99 | 100 | describe("AsyncVoid") { 101 | 102 | beforeEach { 103 | subject = AsyncOp() 104 | opQ = NSOperationQueue() 105 | } 106 | 107 | afterEach { 108 | subject = nil 109 | opQ = nil 110 | } 111 | 112 | context("Success") { 113 | 114 | beforeEach { 115 | subject?.onStart { asyncOp in 116 | asyncOp.finishWithSuccess() 117 | } 118 | opQ?.addOperations([subject!], waitUntilFinished: true) 119 | } 120 | 121 | it("should finish as succeeded") { 122 | expect(subject?.resultStatus).to(equal(AsyncOpResultStatus.Succeeded)) 123 | } 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Tests/AsyncOpTests/AsyncOpGroupTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOpGroupTests.swift 3 | // AsyncOpKit 4 | // 5 | // Created by Jed Lewison on 9/30/15. 6 | // Copyright © 2015 Magic App Factory. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Quick 11 | import Nimble 12 | @testable import AsyncOpKit 13 | 14 | 15 | 16 | class AsyncOpGroupTests : QuickSpec { 17 | 18 | override func spec() { 19 | 20 | var opQ = NSOperationQueue() 21 | var asyncOpGroup = AsyncOpGroup() 22 | var finalResult: String? 23 | var cancelled = false 24 | var randomOutputNumber = 0 25 | var returnedGroup: AsyncOpGroup? 26 | 27 | describe("asyncOpGroup") { 28 | 29 | beforeEach { 30 | randomOutputNumber = random() 31 | finalResult = nil 32 | cancelled = false 33 | opQ = NSOperationQueue() 34 | asyncOpGroup = AsyncOpGroup() 35 | } 36 | 37 | context("an async op group transforming an int to a double to a string") { 38 | 39 | beforeEach { 40 | 41 | returnedGroup = asyncOpGroup.beginWith { () -> AsyncOp in 42 | let firstOp = AsyncOp() 43 | firstOp.setInput(randomOutputNumber) 44 | firstOp.onStart({ (asyncOp) -> Void in 45 | let input = try asyncOp.input.getValue() 46 | asyncOp.finish(with: Double(input)) 47 | }) 48 | return firstOp 49 | }.then({ () -> AsyncOp in 50 | let secondOp = AsyncOp() 51 | secondOp.onStart({ (asyncOp) -> Void in 52 | let input = try asyncOp.input.getValue() 53 | asyncOp.finish(with: String(input)) 54 | }) 55 | 56 | return secondOp 57 | }).finally({ (result) -> () in 58 | switch result { 59 | case .Succeeded(let final): 60 | finalResult = final 61 | case .Cancelled: 62 | cancelled = true 63 | case .Failed(let error): 64 | debugPrint(error) 65 | } 66 | }) 67 | 68 | } 69 | 70 | it("should return a string version of the input") { 71 | opQ.addAsyncOpGroup(returnedGroup) 72 | expect(finalResult).toEventually(equal(String(Double(randomOutputNumber)))) 73 | } 74 | 75 | it("should not be cancelled when the group is not cancelled") { 76 | opQ.addAsyncOpGroup(returnedGroup) 77 | expect(cancelled).toEventually(beFalse()) 78 | } 79 | 80 | it("should be cancelled when the group is cancelled") { 81 | returnedGroup?.cancelGroup() 82 | opQ.addAsyncOpGroup(returnedGroup) 83 | expect(cancelled).toEventually(beTrue()) 84 | } 85 | 86 | } 87 | } 88 | 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /Tests/AsyncOpTests/AsyncOpManualInputTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOpInput.swift 3 | // AsyncOp 4 | // 5 | // Created by Jed Lewison on 9/3/15. 6 | // Copyright © 2015 Magic App Factory. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import Foundation 12 | import Quick 13 | import Nimble 14 | @testable import AsyncOpKit 15 | 16 | 17 | class AsyncOpInput : QuickSpec { 18 | 19 | var randomOutputNumber = random() 20 | 21 | override func spec() { 22 | 23 | describe("Setting async operation input") { 24 | 25 | var subject: AsyncOp! 26 | var inputValue: Int? 27 | var opQ: NSOperationQueue! 28 | var randomOutputNumber: Int! 29 | 30 | beforeEach { 31 | self.randomOutputNumber = random() 32 | randomOutputNumber = self.randomOutputNumber 33 | opQ = NSOperationQueue() 34 | subject = AsyncOp() 35 | inputValue = nil 36 | subject.onStart { operation in 37 | let outValue = try operation.input.getValue() 38 | inputValue = outValue 39 | operation.finish(with: .Some(outValue)) 40 | } 41 | } 42 | 43 | context("An Async operation with an input set before the operation is added to a queue") { 44 | 45 | beforeEach { 46 | subject.setInput(.Some(randomOutputNumber)) 47 | opQ.addOperations([subject], waitUntilFinished: false) 48 | } 49 | 50 | it("should have an input value of randomOutputNumber in the onStart closure") { 51 | expect(subject.input.value).toEventually(equal(randomOutputNumber)) 52 | } 53 | 54 | } 55 | 56 | context("An Async operation with an input set after the operation has started") { 57 | 58 | beforeEach { 59 | opQ.maxConcurrentOperationCount = 1 60 | subject.onStart { operation in 61 | guard let outValue = operation.input.value else { throw AsyncOpError.Unspecified } 62 | inputValue = outValue 63 | operation.finish(with: .Some(outValue)) 64 | } 65 | opQ.addOperations([subject], waitUntilFinished: false) 66 | opQ.addOperationWithBlock { 67 | subject.setInput(.Some(randomOutputNumber)) 68 | } 69 | } 70 | 71 | it("should have an input value of randomOutputNumber in the onStart closure") { 72 | expect(subject.input.value).toEventually(beNil()) 73 | } 74 | } 75 | 76 | context("A paused Async operation with input set after it has been added to an operation queue, and then resumed") { 77 | 78 | beforeEach { 79 | opQ.maxConcurrentOperationCount = 1 80 | subject.pause() 81 | subject.onStart { operation in 82 | guard let outValue = operation.input.value else { throw AsyncOpError.Unspecified } 83 | inputValue = outValue 84 | operation.finish(with: .Some(outValue)) 85 | } 86 | opQ.addOperations([subject], waitUntilFinished: false) 87 | opQ.addOperationWithBlock { 88 | subject.setInput(randomOutputNumber, andResume: true) 89 | } 90 | } 91 | 92 | it("should have an input value of randomOutputNumber in the onStart closure") { 93 | expect(subject.input.value).toEventually(equal(randomOutputNumber)) 94 | } 95 | } 96 | 97 | context("An async operation without any input set") { 98 | 99 | describe("Automatic Input Mode") { 100 | 101 | beforeEach { 102 | opQ.addOperations([subject], waitUntilFinished: false) 103 | } 104 | 105 | it("should not have an input value") { 106 | expect(inputValue).toEventually(beNil()) 107 | } 108 | 109 | } 110 | 111 | describe("Manual Input Mode") { 112 | 113 | beforeEach { 114 | opQ.addOperations([subject], waitUntilFinished: false) 115 | } 116 | 117 | it("should have an input value of nil when it starts") { 118 | expect(inputValue).toEventually(beNil()) 119 | } 120 | 121 | } 122 | 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Tests/AsyncOpTests/AsyncOpPreconditionEvaluatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOpPreconditionEvaluatorTests.swift 3 | // AsyncOp 4 | // 5 | // Created by Jed Lewison on 9/6/15. 6 | // Copyright © 2015 Magic App Factory. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Quick 11 | import Nimble 12 | @testable import AsyncOpKit 13 | 14 | 15 | class AsyncOpPreconditionEvaluatorTests : QuickSpec { 16 | 17 | override func spec() { 18 | 19 | var randomOutputNumber = 0 20 | var subject: AsyncOp? 21 | var opQ: NSOperationQueue? 22 | 23 | describe("Precondition Evaluator") { 24 | 25 | beforeEach { 26 | randomOutputNumber = random() 27 | subject = AsyncOp() 28 | subject?.onStart { op in 29 | op.finish(with: randomOutputNumber) 30 | } 31 | opQ = NSOperationQueue() 32 | } 33 | 34 | afterEach { 35 | subject = nil 36 | opQ = nil 37 | } 38 | 39 | describe("Single precondition scenarios") { 40 | 41 | context("evaluator that instructs operation to continue") { 42 | 43 | beforeEach { 44 | subject?.addPreconditionEvaluator { 45 | return .Continue 46 | } 47 | opQ?.addOperations([subject!], waitUntilFinished: false) 48 | } 49 | 50 | it("the subject should finish normally") { 51 | expect(subject?.output.value).toEventually(equal(randomOutputNumber)) 52 | } 53 | 54 | } 55 | 56 | context("evaluator that instructs operation to cancel") { 57 | 58 | beforeEach { 59 | subject?.addPreconditionEvaluator { 60 | return .Cancel 61 | } 62 | opQ?.addOperations([subject!], waitUntilFinished: false) 63 | } 64 | 65 | it("the subject should be a cancelled operation") { 66 | expect(subject?.cancelled).toEventually(beTrue()) 67 | } 68 | 69 | it("the subject should be cancelled") { 70 | expect(subject?.output.noneError?.cancelled).toEventually(beTrue()) 71 | } 72 | 73 | } 74 | 75 | context("evaluator that instructs operation to fail") { 76 | 77 | beforeEach { 78 | subject?.addPreconditionEvaluator { 79 | return .Fail(AsyncOpError.PreconditionFailure) 80 | } 81 | opQ?.addOperations([subject!], waitUntilFinished: false) 82 | } 83 | 84 | it("the subject should be a cancelled operation") { 85 | expect(subject?.cancelled).toEventually(beTrue()) 86 | } 87 | 88 | it("the subject should be a failed asyncop") { 89 | expect(subject?.output.noneError?.failed).toEventually(beTrue()) 90 | } 91 | 92 | it("the subject shouldhave the same error that it was failed with") { 93 | expect(subject?.output.noneError?.failureError?._code).toEventually(equal(AsyncOpError.PreconditionFailure._code)) 94 | } 95 | 96 | } 97 | 98 | } 99 | 100 | describe("Multiple precondition scenarios") { 101 | 102 | context("multiple evaluators that instruct operation to continue") { 103 | 104 | beforeEach { 105 | subject?.addPreconditionEvaluator { 106 | return .Continue 107 | } 108 | subject?.addPreconditionEvaluator { 109 | return .Continue 110 | } 111 | subject?.addPreconditionEvaluator { 112 | return .Continue 113 | } 114 | subject?.addPreconditionEvaluator { 115 | return .Continue 116 | } 117 | subject?.addPreconditionEvaluator { 118 | return .Continue 119 | } 120 | opQ?.addOperations([subject!], waitUntilFinished: false) 121 | } 122 | 123 | it("the subject should finish normally") { 124 | expect(subject?.output.value).toEventually(equal(randomOutputNumber)) 125 | } 126 | 127 | } 128 | } 129 | 130 | context("multiple evaluators that instruct operation to continue, but one instructs canceling") { 131 | 132 | beforeEach { 133 | subject?.addPreconditionEvaluator { 134 | return .Continue 135 | } 136 | subject?.addPreconditionEvaluator { 137 | return .Continue 138 | } 139 | subject?.addPreconditionEvaluator { 140 | return .Continue 141 | } 142 | subject?.addPreconditionEvaluator { 143 | return .Continue 144 | } 145 | subject?.addPreconditionEvaluator { 146 | return .Cancel 147 | } 148 | opQ?.addOperations([subject!], waitUntilFinished: false) 149 | } 150 | 151 | it("the subject should be a cancelled operation") { 152 | expect(subject?.cancelled).toEventually(beTrue()) 153 | } 154 | 155 | it("the subject should be cancelled") { 156 | expect(subject?.output.noneError?.cancelled).toEventually(beTrue()) 157 | } 158 | 159 | } 160 | 161 | context("multiple evaluators that instruct operation to continue, but one instructs Failing") { 162 | 163 | beforeEach { 164 | subject?.addPreconditionEvaluator { 165 | return .Continue 166 | } 167 | subject?.addPreconditionEvaluator { 168 | return .Continue 169 | } 170 | subject?.addPreconditionEvaluator { 171 | return .Continue 172 | } 173 | subject?.addPreconditionEvaluator { 174 | return .Continue 175 | } 176 | subject?.addPreconditionEvaluator { 177 | return .Fail(AsyncOpError.PreconditionFailure) 178 | } 179 | opQ?.addOperations([subject!], waitUntilFinished: false) 180 | } 181 | 182 | it("the subject should be a cancelled operation") { 183 | expect(subject?.cancelled).toEventually(beTrue()) 184 | } 185 | 186 | it("the subject should be a failed asyncop") { 187 | expect(subject?.output.noneError?.failed).toEventually(beTrue()) 188 | } 189 | 190 | it("the subject shouldhave the same error that it was failed with") { 191 | expect(subject?.output.noneError?.failureError?._code).toEventually(equal(AsyncOpError.PreconditionFailure._code)) 192 | } 193 | 194 | } 195 | 196 | context("multiple evaluators that instruct operation to continue, but one instructs canceling and one instructs Failing") { 197 | 198 | beforeEach { 199 | subject?.addPreconditionEvaluator { 200 | return .Continue 201 | } 202 | subject?.addPreconditionEvaluator { 203 | return .Continue 204 | } 205 | subject?.addPreconditionEvaluator { 206 | return .Continue 207 | } 208 | subject?.addPreconditionEvaluator { 209 | return .Cancel 210 | } 211 | subject?.addPreconditionEvaluator { 212 | return .Fail(AsyncOpError.PreconditionFailure) 213 | } 214 | opQ?.addOperations([subject!], waitUntilFinished: false) 215 | } 216 | 217 | it("the subject should be a cancelled operation") { 218 | expect(subject?.cancelled).toEventually(beTrue()) 219 | } 220 | 221 | it("the subject should be a failed asyncop") { 222 | expect(subject?.output.noneError?.failed).toEventually(beTrue()) 223 | } 224 | 225 | it("the subject shouldhave the same error that it was failed with") { 226 | expect(subject?.output.noneError?.failureError?._code).toEventually(equal(AsyncOpError.PreconditionFailure._code)) 227 | } 228 | 229 | } 230 | 231 | context("multiple evaluators that instruct operation to continue, but one instructs canceling and multiple instruct Failing") { 232 | 233 | beforeEach { 234 | subject?.addPreconditionEvaluator { 235 | return .Continue 236 | } 237 | subject?.addPreconditionEvaluator { 238 | return .Continue 239 | } 240 | subject?.addPreconditionEvaluator { 241 | return .Continue 242 | } 243 | subject?.addPreconditionEvaluator { 244 | return .Cancel 245 | } 246 | subject?.addPreconditionEvaluator { 247 | return .Fail(AsyncOpError.PreconditionFailure) 248 | } 249 | subject?.addPreconditionEvaluator { 250 | return .Fail(AsyncOpError.PreconditionFailure) 251 | } 252 | subject?.addPreconditionEvaluator { 253 | return .Fail(AsyncOpError.PreconditionFailure) 254 | } 255 | opQ?.addOperations([subject!], waitUntilFinished: false) 256 | } 257 | 258 | it("the subject should be a cancelled operation") { 259 | expect(subject?.cancelled).toEventually(beTrue()) 260 | } 261 | 262 | it("the subject should be a failed asyncop") { 263 | expect(subject?.output.noneError?.failed).toEventually(beTrue()) 264 | } 265 | 266 | } 267 | 268 | 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /Tests/AsyncOpTests/AsyncOpResultStatusTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOpResultStatus.swift 3 | // AsyncOp 4 | // 5 | // Created by Jed Lewison on 9/7/15. 6 | // Copyright © 2015 Magic App Factory. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Quick 11 | import Nimble 12 | @testable import AsyncOpKit 13 | 14 | 15 | class AsyncOpResultStatusTests : QuickSpec { 16 | 17 | override func spec() { 18 | 19 | var randomOutputNumber = 0 20 | var subject: AsyncOp? 21 | var opQ: NSOperationQueue? 22 | 23 | describe("Result status") { 24 | 25 | beforeEach { 26 | randomOutputNumber = random() 27 | subject = AsyncOp() 28 | opQ = NSOperationQueue() 29 | } 30 | 31 | afterEach { 32 | subject = nil 33 | opQ = nil 34 | } 35 | 36 | context("Operation hasn't finished") { 37 | 38 | beforeEach { 39 | subject?.onStart { op in 40 | usleep(200000) 41 | op.finish(with: randomOutputNumber) 42 | } 43 | opQ?.addOperations([subject!], waitUntilFinished: false) 44 | } 45 | 46 | it("the result status should be pending") { 47 | expect(subject?.resultStatus).to(equal(AsyncOpResultStatus.Pending)) 48 | } 49 | 50 | } 51 | 52 | context("Operation has finished with success") { 53 | 54 | beforeEach { 55 | subject?.onStart { op in 56 | op.finish(with: randomOutputNumber) 57 | } 58 | opQ?.addOperations([subject!], waitUntilFinished: true) 59 | } 60 | 61 | it("the result status should be succeeded") { 62 | expect(subject?.resultStatus).to(equal(AsyncOpResultStatus.Succeeded)) 63 | } 64 | 65 | } 66 | 67 | context("Operation has finished because it was cancelled") { 68 | 69 | beforeEach { 70 | subject?.addPreconditionEvaluator { return .Cancel } 71 | opQ?.addOperations([subject!], waitUntilFinished: true) 72 | } 73 | 74 | it("the result status should be cancelled") { 75 | expect(subject?.resultStatus).to(equal(AsyncOpResultStatus.Cancelled)) 76 | } 77 | 78 | } 79 | 80 | context("Operation has finished because it failed") { 81 | 82 | beforeEach { 83 | subject?.onStart { op in 84 | op.finish(with: AsyncOpError.Unspecified) 85 | } 86 | opQ?.addOperations([subject!], waitUntilFinished: true) 87 | } 88 | 89 | it("the result status should be failed") { 90 | expect(subject?.resultStatus).to(equal(AsyncOpResultStatus.Failed)) 91 | } 92 | 93 | } 94 | 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Tests/AsyncOpTests/AsyncOpSubclass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOpInferringInputTests.swift 3 | // AsyncOp 4 | // 5 | // Created by Jed Lewison on 9/3/15. 6 | // Copyright © 2015 Magic App Factory. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Quick 11 | import Nimble 12 | @testable import AsyncOpKit 13 | 14 | let StringForTest = "Subclass Done!" 15 | 16 | class AnAsyncOpSubclass : AsyncOp { 17 | 18 | let testValue: String 19 | required init(testData: String) { 20 | testValue = testData 21 | super.init() 22 | onStart( performOperation ) 23 | } 24 | 25 | func performOperation(asyncOp: AsyncOp) throws -> Void { 26 | usleep(50050) 27 | finish(with: .Some(testValue)) 28 | } 29 | } 30 | 31 | class AsyncOpSubclassTests : QuickSpec { 32 | 33 | override func spec() { 34 | 35 | describe("AsyncOp subclass") { 36 | 37 | var subject: AnAsyncOpSubclass! 38 | var opQ: NSOperationQueue! 39 | var outputValue: String? 40 | 41 | beforeEach { 42 | opQ = NSOperationQueue() 43 | opQ.maxConcurrentOperationCount = 1 44 | subject = AnAsyncOpSubclass(testData: StringForTest) 45 | subject.whenFinished { operation in 46 | outputValue = operation.output.value 47 | } 48 | opQ.addOperation(subject) 49 | } 50 | 51 | context("Normal operation") { 52 | it("should out the correct value") { 53 | expect(outputValue).toEventually(equal(StringForTest)) 54 | } 55 | } 56 | 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/AsyncOpTests/AsyncOpTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Quick 3 | import Nimble 4 | @testable import AsyncOpKit 5 | 6 | class AsyncOpTests : QuickSpec { 7 | 8 | override func spec() { 9 | 10 | describe("An Async operation avoiding a retain cycle") { 11 | 12 | let subject = AsyncOp() 13 | 14 | subject.onStart { operation in 15 | usleep(50000) 16 | operation.finish(with: .Some(true)) 17 | } 18 | 19 | subject.onCancel { operation in 20 | operation.finish(with: .None(.Cancelled)) 21 | } 22 | 23 | subject.whenFinished { operation in 24 | print(operation.output) 25 | } 26 | 27 | let opQ = NSOperationQueue() 28 | opQ.addOperations([subject], waitUntilFinished: true) 29 | 30 | it("should let you reference the operation inside the closure without a retain cycle") { 31 | [weak subject] in 32 | expect(subject).toEventually(beNil()) 33 | } 34 | 35 | } 36 | 37 | describe("The behavior of an AsyncOp when added to an operation queue") { 38 | 39 | var opQ = NSOperationQueue() 40 | var subject: AsyncOp? 41 | var output: AsyncOpValue? 42 | var onStartHandlerCalled = false 43 | var cancelHandlerCalled = false 44 | 45 | beforeEach { 46 | 47 | 48 | onStartHandlerCalled = false 49 | cancelHandlerCalled = false 50 | 51 | subject = AsyncOp() 52 | opQ.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount 53 | cancelHandlerCalled = false 54 | 55 | subject?.onStart { operation in 56 | onStartHandlerCalled = true 57 | usleep(5000) 58 | operation.finish(with: .Some(true)) 59 | } 60 | 61 | subject?.onCancel { operation in 62 | cancelHandlerCalled = true 63 | operation.finish(with: .None(.Cancelled)) 64 | } 65 | 66 | subject?.whenFinished { operation in 67 | output = operation.output 68 | } 69 | 70 | } 71 | 72 | afterEach { 73 | opQ = NSOperationQueue() 74 | output = nil 75 | } 76 | 77 | describe("asynchronously after adding to operation queue") { 78 | 79 | beforeEach { 80 | guard let subject = subject else { return } 81 | opQ.addOperation(subject) 82 | } 83 | 84 | it("the operation should eventually deinit") { 85 | [weak subject] in 86 | expect(subject).toEventually(beNil()) 87 | } 88 | 89 | it("should stop executing") { 90 | 91 | expect(subject?.executing).toEventually(beFalse()) 92 | } 93 | 94 | it("should finish") { 95 | expect(subject?.finished).toEventually(beTrue()) 96 | } 97 | 98 | it("should execute its outputs handler") { 99 | expect(output).toEventuallyNot(beNil()) 100 | } 101 | 102 | it("should return a outputs object that does not indicate it is canceled") { 103 | expect(subject?.cancelled).toEventually(beFalse()) 104 | } 105 | } 106 | 107 | context("when an operation finishes normally") { 108 | 109 | beforeEach { 110 | guard let subject = subject else { return } 111 | opQ.addOperation(subject) 112 | } 113 | 114 | it("should not be cancelled") { 115 | expect(subject?.cancelled).toEventuallyNot(beTrue()) 116 | } 117 | 118 | it("should eventually be finished") { 119 | expect(subject?.finished).toEventually(beTrue()) 120 | } 121 | 122 | it("should not be executing") { 123 | expect(subject?.executing).toEventually(beFalse()) 124 | } 125 | 126 | it("the operation should finish and eventually deinit") { 127 | [weak subject] in 128 | expect(subject).toEventually(beNil()) 129 | } 130 | 131 | 132 | 133 | } 134 | 135 | context("When the operation's operation queue immediately cancels all operations") { 136 | 137 | beforeEach { 138 | guard let subject = subject else { return } 139 | opQ.maxConcurrentOperationCount = 1 140 | let blockOp = NSBlockOperation { 141 | usleep(32033) 142 | } 143 | subject.addDependency(blockOp) 144 | opQ.addOperations([blockOp, subject], waitUntilFinished: false) 145 | opQ.cancelAllOperations() 146 | } 147 | 148 | it("should not receive an onstart handler callback") { 149 | expect(onStartHandlerCalled).to(beFalse()) 150 | } 151 | 152 | it("should receive a cancellation handler callback") { 153 | expect(cancelHandlerCalled).to(beTrue()) 154 | } 155 | 156 | it("should eventually be marked as cancelled") { 157 | expect(subject?.cancelled).toEventually(beTrue()) 158 | } 159 | 160 | it("should eventually stop executing and finish") { 161 | expect(subject?.executing).toEventually(beFalse()) 162 | } 163 | 164 | it("the outputs closure outputs object should mark it as cancelled") { 165 | expect(subject?.cancelled).toEventually(beTrue()) 166 | } 167 | 168 | it("the operation should eventually deinit") { 169 | [weak subject] in 170 | expect(subject).toEventually(beNil()) 171 | } 172 | 173 | } 174 | 175 | } 176 | 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Tests/AsyncOpTests/AsyncOpThrowTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Quick 3 | import Nimble 4 | @testable import AsyncOpKit 5 | 6 | private enum TestError : ErrorType { 7 | case UnusedError 8 | case ThrewInClosure 9 | } 10 | 11 | class AsyncOpThrowingTests : QuickSpec { 12 | 13 | var operationQueue = NSOperationQueue() 14 | 15 | override func spec() { 16 | 17 | describe("An Async operation throwing an error from its onStart closure") { 18 | 19 | let subject = AsyncOp() 20 | let thrownError = TestError.ThrewInClosure 21 | 22 | subject.onStart { operation in 23 | usleep(5000) 24 | throw thrownError 25 | } 26 | 27 | subject.onCancel { operation in 28 | operation.finish(with: .None(.Cancelled)) 29 | } 30 | 31 | subject.whenFinished { operation in 32 | print(operation.output) 33 | } 34 | 35 | let opQ = NSOperationQueue() 36 | opQ.addOperations([subject], waitUntilFinished: false) 37 | 38 | it("should eventually have a failed output") { 39 | expect(subject.output.noneError?.failed).toEventually(equal(true)) 40 | } 41 | 42 | it("the output should eventually be the thrown error") { 43 | expect(subject.output.noneError?.failureError?._code).toEventually(equal(thrownError._code)) 44 | } 45 | 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/AsyncOpTests/AsyncOpWhenFinished.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Quick 3 | import Nimble 4 | @testable import AsyncOpKit 5 | 6 | 7 | class AsyncOpWhenFinished : QuickSpec { 8 | 9 | 10 | override func spec() { 11 | 12 | 13 | var randomOutputNumber = 0 14 | var outputValue: Int? 15 | 16 | describe("whenFinished") { 17 | 18 | beforeEach { 19 | randomOutputNumber = random() 20 | } 21 | 22 | 23 | context("whenFinished set before the operation starts") { 24 | 25 | beforeEach { 26 | let subject = AsyncOp() 27 | 28 | subject.onStart { operation in 29 | operation.finish(with: .Some(randomOutputNumber)) 30 | } 31 | 32 | subject.whenFinished { operation in 33 | outputValue = operation.output.value 34 | } 35 | 36 | let opQ = NSOperationQueue() 37 | opQ.addOperations([subject], waitUntilFinished: false) 38 | } 39 | 40 | it("should eventually have a output equal to randomOutputNumber") { 41 | expect(outputValue).toEventually(equal(randomOutputNumber)) 42 | } 43 | 44 | } 45 | 46 | context("whenFinished closure set after the operation is added to a queue") { 47 | 48 | beforeEach { 49 | let subject = AsyncOp() 50 | 51 | subject.onStart { operation in 52 | operation.finish(with: .Some(randomOutputNumber)) 53 | } 54 | 55 | let opQ = NSOperationQueue() 56 | opQ.addOperations([subject], waitUntilFinished: false) 57 | opQ.addOperationWithBlock { 58 | subject.whenFinished { operation in 59 | outputValue = operation.output.value 60 | } 61 | 62 | } 63 | 64 | } 65 | 66 | it("should eventually have a output equal to randomOutputNumber") { 67 | expect(outputValue).toEventually(equal(randomOutputNumber)) 68 | } 69 | 70 | } 71 | 72 | context("whenFinished closure set after the operation has finished and its queue has deallocated") { 73 | 74 | beforeEach { 75 | 76 | let subject = AsyncOp() 77 | 78 | subject.onStart { operation in 79 | operation.finish(with: .Some(randomOutputNumber)) 80 | } 81 | 82 | 83 | var opQ = NSOperationQueue() 84 | opQ.addOperations([subject], waitUntilFinished: true) 85 | opQ = NSOperationQueue() 86 | 87 | opQ.addOperationWithBlock { 88 | subject.whenFinished { operation in 89 | outputValue = operation.output.value 90 | } 91 | 92 | } 93 | 94 | 95 | } 96 | 97 | it("should eventually have a output equal to randomOutputNumber") { 98 | expect(outputValue).toEventually(equal(randomOutputNumber)) 99 | } 100 | 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /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 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | --------------------------------------------------------------------------------