├── .gitignore ├── Async.podspec ├── CHANGELOG.md ├── Classes ├── Async-Prefix.pch ├── Async.h ├── Async.m ├── AsyncTypes.h ├── EachBlockEnumerator.h ├── EachBlockEnumerator.m ├── MapBlockEnumerator.h ├── MapBlockEnumerator.m ├── RepeatBlockUntilEnumerator.h ├── RepeatBlockUntilEnumerator.m ├── WaterfallBlockEnumerator.h ├── WaterfallBlockEnumerator.m ├── ios │ └── .gitkeep └── osx │ └── .gitkeep ├── LICENSE ├── Project ├── AsyncTests.xcworkspace │ └── contents.xcworkspacedata ├── AsyncTests │ ├── AsyncTests.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ └── AsyncTests │ │ ├── AsyncTests-Info.plist │ │ ├── AsyncTests-Prefix.pch │ │ ├── AsyncTests.m │ │ └── en.lproj │ │ └── InfoPlist.strings ├── Podfile └── Podfile.lock ├── README.md └── Rakefile /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | xcshareddata 19 | 20 | #CocoaPods 21 | Pods 22 | -------------------------------------------------------------------------------- /Async.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Async" 3 | s.version = "0.2.0" 4 | s.summary = "Set of functions for working with asynchronous functions" 5 | s.description = <<-DESC 6 | Asynchronous functions are defined by a set of blocks contained in an NSArray. 7 | When a block is finished, it calls success() or failure(). 8 | 9 | Functions include: 10 | 11 | * series: run a set of blocks in sequential order 12 | * parallel: run a set of blocks in parallel 13 | * `eachSeries`: runs a block in series with every item in an array 14 | * `eachParallel`: runs a block in parallel with every item in an array 15 | * mapParallel: runs a block in parallel with every item in an array, collecting all the return values 16 | * mapSeries: runs a block in series with every item in an array, collecting all the return values 17 | * repeatUntilSuccess: repeat a block until it succeeds or maxAttempts is reached 18 | * waterfall: run a set of blocks in series, passing the return value of a block to the next block 19 | 20 | DESC 21 | s.homepage = "https://github.com/johnwana/Async" 22 | s.license = 'MIT' 23 | s.author = { "John Wana" => "john@wana.us" } 24 | s.source = { :git => "https://github.com/johnwana/Async.git", :tag => "0.2.0" } 25 | 26 | s.requires_arc = true 27 | 28 | s.source_files = 'Classes' 29 | #s.resources = 'Assets' 30 | 31 | s.ios.exclude_files = 'Classes/osx' 32 | s.osx.exclude_files = 'Classes/ios' 33 | s.public_header_files = 'Classes/**/Async*.h' 34 | end 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # async CHANGELOG 2 | 3 | ## 0.1.0 4 | 5 | Initial release. 6 | -------------------------------------------------------------------------------- /Classes/Async-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /Classes/Async.h: -------------------------------------------------------------------------------- 1 | // 2 | // Async.h 3 | // Async 4 | // 5 | // Created by John Wana on 12/9/13. 6 | // Copyright (c) 2013 John Wana. All rights reserved. 7 | // 8 | 9 | #import "AsyncTypes.h" 10 | #import 11 | 12 | @interface Async : NSObject 13 | 14 | + (void)series:(NSArray *)block0s 15 | success:(void (^)())success 16 | failure:(void (^)(NSError *error))failure; 17 | 18 | + (void)parallel:(NSArray *)block0s 19 | success:(void (^)())success 20 | failure:(void (^)(NSError *error))failure; 21 | 22 | + (void)mapParallel:(NSArray *)array 23 | mapFunction:(mapFunction)mapFunction 24 | success:(void (^)(NSArray *mappedArray))success 25 | failure:(void (^)(NSError *error))failure; 26 | 27 | + (void)mapSeries:(NSArray *)array 28 | mapFunction:(mapFunction)mapFunction 29 | success:(void (^)(NSArray *mappedObjects))success 30 | failure:(void (^)(NSError *error))failure; 31 | 32 | + (void)eachParallel:(NSArray *)array 33 | block:(eachBlock)block 34 | success:(void (^)())success 35 | failure:(void (^)(NSError *))failure; 36 | 37 | + (void)eachSeries:(NSArray *)array 38 | block:(eachBlock)block 39 | success:(void (^)())success 40 | failure:(void (^)(NSError *))failure; 41 | 42 | + (void)repeatUntilSuccess:(block0)block 43 | maxAttempts:(NSUInteger)maxAttempts 44 | delayBetweenAttemptsInSec:(double)delayInSec 45 | success:(successBlock)success 46 | failure:(failureBlock)failure; 47 | 48 | + (void)waterfall:(NSArray *)blocks 49 | firstParam:(id)firstParam 50 | success:(void (^)(id result))success 51 | failure:(void (^)(NSError *error))failure; 52 | 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Classes/Async.m: -------------------------------------------------------------------------------- 1 | // 2 | // Async.m 3 | // Async 4 | // 5 | // Created by John Wana on 12/9/13. 6 | // Copyright (c) 2013 John Wana. All rights reserved. 7 | // 8 | 9 | #import "MapBlockEnumerator.h" 10 | #import "EachBlockEnumerator.h" 11 | #import "RepeatBlockUntilEnumerator.h" 12 | #import "WaterfallBlockEnumerator.h" 13 | #import "Async.h" 14 | 15 | 16 | @implementation Async 17 | 18 | + (void)seriesWithEnumerator:(NSEnumerator *)block0s 19 | success:(void (^)())success 20 | failure:(void (^)(NSError *))failure 21 | { 22 | block0 block = [block0s nextObject]; 23 | if (!block) { 24 | success(); 25 | } else { 26 | block(^() { 27 | [self seriesWithEnumerator:block0s success:success failure:failure]; 28 | }, ^(NSError *error) { 29 | failure(error); 30 | }); 31 | } 32 | } 33 | 34 | + (void)series:(NSArray *)block0s 35 | success:(void (^)())success 36 | failure:(void (^)(NSError *))failure 37 | { 38 | [self seriesWithEnumerator:[block0s objectEnumerator] success:success failure:failure]; 39 | } 40 | 41 | + (void)parallelWithEnumerator:(NSEnumerator *)block0s 42 | success:(void (^)())success 43 | failure:(void (^)(NSError *))failure 44 | { 45 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 46 | 47 | __block NSUInteger remainingMapCalls = 0; 48 | __block NSError *blockError = nil; 49 | 50 | block0 block = [block0s nextObject]; 51 | if (!block) { 52 | success(); 53 | } else { 54 | for (; block; block = [block0s nextObject]) { 55 | remainingMapCalls++; 56 | dispatch_async(queue, ^{ 57 | block( 58 | ^() { 59 | if (--remainingMapCalls == 0) { 60 | if (blockError) { 61 | failure(blockError); 62 | } else { 63 | success(); 64 | } 65 | } 66 | }, 67 | ^(NSError *error) { 68 | if (--remainingMapCalls == 0) { 69 | failure(error); 70 | } else { 71 | blockError = error; 72 | } 73 | }); 74 | }); 75 | } 76 | } 77 | } 78 | 79 | + (void)parallel:(NSArray *)block0s 80 | success:(void (^)())success 81 | failure:(void (^)(NSError *))failure 82 | { 83 | [self parallelWithEnumerator:[block0s objectEnumerator] success:success failure:failure]; 84 | } 85 | 86 | + (void)mapSeries:(NSArray *)array 87 | mapFunction:(block1)mapFunction 88 | success:(void (^)(NSArray *))success 89 | failure:(void (^)(NSError *))failure 90 | { 91 | MapBlockEnumerator *blockEnumerator = [[MapBlockEnumerator alloc] initMapBlock:mapFunction using:array]; 92 | [Async seriesWithEnumerator:blockEnumerator 93 | success:^{ success(blockEnumerator.results); } 94 | failure:failure]; 95 | } 96 | 97 | + (void)mapParallel:(NSArray *)array 98 | mapFunction:(block1)mapFunction 99 | success:(void (^)(NSArray *))success 100 | failure:(void (^)(NSError *))failure 101 | { 102 | MapBlockEnumerator *blockEnumerator = [[MapBlockEnumerator alloc] initMapBlock:mapFunction using:array]; 103 | [self parallelWithEnumerator:blockEnumerator 104 | success:^{ success(blockEnumerator.results); } 105 | failure:failure]; 106 | } 107 | 108 | + (void)eachSeries:(NSArray *)array 109 | block:(eachBlock)block 110 | success:(void (^)())success 111 | failure:(void (^)(NSError *))failure 112 | { 113 | EachBlockEnumerator *blockEnumerator = [[EachBlockEnumerator alloc] initEachBlock:block 114 | using:array]; 115 | [self seriesWithEnumerator:blockEnumerator 116 | success:success 117 | failure:failure]; 118 | } 119 | 120 | + (void)eachParallel:(NSArray *)array 121 | block:(eachBlock)block 122 | success:(void (^)())success 123 | failure:(void (^)(NSError *))failure 124 | { 125 | EachBlockEnumerator *blockEnumerator = [[EachBlockEnumerator alloc] initEachBlock:block 126 | using:array]; 127 | [self parallelWithEnumerator:blockEnumerator 128 | success:success 129 | failure:failure]; 130 | } 131 | 132 | + (void)repeatUntilSuccess:(block0)block 133 | maxAttempts:(NSUInteger)maxAttempts 134 | delayBetweenAttemptsInSec:(double)delayInSec 135 | success:(successBlock)success 136 | failure:(failureBlock)failure 137 | { 138 | RepeatBlockUntilEnumerator *blockEnumerator = [[RepeatBlockUntilEnumerator alloc] initRepeatBlock:block 139 | maxAttempts:maxAttempts 140 | delayBetweenAttemptsInSec:delayInSec]; 141 | [self seriesWithEnumerator:blockEnumerator 142 | success:success 143 | failure:failure]; 144 | } 145 | 146 | + (void)waterfall:(NSArray *)blocks 147 | firstParam:(id)firstParam 148 | success:(void (^)(id result))success 149 | failure:(void (^)(NSError *))failure 150 | { 151 | WaterfallBlockEnumerator *blockEnumerator = [[WaterfallBlockEnumerator alloc] initBlocks:blocks 152 | firstParam:firstParam]; 153 | [self seriesWithEnumerator:blockEnumerator 154 | success:^{ 155 | success(blockEnumerator.result); 156 | } 157 | failure:failure]; 158 | } 159 | 160 | @end 161 | -------------------------------------------------------------------------------- /Classes/AsyncTypes.h: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncTypes.h 3 | // Async 4 | // 5 | // Created by John Wana on 12/11/13. 6 | // Copyright (c) 2013 John Wana. All rights reserved. 7 | // 8 | 9 | #ifndef Async_AsyncTypes_h 10 | #define Async_AsyncTypes_h 11 | 12 | typedef void (^successBlock)(); 13 | typedef void (^successBlock1)(id obj); 14 | typedef void (^failureBlock)(NSError *error); 15 | typedef void (^block0)(successBlock success, failureBlock failure); 16 | typedef void (^block1)(id obj, successBlock1 success, failureBlock failure); 17 | typedef void (^eachBlock)(id obj, successBlock success, failureBlock failure); 18 | 19 | // FUTURE: get rid of these legacy function types 20 | typedef successBlock1 mapSuccessBlock; 21 | typedef failureBlock mapFailBlock; 22 | typedef block1 mapFunction; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Classes/EachBlockEnumerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // EachBlockEnumerator.h 3 | // Pods 4 | // 5 | // Created by John Wana on 1/13/14. 6 | // 7 | // 8 | 9 | #import "MapBlockEnumerator.h" 10 | 11 | @interface EachBlockEnumerator : MapBlockEnumerator 12 | 13 | - (id)initEachBlock:(eachBlock)block 14 | using:(NSArray *)array; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Classes/EachBlockEnumerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // EachBlockEnumerator.m 3 | // Pods 4 | // 5 | // Created by John Wana on 1/13/14. 6 | // 7 | // 8 | 9 | #import "EachBlockEnumerator.h" 10 | 11 | @implementation EachBlockEnumerator 12 | 13 | - (id)initEachBlock:(eachBlock)block 14 | using:(NSArray *)array 15 | { 16 | NSObject *dummyObject = [NSNull null]; 17 | block1 mapBlock = ^(id obj, successBlock1 success, failureBlock failure) { 18 | block(obj, ^{ success(dummyObject); }, failure); 19 | }; 20 | self = [super initMapBlock:mapBlock using:array]; 21 | if (!self) { 22 | return nil; 23 | } 24 | return self; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Classes/MapBlockEnumerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // MapBlockEnumerator.h 3 | // Async 4 | // 5 | // Created by John Wana on 12/11/13. 6 | // Copyright (c) 2013 John Wana. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AsyncTypes.h" 11 | 12 | @interface MapBlockEnumerator : NSEnumerator 13 | 14 | @property (nonatomic, readonly) NSArray *results; 15 | 16 | - (id)initMapBlock:(block1)block1 using:(NSArray *)array; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Classes/MapBlockEnumerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // MapBlockEnumerator.m 3 | // Async 4 | // 5 | // Created by John Wana on 12/11/13. 6 | // Copyright (c) 2013 John Wana. All rights reserved. 7 | // 8 | 9 | #import "AsyncTypes.h" 10 | #import "MapBlockEnumerator.h" 11 | 12 | 13 | @interface MapBlockEnumerator () { 14 | block1 _block1; 15 | NSArray *_array; 16 | NSUInteger _index; 17 | NSMutableArray *_results; 18 | } 19 | 20 | @end 21 | 22 | @implementation MapBlockEnumerator 23 | 24 | - (id)initMapBlock:(block1)block1 using:(NSArray *)array 25 | { 26 | self = [super init]; 27 | if (!self) { 28 | return nil; 29 | } 30 | 31 | _block1 = block1; 32 | _array = array; 33 | _index = 0; 34 | _results = [NSMutableArray arrayWithCapacity:array.count]; 35 | 36 | id fillerObj = [NSNull null]; 37 | for (NSUInteger i = 0; i < array.count; i++) { 38 | [_results addObject:fillerObj]; 39 | } 40 | 41 | return self; 42 | } 43 | 44 | - (id)nextObject 45 | { 46 | NSUInteger index = _index++; 47 | if (index >= _array.count) { 48 | return nil; 49 | } else { 50 | id obj = [_array objectAtIndex:index]; 51 | return ^(successBlock success, failureBlock failure) { 52 | _block1(obj, ^(id resultObj) { 53 | [_results setObject:resultObj atIndexedSubscript:index]; 54 | success(); 55 | }, failure); 56 | }; 57 | } 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Classes/RepeatBlockUntilEnumerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // RepeatBlockUntilEnumerator.h 3 | // Async 4 | // 5 | // Created by John Wana on 12/11/13. 6 | // Copyright (c) 2013 John Wana. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AsyncTypes.h" 11 | 12 | @interface RepeatBlockUntilEnumerator : NSEnumerator 13 | 14 | - (id)initRepeatBlock:(block0)block maxAttempts:(NSUInteger)maxAttempts delayBetweenAttemptsInSec:(double)delayInSec; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Classes/RepeatBlockUntilEnumerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // RepeatBlockUntilEnumerator.m 3 | // Async 4 | // 5 | // Created by John Wana on 12/11/13. 6 | // Copyright (c) 2013 John Wana. All rights reserved. 7 | // 8 | 9 | #import "RepeatBlockUntilEnumerator.h" 10 | 11 | @interface RepeatBlockUntilEnumerator () { 12 | block0 _block; 13 | NSUInteger _maxAttempts; 14 | NSUInteger _numAttempts; 15 | double _delayInSec; 16 | } 17 | @end 18 | 19 | @implementation RepeatBlockUntilEnumerator 20 | 21 | - (id)initRepeatBlock:(block0)block maxAttempts:(NSUInteger)maxAttempts delayBetweenAttemptsInSec:(double)delayInSec 22 | { 23 | self = [super init]; 24 | if (!self) { 25 | return nil; 26 | } 27 | 28 | _block = block; 29 | _maxAttempts = maxAttempts; 30 | _delayInSec = delayInSec; 31 | _numAttempts = 0; 32 | return self; 33 | } 34 | 35 | - (id)nextObject 36 | { 37 | if (_numAttempts >= _maxAttempts) { 38 | return nil; 39 | } else { 40 | return ^(successBlock success, failureBlock failure) { 41 | _block( 42 | ^() { 43 | _numAttempts = _maxAttempts; 44 | success(); 45 | }, 46 | ^(NSError *error) { 47 | _numAttempts++; 48 | if (_numAttempts >= _maxAttempts) { 49 | failure(error); 50 | } else { 51 | dispatch_time_t pauseTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_delayInSec * NSEC_PER_SEC)); 52 | dispatch_after(pauseTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){ 53 | success(); 54 | }); 55 | } 56 | }); 57 | }; 58 | } 59 | } 60 | 61 | @end 62 | 63 | -------------------------------------------------------------------------------- /Classes/WaterfallBlockEnumerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // WaterfallBlockEnumerator.h 3 | // Async 4 | // 5 | // Created by John Wana on 12/11/13. 6 | // Copyright (c) 2013 John Wana. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AsyncTypes.h" 11 | 12 | @interface WaterfallBlockEnumerator : NSEnumerator 13 | 14 | @property (nonatomic, readonly) id result; 15 | 16 | - (id)initBlocks:(NSArray *)blocks firstParam:(id)firstParam; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Classes/WaterfallBlockEnumerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // WaterfallBlockEnumerator.m 3 | // Async 4 | // 5 | // Created by John Wana on 12/11/13. 6 | // Copyright (c) 2013 John Wana. All rights reserved. 7 | // 8 | 9 | #import "WaterfallBlockEnumerator.h" 10 | 11 | @interface WaterfallBlockEnumerator () { 12 | NSEnumerator *_blockEnumerator; 13 | id _nextParam; 14 | id _result; 15 | } 16 | 17 | @end 18 | 19 | @implementation WaterfallBlockEnumerator 20 | 21 | - (id)initBlocks:blocks firstParam:firstParam 22 | { 23 | self = [super init]; 24 | if (!self) { 25 | return nil; 26 | } 27 | 28 | _blockEnumerator = [blocks objectEnumerator]; 29 | _nextParam = firstParam; 30 | _result = nil; 31 | 32 | return self; 33 | } 34 | 35 | - (id)nextObject 36 | { 37 | mapFunction nextBlock = [_blockEnumerator nextObject]; 38 | if (!nextBlock) { 39 | _result = _nextParam; 40 | return nil; 41 | } else { 42 | return ^(successBlock success, failureBlock failure) { 43 | nextBlock(_nextParam, 44 | ^(id obj) { 45 | _nextParam = obj; 46 | success(); 47 | }, 48 | failure); 49 | }; 50 | } 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /Classes/ios/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwana/Async/dedc6b4d1a34bc51a5fefe7379c02d0da399c862/Classes/ios/.gitkeep -------------------------------------------------------------------------------- /Classes/osx/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwana/Async/dedc6b4d1a34bc51a5fefe7379c02d0da399c862/Classes/osx/.gitkeep -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 John Wana 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 | -------------------------------------------------------------------------------- /Project/AsyncTests.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Project/AsyncTests/AsyncTests.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 702BAEFA186BCA6300E9490A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 702BAEF9186BCA6300E9490A /* XCTest.framework */; }; 11 | 702BAEFC186BCA6300E9490A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 702BAEFB186BCA6300E9490A /* Foundation.framework */; }; 12 | 702BAEFE186BCA6300E9490A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 702BAEFD186BCA6300E9490A /* UIKit.framework */; }; 13 | 702BAF04186BCA6300E9490A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 702BAF02186BCA6300E9490A /* InfoPlist.strings */; }; 14 | 702BAF06186BCA6300E9490A /* AsyncTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 702BAF05186BCA6300E9490A /* AsyncTests.m */; }; 15 | 7156DBC07AD74B4D8AD889E6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DBB8E1480E646FDBF975E1B /* libPods.a */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 2DBB8E1480E646FDBF975E1B /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 702BAEF6186BCA6300E9490A /* AsyncTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AsyncTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 702BAEF9186BCA6300E9490A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 22 | 702BAEFB186BCA6300E9490A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 23 | 702BAEFD186BCA6300E9490A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; 24 | 702BAF01186BCA6300E9490A /* AsyncTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AsyncTests-Info.plist"; sourceTree = ""; }; 25 | 702BAF03186BCA6300E9490A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 26 | 702BAF05186BCA6300E9490A /* AsyncTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AsyncTests.m; sourceTree = ""; }; 27 | 702BAF07186BCA6300E9490A /* AsyncTests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AsyncTests-Prefix.pch"; sourceTree = ""; }; 28 | EC1301439916486F8E3E81A0 /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = ../Pods/Pods.xcconfig; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 702BAEF3186BCA6300E9490A /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | 702BAEFA186BCA6300E9490A /* XCTest.framework in Frameworks */, 37 | 702BAEFE186BCA6300E9490A /* UIKit.framework in Frameworks */, 38 | 702BAEFC186BCA6300E9490A /* Foundation.framework in Frameworks */, 39 | 7156DBC07AD74B4D8AD889E6 /* libPods.a in Frameworks */, 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | 702BAEEA186BC9ED00E9490A = { 47 | isa = PBXGroup; 48 | children = ( 49 | 702BAEFF186BCA6300E9490A /* AsyncTests */, 50 | 702BAEF8186BCA6300E9490A /* Frameworks */, 51 | 702BAEF7186BCA6300E9490A /* Products */, 52 | EC1301439916486F8E3E81A0 /* Pods.xcconfig */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 702BAEF7186BCA6300E9490A /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 702BAEF6186BCA6300E9490A /* AsyncTests.xctest */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 702BAEF8186BCA6300E9490A /* Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 702BAEF9186BCA6300E9490A /* XCTest.framework */, 68 | 702BAEFB186BCA6300E9490A /* Foundation.framework */, 69 | 702BAEFD186BCA6300E9490A /* UIKit.framework */, 70 | 2DBB8E1480E646FDBF975E1B /* libPods.a */, 71 | ); 72 | name = Frameworks; 73 | sourceTree = ""; 74 | }; 75 | 702BAEFF186BCA6300E9490A /* AsyncTests */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 702BAF05186BCA6300E9490A /* AsyncTests.m */, 79 | 702BAF00186BCA6300E9490A /* Supporting Files */, 80 | ); 81 | path = AsyncTests; 82 | sourceTree = ""; 83 | }; 84 | 702BAF00186BCA6300E9490A /* Supporting Files */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 702BAF01186BCA6300E9490A /* AsyncTests-Info.plist */, 88 | 702BAF02186BCA6300E9490A /* InfoPlist.strings */, 89 | 702BAF07186BCA6300E9490A /* AsyncTests-Prefix.pch */, 90 | ); 91 | name = "Supporting Files"; 92 | sourceTree = ""; 93 | }; 94 | /* End PBXGroup section */ 95 | 96 | /* Begin PBXNativeTarget section */ 97 | 702BAEF5186BCA6300E9490A /* AsyncTests */ = { 98 | isa = PBXNativeTarget; 99 | buildConfigurationList = 702BAF08186BCA6300E9490A /* Build configuration list for PBXNativeTarget "AsyncTests" */; 100 | buildPhases = ( 101 | 2533975A914D4E95A71C7D66 /* Check Pods Manifest.lock */, 102 | 702BAEF2186BCA6300E9490A /* Sources */, 103 | 702BAEF3186BCA6300E9490A /* Frameworks */, 104 | 702BAEF4186BCA6300E9490A /* Resources */, 105 | 7D17619DB05740DABABD36DE /* Copy Pods Resources */, 106 | ); 107 | buildRules = ( 108 | ); 109 | dependencies = ( 110 | ); 111 | name = AsyncTests; 112 | productName = AsyncTests; 113 | productReference = 702BAEF6186BCA6300E9490A /* AsyncTests.xctest */; 114 | productType = "com.apple.product-type.bundle.unit-test"; 115 | }; 116 | /* End PBXNativeTarget section */ 117 | 118 | /* Begin PBXProject section */ 119 | 702BAEEB186BC9ED00E9490A /* Project object */ = { 120 | isa = PBXProject; 121 | attributes = { 122 | LastUpgradeCheck = 0500; 123 | }; 124 | buildConfigurationList = 702BAEEE186BC9ED00E9490A /* Build configuration list for PBXProject "AsyncTests" */; 125 | compatibilityVersion = "Xcode 3.2"; 126 | developmentRegion = English; 127 | hasScannedForEncodings = 0; 128 | knownRegions = ( 129 | en, 130 | ); 131 | mainGroup = 702BAEEA186BC9ED00E9490A; 132 | productRefGroup = 702BAEF7186BCA6300E9490A /* Products */; 133 | projectDirPath = ""; 134 | projectRoot = ""; 135 | targets = ( 136 | 702BAEF5186BCA6300E9490A /* AsyncTests */, 137 | ); 138 | }; 139 | /* End PBXProject section */ 140 | 141 | /* Begin PBXResourcesBuildPhase section */ 142 | 702BAEF4186BCA6300E9490A /* Resources */ = { 143 | isa = PBXResourcesBuildPhase; 144 | buildActionMask = 2147483647; 145 | files = ( 146 | 702BAF04186BCA6300E9490A /* InfoPlist.strings in Resources */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXResourcesBuildPhase section */ 151 | 152 | /* Begin PBXShellScriptBuildPhase section */ 153 | 2533975A914D4E95A71C7D66 /* Check Pods Manifest.lock */ = { 154 | isa = PBXShellScriptBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | ); 158 | inputPaths = ( 159 | ); 160 | name = "Check Pods Manifest.lock"; 161 | outputPaths = ( 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | shellPath = /bin/sh; 165 | 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"; 166 | showEnvVarsInLog = 0; 167 | }; 168 | 7D17619DB05740DABABD36DE /* Copy Pods Resources */ = { 169 | isa = PBXShellScriptBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | ); 173 | inputPaths = ( 174 | ); 175 | name = "Copy Pods Resources"; 176 | outputPaths = ( 177 | ); 178 | runOnlyForDeploymentPostprocessing = 0; 179 | shellPath = /bin/sh; 180 | shellScript = "\"${SRCROOT}/../Pods/Pods-resources.sh\"\n"; 181 | showEnvVarsInLog = 0; 182 | }; 183 | /* End PBXShellScriptBuildPhase section */ 184 | 185 | /* Begin PBXSourcesBuildPhase section */ 186 | 702BAEF2186BCA6300E9490A /* Sources */ = { 187 | isa = PBXSourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | 702BAF06186BCA6300E9490A /* AsyncTests.m in Sources */, 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXSourcesBuildPhase section */ 195 | 196 | /* Begin PBXVariantGroup section */ 197 | 702BAF02186BCA6300E9490A /* InfoPlist.strings */ = { 198 | isa = PBXVariantGroup; 199 | children = ( 200 | 702BAF03186BCA6300E9490A /* en */, 201 | ); 202 | name = InfoPlist.strings; 203 | sourceTree = ""; 204 | }; 205 | /* End PBXVariantGroup section */ 206 | 207 | /* Begin XCBuildConfiguration section */ 208 | 702BAEEF186BC9ED00E9490A /* Debug */ = { 209 | isa = XCBuildConfiguration; 210 | buildSettings = { 211 | }; 212 | name = Debug; 213 | }; 214 | 702BAEF0186BC9ED00E9490A /* Release */ = { 215 | isa = XCBuildConfiguration; 216 | buildSettings = { 217 | }; 218 | name = Release; 219 | }; 220 | 702BAF09186BCA6300E9490A /* Debug */ = { 221 | isa = XCBuildConfiguration; 222 | baseConfigurationReference = EC1301439916486F8E3E81A0 /* Pods.xcconfig */; 223 | buildSettings = { 224 | ALWAYS_SEARCH_USER_PATHS = NO; 225 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 227 | CLANG_CXX_LIBRARY = "libc++"; 228 | CLANG_ENABLE_MODULES = YES; 229 | CLANG_ENABLE_OBJC_ARC = YES; 230 | CLANG_WARN_BOOL_CONVERSION = YES; 231 | CLANG_WARN_CONSTANT_CONVERSION = YES; 232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 233 | CLANG_WARN_EMPTY_BODY = YES; 234 | CLANG_WARN_ENUM_CONVERSION = YES; 235 | CLANG_WARN_INT_CONVERSION = YES; 236 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 237 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 238 | COPY_PHASE_STRIP = NO; 239 | FRAMEWORK_SEARCH_PATHS = ( 240 | "$(SDKROOT)/Developer/Library/Frameworks", 241 | "$(inherited)", 242 | "$(DEVELOPER_FRAMEWORKS_DIR)", 243 | ); 244 | GCC_C_LANGUAGE_STANDARD = gnu99; 245 | GCC_DYNAMIC_NO_PIC = NO; 246 | GCC_OPTIMIZATION_LEVEL = 0; 247 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 248 | GCC_PREFIX_HEADER = "AsyncTests/AsyncTests-Prefix.pch"; 249 | GCC_PREPROCESSOR_DEFINITIONS = ( 250 | "DEBUG=1", 251 | "$(inherited)", 252 | ); 253 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 254 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 255 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 256 | GCC_WARN_UNDECLARED_SELECTOR = YES; 257 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 258 | GCC_WARN_UNUSED_FUNCTION = YES; 259 | GCC_WARN_UNUSED_VARIABLE = YES; 260 | INFOPLIST_FILE = "AsyncTests/AsyncTests-Info.plist"; 261 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 262 | ONLY_ACTIVE_ARCH = YES; 263 | PRODUCT_NAME = "$(TARGET_NAME)"; 264 | SDKROOT = iphoneos; 265 | WRAPPER_EXTENSION = xctest; 266 | }; 267 | name = Debug; 268 | }; 269 | 702BAF0A186BCA6300E9490A /* Release */ = { 270 | isa = XCBuildConfiguration; 271 | baseConfigurationReference = EC1301439916486F8E3E81A0 /* Pods.xcconfig */; 272 | buildSettings = { 273 | ALWAYS_SEARCH_USER_PATHS = NO; 274 | ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; 275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 276 | CLANG_CXX_LIBRARY = "libc++"; 277 | CLANG_ENABLE_MODULES = YES; 278 | CLANG_ENABLE_OBJC_ARC = YES; 279 | CLANG_WARN_BOOL_CONVERSION = YES; 280 | CLANG_WARN_CONSTANT_CONVERSION = YES; 281 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 282 | CLANG_WARN_EMPTY_BODY = YES; 283 | CLANG_WARN_ENUM_CONVERSION = YES; 284 | CLANG_WARN_INT_CONVERSION = YES; 285 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 286 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 287 | COPY_PHASE_STRIP = YES; 288 | ENABLE_NS_ASSERTIONS = NO; 289 | FRAMEWORK_SEARCH_PATHS = ( 290 | "$(SDKROOT)/Developer/Library/Frameworks", 291 | "$(inherited)", 292 | "$(DEVELOPER_FRAMEWORKS_DIR)", 293 | ); 294 | GCC_C_LANGUAGE_STANDARD = gnu99; 295 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 296 | GCC_PREFIX_HEADER = "AsyncTests/AsyncTests-Prefix.pch"; 297 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 298 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 299 | GCC_WARN_UNDECLARED_SELECTOR = YES; 300 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 301 | GCC_WARN_UNUSED_FUNCTION = YES; 302 | GCC_WARN_UNUSED_VARIABLE = YES; 303 | INFOPLIST_FILE = "AsyncTests/AsyncTests-Info.plist"; 304 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 305 | PRODUCT_NAME = "$(TARGET_NAME)"; 306 | SDKROOT = iphoneos; 307 | VALIDATE_PRODUCT = YES; 308 | WRAPPER_EXTENSION = xctest; 309 | }; 310 | name = Release; 311 | }; 312 | /* End XCBuildConfiguration section */ 313 | 314 | /* Begin XCConfigurationList section */ 315 | 702BAEEE186BC9ED00E9490A /* Build configuration list for PBXProject "AsyncTests" */ = { 316 | isa = XCConfigurationList; 317 | buildConfigurations = ( 318 | 702BAEEF186BC9ED00E9490A /* Debug */, 319 | 702BAEF0186BC9ED00E9490A /* Release */, 320 | ); 321 | defaultConfigurationIsVisible = 0; 322 | defaultConfigurationName = Release; 323 | }; 324 | 702BAF08186BCA6300E9490A /* Build configuration list for PBXNativeTarget "AsyncTests" */ = { 325 | isa = XCConfigurationList; 326 | buildConfigurations = ( 327 | 702BAF09186BCA6300E9490A /* Debug */, 328 | 702BAF0A186BCA6300E9490A /* Release */, 329 | ); 330 | defaultConfigurationIsVisible = 0; 331 | defaultConfigurationName = Release; 332 | }; 333 | /* End XCConfigurationList section */ 334 | }; 335 | rootObject = 702BAEEB186BC9ED00E9490A /* Project object */; 336 | } 337 | -------------------------------------------------------------------------------- /Project/AsyncTests/AsyncTests.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Project/AsyncTests/AsyncTests/AsyncTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.johnwana.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Project/AsyncTests/AsyncTests/AsyncTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /Project/AsyncTests/AsyncTests/AsyncTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncTests.m 3 | // AsyncTests 4 | // 5 | // Created by John Wana on 12/9/13. 6 | // Copyright (c) 2013 John Wana. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Async.h" 11 | 12 | const int64_t TEST_TIMEOUT = 5000000000; // 5 sec 13 | const int64_t TEST_PAUSE_AFTER_FINISH = 1000000000; // 1 sec 14 | 15 | 16 | @interface AsyncTests : XCTestCase 17 | 18 | @end 19 | 20 | @implementation AsyncTests 21 | 22 | - (void)setUp 23 | { 24 | [super setUp]; 25 | // Put setup code here. This method is called before the invocation of each test method in the class. 26 | } 27 | 28 | - (void)tearDown 29 | { 30 | // Put teardown code here. This method is called after the invocation of each test method in the class. 31 | [super tearDown]; 32 | } 33 | 34 | typedef void (^testSuccessBlock)(); 35 | - (void)runAsyncTest:(void(^)(testSuccessBlock testSuccessBlock))testBlock 36 | { 37 | dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); 38 | testBlock(^ { dispatch_semaphore_signal(semaphore); }); 39 | if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, TEST_TIMEOUT)) != 0) { 40 | XCTFail(@"Semaphore test timeout!"); 41 | } 42 | 43 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, TEST_PAUSE_AFTER_FINISH), 44 | dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), 45 | ^ { 46 | dispatch_semaphore_signal(semaphore); 47 | }); 48 | 49 | dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, TEST_TIMEOUT)); 50 | 51 | 52 | NSLog(@"Done"); 53 | } 54 | 55 | - (void)testSeriesSuccess 56 | { 57 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 58 | NSMutableArray *results = [NSMutableArray array]; 59 | block0 first = ^(successBlock success ,failureBlock failure) { 60 | [results addObject:@"a"]; 61 | success(); 62 | }; 63 | block0 second = ^(successBlock success ,failureBlock failure) { 64 | [results addObject:@"b"]; 65 | success(); 66 | }; 67 | block0 third = ^(successBlock success ,failureBlock failure) { 68 | [results addObject:@"c"]; 69 | success(); 70 | }; 71 | 72 | NSArray *blocks = [NSArray arrayWithObjects:first, second, third, nil]; 73 | 74 | [Async series:blocks 75 | success:^{ 76 | XCTAssertEqual(blocks.count, results.count, @"Not all blocks called"); 77 | NSArray *expectedResults = [NSArray arrayWithObjects:@"a", @"b", @"c", nil]; 78 | XCTAssertEqualObjects(results, expectedResults, @"Results do not match expected"); 79 | testSuccessBlock(); 80 | } 81 | failure:^(NSError *error) { 82 | XCTFail(@"Should not fail"); 83 | } 84 | ]; 85 | }]; 86 | } 87 | 88 | - (void)testSeriesFailure 89 | { 90 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 91 | NSMutableArray *results = [NSMutableArray array]; 92 | block0 first = ^(successBlock success ,failureBlock failure) { 93 | [results addObject:@"a"]; 94 | success(); 95 | }; 96 | block0 second = ^(successBlock success ,failureBlock failure) { 97 | [results addObject:@"b"]; 98 | failure([NSError errorWithDomain:@"testSeriesFailure" code:321 userInfo:nil]); 99 | }; 100 | block0 third = ^(successBlock success ,failureBlock failure) { 101 | [results addObject:@"c"]; 102 | success(); 103 | }; 104 | 105 | NSArray *blocks = [NSArray arrayWithObjects:first, second, third, nil]; 106 | [Async series:blocks 107 | success:^{ 108 | XCTFail(@"Should not succeed"); 109 | } 110 | failure:^(NSError *error) { 111 | XCTAssertEqual((NSUInteger)2, [results count], @"Not all blocks called"); 112 | NSArray *expectedResults = [NSArray arrayWithObjects:@"a", @"b", nil]; 113 | XCTAssertEqualObjects(results, expectedResults, @"Results do not match expected"); 114 | testSuccessBlock(); 115 | } 116 | ]; 117 | }]; 118 | } 119 | 120 | - (void)testParallelSuccess 121 | { 122 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 123 | NSMutableArray *hasRun = [NSMutableArray arrayWithCapacity:3]; 124 | block0 firstBlock = ^(successBlock success, failureBlock failure) { 125 | [hasRun addObject:@"1"]; 126 | success(); 127 | }; 128 | block0 secondBlock = ^(successBlock success, failureBlock failure) { 129 | [hasRun addObject:@"2"]; 130 | success(); 131 | }; 132 | block0 thirdBlock = ^(successBlock success, failureBlock failure) { 133 | [hasRun addObject:@"3"]; 134 | success(); 135 | }; 136 | [Async parallel:[NSArray arrayWithObjects:firstBlock, secondBlock, thirdBlock, nil] 137 | success:^{ 138 | XCTAssertEqual(hasRun.count, (NSUInteger)3, @"Invalid number of runs"); 139 | XCTAssertNotEqual([hasRun indexOfObject:@"1"], NSNotFound, @"Cannot find result"); 140 | XCTAssertNotEqual([hasRun indexOfObject:@"2"], NSNotFound, @"Cannot find result"); 141 | XCTAssertNotEqual([hasRun indexOfObject:@"3"], NSNotFound, @"Cannot find result"); 142 | testSuccessBlock(); 143 | } 144 | failure:^(NSError *error) { 145 | XCTFail(@"Should not fail"); 146 | } 147 | ]; 148 | 149 | }]; 150 | } 151 | 152 | - (void)testParallelFailure 153 | { 154 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 155 | NSMutableArray *hasRun = [NSMutableArray arrayWithCapacity:3]; 156 | block0 firstBlock = ^(successBlock success, failureBlock failure) { 157 | [hasRun addObject:@"1"]; 158 | success(); 159 | }; 160 | block0 secondBlock = ^(successBlock success, failureBlock failure) { 161 | [hasRun addObject:@"2"]; 162 | failure([NSError errorWithDomain:@"testParallelFailure" code:123 userInfo:nil]); 163 | }; 164 | block0 thirdBlock = ^(successBlock success, failureBlock failure) { 165 | [hasRun addObject:@"3"]; 166 | failure([NSError errorWithDomain:@"testParallelFailure" code:456 userInfo:nil]); 167 | }; 168 | __block BOOL failureCalled = NO; 169 | [Async parallel:[NSArray arrayWithObjects:firstBlock, secondBlock, thirdBlock, nil] 170 | success:^{ 171 | XCTFail(@"Should not succeed"); 172 | } 173 | failure:^(NSError *error) { 174 | XCTAssertEqual(failureCalled, NO, @"Failure already called"); 175 | failureCalled = YES; 176 | testSuccessBlock(); 177 | } 178 | ]; 179 | 180 | }]; 181 | } 182 | 183 | // TODO: run sync test also 184 | - (void)testMapSuccess 185 | { 186 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 187 | __block BOOL successCalled = NO; 188 | NSArray *array = [NSArray arrayWithObjects:@"a", @"b", @"c", @"d", nil]; 189 | 190 | [Async mapParallel:array 191 | mapFunction:^(id obj, mapSuccessBlock success, mapFailBlock failure) { 192 | unichar val = [(NSString *)obj characterAtIndex:0]; 193 | success([NSNumber numberWithShort:val]); 194 | } 195 | success:^(NSArray *mappedArray) { 196 | XCTAssertEqual(successCalled, NO, @"Success already called"); 197 | successCalled = YES; 198 | XCTAssertEqual(mappedArray.count, array.count); 199 | for (NSUInteger i = 0; i < mappedArray.count; i++) { 200 | NSString *restoredString = [NSString stringWithFormat:@"%c", [mappedArray[i] shortValue]]; 201 | XCTAssertEqualObjects(restoredString, array[i], @"%@ != %@", restoredString, array[i]); 202 | } 203 | testSuccessBlock(); 204 | } 205 | failure:^(NSError *error) { 206 | XCTFail(@"Should not fail"); 207 | } 208 | ]; 209 | }]; 210 | } 211 | 212 | // TODO: run sync test also 213 | - (void)testEmptyMapSuccess 214 | { 215 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 216 | [Async mapParallel:@[] 217 | mapFunction:^(id obj, mapSuccessBlock success, mapFailBlock failure) { 218 | success(obj); 219 | } 220 | success:^(NSArray *mappedArray) { 221 | XCTAssertEqual(mappedArray.count, 0); 222 | testSuccessBlock(); 223 | } 224 | failure:^(NSError *error) { 225 | XCTFail(@"Should not fail"); 226 | } 227 | ]; 228 | }]; 229 | } 230 | 231 | // TODO: run sync test also 232 | - (void)testMapFailure 233 | { 234 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 235 | __block BOOL failureCalled = NO; 236 | NSArray *array = [NSArray arrayWithObjects:@"a", @"b", @"c", @"d", nil]; 237 | [Async mapParallel:array 238 | mapFunction:^(id obj, mapSuccessBlock success, mapFailBlock failure) { 239 | failure([NSError errorWithDomain:@"testMapFailure" code:123 userInfo:nil]); 240 | } 241 | success:^(NSArray *mappedArray) { 242 | XCTFail(@"Should not succeed"); 243 | } 244 | failure:^(NSError *error) { 245 | XCTAssertEqual(failureCalled, NO, @"Failure already called"); 246 | failureCalled = YES; 247 | XCTAssertEqualObjects(error.domain, @"testMapFailure", @"Unknown error domain"); 248 | XCTAssertEqual(error.code, 123, @"Unknown error code"); 249 | testSuccessBlock(); 250 | } 251 | ]; 252 | }]; 253 | } 254 | 255 | // TODO: run sync test also 256 | - (void)testEachSuccess 257 | { 258 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 259 | __block int val = 0; 260 | eachBlock block = ^(id obj, successBlock success, failureBlock failure) { 261 | NSString *s = obj; 262 | val += [s intValue]; 263 | success(); 264 | }; 265 | [Async eachParallel:[NSArray arrayWithObjects:@"1", @"2", @"3", nil] 266 | block:(eachBlock)block 267 | success:^ { 268 | XCTAssertEqual(val, 6, @"Final value wrong"); 269 | testSuccessBlock(); 270 | } 271 | failure:^(NSError *error) { 272 | XCTFail(@"Should not fail"); 273 | }]; 274 | 275 | }]; 276 | } 277 | 278 | // TODO: run sync test also 279 | - (void)testEachFailure 280 | { 281 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 282 | __block BOOL failureCalled = NO; 283 | eachBlock block = ^(id obj, successBlock success, failureBlock failure) { 284 | failure([NSError errorWithDomain:@"testEachFailure" code:123 userInfo:nil]); 285 | }; 286 | [Async eachParallel:[NSArray arrayWithObjects:@"1", @"2", @"3", nil] 287 | block:(eachBlock)block 288 | success:^ { 289 | XCTFail(@"Should not succeed"); 290 | } 291 | failure:^(NSError *error) { 292 | XCTAssertEqual(failureCalled, NO, @"Failure already called"); 293 | failureCalled = YES; 294 | XCTAssertEqualObjects(error.domain, @"testEachFailure", @"Unknown error domain"); 295 | XCTAssertEqual(error.code, 123, @"Unknown error code"); 296 | testSuccessBlock(); 297 | }]; 298 | }]; 299 | } 300 | 301 | - (void)testRepeatUntilSuccess 302 | { 303 | const NSUInteger MAX_ATTEMPTS = 3; 304 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 305 | __block NSMutableArray *results = [NSMutableArray arrayWithCapacity:MAX_ATTEMPTS]; 306 | [Async repeatUntilSuccess:^(successBlock success, failureBlock failure) { 307 | [results addObject:[NSNumber numberWithUnsignedInteger:results.count]]; 308 | if (results.count < MAX_ATTEMPTS) { 309 | failure([NSError errorWithDomain:@"testRepeatUntilSuccess" code:0 userInfo:nil]); 310 | } else { 311 | success(); 312 | } 313 | } 314 | maxAttempts:MAX_ATTEMPTS 315 | delayBetweenAttemptsInSec:0.1 316 | success:^{ 317 | XCTAssertEqual(results.count, MAX_ATTEMPTS, @"Wrong number of calls"); 318 | XCTAssertEqual([results indexOfObject:[NSNumber numberWithUnsignedInteger:0]], (NSUInteger)0, @"Missing result"); 319 | XCTAssertEqual([results indexOfObject:[NSNumber numberWithUnsignedInteger:1]], (NSUInteger)1, @"Missing result"); 320 | XCTAssertEqual([results indexOfObject:[NSNumber numberWithUnsignedInteger:2]], (NSUInteger)2, @"Missing result"); 321 | testSuccessBlock(); 322 | } 323 | failure:^(NSError *error) { 324 | XCTFail(@"Should not fail"); 325 | } 326 | ]; 327 | }]; 328 | } 329 | 330 | - (void)testRepeatUntilFailure 331 | { 332 | const NSUInteger MAX_ATTEMPTS = 3; 333 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 334 | __block NSMutableArray *results = [NSMutableArray arrayWithCapacity:MAX_ATTEMPTS]; 335 | [Async repeatUntilSuccess:^(successBlock success, failureBlock failure) { 336 | [results addObject:[NSNumber numberWithUnsignedInteger:results.count]]; 337 | failure([NSError errorWithDomain:@"testRepeatUntilFailure" code:456 userInfo:nil]); 338 | } 339 | maxAttempts:MAX_ATTEMPTS 340 | delayBetweenAttemptsInSec:0.1 341 | success:^{ 342 | XCTFail(@"Should not succeed"); 343 | } 344 | failure:^(NSError *error) { 345 | XCTAssertEqual(results.count, MAX_ATTEMPTS, @"Wrong number of calls"); 346 | XCTAssertEqual([results indexOfObject:[NSNumber numberWithUnsignedInteger:0]], (NSUInteger)0, @"Missing result"); 347 | XCTAssertEqual([results indexOfObject:[NSNumber numberWithUnsignedInteger:1]], (NSUInteger)1, @"Missing result"); 348 | XCTAssertEqual([results indexOfObject:[NSNumber numberWithUnsignedInteger:2]], (NSUInteger)2, @"Missing result"); 349 | XCTAssertEqualObjects(error.domain, @"testRepeatUntilFailure", @"Unknown error domain"); 350 | XCTAssertEqual(error.code, 456, @"Unknown error code"); 351 | testSuccessBlock(); 352 | } 353 | ]; 354 | }]; 355 | } 356 | 357 | - (void)testWaterfallSuccess 358 | { 359 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 360 | NSMutableArray *results = [NSMutableArray arrayWithCapacity:3]; 361 | block1 firstBlock = ^(id obj, successBlock1 success, failureBlock failure) { 362 | [results addObject:obj]; 363 | success([NSNumber numberWithInt:[obj intValue] + 1]); 364 | }; 365 | block1 secondBlock = ^(id obj, successBlock1 success, failureBlock failure) { 366 | [results addObject:obj]; 367 | success([NSNumber numberWithInt:[obj intValue] + 2]); 368 | }; 369 | block1 thirdBlock = ^(id obj, successBlock1 success, failureBlock failure) { 370 | [results addObject:obj]; 371 | success([NSNumber numberWithInt:[obj intValue] + 3]); 372 | }; 373 | 374 | [Async waterfall:[NSArray arrayWithObjects:firstBlock, secondBlock, thirdBlock, nil] 375 | firstParam:[NSNumber numberWithInt:5] 376 | success:^(id result) { 377 | XCTAssertEqual(results.count, (NSUInteger)3, @"Wrong number of results"); 378 | XCTAssertEqual([results[0] intValue], 5, @"Wrong result"); 379 | XCTAssertEqual([results[1] intValue], 6, @"Wrong result"); 380 | XCTAssertEqual([results[2] intValue], 8, @"Wrong result"); 381 | XCTAssertEqual([result intValue], 11, @"Wrong result"); 382 | testSuccessBlock(); 383 | } 384 | failure:^(NSError *error) { 385 | XCTFail(@"Should not fail"); 386 | }]; 387 | }]; 388 | } 389 | 390 | - (void)testWaterfallFailure 391 | { 392 | [self runAsyncTest:^(testSuccessBlock testSuccessBlock) { 393 | NSMutableArray *results = [NSMutableArray arrayWithCapacity:3]; 394 | block1 firstBlock = ^(id obj, successBlock1 success, failureBlock failure) { 395 | [results addObject:obj]; 396 | success([NSNumber numberWithInt:[obj intValue] + 1]); 397 | }; 398 | block1 secondBlock = ^(id obj, successBlock1 success, failureBlock failure) { 399 | [results addObject:obj]; 400 | failure([NSError errorWithDomain:@"testWaterfallFailure" code:999 userInfo:nil]); 401 | }; 402 | block1 thirdBlock = ^(id obj, successBlock1 success, failureBlock failure) { 403 | [results addObject:obj]; 404 | success([NSNumber numberWithInt:[obj intValue] + 3]); 405 | }; 406 | 407 | [Async waterfall:[NSArray arrayWithObjects:firstBlock, secondBlock, thirdBlock, nil] 408 | firstParam:[NSNumber numberWithInt:5] 409 | success:^(id result) { 410 | XCTFail(@"Should not succeed"); 411 | } 412 | failure:^(NSError *error) { 413 | XCTAssertEqual(results.count, (NSUInteger)2, @"Wrong number of results"); 414 | XCTAssertEqual([results[0] intValue], 5, @"Wrong result"); 415 | XCTAssertEqual([results[1] intValue], 6, @"Wrong result"); 416 | XCTAssertEqualObjects(error.domain, @"testWaterfallFailure", @"Unknown error domain"); 417 | XCTAssertEqual(error.code, 999, @"Unknown error code"); 418 | testSuccessBlock(); 419 | }]; 420 | }]; 421 | } 422 | 423 | 424 | @end 425 | -------------------------------------------------------------------------------- /Project/AsyncTests/AsyncTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Project/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios 2 | 3 | xcodeproj 'AsyncTests/AsyncTests.xcodeproj' 4 | 5 | pod "Async", :path => "../Async.podspec" 6 | 7 | -------------------------------------------------------------------------------- /Project/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Async (0.1.0) 3 | 4 | DEPENDENCIES: 5 | - Async (from `../Async.podspec`) 6 | 7 | EXTERNAL SOURCES: 8 | Async: 9 | :path: ../Async.podspec 10 | 11 | SPEC CHECKSUMS: 12 | Async: ce0c118443371de619226abfb19d3b759683ddef 13 | 14 | COCOAPODS: 0.29.0 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Async 2 | 3 | [![Version](http://cocoapod-badges.herokuapp.com/v/async/badge.png)](http://cocoadocs.org/docsets/async) 4 | [![Platform](http://cocoapod-badges.herokuapp.com/p/async/badge.png)](http://cocoadocs.org/docsets/async) 5 | 6 | Async is a set of functions for working with asynchronous blocks in Objective-C. 7 | 8 | ## Getting Started 9 | 10 | `Project/AsyncTests.xcodeproj` contains a simple set of tests that demonstrate how to use the library. 11 | 12 | ## Usage 13 | 14 | * Include `Async.h` in your project. 15 | * All Async functions are class-level methods on the Async object. 16 | * Sets of blocks are passed in as an NSArray. 17 | * These blocks always have success and failure callback blocks which conclude the asynchronous function. 18 | * Blocks may additionally receive and return a parameter depending on the method used 19 | * All Async functions have a final `success` and `failure` block. If any one block fails, `failure()` will be called. If not, `succcess()` is called 20 | 21 | ### Async Functions 22 | 23 | * `series`: run a set of blocks in sequential order 24 | * `parallel`: run a set of blocks in parallel 25 | * `eachSeries`: runs a block in series with every item in an array 26 | * `eachParallel`: runs a block in parallel with every item in an array 27 | * `mapParallel`: runs a block in parallel with every item in an array, collecting all the return values 28 | * `mapSeries`: runs a block in series with every item in an array, collecting all the return values 29 | * `repeatUntilSuccess`: repeat a block until it succeeds or maxAttempts is reached 30 | * `waterfall`: run a set of blocks in series, passing the return value of a block to the next block 31 | 32 | ## Author 33 | 34 | John Wana, john@wana.us 35 | 36 | ## License 37 | 38 | Async is available under the MIT license. See the LICENSE file for more info. 39 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | desc "Runs the specs [EMPTY]" 2 | task :spec do 3 | # Provide your own implementation 4 | end 5 | 6 | task :version do 7 | git_remotes = `git remote`.strip.split("\n") 8 | 9 | if git_remotes.count > 0 10 | puts "-- fetching version number from github" 11 | sh 'git fetch' 12 | 13 | remote_version = remote_spec_version 14 | end 15 | 16 | if remote_version.nil? 17 | puts "There is no current released version. You're about to release a new Pod." 18 | version = "0.0.1" 19 | else 20 | puts "The current released version of your pod is " + remote_spec_version.to_s() 21 | version = suggested_version_number 22 | end 23 | 24 | puts "Enter the version you want to release (" + version + ") " 25 | new_version_number = $stdin.gets.strip 26 | if new_version_number == "" 27 | new_version_number = version 28 | end 29 | 30 | replace_version_number(new_version_number) 31 | end 32 | 33 | desc "Release a new version of the Pod" 34 | task :release do 35 | 36 | puts "* Running version" 37 | sh "rake version" 38 | 39 | unless ENV['SKIP_CHECKS'] 40 | if `git symbolic-ref HEAD 2>/dev/null`.strip.split('/').last != 'master' 41 | $stderr.puts "[!] You need to be on the `master' branch in order to be able to do a release." 42 | exit 1 43 | end 44 | 45 | if `git tag`.strip.split("\n").include?(spec_version) 46 | $stderr.puts "[!] A tag for version `#{spec_version}' already exists. Change the version in the podspec" 47 | exit 1 48 | end 49 | 50 | puts "You are about to release `#{spec_version}`, is that correct? [y/n]" 51 | exit if $stdin.gets.strip.downcase != 'y' 52 | end 53 | 54 | puts "* Running specs" 55 | sh "rake spec" 56 | 57 | puts "* Linting the podspec" 58 | sh "pod lib lint" 59 | 60 | # Then release 61 | sh "git commit #{podspec_path} CHANGELOG.md -m 'Release #{spec_version}'" 62 | sh "git tag -a #{spec_version} -m 'Release #{spec_version}'" 63 | sh "git push origin master" 64 | sh "git push origin --tags" 65 | sh "pod push master #{podspec_path}" 66 | end 67 | 68 | # @return [Pod::Version] The version as reported by the Podspec. 69 | # 70 | def spec_version 71 | require 'cocoapods' 72 | spec = Pod::Specification.from_file(podspec_path) 73 | spec.version 74 | end 75 | 76 | # @return [Pod::Version] The version as reported by the Podspec from remote. 77 | # 78 | def remote_spec_version 79 | require 'cocoapods-core' 80 | 81 | if spec_file_exist_on_remote? 82 | remote_spec = eval(`git show origin/master:#{podspec_path}`) 83 | remote_spec.version 84 | else 85 | nil 86 | end 87 | end 88 | 89 | # @return [Bool] If the remote repository has a copy of the podpesc file or not. 90 | # 91 | def spec_file_exist_on_remote? 92 | test_condition = `if git rev-parse --verify --quiet origin/master:#{podspec_path} >/dev/null; 93 | then 94 | echo 'true' 95 | else 96 | echo 'false' 97 | fi` 98 | 99 | 'true' == test_condition.strip 100 | end 101 | 102 | # @return [String] The relative path of the Podspec. 103 | # 104 | def podspec_path 105 | podspecs = Dir.glob('*.podspec') 106 | if podspecs.count == 1 107 | podspecs.first 108 | else 109 | raise "Could not select a podspec" 110 | end 111 | end 112 | 113 | # @return [String] The suggested version number based on the local and remote version numbers. 114 | # 115 | def suggested_version_number 116 | if spec_version != remote_spec_version 117 | spec_version.to_s() 118 | else 119 | next_version(spec_version).to_s() 120 | end 121 | end 122 | 123 | # @param [Pod::Version] version 124 | # the version for which you need the next version 125 | # 126 | # @note It is computed by bumping the last component of the versino string by 1. 127 | # 128 | # @return [Pod::Version] The version that comes next after the version supplied. 129 | # 130 | def next_version(version) 131 | version_components = version.to_s().split("."); 132 | last = (version_components.last.to_i() + 1).to_s 133 | version_components[-1] = last 134 | Pod::Version.new(version_components.join(".")) 135 | end 136 | 137 | # @param [String] new_version_number 138 | # the new version number 139 | # 140 | # @note This methods replaces the version number in the podspec file with a new version number. 141 | # 142 | # @return void 143 | # 144 | def replace_version_number(new_version_number) 145 | text = File.read(podspec_path) 146 | text.gsub!(/(s.version( )*= ")#{spec_version}(")/, "\\1#{new_version_number}\\3") 147 | File.open(podspec_path, "w") { |file| file.puts text } 148 | end --------------------------------------------------------------------------------