├── .gitignore ├── CHANGELOG.md ├── Common ├── RXTimer.h └── RXTimer.m ├── INSTALL.md ├── LICENSE.md ├── README.md ├── RXPromise.podspec ├── RXPromise.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ ├── RXPromise-MacOS-Tests.xcscheme │ ├── RXPromise-MacOS.xcscheme │ ├── RXPromise-WatchOS.xcscheme │ ├── RXPromise-iOS-Tests.xcscheme │ ├── RXPromise-iOS.xcscheme │ ├── RXPromise-tvOS-Tests.xcscheme │ └── RXPromise-tvOS.xcscheme ├── RXPromise.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── Promise.xccheckout │ └── RXPromise.xccheckout ├── Rakefile ├── Samples ├── Sample1 │ ├── Sample1-Prefix.pch │ └── main.m ├── Sample2 │ ├── Sample2-Prefix.pch │ └── main.m ├── Sample3 │ ├── Sample3-Prefix.pch │ └── main.m ├── Sample4 │ ├── Sample4-Prefix.pch │ └── main.m ├── Sample5 │ ├── Sample5-Prefix.pch │ └── main.m ├── Sample6 │ ├── Sample6-Prefix.pch │ └── main.m ├── Sample7 │ └── main.m ├── Sample8 │ ├── Sample8-Prefix.pch │ └── main.m └── Samples.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ └── xcschemes │ ├── Sample1.xcscheme │ ├── Sample2.xcscheme │ ├── Sample3.xcscheme │ ├── Sample4.xcscheme │ ├── Sample5.xcscheme │ ├── Sample6.xcscheme │ ├── Sample7.xcscheme │ └── Sample8.xcscheme ├── Source ├── Info.plist ├── RXPromise+Private.h ├── RXPromise+RXExtension.h ├── RXPromise+RXExtension.mm ├── RXPromise.h ├── RXPromise.mm ├── RXPromiseHeader.h ├── RXSettledResult.h ├── RXSettledResult.mm └── utility │ └── DLog.h └── Tests ├── DLog.h ├── Info.plist └── RXPromiseTest.mm /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X folder attributes 2 | .DS_Store 3 | 4 | # Xcode's folder for intermediate data: 5 | Build 6 | DerivedData 7 | 8 | # temp nibs and swap files 9 | *~.nib 10 | *.swp 11 | 12 | 13 | #exclude generated html files in folder "doc": 14 | doc/ 15 | 16 | # Xcode's user settings 17 | *.mode1v3 18 | *.mode2v3 19 | *.pbxuser 20 | *.perspectivev3 21 | xcuserdata 22 | project.xcworkspace -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## This file describes API changes, new features and bug fixes on a high level point of view. 2 | 3 | # RXPromise 4 | 5 | ## Version 0.1.0 beta (22.05.2013) 6 | 7 | * Initial Version 8 | 9 | 10 | 11 | ## Version 0.2.0 beta (29.05.2013) 12 | 13 | #### Changes 14 | 15 | * Improved runtime memory requirements of a promise instance. 16 | 17 | 18 | ## Version 0.3.0 beta (30.05.2013) 19 | 20 | #### Bug Fixes 21 | 22 | * Fixed issue with dispatch objects becoming "retainable object pointers. Compiles now for deployment targets iOS >= 6.0 and Mac OS X >= 10.8. 23 | 24 | 25 | ## Version 0.4.0 beta (31.05.2013) 26 | 27 | #### Changes 28 | 29 | * Added a method `bind` in the "Deferred" API. 30 | 31 | `bind` can be used by an asynchronous result provider if it itself uses another asynchronous result provider's promise in order to resolve its own promise. 32 | 33 | A `cancel` message will be forwarded to the bound promise. 34 | 35 | 36 | 37 | ### Version 0.4.1 beta (31.05.2013) 38 | 39 | * Minor fixes in accompanying documents 40 | 41 | 42 | ### Version 0.4.2 beta (31.05.2013) 43 | 44 | #### Bug Fixes 45 | 46 | * Fixed bug in a macro used for source code compatibility for different OS versions. This should now definitely fix the OS version issue. 47 | 48 | 49 | ## Version 0.5.0 beta (1.06.2013) 50 | 51 | #### Changes 52 | 53 | * Created Library projects for Mac OS X (framework and static) and iOS (static) 54 | 55 | The libraries require deployment target >= Mac OS X 10.7, respectively >= iOS 5.1 56 | 57 | Due to moving the code into libraries, the logging mechanism became an implementation detail. Log level has been set to `DEBUG_LOGLEVEL_WARN` for Debug configurations - that is, only warning messages will be printed which may indicate an error somewhere. For Release configuration the debug log level has been set to `DEBUG_LOGLEVEL_ERROR`, which always means a really serious error. 58 | 59 | 60 | 61 | ### Version 0.5.1 beta (4.06.2013) 62 | 63 | #### Bug Fixes 64 | 65 | * Fixed Copy Header path for Mac OS X static library 66 | 67 | 68 | 69 | ## Version 0.6 beta (5.07.2013) 70 | 71 | #### Changes 72 | 73 | * The implementation became more memory efficient. However, this required to use a standard container from the C++ standard library. This has the consequence, that an application which links against the static library or incorporates the sources directly need to link against the standard C++ library by adding the compiler option "-lc++" to the "Other Linker Flags" build setting. When linking against the framework, this setting is not required. 74 | 75 | 76 | * Added a few samples to show some advanced use cases. 77 | 78 | 79 | * Removed the "preliminary" API for the process handler. IMO, a process handler is not appropriate for a Promise - that may be moved to the asynchronous provider. 80 | 81 | 82 | * A few new unit tests have been added to specifically test subtle edge-cases. The implementation appears to be quite stable and no failure could have been detected. 83 | 84 | 85 | ## Version 0.7 beta (6.08.2013) 86 | 87 | #### Changes 88 | 89 | ** BREAKING CHANGES ** 90 | 91 | In version 0.6 and before, _all_ handlers have been executed _serially_ on a private dispatch queue. 92 | 93 | In version 0.7 and later, the `then` property now assumes an _implicit_ execution context which is a _concurrent dispatch queue_. That means, when using the `then` property - without an explicitly specified execution context for the handlers - the handler will execute concurrently on any thread respectively on an concurrent queue. The consequence is that handlers are no more serialized and concurrent access to shared resources is no more automatically thread safe! 94 | 95 | Concurrent access to shared resources can now be achieved through _explicitly_ specifying an execution context with the new `thenOn` property. With this property one can explicitly specify the execution context (a dispatch queue) where the handler shall be executed with the first parameter to the returned then block: 96 | 97 | Example: 98 | 99 | dispatch_queue_t sync_queue = dispatch_queue_create("sync.queue", NULL); 100 | 101 | asyncFoo().thenOn(sync_queue, onCompletion, onError); 102 | 103 | 104 | Here, _sync_queue_ is a serial queue, and due to this, concurrent access from within the handlers to shared resources through this queue is safe. The sync_queue may also be used from elsewhere in order to guarantee safe access, not just handlers. 105 | 106 | The _explicit_ execution context can also be a concurrent dispatch queue. 107 | 108 | 109 | The advantage of an implicit concurrent execution context is, that handlers now do execute independently from each other, and handlers will not have to wait until one other handler is finished. This improves CPU utilization and is less prone to unwanted blocking. 110 | 111 | Note though, that handler still SHOULD NOT perform lengthy tasks and SHOULD NOT block. If this is the case, the handler should be wrapped into an asynchronous task. 112 | 113 | 114 | * Added new API: 115 | 116 | The `thenOn` property has been added which provides a means to explicitly specify where the handlers are executed. 117 | 118 | 119 | 120 | ### Version 0.7.3 beta (31.08.2013) 121 | 122 | #### Bug Fixes 123 | 124 | * Fixed a bug in method bind, which erroneously fulfilled the target promise if the other promise was rejected. 125 | 126 | 127 | 128 | ## Version 0.8 beta (08.09.2013) 129 | 130 | 131 | #### New APIs 132 | 133 | - (RXPromise*) setTimeout:(NSTimeInterval)timeout; 134 | 135 | This sets a timeout for the promise. If the timeout expires before the promise has been resolved, the promise will be rejected with an error with domain: @"RXPromise", code:-1001, userInfo:@{NSLocalizedFailureReasonErrorKey: @"timeout"} 136 | 137 | 138 | 139 | - (void) runLoopWait; 140 | 141 | Runs the current run loop until after the receiver has been resolved, 142 | and previously queued handlers have been finished. 143 | 144 | 145 | 146 | ### Version 0.8.1 beta (08.09.2013) 147 | 148 | #### Changes: 149 | 150 | Added a strict requirement when using `runLoopWait`: The current thread MUST have a run loop and at least one event source. Otherwise the behavior is undefined. 151 | 152 | Well, the main thread will always fulfill this prerequisite - but it may not be true for secondary threads unless the program to test is carefully designed and has an event source attached to the secondary thread (e.g. a NSURLConnection). 153 | In the current implementation and in the _worst case_, the behavior *MAY* be such that `runLoopWait` MAY _busy wait_ and hog a CPU for a short time. This is entirely a cause of how `NSRunLoop` is implemented internally. 154 | 155 | 156 | 157 | ### Version 0.8.2 beta (12.09.2013) 158 | 159 | 160 | #### Changes 161 | 162 | - The logging feature - primarily a means for debugging and hunting subtle bugs - has been effectively disabled by default. The verbosity of and the "severity" of the log messages will be controlled by the macro `DEBUG_LOG`. Unless it is defined in a build setting or elsewhere, `DEBUG_LOG` will be defined in RXPromise.mm such that only errors will be logged. Defining it to 2, 3, or 4 will increase the verbosity. 163 | 164 | 165 | - A few typos have been fixed in code and README.md (Contributed by Rob Ryan) 166 | 167 | 168 | 169 | #### Bug Fixes 170 | 171 | A Unit Test has been fixed which potentially has reported a false positive. 172 | 173 | 174 | #### Misc: 175 | 176 | Fixed a spurious Static Analyzer warning. 177 | 178 | 179 | 180 | ### Version 0.8.3 beta (13.09.2013) 181 | 182 | 183 | #### Changes 184 | 185 | Added a "How To Install" section in the README.md file. 186 | 187 | 188 | 189 | ## Version 0.9 beta (2013-09-20) 190 | 191 | 192 | #### Changes 193 | 194 | - Updated Xcode Project for Xcode 5 195 | 196 | - Now using XCTest for Unit Tests. 197 | 198 | - Documentation style is optimized for Xcode's 5 inline help bubbles. 199 | 200 | 201 | #### BREAKING CHANGES 202 | 203 | The class method 204 | 205 | `+ (RXPromise*)all:(NSArray*)promises;` 206 | 207 | now returns a `RXPromise` whose success handler returns an array containing the _result_ of each asynchronous task (in the corresponding order). 208 | Before, the _results_ parameter contained the array of promises. So basically, it _was_ the same array as the array specified in parameter _promises_. 209 | 210 | 211 | #### New APIs 212 | 213 | - Added two convenient class methods 214 | 215 | `+ (RXPromise*) promiseWithTask:(id(^)(void))task;` 216 | 217 | and 218 | 219 | `+ (RXPromise*) promiseWithQueue:(dispatch_queue_t)queue task:(id(^)(void))task;` 220 | 221 | 222 | - Added a property `root` which returns the root promise. 223 | 224 | 225 | - Added a class method 226 | 227 | `+ (RXPromise*) sequence:(NSArray*)inputs task:(RXPromise* (^)(id input)) task;` 228 | 229 | which can be used to chain a number of tasks which can be initialized from the 230 | inputs array. The sequence method supports cancellation. 231 | 232 | 233 | 234 | ### Version 0.9.1 beta (2013-09-20) 235 | 236 | 237 | #### Changes 238 | 239 | Minor updates in documentation. 240 | 241 | 242 | ### Version 0.9.2 beta (2013-09-27) 243 | 244 | 245 | #### Bug Fixes 246 | 247 | Fixed a subtle race condition in method `setTimeout:`. 248 | 249 | 250 | #### Changes 251 | 252 | - Improved memory management in method `sequence:task:` 253 | 254 | - Xcode configuration files updated for iOS 7. 255 | 256 | - Added Unit Tests for iOS running on the device. 257 | 258 | 259 | 260 | 261 | ### Version 0.9.3 beta (2013-09-20) 262 | ### Version 0.9.4 beta (2013-09-20) 263 | 264 | #### Bug Fixes 265 | 266 | Fixed silly typos that slipped into the sources accidentally. 267 | 268 | 269 | 270 | 271 | ### Version 0.9.5 beta (2013-10-21) 272 | 273 | #### Bug Fixes 274 | 275 | Class `RXPromise` now can be properly subclassed. The then_block now returns a promise of the subclass, for example: 276 | 277 | MyPromise* promise1 = ... 278 | MyPromise* promise2 = promise1.then(^id(id result){ return @"OK; }, nil); 279 | 280 | 281 | Likewise, inherited class factory methods now return on object of the subclass, for example: 282 | 283 | MyPromise* promise = [MyPromise all:array]; 284 | 285 | 286 | 287 | 288 | #### Changes 289 | 290 | - Improved documentation in the README.md. 291 | 292 | 293 | 294 | 295 | #### Known Issues 296 | 297 | - Due to an issue in Xcode 5, it's not possible to run *individual* unit test methods when clicking on the diamond in the gutter for an Mac OS X test bundle. This happens when the same unit test source code is shared for an iOS test bundle and a Mac OS X test bundle. The whole test runs without problems, and individual unit tests can be run from within the Test Navigation pane. 298 | 299 | 300 | 301 | ### Version 0.9.6 beta (2013-11-10) 302 | 303 | #### Changes 304 | 305 | - Removed LTO optimization and added 64-bit architecture from iOS static library project. 306 | 307 | 308 | 309 | 310 | ## Version 0.10.0 beta (2013-11-11) 311 | 312 | 313 | #### Added `RXPromise+RXExtension` module 314 | 315 | 316 | The library no longer will be comprised by a single file. 317 | 318 | Now, the core functionality of a RXPromise remains in module `RXPromise.m` and the corresponding header file. All additional "extensions" like the class methods `+all:`, `+any:`, `+sequence:task:` and `+repeat:` have been moved into a Category "RXExtension" and into a new file: `RXPromise+RXExtension.m` along with a corresponding header file. 319 | 320 | Using the extension methods requires to import the header file `RXPromise+RXExtension.h`. 321 | 322 | 323 | 324 | #### New APIs 325 | 326 | ##### Added a class method `repeat:`: 327 | 328 | typedef RXPromise* (^rxp_nullary_task)(); 329 | 330 | + (instancetype) repeat: (rxp_nullary_task)block; 331 | 332 | 333 | This class method asynchronously executes the block in a loop until either the 334 | tasks returns `nil` signaling the end of the repeat loop, or the returned promise 335 | will be rejected. 336 | 337 | The API is available in the RXExtension category. 338 | 339 | 340 | ##### Example 341 | 342 | NSArray* urls = ...; 343 | const NSUInteger count = [urls count]; 344 | __block NSUInteger i = 0; 345 | [RXPromise repeat:^RXPromise *{ 346 | if (i >= count) { 347 | return nil; 348 | } 349 | return [self fetchImageWithURL:urls[i]].then(^id(id image){ 350 | [self cacheImage:image withURL:[urls[i]]]; 351 | ++i; 352 | return nil; 353 | }, nil); 354 | }]; 355 | 356 | 357 | 358 | 359 | #### Changes 360 | 361 | - Improved INSTALL documentation. 362 | 363 | 364 | 365 | 366 | ### Version 0.10.1 beta (2013-11-21) 367 | 368 | #### Bug Fixes 369 | 370 | Fixed import directives. This caused linker issues when including the headers and sources directly into a project. 371 | 372 | 373 | 374 | ### Version 0.10.2 beta (2013-11-28) 375 | 376 | #### Changes 377 | 378 | 379 | When the `thenOn` property is used to specify a *concurrent* dispatch queue where the handler will be executed, `RXPromise` will dispatch the handler blocks using a barrier as shown below: 380 | 381 | ```objective-c 382 | if ([result isKindOfClass:[NSError class]) { 383 | dispatch_barrier_async(queue, error_handler(result)); 384 | } 385 | else { 386 | dispatch_barrier_async(queue, success_handler(result)); 387 | } 388 | ``` 389 | 390 | When using `dispatch_barrier_async` handlers will use the queue exclusively which makes concurrent write and read access from within handlers to shared resources thread-safe. 391 | 392 | 393 | When registering handlers with the `then` property, one should not make any assumptions about the execution context. Currently, the handlers will be executed on a private concurrent queue using `dispatch_async`. Thus, when accessing shared resources from within handlers registered with `then`, thread safety is not guaranteed. 394 | 395 | 396 | 397 | ### Version 0.10.3 beta (2013-11-28) 398 | 399 | Fixed typos in README. 400 | 401 | 402 | 403 | ### Version 0.10.4 beta (2014-02-28) 404 | 405 | ### Bug Fixes 406 | 407 | Fixed the implementation of the second designated initializer `initWithResult:` 408 | 409 | #### Changes 410 | 411 | 412 | Added Sample6 which demonstrates how an asynchronous task can be cancelled when there are no more "observers" to the promise anymore. 413 | 414 | Added Sample7 showing how to use class method `repeat`. 415 | 416 | 417 | 418 | ### Version 0.10.5 beta (2014-03-03) 419 | 420 | #### Supporting CocoaPods 421 | 422 | Client Xcode projects can install the RXPromise library utilizing CocoaPods. 423 | 424 | 425 | 426 | ## Version 0.11.0 beta (2014-03-11) 427 | 428 | #### Bug Fixes 429 | 430 | - Fixed a glaring bug in the class methods `all` and `any` which may have caused 431 | crashes. 432 | 433 | ### Breaking API Changes 434 | 435 | - The behavior of the class methods `all:` and `any` has been changed. 436 | 437 | Now, the methods don't cancel any other promise in the given array if any has 438 | been resolved or if the returned promise has been cancelled. 439 | 440 | This is more consistent with the rule that a promise if cancelled shall not forward the cancellation to its parents. The promises in the given array can be viewed as the "parents" of the returned promise. Now, canceling the returned promise won't touch the promises in the given array. 441 | 442 | Furthermore, not forwarding the cancel message or canceling all other promises if one has been resolved enables to use promises within the array which are part of any other promise tree, without affecting this other tree. 443 | 444 | Now, it is suggested to take any required action in the *handlers* of the returned promise. 445 | 446 | For example, cancel all other promises when one has been resolved: 447 | 448 | NSArray* promises = @[async(@"a"), @async(@"b")]; 449 | [RXPromise all:promises] 450 | .then(^id(id result){ 451 | for (RXPromise* p in promises) { 452 | [p cancel]; 453 | } 454 | return nil; 455 | }, ^id(NSError*error){ 456 | for (RXPromise* p in promises) { 457 | [p cancelWithReason:error]; 458 | } 459 | return error; 460 | }); 461 | 462 | 463 | 464 | 465 | #### API Additions 466 | 467 | - Added an instance method `makeBackgroundTaskWithName`. 468 | 469 | /** 470 | Executes the asynchronous task associated to the receiver as an 471 | iOS Background Task. 472 | 473 | @discussion The receiver requests background execution time from 474 | the system which delays suspension of the app up until the receiver 475 | will be resolved or cancelled. 476 | 477 | Since Apps are given only a limited amount of time to finish 478 | background tasks, this time may expire before the task finishes. 479 | In this case the receiver's root will be cancelled which in turn 480 | propagates the cancel event to all children of the receiver, 481 | including the receiver. 482 | 483 | Tasks may want to handle the cancellation in order to execute 484 | additional code which orderly closes the task. This should not take 485 | too long, since by the time the cancel handler is called, the app is 486 | already very close to its time limit. 487 | 488 | @warning Handlers registered on child promises may not be executed 489 | when the app is in background. 490 | 491 | @param taskName The name to display in the debugger when viewing the 492 | background task. 493 | 494 | If you specify \c nil for this parameter, this method generates a 495 | name based on the name of the calling function or method. 496 | */ 497 | - (void) makeBackgroundTaskWithName:(NSString*)taskName; 498 | 499 | 500 | 501 | #### Unit Tests 502 | 503 | - Fixed issues with unit tests having promises whose handlers may still execute 504 | after the test finished and which modified the stack. This caused a crash in the subsequent test. 505 | 506 | 507 | #### Implementation 508 | 509 | - Method `registerWithQueue:onSuccess:onFailure:returnPromise:` has been rewritten. 510 | Observable behavior is still the same, though. 511 | 512 | - Changed code and added attributes to function/method declarations which now 513 | should help the compiler during ARC optimization to avoid putting objects into the 514 | autorelease pool. 515 | 516 | There is still one occurrence where the ARC optimizer cannot prevent this: when an 517 | object is created and returned in handlers, these objects will be put into the 518 | autorelease pool. This does not happen when the source code is directly included in 519 | the client project. 520 | 521 | 522 | 523 | 524 | ### Version 0.11.1 beta (2014-03-19) 525 | 526 | - Simplified implementation of class method `all` and `any`. 527 | 528 | - Improved setup for iOS Unit Tests. 529 | 530 | 531 | 532 | ### Version 0.11.2 beta (2014-04-01) 533 | 534 | - Fixed a bug in method `runLoopWait`. 535 | 536 | - Minimum Deployment target for Mac OS X is now 10.8. 537 | 538 | 539 | 540 | ### Version 0.11.3 beta (2014-04-05) 541 | 542 | - Fixed a bug where the cancel reason for promises returned by methods `repeat` and `sequence` has not been forwarded to the current task. 543 | 544 | - Added Unit Tests to confirm that cancel reasons get forwarded correctly. 545 | 546 | 547 | 548 | 549 | ## Version 0.12.0 beta (2014-04-09) 550 | 551 | ### API Changes 552 | 553 | - Additional Execution Contexts 554 | 555 | Now, the following kind of execution contects can be specified with the `thenOn` property: 556 | 557 | - dispatch_queue 558 | - NSOperationQueue 559 | - NSThread 560 | - NSManagedObjectContext 561 | 562 | For example: 563 | 564 | ```Objective-C 565 | NSOperationQueue* operationQueue = [[NSOperationQueue alloc] init]; 566 | 567 | promise.thenOn(operationQueue, ^id(id result){ 568 | // executing on the NSOperationQueue 569 | ... 570 | return nil; 571 | }, nil); 572 | ``` 573 | - Added a property `thenOnMain` for convenience which is functional equivalent to 574 | `thenOn(dispatch_get_main_queue(), .., ...)` 575 | 576 | 577 | ### Bug Fixes 578 | 579 | - Minimum iOS deployment target (since 0.11.0) MUST be iOS 6.0 580 | 581 | - Fixed a Unit Test 582 | 583 | ### Documentation 584 | 585 | - The documentation in the README.md file has been revised. 586 | 587 | ### Miscellaneous 588 | 589 | - Minor changes in project structure and naming of projects and targets. 590 | 591 | 592 | 593 | ## Version 0.12.1 beta (2014-05-17) 594 | 595 | ### Enhancements 596 | 597 | - Class extension method `all:` now stores `nil` results as a `NSNull` object into the result array. 598 | 599 | 600 | 601 | ## Version 0.13.0 beta (2014-05-17) 602 | 603 | 604 | ### Enhancements 605 | 606 | - Merged Pull request. 607 | 608 | - Added extension class method `allSettled:`. 609 | 610 | 611 | 612 | ## Version 0.13.1 beta (2014-05-18) 613 | 614 | ### Changes 615 | 616 | - Class method `all:` and `allSettled:` now fulfill the returned promise with an empty `NSArray` if no promise are given, instead of rejecting the promise. 617 | 618 | 619 | ## Version 1.0.0 (2016-04-21) 620 | 621 | ### Minimum Deployment Versions 622 | 623 | - MacOS X: 10.9 624 | - iOS: 8.0 625 | 626 | Note: RXPromise can still be build for iOS 7 if a static library target will be added to the project. 627 | 628 | 629 | ## Version 1.0.2 (2016-04-21) 630 | 631 | - Fixed missing public headers in Podspec. 632 | 633 | 634 | ## Version 1.0.3 (2016-06-14) 635 | 636 | - Added "Master Header File". 637 | 638 | Now, clients only need to import the master header file `RXPromise.h` in the source files. All other public headers, like `RXSettledResult.h` and `RXPromise+RXExtension.h`, will be included through this master file. 639 | 640 | 641 | ## Version 1.0.4 (2016-09-23) 642 | 643 | - Update project settings for Xcode 8 644 | 645 | 646 | ## Version 1.0.5 (2016-09-23) 647 | 648 | - Added missing targets tvOS and watchOS in podspec. 649 | 650 | 651 | 652 | -------------------------------------------------------------------------------- /Common/RXTimer.h: -------------------------------------------------------------------------------- 1 | // 2 | // RXTimer.h 3 | // 4 | // Copyright 2013 Andreas Grosam 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | #import 19 | 20 | @class RXTimer; 21 | typedef void (^RXTimerHandler)(RXTimer* timer); 22 | 23 | @interface RXTimer : NSObject 24 | 25 | 26 | /** 27 | Initalizes a cancelable, one-shot timer in suspended state. 28 | 29 | @discussion Setting a tolerance for a timer allows it to fire later than the scheduled fire 30 | date, improving the ability of the system to optimize for increased power savings 31 | and responsiveness. The timer may fire at any time between its scheduled fire date 32 | and the scheduled fire date plus the tolerance. The timer will not fire before the 33 | scheduled fire date. The default value is zero, which means no additional tolerance 34 | is applied. 35 | 36 | As the user of the timer, you will have the best idea of what an appropriate tolerance 37 | for a timer may be. A general rule of thumb, though, is to set the tolerance to at 38 | least 10% of the interval, for a repeating timer. Even a small amount of tolerance 39 | will have a significant positive impact on the power usage of your application. 40 | The system may put a maximum value of the tolerance. 41 | 42 | 43 | @param: delay The delay in seconds after the timer will fire 44 | 45 | @param queue The queue on which to submit the block. 46 | 47 | @param block The block to submit. This parameter cannot be NULL. 48 | 49 | @param tolearance A tolerance in seconds the fire data can deviate. Must be 50 | positive. 51 | 52 | @return An initialized \p RXTimer object. 53 | 54 | 55 | */ 56 | 57 | - (id)initWithTimeIntervalSinceNow:(NSTimeInterval)delay 58 | tolorance:(double)tolerance 59 | queue:(dispatch_queue_t)queue 60 | block:(RXTimerHandler)block; 61 | 62 | 63 | /** 64 | Starts the timer. 65 | 66 | The timer fires once after the specified delay plus the specified tolerance. 67 | */ 68 | - (void) start; 69 | 70 | /** 71 | Cancels the timer. 72 | 73 | The timer becomes invalid and its block will not be executed. 74 | */ 75 | - (void)cancel; 76 | 77 | /** 78 | Returns YES if the timer has not yet been fired and it is not cancelled. 79 | */ 80 | - (BOOL)isValid; 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /Common/RXTimer.m: -------------------------------------------------------------------------------- 1 | // 2 | // RXTimer.m 3 | // 4 | // Copyright 2013 Andreas Grosam 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | #import "RXTimer.h" 19 | 20 | 21 | @interface RXTimer () 22 | @end 23 | 24 | @implementation RXTimer { 25 | dispatch_source_t _timer; 26 | uint64_t _interval; 27 | uint64_t _leeway; 28 | } 29 | 30 | 31 | - (id) initWithTimeIntervalSinceNow:(NSTimeInterval)delay 32 | tolorance:(double)tolerance 33 | queue:(dispatch_queue_t)queue 34 | block:(RXTimerHandler)block; 35 | { 36 | self = [super init]; 37 | if (self) { 38 | _interval = delay * NSEC_PER_SEC; 39 | _leeway = tolerance * NSEC_PER_SEC; 40 | _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); 41 | 42 | dispatch_source_set_event_handler(_timer, ^{ 43 | dispatch_source_cancel(_timer); // one shot timer 44 | if (block) { 45 | block(self); 46 | } 47 | }); 48 | } 49 | return self; 50 | } 51 | 52 | - (void) dealloc { 53 | dispatch_source_cancel(_timer); 54 | //dispatch_release(_timer); 55 | } 56 | 57 | 58 | 59 | // Invoking this method has no effect if the timer source has already been canceled. 60 | - (void) start { 61 | assert(_timer); 62 | dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, _interval), 63 | DISPATCH_TIME_FOREVER /*one shot*/, _leeway); 64 | dispatch_resume(_timer); 65 | } 66 | 67 | - (void) cancel { 68 | dispatch_source_cancel(_timer); 69 | } 70 | 71 | - (BOOL) isValid { 72 | return _timer && 0 == dispatch_source_testcancel(_timer); 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | 4 | Important note beforehand: 5 | 6 | RXPromise is a *pure* Objective-C API. Even though it depends itself on the standard C++ library it does not affect (or "infect") *your* Objective-C sources in any way with C++. 7 | 8 | 9 | The minimum deployment version for iOS is 8.0 and for Mac OS X it is 10.9. Since version 1.0.0 the project does not contain any static library targets anymore - but this can be added when desired. 10 | 11 | There are two recommended ways to incorporate the library into your project: 12 | 13 | 1. Using CocoaPods 14 | 2. Using Carthage 15 | 16 | **Note:** 17 | > `RXPromise` version number system adheres to the rules of [Semantic Versioning](http://semver.org). 18 | 19 | 20 | #### Using CocoaPods 21 | 22 | 23 | For reference, see [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html). 24 | 25 | As a minimum, add the following line to your [Podfile](http://guides.cocoapods.org/using/the-podfile.html) in your project folder: 26 | 27 | ```ruby 28 | pod 'RXPromise' 29 | ``` 30 | 31 | The above declaration loads the _most recent_ published version from Cocoapods. 32 | 33 | You may specify a certain version or a certain _range_ of available versions. For example: 34 | ```ruby 35 | pod 'RXPromise', '~> 1.0' 36 | ``` 37 | 38 | This automatically selects the most recent version in the repository in the range from 1.0.0 and up to 2.0, not including 2.0 and higher. 39 | 40 | See more help here: [Specifying pod versions](http://guides.cocoapods.org/using/the-podfile.html#specifying-pod-versions). 41 | 42 | 43 | Example Podfile: 44 | 45 | ```ruby 46 | # MyProject.Podfile 47 | 48 | use_frameworks! 49 | 50 | target 'MyTarget' do 51 | pod 'RXPromise', '~> 1.0' # Version 1.0 and the versions up to 2.0, not including 2.0 and higher 52 | end 53 | ``` 54 | 55 | After you edited the Podfile, open Terminal, cd to the directory where the Podfile is located and type the following command in the console: 56 | 57 | ```console 58 | $ pod install 59 | ``` 60 | 61 | ### Carthage 62 | 63 | 64 | > **Note:** Carthage only supports dynamic frameworks which are supported in Mac OS X and iOS 8 and later. 65 | For further reference see [Carthage](https://github.com/Carthage/Carthage). 66 | 67 | 68 | 1. Follow the instructions [Installing Carthage](https://github.com/Carthage/Carthage) to install Carthage on your system. 69 | 2. Follow the instructions [Adding frameworks to an application](https://github.com/Carthage/Carthage). Then add 70 | `github "couchdeveloper/RXPromise"` 71 | to your Cartfile. 72 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | If not otherwise noted, the content in this package is licensed 2 | under the following license: 3 | 4 | Copyright 2012 Andreas Grosam 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | -------------------------------------------------------------------------------- /RXPromise.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "RXPromise" 3 | s.version = "1.0.5" 4 | s.summary = "A thread safe implementation of the Promises/A+ specification in Objective-C with extensions." 5 | s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE.md'} 6 | s.authors = { "Andreas Grosam" => "agrosam@onlinehome.de" } 7 | s.homepage = "https://github.com/couchdeveloper" 8 | s.source = { :git => "https://github.com/couchdeveloper/RXPromise.git", :tag => s.version.to_s } 9 | 10 | s.ios.deployment_target = '8.0' 11 | s.osx.deployment_target = '10.9' 12 | s.tvos.deployment_target = '9.0' 13 | s.watchos.deployment_target = '2.0' 14 | 15 | s.requires_arc = true 16 | 17 | s.source_files = "Source/**/*.{h,m,mm}" 18 | s.public_header_files = "Source/RXPromise.h", "Source/RXPromiseHeader.h", "Source/RXPromise+RXExtension.h", "Source/RXSettledResult.h" 19 | s.header_mappings_dir = "Source" 20 | s.libraries = 'c++' 21 | 22 | s.weak_framework = 'CoreData' 23 | 24 | s.compiler_flags = '-O3', '-std=c++11', '-stdlib=libc++', '-DNDEBUG', '-DDEBUG_LOG=1', '-DNS_BLOCK_ASSERTIONS', '-D__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES=0' 25 | 26 | s.xcconfig = { 'OTHER_LDFLAGS' => '-ObjC' } 27 | 28 | end 29 | -------------------------------------------------------------------------------- /RXPromise.xcodeproj/xcshareddata/xcschemes/RXPromise-MacOS-Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /RXPromise.xcodeproj/xcshareddata/xcschemes/RXPromise-MacOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /RXPromise.xcodeproj/xcshareddata/xcschemes/RXPromise-WatchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /RXPromise.xcodeproj/xcshareddata/xcschemes/RXPromise-iOS-Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /RXPromise.xcodeproj/xcshareddata/xcschemes/RXPromise-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /RXPromise.xcodeproj/xcshareddata/xcschemes/RXPromise-tvOS-Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /RXPromise.xcodeproj/xcshareddata/xcschemes/RXPromise-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /RXPromise.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 21 | 22 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /RXPromise.xcworkspace/xcshareddata/Promise.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | D70EC8F1-814B-4DA4-8E09-C78E2D0AF830 9 | IDESourceControlProjectName 10 | Promise 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | DB8D3437-73A3-4A31-884F-DEFFC8785D75 14 | https://github.com/couchdeveloper/RXPromise.git 15 | 16 | IDESourceControlProjectPath 17 | Promise.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | DB8D3437-73A3-4A31-884F-DEFFC8785D75 21 | .. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/couchdeveloper/RXPromise.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | DB8D3437-73A3-4A31-884F-DEFFC8785D75 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | DB8D3437-73A3-4A31-884F-DEFFC8785D75 36 | IDESourceControlWCCName 37 | RXPromise 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /RXPromise.xcworkspace/xcshareddata/RXPromise.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 5BBBEEC2-C9DC-4DBC-9056-6C188F5FE09C 9 | IDESourceControlProjectName 10 | RXPromise 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | C6D05B39-31EA-4BF9-B515-A8A875F0C3F3 14 | https://github.com/couchdeveloper/RXPromise.git 15 | 16 | IDESourceControlProjectPath 17 | RXPromise.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | C6D05B39-31EA-4BF9-B515-A8A875F0C3F3 21 | .. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/couchdeveloper/RXPromise.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | C6D05B39-31EA-4BF9-B515-A8A875F0C3F3 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | C6D05B39-31EA-4BF9-B515-A8A875F0C3F3 36 | IDESourceControlWCCName 37 | RXPromise 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | 2 | def git_has_uncommitted_changes 3 | return system("git diff-index --quiet --cached HEAD") == false 4 | end 5 | 6 | def git_has_dirty_workspace 7 | return system("git diff-files --quiet") == false 8 | end 9 | 10 | def git_has_untracked_files 11 | return `git ls-files --exclude-standard --others`.strip.length > 0 12 | end 13 | 14 | 15 | def git_is_status_clean 16 | return `git status --porcelain`.strip.empty? 17 | end 18 | 19 | def git_version 20 | return `git describe --long`.strip 21 | end 22 | 23 | 24 | $workspace = "RXPromise.xcworkspace" 25 | $allSchemes = ['RXPromise-MacOS', 'RXPromise-iOS', 'RXPromise-tvOS', 'RXPromise-WatchOS'] 26 | $testSchemes = ['RXPromise-MacOS', 'RXPromise-iOS', 'RXPromise-tvOS'] 27 | 28 | 29 | desc "Build all targets defined within the given schemes #{$allSchemes}" 30 | task :build do 31 | $allSchemes.each { |scheme| 32 | sh "xctool -workspace #{$workspace} -scheme #{scheme} build" 33 | } 34 | end 35 | 36 | desc "Clean all targets defined within the given schemes #{$allSchemes}" 37 | task :clean do 38 | $allSchemes.each { |scheme| 39 | sh "xctool -workspace #{$workspace} -scheme #{scheme} clean" 40 | } 41 | end 42 | 43 | 44 | 45 | desc "Run all tests defined within the given schemes" 46 | task :test => [:build] do 47 | sh "xcrun xcodebuild test -workspace RXPromise.xcworkspace -scheme RXPromise-MacOS -destination 'arch=x86_64'| xcpretty" 48 | sh "xcrun xcodebuild test -workspace RXPromise.xcworkspace -scheme RXPromise-iOS -destination 'platform=iOS Simulator,name=iPhone 6' test | xcpretty" 49 | sh "xcrun xcodebuild test -workspace RXPromise.xcworkspace -scheme RXPromise-tvOS -destination 'platform=tvOS Simulator,name=Apple TV 1080p' test | xcpretty" 50 | end 51 | 52 | 53 | 54 | 55 | namespace :version do 56 | 57 | desc "Print a description of the current version" 58 | task :describe do 59 | puts "git HEAD: #{`git describe --dirty`}" 60 | puts "Marketing version: #{`agvtool what-marketing-version -terse1`}" 61 | puts "Build number: #{`agvtool what-version -terse`}" 62 | end 63 | 64 | desc "Udates BUNDLE_SHORT_VERSION with the specified version, then commits and pushes to origin" 65 | task :release do 66 | currentBranch = `git symbolic-ref --short HEAD`.strip 67 | if currentBranch != 'master' 68 | abort "Error: You must be on the master branch in order to define a new release!" 69 | end 70 | if !git_is_status_clean 71 | abort "Error: There are uncommitted changes. Please run tests and commit changes before creating a new release!" 72 | end 73 | 74 | puts "Current git description: #{`git describe --dirty`}" 75 | puts "Current Marketing version: #{`agvtool what-marketing-version -terse1`}" 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /Samples/Sample1/Sample1-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Sample1' target in the 'Sample1' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /Samples/Sample1/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Sample1 4 | // 5 | 6 | #import 7 | #import 8 | 9 | /* 10 | Objective: 11 | 12 | Invoke an asynchronous function which returns an array of items. 13 | Then, asynchronously process each item in parallel and return an 14 | array containing the processed items. 15 | 16 | */ 17 | 18 | 19 | 20 | static RXPromise* getNames() { 21 | RXPromise* promise = [RXPromise new]; 22 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 23 | NSArray* result = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"]; 24 | [promise fulfillWithValue:result]; 25 | }); 26 | return promise; 27 | } 28 | 29 | static RXPromise* processName(NSString* name) { 30 | RXPromise* promise = [RXPromise new]; 31 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 32 | NSString* capitalizedName = [name capitalizedString]; 33 | [promise fulfillWithValue:capitalizedName]; 34 | }); 35 | return promise; 36 | } 37 | 38 | 39 | static RXPromise* getNamesAndProcessNames() { 40 | RXPromise* promise = [RXPromise new]; 41 | getNames().then(^id(id array){ 42 | NSMutableArray* processedNames = [[NSMutableArray alloc] init]; 43 | __block NSUInteger count = [array count]; 44 | for (NSString* name in array) { 45 | processName(name).then(^id(id processedName) { 46 | [processedNames addObject:processedName]; 47 | --count; 48 | if (count == 0) { 49 | [promise fulfillWithValue:processedNames]; 50 | } 51 | return nil; 52 | }, ^id(NSError* error) { 53 | [promise rejectWithReason:error]; 54 | return nil; 55 | }); 56 | } 57 | return nil; 58 | }, ^id(NSError* error){ 59 | [promise rejectWithReason:error]; 60 | return nil; 61 | }); 62 | return promise; 63 | } 64 | 65 | 66 | 67 | int main(int argc, const char * argv[]) 68 | { 69 | @autoreleasepool { 70 | RXPromise* result = getNamesAndProcessNames(); 71 | [result.thenOnMain(^id(NSArray* processedNames) { 72 | NSLog(@"processedNames: %@", processedNames); 73 | return @"OK"; 74 | }, ^id(NSError* error) { 75 | NSLog(@"ERROR: %@", error); 76 | return error; 77 | }) runLoopWait]; 78 | NSLog(@"result promise: %@", result); 79 | } 80 | 81 | return 0; 82 | } 83 | 84 | -------------------------------------------------------------------------------- /Samples/Sample2/Sample2-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Sample2' target in the 'Sample2' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /Samples/Sample2/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Sample2 4 | // 5 | 6 | #import 7 | 8 | #import 9 | #import 10 | #import 11 | 12 | /* 13 | Objective: 14 | 15 | Asynchronously process an array of items - one after the other. 16 | Return an array of the transformed items. If an error occured during processing 17 | the items stop iterating and return the error of the failing item. 18 | 19 | Demonstrate how to cancel the sequential asynchronous tasks and how failures 20 | are handled. 21 | 22 | Demonstrate a "traditional" implemention utilizing dispatch lib and blocks where 23 | error handling may become elaborated and where cancellation is very likely 24 | impossible to implement. 25 | 26 | 27 | see also: RXPromise' class method `sequence:task:` 28 | 29 | */ 30 | 31 | 32 | /******************************************************************************* 33 | 1. Utilizing dispatch and blocks 34 | *******************************************************************************/ 35 | 36 | typedef void (^d_completion_t)(id result); 37 | typedef void (^d_unary_async_t)(id input, d_completion_t completion); 38 | 39 | static void d_transformEach(NSArray* inArray, d_unary_async_t task, d_completion_t completion); 40 | 41 | static void d_do_each(NSEnumerator* iter, d_unary_async_t task, NSMutableArray* outArray, d_completion_t completion) 42 | { 43 | id obj = [iter nextObject]; 44 | if (obj == nil) { 45 | if (completion) 46 | completion([outArray copy]); 47 | return; 48 | } 49 | task(obj, ^(id result){ 50 | [outArray addObject:result]; 51 | d_do_each(iter, task, outArray, completion); 52 | }); 53 | } 54 | 55 | static void d_transformEach(NSArray* inArray, d_unary_async_t task, d_completion_t completion) { 56 | NSMutableArray* outArray = [[NSMutableArray alloc] initWithCapacity:[inArray count]]; 57 | NSEnumerator* iter = [inArray objectEnumerator]; 58 | d_do_each(iter, task, outArray, completion); 59 | } 60 | 61 | d_unary_async_t d_capitalize = ^(id input, d_completion_t completion) { 62 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 63 | sleep(1); 64 | if ([input respondsToSelector:@selector(capitalizedString)]) { 65 | NSLog(@"processing: %@", input); 66 | NSString* result = [input capitalizedString]; 67 | if (completion) 68 | completion(result); 69 | } 70 | }); 71 | }; 72 | 73 | 74 | /******************************************************************************* 75 | 2. Utilizing RXPromise 76 | 77 | Note: this simplified aproach does not forward the cancel signal sent to the 78 | returned promise to the _current active task_. A cancellation only stops the 79 | iteration. 80 | ******************************************************************************/ 81 | 82 | @interface RXPromiseWrapper : NSObject 83 | @property (nonatomic) RXPromise* promise; 84 | @end 85 | @implementation RXPromiseWrapper 86 | @end 87 | 88 | 89 | typedef RXPromise* (^unary_async_t)(id object); 90 | 91 | static void do_transformSequence(NSEnumerator* iter, RXPromise* returnedPromise, 92 | unary_async_t task, NSMutableArray* outArray, 93 | RXPromiseWrapper* currentTaskPromise, 94 | dispatch_queue_t sync_queue) 95 | { 96 | if (returnedPromise.isCancelled) { 97 | return; 98 | } 99 | id obj = [iter nextObject]; 100 | if (obj == nil) { 101 | [returnedPromise fulfillWithValue:outArray]; 102 | return; 103 | } 104 | currentTaskPromise.promise = task(obj); 105 | currentTaskPromise.promise.thenOn(sync_queue, ^id(id result){ 106 | [outArray addObject:result]; 107 | do_transformSequence(iter, returnedPromise, task, outArray, currentTaskPromise, sync_queue); 108 | return nil; // result not used 109 | }, ^id(NSError*error){ 110 | [returnedPromise rejectWithReason:error]; 111 | return nil; // result not used 112 | }); 113 | } 114 | 115 | static RXPromise* transformSequence(NSArray* inArray, unary_async_t task) { 116 | NSMutableArray* outArray = [[NSMutableArray alloc] initWithCapacity:[inArray count]]; 117 | RXPromise* returnedPromise = [RXPromise new]; 118 | RXPromiseWrapper* currentTaskPromise = [[RXPromiseWrapper alloc] init]; 119 | NSEnumerator* iter = [inArray objectEnumerator]; 120 | dispatch_queue_t sync_queue = dispatch_queue_create("sync_queue", 0); 121 | 122 | dispatch_sync(sync_queue, ^{ 123 | do_transformSequence(iter, returnedPromise, task, outArray, currentTaskPromise, sync_queue); 124 | 125 | // Register an error handler which cancels the current task's root: 126 | returnedPromise.thenOn(sync_queue, nil, ^id(NSError*error){ 127 | NSLog(@"cancelling current task promise's root: %@", currentTaskPromise.promise.root); 128 | [currentTaskPromise.promise.root cancelWithReason:error]; 129 | return error; 130 | }); 131 | }); 132 | 133 | return returnedPromise; 134 | } 135 | 136 | unary_async_t capitalize = ^RXPromise*(id object) { 137 | RXPromise* promise = [RXPromise new]; 138 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 139 | NSLog(@"processing: %@", object); 140 | int count = 1000; 141 | while (count-- && promise.isPending) { 142 | usleep(1000); 143 | } 144 | if (!promise.isPending) { 145 | NSLog(@"task cancelled"); 146 | return; 147 | } 148 | if ([object respondsToSelector:@selector(capitalizedString)]) { 149 | NSString* s = [object capitalizedString]; 150 | NSLog(@"finished processing with result: %@", s); 151 | [promise fulfillWithValue:s]; 152 | } 153 | else { 154 | [promise rejectWithReason:[NSString stringWithFormat:@"Object [%@] does not respond to capitalize", [object class]]]; 155 | } 156 | }); 157 | return promise; 158 | }; 159 | 160 | 161 | int main(int argc, const char * argv[]) 162 | { 163 | @autoreleasepool { 164 | 165 | NSLog(@"\n=== Started with traditional dispatch and completion blocks ==="); 166 | dispatch_semaphore_t sem = dispatch_semaphore_create(0); 167 | d_transformEach(@[@"a", @"b", @"c"], d_capitalize, ^(id result){ 168 | NSLog(@"Result: %@", result); 169 | dispatch_semaphore_signal(sem); 170 | }); 171 | dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); 172 | NSLog(@"Finished with completion blocks"); 173 | 174 | NSLog(@"\n=== Started with RXPromise ==="); 175 | NSLog(@"Result: %@", [transformSequence(@[@"a", @"b", @"c"], capitalize) get]); 176 | NSLog(@"Finished with RXPromise"); 177 | 178 | 179 | NSLog(@"\n=== Started with RXPromise and input array resulting in failure ==="); 180 | NSLog(@"Result: %@", [transformSequence(@[@"a", @"b", [NSNull null]], capitalize) get]); 181 | NSLog(@"Finished with with RXPromise and input array resulting in failure"); 182 | 183 | 184 | NSLog(@"\n===Started with RXPromise and cancelleing after 2 seconds ==="); 185 | RXPromise* result = transformSequence(@[@"a", @"b", @"c", [NSObject new]], capitalize); 186 | sleep(2); 187 | NSLog(@"cancelling..."); 188 | [result cancel]; 189 | NSLog(@"Result: %@", [result get]); 190 | NSLog(@"Finished with RXPromise and cancelleing"); 191 | } 192 | return 0; 193 | } 194 | -------------------------------------------------------------------------------- /Samples/Sample3/Sample3-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Sample3' target in the 'Sample3' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /Samples/Sample3/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Sample3 4 | // 5 | 6 | #import 7 | #import 8 | 9 | 10 | /* 11 | 12 | Objective: Sequencially invoke N times an asynchronous function. 13 | 14 | See also: RXPromise' class method `sequence:task:` 15 | 16 | */ 17 | 18 | 19 | typedef RXPromise* (^task_block_t)(void); 20 | 21 | 22 | /** 23 | 24 | @brief Call the task _task_ N times asynchrounously in sequence. The function 25 | _times_ forms itself an asynchronous task. 26 | 27 | @discussion The result value of each task is ignored (one might define a 28 | "task-completion" block in order to achieve that). 29 | 30 | When the task _times_ will be cancelled, it cancelles the current active 31 | task and stops by rejecting its promise. 32 | 33 | @param n The number of times _task_ shall be invoked. 34 | 35 | @param task The task to be invoked. 36 | 37 | @return A promise. 38 | 39 | */ 40 | RXPromise* times(int n, task_block_t task); 41 | 42 | 43 | 44 | 45 | static RXPromise* do_times(int n, task_block_t task, RXPromise* returnedPromise) 46 | { 47 | promise_completionHandler_t onSuccess = ^id(id result) { 48 | do_times(n-1, task, returnedPromise); 49 | return nil; 50 | }; 51 | promise_errorHandler_t onError = ^id(NSError*error){ 52 | [returnedPromise rejectWithReason:error]; 53 | return nil; 54 | }; 55 | 56 | if (n == 0) { 57 | [returnedPromise fulfillWithValue:@"OK"]; 58 | return returnedPromise; 59 | } 60 | if (returnedPromise.isCancelled) { 61 | return returnedPromise; 62 | } 63 | #if 1 64 | task().then(onSuccess, onError); 65 | #else 66 | [task() registerWithQueue:dispatch_get_global_queue(0, 0) 67 | onSuccess:onSuccess 68 | onFailure:onError 69 | returnPromise:NO]; 70 | 71 | #endif 72 | return returnedPromise; 73 | } 74 | 75 | RXPromise* times(int n, task_block_t task) 76 | { 77 | RXPromise* returnedPromise = [RXPromise new]; 78 | do_times(n, task, returnedPromise); 79 | return returnedPromise; 80 | } 81 | 82 | // Some nasty workload: 83 | unsigned int fibonacci_recursive(unsigned int n) 84 | { 85 | if (n == 0) { 86 | return 0; 87 | } 88 | if (n == 1) { 89 | return 1; 90 | } 91 | return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2); 92 | } 93 | 94 | 95 | int main(int argc, const char * argv[]) 96 | { 97 | RXPromise* promise =[RXPromise new]; 98 | NSArray* promises = @[promise]; 99 | RXPromise* promiseAll = [RXPromise all: promises]; 100 | 101 | @autoreleasepool { 102 | NSLog(@"This sample is meant to be profiled. It may take a while to finish."); 103 | 104 | #if 1 105 | // The time it takes to finish one fibonacci_recursive(30) call is roughly 106 | // 15 ms. fibonacci_recursive is a CPU bound operation. 107 | // Profiling reveals, that in this setup the overhead due to promises is 108 | // merely 0.5% (99.5% time spent in function fibonacci_recursive). 109 | // Four threads are in use (main thread which is blocked and three 110 | // dispatch worker threads). 111 | NSLog(@"start\n"); 112 | [times(1000, ^RXPromise*(){ 113 | RXPromise* promise = [RXPromise new]; 114 | __block int result; 115 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 116 | result = fibonacci_recursive(30); 117 | [promise fulfillWithValue:[NSNumber numberWithInt:result]]; 118 | }); 119 | return promise; 120 | }) 121 | wait]; 122 | NSLog(@"finished\n"); 123 | #else 124 | // For comparision: 125 | // This code seems to benefit from a better utilization of CPU caches 126 | // - which makes the fibonacci code about 15% faster than above. 127 | // Only one thread is used. 128 | 129 | int r = 0; 130 | NSLog(@"start\n"); 131 | for (int i = 0; i < 1000; ++i) { 132 | int result = fibonacci_recursive(30); 133 | NSNumber* number = [NSNumber numberWithInt:result]; 134 | r += result; 135 | number = nil; 136 | } 137 | NSLog(@"finished %d\n", r); 138 | #endif 139 | } 140 | 141 | 142 | return 0; 143 | } 144 | 145 | -------------------------------------------------------------------------------- /Samples/Sample4/Sample4-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 | -------------------------------------------------------------------------------- /Samples/Sample4/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // NSArrayExtension 4 | // 5 | // Created by Andreas Grosam on 16.07.13. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef RXPromise* (^unary_async_t)(id object); 13 | 14 | static RXPromise* serial_for_each(NSArray* array, unary_async_t task); 15 | 16 | 17 | static RXPromise* do_serial_each(NSEnumerator* iter, RXPromise* promiseResult, unary_async_t task) 18 | { 19 | if (!promiseResult.isPending) { 20 | return promiseResult; 21 | } 22 | id obj = [iter nextObject]; 23 | if (obj == nil) { 24 | [promiseResult fulfillWithValue:nil]; 25 | return promiseResult; 26 | } 27 | RXPromise* p = task(obj).then(^id(id result){ 28 | /* result ignored */ 29 | return do_serial_each(iter, promiseResult, task); 30 | }, ^id(NSError*error){ 31 | [promiseResult rejectWithReason:error]; 32 | return nil; 33 | }); 34 | promiseResult.then(nil, ^id(NSError* error) { 35 | [p cancelWithReason:error]; 36 | return nil; 37 | }); 38 | 39 | return promiseResult; 40 | } 41 | 42 | static RXPromise* serial_for_each(NSArray* inArray, unary_async_t task) { 43 | RXPromise* promise = [RXPromise new]; 44 | NSEnumerator* iter = [inArray objectEnumerator]; 45 | return do_serial_each(iter, promise, task); 46 | } 47 | 48 | static RXPromise* concurrent_for_each(NSArray* inArray, unary_async_t task) { 49 | NSUInteger count = [inArray count]; 50 | NSMutableArray* promises = [[NSMutableArray alloc] initWithCapacity:count]; 51 | for (int i = 0; i < count; ++i) { 52 | [promises addObject:task([inArray objectAtIndex:i])]; 53 | } 54 | return [RXPromise all:promises]; 55 | } 56 | 57 | 58 | 59 | @interface NSArray (RXExtension) 60 | 61 | - (RXPromise*) rx_serialForEach:(unary_async_t)task; 62 | - (RXPromise*) rx_concurrentForEach:(unary_async_t)task; 63 | 64 | @end 65 | 66 | @implementation NSArray (RXExtension) 67 | 68 | - (RXPromise*) rx_serialForEach:(unary_async_t)task { 69 | return serial_for_each(self, task); 70 | } 71 | 72 | - (RXPromise*) rx_concurrentForEach:(unary_async_t)task { 73 | return concurrent_for_each(self, task); 74 | } 75 | 76 | @end 77 | 78 | 79 | static RXPromise* processName(NSString* name) { 80 | RXPromise* promise = [RXPromise new]; 81 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 82 | NSString* capitalizedName = [name capitalizedString]; 83 | int count = 1000; 84 | while (count--) { 85 | if ([promise isCancelled]) 86 | break; 87 | usleep(1000); 88 | } 89 | if (![promise isCancelled]) { 90 | if ([@"X" isEqualToString:capitalizedName]) { 91 | [promise rejectWithReason:@"X"]; 92 | } else { 93 | [promise fulfillWithValue:capitalizedName]; 94 | } 95 | } 96 | else { 97 | NSLog(@"cancelled"); 98 | } 99 | }); 100 | return promise; 101 | } 102 | 103 | 104 | 105 | int main(int argc, const char * argv[]) 106 | { 107 | @autoreleasepool { 108 | 109 | NSArray* a1 = @[@"a", @"b", @"c", @"d", @"e"]; 110 | NSLog(@"ForEach Serial"); 111 | 112 | [[a1 rx_serialForEach:^RXPromise *(id object) { 113 | RXPromise* taskPromise = processName(object); 114 | RXPromise* returnedPromise = taskPromise.then(^id(id result) { 115 | NSLog(@"%@", result); 116 | return result; 117 | }, nil); 118 | returnedPromise.then(nil, 119 | ^id(NSError* error) { 120 | [taskPromise cancel]; 121 | return error; 122 | }); 123 | return returnedPromise; 124 | }] 125 | .then(nil, ^id(NSError* error) { 126 | NSLog(@"ERROR: %@", error); 127 | return nil; 128 | }) wait]; 129 | 130 | 131 | RXPromise* all = [a1 rx_concurrentForEach:^RXPromise*(id object) { 132 | RXPromise* taskPromise = processName(object); 133 | RXPromise* returnedPromise = taskPromise.then(^id(id result) { 134 | NSLog(@"%@", result); 135 | return result; 136 | }, nil); 137 | returnedPromise.then(nil, 138 | ^id(NSError* error) { 139 | [taskPromise cancel]; 140 | return error; 141 | }); 142 | return returnedPromise; 143 | }]; 144 | 145 | all.then(nil, ^id(NSError* error) { 146 | NSLog(@"ERROR: %@", error); 147 | return nil; 148 | }); 149 | 150 | NSLog(@"ForEach Concurrent"); 151 | NSLog(@"Start"); 152 | [all wait]; 153 | NSLog(@"End"); 154 | 155 | } 156 | return 0; 157 | } 158 | 159 | -------------------------------------------------------------------------------- /Samples/Sample5/Sample5-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 | -------------------------------------------------------------------------------- /Samples/Sample5/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Sample5 4 | // 5 | // Objective: Chaining asynchronous tasks from an array of objects 6 | // 7 | // Given: 8 | // - an array of input values 9 | // - an asychronous function processing an input value 10 | // 11 | // also: Delayed Task with Cancellation 12 | 13 | 14 | #import 15 | #import 16 | 17 | #import 18 | #import "RXTimer.h" 19 | 20 | 21 | 22 | // As an example add a category for NSString to simulate an asynchronous task 23 | // (Actually, this is a delayed task with cancellation) 24 | @implementation NSString (Example) 25 | 26 | - (RXPromise*) asyncTask 27 | { 28 | RXPromise* promise = [[RXPromise alloc] init]; 29 | 30 | RXTimerHandler block = ^(RXTimer* timer) { 31 | id result = [self capitalizedString]; 32 | [promise fulfillWithValue:result]; 33 | }; 34 | RXTimer* timer = [[RXTimer alloc] initWithTimeIntervalSinceNow:0.5 35 | tolorance:0.1 36 | queue:dispatch_get_global_queue(0, 0) 37 | block:block]; 38 | promise.then(nil, ^id(NSError*error){ 39 | [timer cancel]; 40 | NSLog(@"Operation with receiver '%@' cancelled", self); 41 | return nil; 42 | }); 43 | 44 | [timer start]; 45 | //NSLog(@"Creating task promise: %@", promise); 46 | return promise; 47 | } 48 | 49 | @end 50 | 51 | 52 | 53 | int main(int argc, const char * argv[]) 54 | { 55 | @autoreleasepool { 56 | 57 | NSArray* inputs = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g"]; 58 | 59 | RXPromise* finished = [RXPromise sequence:inputs task:^RXPromise*(id input) { 60 | return [input asyncTask] 61 | .thenOn(dispatch_get_main_queue(), ^id(id result) { 62 | printf("%s", [[result description] UTF8String]); 63 | return nil; 64 | }, nil); 65 | }]; 66 | 67 | [finished runLoopWait]; 68 | printf("\n"); 69 | 70 | 71 | // Again with a timeout, cancelling the operations: 72 | 73 | finished = [RXPromise sequence:inputs task:^RXPromise*(id input) { 74 | return [input asyncTask] 75 | .thenOn(dispatch_get_main_queue(), ^id(id result) { 76 | printf("%s", [[result description] UTF8String]); 77 | return nil; 78 | }, nil); 79 | }]; 80 | 81 | 82 | // A timeout will cause to signal an error to the promise, which in turn causes 83 | // the sequence to cancel the current task's root promise: 84 | [finished setTimeout:1.25]; 85 | 86 | 87 | // finished.then(nil, ^id(NSError*error){ 88 | // [finished.parent cancel]; // the task promise is the finished's parent promise 89 | // return nil; 90 | // }); 91 | 92 | [finished runLoopWait]; 93 | sleep(1); 94 | printf("\n"); 95 | 96 | } 97 | return 0; 98 | } 99 | 100 | -------------------------------------------------------------------------------- /Samples/Sample6/Sample6-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 | -------------------------------------------------------------------------------- /Samples/Sample6/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Sample6 4 | // 5 | // Created by Andreas Grosam on 03.12.13. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | /** 13 | 14 | Objective: Cancel an asynchronous task when there are no more "subscribers". 15 | 16 | A "subscriber" is a named strong reference to a promise, or a handler which has 17 | been registered and which is not yet called. A "named" strong reference to a promise 18 | is an ivar, a captured promise in a block, or any named temporary variable, etc.. 19 | Handlers will get "unregistered" after they have been called when the promise 20 | has been resolved. 21 | 22 | What we want to achieve is that the task will be automaticalyl cancelled when 23 | there is no one waiting for the result, withou a call-site explicitly sending 24 | `cancel` to the promise which is associated to the task. 25 | 26 | */ 27 | 28 | 29 | /** 30 | 31 | Canonical Subclass of NSOperation 32 | 33 | A cancelable, asynchronous operation 34 | 35 | */ 36 | 37 | typedef void (^completion_block_t)(id result); 38 | 39 | @interface MyOperation : NSOperation 40 | 41 | // Designated Initializer 42 | // Parameter count equals the duration in 1/10 seconds until the task is fininshed. 43 | - (id)initWithCount:(int)count completion:(completion_block_t)completioHandler; 44 | 45 | @property (nonatomic, readonly) id result; 46 | @property (nonatomic, copy) completion_block_t completionHandler; 47 | @end 48 | 49 | @implementation MyOperation { 50 | BOOL _isExecuting; 51 | BOOL _isFinished; 52 | 53 | dispatch_queue_t _syncQueue; 54 | int _count; 55 | id _result; 56 | completion_block_t _completionHandler; 57 | id _self; // immortality 58 | } 59 | 60 | - (id)initWithCount:(int)count completion:(completion_block_t)completionHandler 61 | { 62 | self = [super init]; 63 | if (self) { 64 | _count = count; 65 | _syncQueue = dispatch_queue_create("op.sync_queue", NULL); 66 | _completionHandler = [completionHandler copy]; 67 | } 68 | return self; 69 | } 70 | 71 | - (id) result { 72 | __block id result; 73 | dispatch_sync(_syncQueue, ^{ 74 | result = _result; 75 | }); 76 | return result; 77 | } 78 | 79 | - (void) start 80 | { 81 | dispatch_async(_syncQueue, ^{ 82 | if (!self.isCancelled && !_isFinished && !_isExecuting) { 83 | self.isExecuting = YES; 84 | _self = self; // make self immortal for the duration of the task 85 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 86 | 87 | // Simulated work load: 88 | int count = _count; 89 | while (count > 0) { 90 | if (self.isCancelled) { 91 | break; 92 | } 93 | printf("."); 94 | usleep(100*1000); 95 | --count; 96 | } 97 | 98 | // Set result and terminate 99 | dispatch_async(_syncQueue, ^{ 100 | if (_result == nil && count == 0) { 101 | _result = @"OK"; 102 | } 103 | [self terminate]; 104 | }); 105 | }); 106 | } 107 | }); 108 | } 109 | 110 | - (void) terminate { 111 | self.isExecuting = NO; 112 | self.isFinished = YES; 113 | completion_block_t completionHandler = _completionHandler; 114 | _completionHandler = nil; 115 | id result = _result; 116 | _self = nil; 117 | if (completionHandler) { 118 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 119 | completionHandler(result); 120 | }); 121 | } 122 | } 123 | 124 | 125 | - (BOOL) isConcurrent { 126 | return YES; 127 | } 128 | 129 | - (BOOL) isExecuting { 130 | return _isExecuting; 131 | } 132 | - (void) setIsExecuting:(BOOL)isExecuting { 133 | if (_isExecuting != isExecuting) { 134 | [self willChangeValueForKey:@"isExecuting"]; 135 | _isExecuting = isExecuting; 136 | [self didChangeValueForKey:@"isExecuting"]; 137 | } 138 | } 139 | 140 | - (BOOL) isFinished { 141 | return _isFinished; 142 | } 143 | - (void) setIsFinished:(BOOL)isFinished { 144 | if (_isFinished != isFinished) { 145 | [self willChangeValueForKey:@"isFinished"]; 146 | _isFinished = isFinished; 147 | [self didChangeValueForKey:@"isFinished"]; 148 | } 149 | } 150 | 151 | - (void) cancel { 152 | dispatch_async(_syncQueue, ^{ 153 | if (_result == nil) { 154 | NSLog(@"Operation cancelled"); 155 | [super cancel]; 156 | _result = [[NSError alloc] initWithDomain:@"MyOperation" 157 | code:-1000 158 | userInfo:@{NSLocalizedDescriptionKey: @"cancelled"}]; 159 | } 160 | }); 161 | } 162 | 163 | @end 164 | 165 | 166 | 167 | int main(int argc, const char * argv[]) 168 | { 169 | 170 | @autoreleasepool { 171 | 172 | typedef RXPromise* (^task_t)(); 173 | 174 | task_t task = ^RXPromise*{ 175 | 176 | // This is the "tricky" part: 177 | 178 | // In order to acmomplish our objective we use a variant of the 179 | // promise where we can specify a handler when the promise gets 180 | // deallocated. However, we need painstakenly care about object 181 | // references: we need to use weak refernces, in order to avoid 182 | // keeping either the promise or the task for lifing longer than 183 | // neccessary: 184 | 185 | RXPromise* promise; 186 | MyOperation* op = [[MyOperation alloc] 187 | initWithCount:1000 188 | completion:nil]; 189 | 190 | // First off: the operation MUST NOT keep a strong reference to its 191 | // returned promise! 192 | // When the promise gets deallocated, we cancel the operation. 193 | // We need a weak referece of the operation in the dealloc handler. 194 | // Otherwise, the compeltion block would retain the op, and op would 195 | // live until after the promise get deallocated. However, we don't want 196 | // the op have any dependency to the promise's life. 197 | __weak MyOperation* weakOp = op; 198 | promise = [RXPromise promiseWithDeallocHandler:^{ 199 | MyOperation* strongOp = weakOp; 200 | [strongOp cancel]; 201 | }]; 202 | // We also require a weak referece of the promise where the operation 203 | // resolves the promise. Otherwise, the promise wouldn't be deallocated, 204 | // since the operation is itself a "subscriber". 205 | __weak RXPromise* weakPromise = promise; 206 | completion_block_t completionHandler = ^(id result) { 207 | if ([result isKindOfClass:[NSError class]]) { 208 | [weakPromise rejectWithReason:result]; 209 | } 210 | else { 211 | [weakPromise fulfillWithValue:result]; 212 | } 213 | }; 214 | op.completionHandler = completionHandler; 215 | [op start]; 216 | return promise; 217 | }; 218 | 219 | 220 | @autoreleasepool { 221 | __block RXPromise* promise; 222 | promise = task(); 223 | [promise setTimeout:1.0]; 224 | [promise.then(^id(id result) { 225 | NSLog(@"Result: %@", result); 226 | return nil; 227 | }, ^id(NSError* error) { 228 | NSLog(@"Error: %@", error); 229 | promise = nil; 230 | return nil; 231 | }) runLoopWait]; 232 | } 233 | sleep(1); 234 | } 235 | return 0; 236 | } 237 | 238 | -------------------------------------------------------------------------------- /Samples/Sample7/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Sample7 4 | // 5 | // Created by Andreas Grosam on 26.02.14. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | /** 14 | 15 | Objective: Demonstrate how to use `repeat`. 16 | 17 | */ 18 | 19 | 20 | // As an example add a category for NSString to simulate an asynchronous task 21 | @implementation NSString (Example) 22 | 23 | - (RXPromise*) asyncTask 24 | { 25 | RXPromise* promise = [[RXPromise alloc] init]; 26 | 27 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 28 | int count = 10; 29 | while (count--) { 30 | usleep(100*1000); 31 | } 32 | if ([self isEqualToString:@"X"]) { 33 | [promise rejectWithReason:@"Bad Input"]; 34 | } 35 | else { 36 | [promise fulfillWithValue:[self capitalizedString]]; 37 | } 38 | }); 39 | 40 | return promise; 41 | } 42 | 43 | @end 44 | 45 | 46 | // Contains a more or less "canonical" implementation of the "asynchronous loop" 47 | // with the class method `repeat`. 48 | RXPromise* performTasksWithArray(NSArray* inputs) 49 | { 50 | const NSUInteger count = [inputs count]; 51 | __block NSUInteger i = 0; 52 | return [RXPromise repeat:^RXPromise*{ 53 | if (i >= count) { 54 | return nil; 55 | } 56 | return [inputs[i++] asyncTask].then(^id(id result){ 57 | NSLog(@"%@", result); 58 | return nil; // intermediate result not used in repeat 59 | }, nil); 60 | }]; 61 | } 62 | 63 | 64 | int main(int argc, const char * argv[]) 65 | { 66 | @autoreleasepool { 67 | 68 | NSArray* inputs = @[@"a", @"b", @"c", @"X", @"e", @"f", @"g"]; 69 | 70 | [performTasksWithArray(inputs) 71 | .then(^id(id result){ 72 | NSLog(@"Finished: %@", result); 73 | return nil; 74 | }, ^id(NSError* error){ 75 | NSLog(@"Error occured: %@", error); 76 | return nil; 77 | }) runLoopWait]; 78 | 79 | } 80 | return 0; 81 | } 82 | 83 | -------------------------------------------------------------------------------- /Samples/Sample8/Sample8-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 | -------------------------------------------------------------------------------- /Samples/Sample8/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Sample8 4 | // 5 | // Created by Andreas Grosam on 06.03.14. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | #include 12 | 13 | 14 | /** 15 | 16 | Demonstrates: 17 | 18 | Returned objects with retain count +1 in handlers *may* be autoreleased. 19 | Hence, handlers which create objects and return it as the return value 20 | in a handler shall use an autorelease pool. 21 | 22 | */ 23 | 24 | @interface MyObject : NSObject 25 | @end 26 | @implementation MyObject 27 | - (id)init 28 | { 29 | self = [super init]; 30 | if (self) { 31 | ; 32 | } 33 | NSLog(@"+++ Object created: 0x%p", (__bridge void*)self); 34 | return self; 35 | } 36 | 37 | - (void) dealloc { 38 | NSLog(@"-- Object destroyed: 0x%p", (__bridge void*)self); 39 | } 40 | @end 41 | 42 | 43 | 44 | 45 | static RXPromise* async() { 46 | RXPromise* promise = [[RXPromise alloc] init]; 47 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 48 | usleep(1000); 49 | [promise fulfillWithValue:@"OK"]; 50 | }); 51 | return promise; 52 | } 53 | 54 | int test() 55 | { 56 | @autoreleasepool { 57 | dispatch_semaphore_t sem = dispatch_semaphore_create(0); 58 | 59 | async().then(^id(id result) { 60 | return [[MyObject alloc] init]; 61 | }, nil) 62 | .then(^id(id result) { 63 | return [[MyObject alloc] init]; 64 | }, nil) 65 | .then(^id(id result) { 66 | return [[MyObject alloc] init]; 67 | }, nil) 68 | .then(^id(id result) { 69 | return [[MyObject alloc] init]; 70 | }, nil) 71 | .then(^id(id result) { 72 | return [[MyObject alloc] init]; 73 | }, nil) 74 | .then(^id(id result) { 75 | return [[MyObject alloc] init]; 76 | }, nil) 77 | .then(^id(id result) { 78 | dispatch_semaphore_signal(sem); 79 | return nil; 80 | }, nil); 81 | 82 | dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); 83 | 84 | sleep(1); 85 | } 86 | 87 | return 0; 88 | } 89 | 90 | 91 | int main(int argc, const char * argv[]) 92 | { 93 | @autoreleasepool { 94 | NSLog(@"start"); 95 | for (int i = 0; i < 10; ++i) { 96 | test(); 97 | } 98 | NSLog(@"finished"); 99 | } 100 | return 0; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /Samples/Samples.xcodeproj/xcshareddata/xcschemes/Sample1.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Samples/Samples.xcodeproj/xcshareddata/xcschemes/Sample2.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Samples/Samples.xcodeproj/xcshareddata/xcschemes/Sample3.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Samples/Samples.xcodeproj/xcshareddata/xcschemes/Sample4.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Samples/Samples.xcodeproj/xcshareddata/xcschemes/Sample5.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Samples/Samples.xcodeproj/xcshareddata/xcschemes/Sample6.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Samples/Samples.xcodeproj/xcshareddata/xcschemes/Sample7.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Samples/Samples.xcodeproj/xcshareddata/xcschemes/Sample8.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Source/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.0.5 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/RXPromise+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // RXPromise+Private.h 3 | // 4 | // Copyright 2013 Andreas Grosam 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | #import 19 | #import "RXPromise.h" 20 | #import 21 | #include 22 | #include "utility/DLog.h" 23 | 24 | 25 | #if defined(__has_feature) && __has_feature(objc_arc) 26 | #define RX_ARC_ENABLED 1 27 | #else 28 | #error ARC must be enabled 29 | #endif 30 | 31 | 32 | #if !defined (OS_OBJECT_HAVE_OBJC_SUPPORT) 33 | #error missing include os/object.h 34 | #endif 35 | 36 | #if !OS_OBJECT_HAVE_OBJC_SUPPORT 37 | #error OS_OBJECT_HAVE_OBJC_SUPPORT must be enabled 38 | #endif 39 | 40 | 41 | 42 | 43 | /** RXPromise_State */ 44 | typedef enum RXPromise_StateT { 45 | Pending = 0x0, 46 | Fulfilled = 0x01, 47 | Rejected = 0x02, 48 | Cancelled = 0x06 49 | } RXPromise_State; 50 | 51 | 52 | struct RXPromise_StateAndResult { 53 | RXPromise_StateT state; 54 | id result; 55 | }; 56 | 57 | 58 | @class RXPromise; 59 | 60 | namespace rxpromise { 61 | 62 | static_assert(OS_OBJECT_HAVE_OBJC_SUPPORT == 1, ""); 63 | 64 | struct shared { 65 | typedef std::multimap assocs_t; 66 | 67 | dispatch_queue_t sync_queue; 68 | static constexpr char const* sync_queue_id = "RXPromise.shared_sync_queue"; 69 | 70 | dispatch_queue_t default_concurrent_queue; 71 | static constexpr char const* default_concurrent_queue_id = "RXPromise.default_concurrent_queue"; 72 | 73 | static constexpr char const* QueueID = "RXPromise.queue_id"; 74 | 75 | assocs_t assocs; 76 | 77 | shared() 78 | : sync_queue(dispatch_queue_create(sync_queue_id, NULL)), 79 | default_concurrent_queue(dispatch_queue_create(default_concurrent_queue_id, DISPATCH_QUEUE_CONCURRENT)) 80 | { 81 | assert(sync_queue); 82 | assert(default_concurrent_queue); 83 | dispatch_queue_set_specific(sync_queue, QueueID, (void*)(sync_queue_id), NULL); 84 | DLogInfo(@"created: sync_queue (0x%p), default_concurrent_queue (0y%p) ", (sync_queue), (default_concurrent_queue)); 85 | } 86 | 87 | ~shared() { 88 | DLogInfo(@"destroyed: sync_queue (0x%p), default_concurrent_queue (0y%p) ", (sync_queue), (default_concurrent_queue)); 89 | #if defined (DEBUG) 90 | // Note: at exit, the sync queue *may* still have enqueued blocks, 91 | // which insert/remove associations between a parent and its children 92 | // running on a secondary thread. At exit, this running thread will be 93 | // forced to terminate and the assocs container may not be clean. This 94 | // is considered harmless. 95 | if (assocs.size() != 0) { 96 | DLogInfo(@"Association container not empty"); 97 | } 98 | #endif 99 | } 100 | 101 | }; 102 | 103 | 104 | } 105 | 106 | extern rxpromise::shared Shared; 107 | 108 | 109 | @interface RXPromise (Private) 110 | - (RXPromise_StateAndResult) peakStateAndResult; 111 | - (RXPromise_StateAndResult) synced_peakStateAndResult; 112 | - (id) synced_peakResult; 113 | @end 114 | -------------------------------------------------------------------------------- /Source/RXPromise+RXExtension.h: -------------------------------------------------------------------------------- 1 | // 2 | // RXPromise+RXExtension.h 3 | // 4 | // Copyright 2013 Andreas Grosam 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | #import "RXPromise.h" 19 | 20 | /* Synopsis 21 | 22 | typedef RXPromise* (^rxp_unary_task)(id input); 23 | typedef RXPromise* (^rxp_nullary_task)(); 24 | 25 | 26 | @interface RXPromise (RXExtension) 27 | 28 | + (RXPromise*) all:(NSArray*)promises; 29 | + (RXPromise*) any:(NSArray*)promises; 30 | + (RXPromise*) sequence:(NSArray*)inputs task:(RXPromise* (^)(id input)) task; 31 | + (instancetype) repeat:(rxp_nullary_task)block; 32 | 33 | @end 34 | 35 | */ 36 | 37 | 38 | 39 | /** 40 | @brief Type definition for an asynchronous block taking one input parameter and 41 | returning a \c RXPromise. 42 | */ 43 | typedef RXPromise* (^rxp_unary_task)(id input); 44 | 45 | 46 | /** 47 | @brief Type definition for an asynchronous block taking no parameter and 48 | returning a \c RXPromise. 49 | */ 50 | typedef RXPromise* (^rxp_nullary_task)(); 51 | 52 | 53 | 54 | 55 | @interface RXPromise (RXExtension) 56 | 57 | /** 58 | @brief Returns a new \p RXPromise object. If \a all promises in the given array 59 | \p promises have been \a fulfilled, the returned promise will be fulfilled with 60 | an array containing the result of each promise. Otherwise, the returned promise 61 | will be rejected with the error reason of the first failing promise in the array. 62 | 63 | @discussion If the given array is \c nil or empty, the returned promise will be 64 | fulfilled with an empty \c NSArray. Otherwise, if all promises have been fulfilled, 65 | the returned promise will be fulfilled with an \c NSArray object containing the 66 | result value of each promise from the given array @p promises in the corresponding 67 | order. 68 | 69 | @par If the result of any promise equals `nil` a \c NSNull object will be stored 70 | into the result array instead. 71 | 72 | @par If the returned promise will be cancelled or if any promise in the array has 73 | been rejected or cancelled, all other promises in the array will remain unaffected. 74 | If it is desired to cancel promises if any promise within the array failed, it is 75 | suggested to do this in the error handler. 76 | 77 | @note If more than one promises will be rejected, the error reason of the subsequent 78 | promises will be ignored. It is suggested to register error handlers in order 79 | to track the errors if required. 80 | 81 | @param promises A @c NSArray containing promises. It may be empty or \c nil. 82 | 83 | @return A new promise whose value is a \c NSArray containing the result of each 84 | promise, or an empty \c NSArray. 85 | 86 | 87 | @par \b Example: @code 88 | [RXPromise all:@[ 89 | [self asyncA] 90 | .then(^id(id result){ 91 | return @"A"; 92 | }, ^id(NSError*error){ 93 | return error; 94 | }); 95 | , [self asyncB] 96 | .then(^id(id result){ 97 | return @"B"; 98 | }, ^id(NSError*error){ 99 | return error; 100 | }); 101 | ] 102 | .then(^id(id results){ 103 | // result equals @[@"A", @"B"] 104 | id result = [self asyncWithParamA:results[0] 105 | paramB:results[1]] 106 | assert(result != nil); 107 | return result; 108 | }, ^id(NSError*error){ 109 | for (RXPromise* p in promises) {[p.parent cancelWithReason:error];} 110 | return nil; 111 | }); 112 | @endcode 113 | 114 | */ 115 | + (instancetype)all:(NSArray*)promises; 116 | 117 | 118 | 119 | /** 120 | @brief Returns a new \p RXPromise object. If \a all promises in the given array 121 | \p promises have been fulfilled \a or rejected, the returned promise will be 122 | \a fulfilled with an array containing \c RXSettledResult objects each representing 123 | the result of the correspoding promise in the same order. 124 | 125 | @discussion Each \c RXSettledResult object will have either \c isFulfilled or 126 | \c isRejected set to \c YES, and the \c result property will hold the value \a or 127 | the error reason. If the given array is \c nil or empty, the returned promise 128 | will be fulfilled with an empty \c NSArray. 129 | 130 | @note The returned promise will always be be \a fulfilled when all promises in the 131 | array have been resolved - no matter if they get fulfilled \a or rejected or cancelled. 132 | 133 | @par If the returned promise will be cancelled or if any promise in the array has 134 | been rejected or cancelled, all other promises in the array will remain unaffected. 135 | If it is desired to cancel promises if any promise in the array has been rejected, 136 | it is suggested to do this in the error handler. 137 | 138 | @param promises A @c NSArray containing promises. It may be empty or \c nil. 139 | 140 | @return A new promise whose value is a \c NSArray containing \c RXSettledResult 141 | objects, or an empty \c NSArray. 142 | */ 143 | + (instancetype)allSettled:(NSArray*)promises; 144 | 145 | 146 | /** 147 | @brief Returns a new \c RXPromise object. If \a any promise in the given array 148 | @p promises has been \a fulfilled, the returned promise will be fulfilled with 149 | the value of this first fulfilled promise. The returned promise will be rejected 150 | only after when \a all promises in the given array have been rejected. 151 | 152 | @discussion If more than one promise will be fulfilled, the result of the subsequent 153 | promises will be ignored. If any promise in the array will be resolved or cancelled, 154 | all other promises will be unaffected. When it is desired to cancel all other promises 155 | it is suggested to do so in the completion respectively the error handler of the 156 | returned promise. 157 | 158 | @par \b Example:@code 159 | NSArray* promises = @[async(a), async(b), async(c)]; 160 | RXPromise* any = [RXPromise any:promises] 161 | .then(^id(id result){ 162 | NSLog(@"first result: %@", result); 163 | for (RXPromise* p in promises) {[p cancel];} 164 | return nil; 165 | },^id(NSError* error){ 166 | NSLog(@"Error: %@", error); 167 | for (RXPromise* p in promises) {[p cancelWithReason:error];} 168 | return nil; 169 | }); 170 | @endcode 171 | 172 | @param promises A \c NSArray containing promises. 173 | 174 | @note The returned promise will be rejected with reason \c \@"parameter error" if 175 | the parameter \p promises is \c nil or empty. 176 | 177 | @return A new promise whose value is the value of the first fulfilled promise. 178 | */ 179 | + (instancetype)any:(NSArray*)promises; 180 | 181 | 182 | /** 183 | For each element in array \p inputs sequentially call the asynchronous task 184 | passing it the element as its input argument. 185 | 186 | 187 | @discussion If the task succeeds, the task will be invoked with the next input, 188 | if any. The eventual result of each task is ignored. If the tasks fails, no further 189 | inputs will be processed and the returned promise will be resolved with the error. 190 | If all inputs have been processed successfully the returned promise will be 191 | resoveld with @"OK". 192 | 193 | The tasks are cancelable. That is, if the returned promise will be cancelled, the 194 | cancel signal will be forwarded to the current running task via cancelling the 195 | root promise of task's returned promise. 196 | 197 | @param inputs A array of input values. 198 | 199 | @param task The unary task to be invoked. 200 | 201 | @return A promise. 202 | */ 203 | + (instancetype) sequence:(NSArray*)inputs task:(RXPromise* (^)(id input)) task; 204 | 205 | 206 | /** 207 | Executes the asynchronous block repeatedly until the block returns \c nil or the 208 | promise returned from the current block will be rejected. 209 | 210 | The block is an asynchronous task returning a new promise. The receiver will 211 | \c sequentially invoke the asynchronous block until either it returns \c nil 212 | or its returned promise will be rejected. The next block will be executed 213 | only after the promise of the previous block has been fulfilled. 214 | 215 | The method \c repeat is itself asynchronous. It can be cancelled by sending the 216 | returned promise a \c cancel message. 217 | 218 | @param block The block shall return a new promise returned from an asynchronous 219 | task, or \c nil in order to indicate the stop condition for the loop. 220 | 221 | @return A new promise. If the \p repeat: method could be executed successfully, 222 | the promise will be fulfilled with @"OK". Otherwise the promise will be rejected 223 | with the error reason of the promise which has been rejected by the underlying task. 224 | */ 225 | + (instancetype) repeat:(rxp_nullary_task)block; 226 | 227 | 228 | 229 | #pragma mark - iOS Specific 230 | 231 | #if defined(TARGET_OS_IOS) && TARGET_OS_IOS 232 | 233 | /** 234 | Executes the asynchronous task associated to the receiver as an iOS Background Task. 235 | 236 | @discussion The receiver requests background execution time from the system which 237 | delays suspension of the app up until the receiver will be resolved or cancelled. 238 | 239 | Since Apps are given only a limited amount of time to finish background tasks, 240 | this time may expire before the task finishes. In this case the receiver's root 241 | will be cancelled which in turn propagates the cancel event to all children of 242 | the reciever, including the receiver. 243 | 244 | Tasks may want to handle the cancellation in order to execute additional code which 245 | orderly closes the task. This should not take too long, since by the time the cancel 246 | handler is called, the app is already very close to its time limit. 247 | 248 | @warning Handlers registered on child promises may not be executed when the app 249 | is in background. 250 | 251 | @param taskName The name to display in the debugger when viewing the background task. 252 | If you specify \c nil for this parameter, this method generates a name based on the 253 | name of the calling function or method. 254 | 255 | */ 256 | - (void) makeBackgroundTaskWithName:(NSString*)taskName; 257 | 258 | 259 | #endif 260 | 261 | @end 262 | -------------------------------------------------------------------------------- /Source/RXPromise+RXExtension.mm: -------------------------------------------------------------------------------- 1 | // 2 | // RXPromise+RXExtension.m 3 | // 4 | // Copyright 2013 Andreas Grosam 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | #import "RXPromise+RXExtension.h" 19 | #import "RXPromise.h" 20 | #import "RXSettledResult.h" 21 | #import "RXPromise+Private.h" 22 | #if defined(TARGET_OS_IOS) && TARGET_OS_IOS 23 | #import 24 | #endif 25 | #include 26 | 27 | // Set default logger severity to "Error" (logs only errors) 28 | #if !defined (DEBUG_LOG) 29 | #define DEBUG_LOG 1 30 | #endif 31 | #import "utility/DLog.h" 32 | #import 33 | 34 | 35 | @interface RXPromiseWrapper : NSObject 36 | @property (nonatomic, strong) RXPromise* promise; 37 | @end 38 | 39 | @implementation RXPromiseWrapper 40 | @synthesize promise = _promise; 41 | @end 42 | 43 | 44 | namespace { 45 | 46 | void sync_sequence(NSEnumerator* iter, __weak RXPromise* weakReturnedPromise, 47 | RXPromiseWrapper* taskPromise, rxp_unary_task task) 48 | { 49 | // Implementation notes: 50 | // We have a shared resource `root` which will be multiple times set by this 51 | // method and which is retrieved in the error handler of the returned promise 52 | // which is registered only once. 53 | 54 | assert(Shared.sync_queue); 55 | assert(task); 56 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 57 | 58 | RXPromise* returnedPromise = weakReturnedPromise; 59 | 60 | // If the returned promise has been cancelled or otherwise resolved, bail out: 61 | if (returnedPromise && !returnedPromise.isPending) { 62 | return; 63 | } 64 | id obj = [iter nextObject]; 65 | if (obj == nil) { 66 | // Finished processing the inputs: 67 | [returnedPromise fulfillWithValue:@"OK"]; 68 | return; 69 | } 70 | 71 | // Execute the task and get the task promise: 72 | taskPromise.promise = task(obj); 73 | 74 | // Register a continuation for the next object: 75 | taskPromise.promise.thenOn(Shared.sync_queue, ^id(id result){ 76 | sync_sequence(iter, returnedPromise, taskPromise, task); 77 | return returnedPromise; 78 | }, ^id(NSError*error){ 79 | return error; 80 | }); 81 | 82 | } 83 | 84 | void rxp_while(__weak RXPromise* weakReturnedPromise, rxp_nullary_task block) { 85 | RXPromise* returnedPromise = weakReturnedPromise; 86 | if (returnedPromise == nil || block == nil || returnedPromise.isCancelled) { 87 | return; 88 | } 89 | RXPromise* taskPromise = block(); 90 | if (taskPromise == nil) { 91 | [returnedPromise fulfillWithValue:@"OK"]; 92 | return; 93 | } 94 | taskPromise.then(^id(id result) { 95 | rxp_while(returnedPromise, block); 96 | return nil; 97 | }, ^id(NSError* error) { 98 | [returnedPromise rejectWithReason:error]; 99 | return nil; 100 | }); 101 | returnedPromise.then(nil, ^id(NSError* error) { 102 | [taskPromise cancelWithReason:error]; 103 | return nil; 104 | }); 105 | } 106 | 107 | } 108 | 109 | 110 | @implementation RXPromise (RXExtension) 111 | 112 | #pragma mark - 113 | 114 | 115 | +(instancetype) all:(NSArray*)promises 116 | { 117 | __block NSUInteger count = [promises count]; 118 | if (count == 0) { 119 | return [RXPromise promiseWithResult:@[]]; 120 | } 121 | RXPromise* promise = [[self alloc] init]; 122 | __weak RXPromise* weakPromise = promise; 123 | promise_completionHandler_t onSuccess = ^id(id result) { 124 | --count; 125 | RXPromise* strongPromise = weakPromise; 126 | if (count == 0 and strongPromise) { 127 | NSMutableArray* results = [[NSMutableArray alloc] initWithCapacity:[promises count]]; 128 | for (RXPromise* p in promises) { 129 | id result = [p synced_peakResult]; 130 | [results addObject:result ? result : [NSNull null]]; 131 | } 132 | [strongPromise fulfillWithValue:results]; 133 | } 134 | return nil; 135 | }; 136 | promise_errorHandler_t onError = ^id(NSError* error) { 137 | [weakPromise rejectWithReason:error]; 138 | return nil; 139 | }; 140 | for (RXPromise* p in promises) { 141 | p.thenOn(Shared.sync_queue, onSuccess, onError); 142 | } 143 | return promise; 144 | } 145 | 146 | +(instancetype) allSettled:(NSArray*)promises 147 | { 148 | __block NSUInteger count = [promises count]; 149 | if (count == 0) { 150 | return [RXPromise promiseWithResult:@[]]; 151 | } 152 | RXPromise* promise = [[self alloc] init]; 153 | __weak RXPromise* weakPromise = promise; 154 | promise_completionHandler_t onSuccess = ^id(id result) { 155 | --count; 156 | RXPromise* strongPromise = weakPromise; 157 | if (count == 0 and strongPromise) { 158 | NSMutableArray* results = [[NSMutableArray alloc] initWithCapacity:[promises count]]; 159 | for (RXPromise* p in promises) { 160 | RXSettledResult *result = [[RXSettledResult alloc] initWithFulfilled:p.isFulfilled andResult:[p synced_peakResult]]; 161 | [results addObject:result]; 162 | } 163 | [strongPromise fulfillWithValue:results]; 164 | } 165 | return nil; 166 | }; 167 | for (RXPromise* p in promises) { 168 | p.thenOn(Shared.sync_queue, onSuccess, onSuccess); 169 | } 170 | return promise; 171 | } 172 | 173 | 174 | + (instancetype) any:(NSArray*)promises 175 | { 176 | RXPromise* promise = [[self alloc] init]; 177 | __block NSUInteger count = [promises count]; 178 | if (count == 0) { 179 | [promise rejectWithReason:@"parameter error"]; 180 | return promise; 181 | } 182 | __weak RXPromise* weakPromise = promise; 183 | promise_completionHandler_t onSuccess = ^(id result){ 184 | [weakPromise fulfillWithValue:result]; 185 | return result; 186 | }; 187 | promise_errorHandler_t onError = ^(NSError* error) { 188 | --count; 189 | if (count == 0) { 190 | [weakPromise rejectWithReason:@"none succeeded"]; 191 | } 192 | return error; 193 | }; 194 | for (RXPromise* p in promises) { 195 | p.thenOn(Shared.sync_queue, onSuccess, onError); 196 | } 197 | return promise; 198 | } 199 | 200 | 201 | + (void) cancelAll:(NSArray*)promises { 202 | for (RXPromise* p in promises) { 203 | [p cancel]; 204 | } 205 | } 206 | 207 | 208 | 209 | + (instancetype) sequence:(NSArray*)inputs task:(rxp_unary_task)task 210 | { 211 | NSParameterAssert(task); 212 | assert(dispatch_get_specific(rxpromise::shared::QueueID) != rxpromise::shared::sync_queue_id); 213 | NSEnumerator* iter = [inputs objectEnumerator]; 214 | 215 | // A promise wrapper holding the current task promise: 216 | RXPromiseWrapper* currentTaskPromise = [[RXPromiseWrapper alloc] init]; 217 | 218 | // Create the returned promise: 219 | RXPromise* returnedPromise = [[self alloc] init]; 220 | // Register an error handler which cancels the current task's root: 221 | returnedPromise.thenOn(Shared.sync_queue, nil, ^id(NSError*error){ 222 | DLogInfo(@"cancelling task promise's root: %@", currentTaskPromise.promise.root); 223 | [currentTaskPromise.promise.root cancelWithReason:error]; 224 | return error; 225 | }); 226 | 227 | dispatch_sync(Shared.sync_queue, ^{ 228 | sync_sequence(iter, returnedPromise, currentTaskPromise, task); 229 | }); 230 | return returnedPromise; 231 | } 232 | 233 | 234 | + (instancetype) repeat: (rxp_nullary_task)block { 235 | RXPromise* promise = [[self alloc] init]; 236 | rxp_while(promise, block); 237 | return promise; 238 | } 239 | 240 | 241 | 242 | 243 | #pragma mark - iOS Specific 244 | 245 | #if defined(TARGET_OS_IOS) && TARGET_OS_IOS 246 | 247 | - (void) makeBackgroundTaskWithName:(NSString*)taskName { 248 | 249 | UIBackgroundTaskIdentifier backgroundTaskIdentifier = 250 | [[UIApplication sharedApplication] beginBackgroundTaskWithName:taskName 251 | expirationHandler:^{ 252 | [[self root] cancelWithReason:@"Background execution time expired"]; 253 | }]; 254 | self.thenOn(dispatch_get_main_queue(), ^id(id result) { 255 | [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskIdentifier]; 256 | return nil; 257 | }, ^id(NSError* error) { 258 | [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskIdentifier]; 259 | return nil; 260 | }); 261 | } 262 | 263 | #endif 264 | 265 | @end 266 | -------------------------------------------------------------------------------- /Source/RXPromise.h: -------------------------------------------------------------------------------- 1 | // 2 | // RXPromise.h 3 | // RXPromise 4 | // 5 | // Created by Andreas Grosam on 14/06/16. 6 | // 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for RXPromise. 12 | FOUNDATION_EXPORT double RXPromise_VersionNumber; 13 | 14 | //! Project version string for RXPromise. 15 | FOUNDATION_EXPORT const unsigned char RXPromise_VersionString[]; 16 | 17 | 18 | #import 19 | #import 20 | #import 21 | -------------------------------------------------------------------------------- /Source/RXPromise.mm: -------------------------------------------------------------------------------- 1 | // 2 | // RXPromise.mm 3 | // 4 | // Copyright 2013 Andreas Grosam 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | 19 | #if (!__has_feature(objc_arc)) 20 | #error this file requires arc enabled 21 | #endif 22 | 23 | #import "RXPromise.h" 24 | #import "RXPromise+Private.h" 25 | #import 26 | #import 27 | #include 28 | #include 29 | #include 30 | 31 | // Set default logger serverity to "Error" (logs only errors) 32 | #if !defined (DEBUG_LOG) 33 | #define DEBUG_LOG 1 34 | #endif 35 | #import "utility/DLog.h" 36 | 37 | 38 | /** 39 | See for specification. 40 | */ 41 | 42 | 43 | 44 | 45 | @interface NSError (RXResolver) 46 | - (void) rxp_synced_resolvePromise:(RXPromise*)promise; 47 | @end 48 | 49 | @interface NSObject (RXResolver) 50 | - (void) rxp_synced_resolvePromise:(RXPromise*)promise; 51 | @end 52 | 53 | @interface RXPromise (RXResolver) 54 | - (void) rxp_synced_resolvePromise:(RXPromise*)promise; 55 | @end 56 | 57 | 58 | #pragma mark ExecutionContext - NSThread 59 | @interface NSThread (RXPromise) 60 | - (void) rxp_dispatchBlock:(void(^)())block; 61 | - (void) rxp_performBlock:(void(^)())block; 62 | @end 63 | 64 | @implementation NSThread (RXPromise) 65 | - (void) rxp_dispatchBlock:(void(^)())block { 66 | if (block) { 67 | [self performSelector:@selector(rxp_performBlock:) onThread:self withObject:block waitUntilDone:NO]; 68 | } 69 | } 70 | - (void) rxp_performBlock:(void(^)())block { 71 | if (block) { 72 | block(); 73 | } 74 | } 75 | 76 | @end 77 | 78 | 79 | #pragma mark ExecutionContext - NSManagedObjectContext 80 | @interface NSManagedObjectContext (RXPromise) 81 | - (void) rxp_dispatchBlock:(void(^)())block; 82 | @end 83 | 84 | @implementation NSManagedObjectContext (RXPromise) 85 | - (void) rxp_dispatchBlock:(void(^)())block { 86 | [self performBlock:block]; 87 | } 88 | @end 89 | 90 | 91 | #pragma mark ExecutionContext - NSOperationQueue 92 | @interface NSOperationQueue (RXPromise) 93 | - (void) rxp_dispatchBlock:(void(^)())block; 94 | @end 95 | 96 | @implementation NSOperationQueue (RXPromise) 97 | - (void) rxp_dispatchBlock:(void(^)())block { 98 | [self addOperationWithBlock:block]; 99 | } 100 | @end 101 | 102 | 103 | 104 | rxpromise::shared Shared; 105 | 106 | #pragma mark - 107 | namespace { 108 | 109 | DISPATCH_RETURNS_RETAINED 110 | NSError* makeTimeoutError() { 111 | return [[NSError alloc] initWithDomain:@"RXPromise" code:-1001 userInfo:@{NSLocalizedFailureReasonErrorKey: @"timeout"}]; 112 | } 113 | 114 | 115 | DISPATCH_RETURNS_RETAINED 116 | inline dispatch_queue_t createHandlerQueue(bool suspended, void* tag) { 117 | char buffer[64]; 118 | snprintf(buffer, sizeof(buffer),"RXPromise.handler_queue-%p", tag); 119 | dispatch_queue_t handler_queue = dispatch_queue_create(buffer, NULL); 120 | assert(handler_queue); 121 | dispatch_set_target_queue(handler_queue, Shared.sync_queue); 122 | if (suspended) { 123 | dispatch_suspend(handler_queue); 124 | } 125 | return handler_queue; 126 | } 127 | 128 | } 129 | 130 | 131 | 132 | #pragma mark - RXPromise 133 | 134 | @interface RXPromise () 135 | @property (nonatomic) id result; 136 | @property (nonatomic, readwrite) RXPromise* parent; 137 | @end 138 | 139 | 140 | 141 | @implementation RXPromise { 142 | RXPromise* _parent; 143 | dispatch_queue_t _handler_queue; // a serial queue, uses target queue: s_sync_queue 144 | id _result; 145 | RXPromise_State _state; 146 | } 147 | @synthesize result = _result; 148 | @synthesize parent = _parent; 149 | 150 | 151 | 152 | - (void) dealloc { 153 | DLogInfo(@"dealloc: %p", (__bridge void*)self); 154 | if (_handler_queue) { 155 | if (_state == Pending) { 156 | DLogWarn(@"handlers not signaled"); 157 | dispatch_resume(_handler_queue); 158 | } 159 | } 160 | void const* key = (__bridge void const*)(self); 161 | if (dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id) { 162 | Shared.assocs.erase(key); 163 | } else { 164 | dispatch_barrier_sync(Shared.sync_queue, ^{ 165 | Shared.assocs.erase(key); 166 | }); 167 | } 168 | } 169 | 170 | #pragma mark - 171 | 172 | 173 | - (BOOL) isPending { 174 | return _state == Pending ? YES : NO; 175 | } 176 | 177 | - (BOOL) isFulfilled { 178 | return _state == Fulfilled ? YES : NO; 179 | } 180 | 181 | - (BOOL) isRejected { 182 | return ((_state & Rejected) != 0) ? YES : NO; 183 | } 184 | 185 | - (BOOL) isCancelled { 186 | return _state == Cancelled ? YES : NO; 187 | } 188 | 189 | 190 | - (RXPromise*) root { 191 | RXPromise* root = self; 192 | while (root.parent) { 193 | root = root.parent; 194 | } 195 | return root; 196 | } 197 | 198 | 199 | - (RXPromise_StateAndResult) peakStateAndResult { 200 | if (dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id) { 201 | return {_state, _result}; 202 | } 203 | else { 204 | __block RXPromise_StateAndResult result; 205 | dispatch_sync(Shared.sync_queue, ^{ 206 | result.result = _result; 207 | result.state = _state; 208 | }); 209 | return result; 210 | } 211 | } 212 | 213 | 214 | - (RXPromise_StateAndResult) synced_peakStateAndResult { 215 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 216 | return {_state, _result}; 217 | } 218 | 219 | - (id) synced_peakResult { 220 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 221 | assert(_state != Pending); 222 | return _result; 223 | } 224 | 225 | 226 | 227 | - (void) cancel { 228 | [self cancelWithReason:@"cancelled"]; 229 | } 230 | 231 | - (void) cancelWithReason:(id)reason { 232 | dispatch_barrier_async(Shared.sync_queue, ^{ // async, in order to be less prone to dead locks 233 | [self synced_cancelWithReason:reason]; 234 | }); 235 | } 236 | 237 | 238 | - (RXPromise*) setTimeout:(NSTimeInterval)timeout { 239 | if (timeout < 0) { 240 | return self; 241 | } 242 | else if (timeout == 0) { 243 | [self rejectWithReason:makeTimeoutError()]; 244 | return self; 245 | } 246 | dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, Shared.sync_queue); 247 | dispatch_source_set_event_handler(timer, ^{ 248 | dispatch_source_cancel(timer); // one shot timer 249 | [self rejectWithReason:makeTimeoutError()]; 250 | }); 251 | dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), 252 | DISPATCH_TIME_FOREVER /*one shot*/, 0 /*_leeway*/); 253 | dispatch_resume(timer); 254 | 255 | id executionContext = Shared.sync_queue; 256 | [self registerWithExecutionContext:executionContext onSuccess:^id(id result) { 257 | dispatch_source_cancel(timer); 258 | return nil; 259 | } onFailure:^id(NSError *error) { 260 | dispatch_source_cancel(timer); 261 | return nil; 262 | } 263 | returnPromise:NO]; 264 | 265 | return self; 266 | } 267 | 268 | 269 | 270 | - (void) synced_resolveWithResult:(id)result { 271 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 272 | if (result == nil) { 273 | [self synced_fulfillWithValue:nil]; 274 | } 275 | else { 276 | [result rxp_synced_resolvePromise:self]; 277 | } 278 | } 279 | 280 | 281 | - (void) synced_fulfillWithValue:(id)result { 282 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 283 | if (_state != Pending) { 284 | return; 285 | } 286 | _result = result; 287 | _state = Fulfilled; 288 | if (_handler_queue) { 289 | dispatch_resume(_handler_queue); 290 | } 291 | } 292 | 293 | 294 | - (void) synced_rejectWithReason:(id)reason { 295 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 296 | if (_state != Pending) { 297 | return; 298 | } 299 | if (![reason isKindOfClass:[NSError class]]) { 300 | reason = [[NSError alloc] initWithDomain:@"RXPromise" 301 | code:-1000 302 | userInfo:@{NSLocalizedFailureReasonErrorKey: reason ? reason : @""}]; 303 | } 304 | _result = reason; 305 | _state = Rejected; 306 | if (_handler_queue) { 307 | dispatch_resume(_handler_queue); 308 | } 309 | } 310 | 311 | 312 | - (void) synced_cancelWithReason:(id)reason { 313 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 314 | if (_state == Cancelled) { 315 | return; 316 | } 317 | if (![reason isKindOfClass:[NSError class]]) { 318 | reason = [[NSError alloc] initWithDomain:@"RXPromise" 319 | code:-1 320 | userInfo:@{NSLocalizedFailureReasonErrorKey: reason ? reason : @""}]; 321 | } 322 | if (_state == Pending) { 323 | DLogDebug(@"cancelled %p.", (__bridge void*)(self)); 324 | _result = reason; 325 | _state = Cancelled; 326 | if (_handler_queue) { 327 | dispatch_resume(_handler_queue); 328 | } 329 | } 330 | else { 331 | // We cancelled the promise at a time as it already was resolved. 332 | // That means, the _handler_queue is gone and we cannot forward the 333 | // cancellation event to any child ("returnedPromise") anymore. 334 | // In order to cancel the possibly already resolved children promises, 335 | // we need to send cancel to each promise in the children list: 336 | void const* key = (__bridge void const*)(self); 337 | auto range = Shared.assocs.equal_range(key); 338 | while (range.first != range.second) { 339 | DLogDebug(@"%p forwarding cancel to %p", key, (__bridge void*)((*(range.first)).second)); 340 | [(*(range.first)).second cancelWithReason:reason]; 341 | ++range.first; 342 | } 343 | Shared.assocs.erase(key); 344 | } 345 | } 346 | 347 | 348 | // Registers success and failure handlers. 349 | // The receiver will be retained and only released when the receiver will be 350 | // resolved (see "Requirements for an asynchronous result provider"). 351 | // Returns a new promise which represents the return values of the handler 352 | // blocks. 353 | - (instancetype) registerWithExecutionContext:(id)executionContext 354 | onSuccess:(promise_completionHandler_t)onSuccess 355 | onFailure:(promise_errorHandler_t)onFailure 356 | returnPromise:(BOOL)returnPromise 357 | { 358 | RXPromise* returnedPromise = returnPromise ? ([[[self class] alloc] init]) : nil; 359 | returnedPromise.parent = self; 360 | __weak RXPromise* weakReturnedPromise = returnedPromise; 361 | __block RXPromise* blockSelf = self; 362 | if (executionContext == nil) { 363 | executionContext = Shared.default_concurrent_queue; 364 | } 365 | dispatch_block_t registerBlock = ^{ 366 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 367 | if (_handler_queue == nil) { 368 | _handler_queue = createHandlerQueue(_state == Pending, (__bridge void*)self); 369 | } 370 | // Finally, *enqueue* a wrapper block which eventually gets invoked when the 371 | // promise will be resolved: 372 | dispatch_async(_handler_queue, ^{ 373 | // The continuation has been fired! 374 | assert(executionContext); 375 | // Get the state of the promise: 376 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 377 | RXPromise_StateT promise_state = blockSelf->_state; 378 | __strong id promise_result = blockSelf->_result; 379 | 380 | dispatch_block_t handlerBlock = ^{ 381 | // The handler block will be executed in the specified execution 382 | // context - it can be Shared.sync_queue, too - when invoked internally! 383 | // If the execution context equals the Shared.sync_queue, the block 384 | // must be enqueued with a barrier! (implementation details) 385 | @autoreleasepool { 386 | assert(promise_state != Pending); 387 | RXPromise_StateT state = promise_state; 388 | __strong id result = promise_result; 389 | if (state == Fulfilled && onSuccess) { 390 | result = onSuccess(blockSelf->_result); 391 | } 392 | else if (state != Fulfilled && onFailure) { 393 | result = onFailure(blockSelf->_result); 394 | } 395 | RXPromise* strongReturnedPromise = weakReturnedPromise; 396 | if (strongReturnedPromise) { 397 | assert(result != strongReturnedPromise); // @"cyclic promise error"); 398 | if (state == Cancelled) { 399 | [strongReturnedPromise cancelWithReason:result]; 400 | } 401 | else { 402 | DLogInfo(@"%p add child %p", (__bridge void*)(blockSelf), (__bridge void*)(strongReturnedPromise)); 403 | if (executionContext == Shared.sync_queue) { 404 | // prerequiste: the block must have been enqueued with a barrier! 405 | Shared.assocs.emplace((__bridge void*)(blockSelf), weakReturnedPromise); 406 | } else { 407 | void const* parent_pointer = (__bridge void*)(blockSelf); 408 | dispatch_barrier_async(Shared.sync_queue, ^{ // TODO: 409 | Shared.assocs.emplace(parent_pointer, weakReturnedPromise); 410 | }); 411 | } 412 | // §2.2: if parent is fulfilled, fulfill the "returned promise" with the same value 413 | // §2.3: if parent is rejected, reject the "returned promise" with the same value. 414 | // 415 | // There are four cases how the "returned promise" (child) will be resolved: 416 | // 1. result isKindOfClass NSError -> rejected with reason error 417 | // 2. result isKindOfClass RXPromise -> fulFilled with promise 418 | // 3. result equals nil -> fulFilled with nil 419 | // 4 result is any other object -> fulFilled with value 420 | // 421 | // Note: if parent is cancelled, the "returned promise" will NOT be cancelled - it just adopts the error reason! 422 | if (result && [result isKindOfClass:[NSError class]]) { 423 | [strongReturnedPromise rejectWithReason:result]; 424 | } 425 | else if (result && [result isKindOfClass:[RXPromise class]]) { 426 | [strongReturnedPromise bind:result]; 427 | } 428 | else { 429 | [strongReturnedPromise fulfillWithValue:result]; 430 | } 431 | } 432 | } 433 | else { 434 | DLogInfo(@"parent's %p returned promisze %p died", (__bridge void*)(blockSelf), (__bridge void*)(strongReturnedPromise)); 435 | } 436 | blockSelf = nil; 437 | }//@autoreleasepool 438 | }; 439 | 440 | if (executionContext == Shared.default_concurrent_queue) { 441 | // If the continuation has been registered with `then`, we run 442 | // the handler is parallel: 443 | dispatch_async(executionContext, handlerBlock); 444 | } 445 | else if ([executionContext conformsToProtocol:@protocol(OS_dispatch_queue)]) { 446 | // If the continuation has been registered with `thenOn:` and when the 447 | // execution context is a dispatch queue, we run the handler serially: 448 | dispatch_barrier_async(executionContext, handlerBlock); 449 | } 450 | else { 451 | // Otherwise, the execution context is not a dispatch_queue. Dispatch 452 | // to the corresponding execution context: 453 | [executionContext rxp_dispatchBlock:handlerBlock]; 454 | } 455 | }); 456 | }; 457 | 458 | if (dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id) { 459 | // when entering here, we need to ensure the block has been dispatched with a barrier! 460 | // (currently, this path only gets executed when invoking `resolveWithResult:` and `bind:`) 461 | registerBlock(); 462 | } 463 | else { 464 | assert(Shared.sync_queue); 465 | dispatch_barrier_sync(Shared.sync_queue, registerBlock); 466 | } 467 | return returnedPromise; 468 | } 469 | 470 | 471 | - (then_block_t) then { 472 | return ^RXPromise*(promise_completionHandler_t onSuccess, promise_errorHandler_t onFailure) { 473 | return [self registerWithExecutionContext:nil onSuccess:onSuccess onFailure:onFailure returnPromise:YES]; 474 | }; 475 | } 476 | 477 | 478 | - (then_on_block_t) thenOn { 479 | return ^RXPromise*(id executionContext, promise_completionHandler_t onSuccess, promise_errorHandler_t onFailure) { 480 | return [self registerWithExecutionContext:executionContext onSuccess:onSuccess onFailure:onFailure returnPromise:YES]; 481 | }; 482 | } 483 | 484 | 485 | - (then_on_main_block_t) thenOnMain { 486 | return ^RXPromise*(promise_completionHandler_t onSuccess, promise_errorHandler_t onFailure) { 487 | return [self registerWithExecutionContext:dispatch_get_main_queue() onSuccess:onSuccess onFailure:onFailure returnPromise:YES]; 488 | }; 489 | } 490 | 491 | - (catch_on_block_t) catchOn { 492 | return ^RXPromise*(id executionContext, promise_errorHandler_t onFailure) { 493 | return [self registerWithExecutionContext:executionContext onSuccess:nil onFailure:onFailure returnPromise:YES]; 494 | }; 495 | } 496 | 497 | - (catch_on_main_block_t) catchOnMain { 498 | return ^RXPromise*(promise_errorHandler_t onFailure) { 499 | return [self registerWithExecutionContext:dispatch_get_main_queue() onSuccess:nil onFailure:onFailure returnPromise:YES]; 500 | }; 501 | } 502 | 503 | 504 | 505 | #pragma mark - 506 | 507 | - (id) get { 508 | return [self getWithTimeout:-1.0]; // negative means infinitive 509 | } 510 | 511 | 512 | - (id) getWithTimeout:(NSTimeInterval)timeout 513 | { 514 | assert(dispatch_get_specific(rxpromise::shared::QueueID) != rxpromise::shared::sync_queue_id); // Must not execute on the private sync queue! 515 | 516 | __block id result; 517 | __block dispatch_semaphore_t sem = NULL; 518 | dispatch_sync(Shared.sync_queue, ^{ 519 | if (_state != Pending) { 520 | result = _result; 521 | } else { 522 | sem = dispatch_semaphore_create(0); 523 | if (_handler_queue == nil) { 524 | _handler_queue = createHandlerQueue(_state == Pending, (__bridge void*)self); 525 | } 526 | dispatch_async(_handler_queue, ^{ 527 | dispatch_semaphore_signal(sem); 528 | }); 529 | } 530 | }); 531 | if (sem) { 532 | // result was not yet availbale: queue a handler 533 | dispatch_time_t t = timeout < 0 ? DISPATCH_TIME_FOREVER : dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); 534 | if (dispatch_semaphore_wait(sem, t) == 0) { // wait until handler_queue will be resumed ... 535 | dispatch_sync(Shared.sync_queue, ^{ // safely retrieve _result 536 | result = _result; 537 | }); 538 | } 539 | else { 540 | result = makeTimeoutError(); 541 | } 542 | } 543 | return result; 544 | } 545 | 546 | 547 | 548 | - (void) wait { 549 | [self get]; 550 | } 551 | 552 | 553 | - (void) runLoopWait 554 | { 555 | // The current thread MUST have a run loop and at least one event source! 556 | // This is difficult to verfy in this method - thus this is simply 557 | // a prerequisite which must be ensured by the client. If there is no 558 | // event source, the run lopp may quickly return with the effect that the 559 | // while loop will "busy wait". 560 | 561 | static CFRunLoopSourceContext context; 562 | 563 | CFRunLoopRef runLoop = CFRunLoopGetCurrent(); 564 | CFRunLoopSourceRef runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context); 565 | CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode); 566 | 567 | self.then(^id(id result) { 568 | CFRunLoopStop(runLoop); 569 | return nil; 570 | }, ^id(NSError* error) { 571 | CFRunLoopStop(runLoop); 572 | return nil; 573 | }); 574 | while (1) { 575 | if (!self.isPending) { 576 | break; 577 | } 578 | CFRunLoopRun(); 579 | } 580 | CFRunLoopRemoveSource(runLoop, runLoopSource, kCFRunLoopDefaultMode); 581 | CFRelease(runLoopSource); 582 | } 583 | 584 | 585 | 586 | - (void) bind:(RXPromise*) other 587 | { 588 | if (dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id) { 589 | [self synced_bind:other]; 590 | } 591 | else { 592 | dispatch_barrier_async(Shared.sync_queue, ^{ 593 | [self synced_bind:other]; 594 | }); 595 | } 596 | } 597 | 598 | 599 | // The receiver will adopt the state of the promise `other`. 600 | // 601 | // Promise `other` will be retained and released only until after `other` will be 602 | // resolved. 603 | // 604 | // Caution: if _other_ is or will be cancelled, the receiver (the "bound promise") 605 | // will be cancelled as well! Unlike in a parent-child relationship, method `bind` 606 | // will make the receiver not just adopt the error reason - but also adopt its 607 | // cancellation! 608 | - (void) synced_bind:(RXPromise*) other { 609 | assert(other != nil); 610 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 611 | 612 | if (_state == Cancelled) { 613 | [other cancelWithReason:_result]; 614 | return; 615 | } 616 | if (_state != Pending) { 617 | return; 618 | } 619 | RXPromise_StateAndResult ps = [other synced_peakStateAndResult]; 620 | switch (ps.state) { 621 | case Fulfilled: 622 | [self synced_fulfillWithValue:ps.result]; 623 | break; 624 | case Rejected: 625 | [self synced_rejectWithReason:ps.result]; 626 | break; 627 | case Cancelled: 628 | [self synced_cancelWithReason:ps.result]; 629 | break; 630 | default: { 631 | __weak RXPromise* weakSelf = self; 632 | [other registerWithExecutionContext:Shared.sync_queue onSuccess:^id(id result) { 633 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 634 | RXPromise* strongSelf = weakSelf; 635 | [strongSelf synced_fulfillWithValue:result]; 636 | return nil; 637 | } onFailure:^id(NSError *error) { 638 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 639 | RXPromise* strongSelf = weakSelf; 640 | if (other.isCancelled) 641 | [strongSelf synced_cancelWithReason:error]; 642 | else { 643 | [strongSelf synced_rejectWithReason:error]; 644 | } 645 | return nil; 646 | } returnPromise:NO]; 647 | } 648 | } 649 | __weak RXPromise* weakSelf = self; 650 | __weak RXPromise* weakOther = other; 651 | [self registerWithExecutionContext:Shared.sync_queue onSuccess:nil onFailure:^id(NSError *error) { 652 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 653 | RXPromise* strongSelf = weakSelf; 654 | if (strongSelf) { 655 | if (strongSelf->_state == Cancelled) { 656 | RXPromise* strongOther = weakOther; 657 | [strongOther synced_cancelWithReason:error]; 658 | } 659 | } 660 | return error; 661 | } returnPromise:NO]; 662 | 663 | } 664 | 665 | 666 | 667 | 668 | 669 | #pragma mark - 670 | 671 | - (NSString*) description { 672 | __block NSString* desc; 673 | dispatch_sync(Shared.sync_queue, ^{ 674 | desc = [self rxp_descriptionLevel:0]; 675 | }); 676 | return desc; 677 | } 678 | 679 | - (NSString*) debugDescription { 680 | return [self rxp_descriptionLevel:0]; 681 | } 682 | 683 | 684 | - (NSString*) rxp_descriptionLevel:(int)level { 685 | NSString* indent = [NSString stringWithFormat:@"%*s",4*level,""]; 686 | NSMutableString* desc = [[NSMutableString alloc] initWithFormat:@"%@<%@:%p> %ld { %@ }", 687 | indent, 688 | NSStringFromClass([self class]), (__bridge void*)self, 689 | CFGetRetainCount((__bridge CFTypeRef)self), 690 | ( (_state == Fulfilled)?[NSString stringWithFormat:@"fulfilled with value: %@", _result]: 691 | (_state == Rejected)?[NSString stringWithFormat:@"rejected with reason: %@", _result]: 692 | (_state == Cancelled)?[NSString stringWithFormat:@"cancelled with reason: %@", _result] 693 | :@"pending") 694 | ]; 695 | void* key = (__bridge void*)(self); 696 | auto range = Shared.assocs.equal_range(key); 697 | if (range.first != range.second) { 698 | [desc appendString:[NSString stringWithFormat:@", children: [\n"]]; 699 | while (range.first != range.second) { 700 | RXPromise* p = (*(range.first)).second; 701 | [desc appendString:p ? [p rxp_descriptionLevel:level+1] : @""]; 702 | [desc appendString:@"\n"]; 703 | ++range.first; 704 | } 705 | [desc appendString:[NSString stringWithFormat:@"%@]", indent]]; 706 | } 707 | 708 | return desc; 709 | } 710 | 711 | - (NSString*) rxp_debugSummary { 712 | NSString* result = [_result description]; 713 | NSMutableString* summary = [[NSMutableString alloc] initWithFormat:@"<%@>{%@}", 714 | NSStringFromClass([self class]), 715 | ( (_state == Fulfilled)?[NSString stringWithFormat:@"fulfilled with value: %@", result]: 716 | (_state == Rejected)?[NSString stringWithFormat:@"rejected with reason: %@", result]: 717 | (_state == Cancelled)?[NSString stringWithFormat:@"cancelled with reason: %@", result] 718 | :@"pending") 719 | ]; 720 | return summary; 721 | } 722 | 723 | @end 724 | 725 | 726 | #pragma mark - RXPromise (RXResolver) 727 | 728 | @implementation RXPromise (RXResolver) 729 | - (void) rxp_synced_resolvePromise:(RXPromise*)promise 730 | { 731 | assert(dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id); 732 | [promise synced_bind:self]; 733 | } 734 | @end 735 | 736 | @implementation NSObject (RXResolver) 737 | - (void) rxp_synced_resolvePromise:(RXPromise*)promise { 738 | // This is not strict according the spec: 739 | // If value is an object we require it to be a `thenable` or we must 740 | // reject the promise with an appropriate error. 741 | // However this API supports only objects, that is, our value is always 742 | // an `id` and not a struct or other primitive C type or a C++ class, etc. 743 | // We also do not support `thenables`. 744 | // So, we handle values which are not RXPromises and not NSErrors as if 745 | // they were non-objects and simply fulfill the promise with this value. 746 | [promise fulfillWithValue:self]; // forward result 747 | } 748 | 749 | @end 750 | 751 | @implementation NSError (RXResolver) 752 | 753 | - (void) rxp_synced_resolvePromise:(RXPromise*)promise { 754 | [promise rejectWithReason:self]; 755 | } 756 | 757 | @end 758 | 759 | 760 | 761 | 762 | #pragma mark - RXPromiseD 763 | 764 | 765 | @interface RXPromiseD : RXPromise 766 | - (instancetype) initWithDeallocHandler:(void(^)())deallocHandler; 767 | @end 768 | 769 | @implementation RXPromiseD { 770 | dispatch_block_t _deallocHandler; 771 | } 772 | 773 | 774 | - (instancetype) initWithDeallocHandler:(void(^)())deallocHandler 775 | { 776 | self = [super init]; 777 | if (self) { 778 | _deallocHandler = [deallocHandler copy]; 779 | } 780 | return self; 781 | } 782 | 783 | - (void) dealloc { 784 | if (_deallocHandler) { 785 | _deallocHandler(); 786 | } 787 | } 788 | 789 | @end 790 | 791 | 792 | 793 | 794 | #pragma mark - RXPromise (Deferred) 795 | 796 | 797 | @implementation RXPromise (Deferred) 798 | 799 | #pragma mark Convenient Class Methods 800 | 801 | + (instancetype) promiseWithResult:(id)result { 802 | RXPromise* promise = [[self alloc] initWithResult:result]; 803 | return promise; 804 | } 805 | 806 | + (RXPromise*) promiseWithDeallocHandler:(void(^)())deallocHandler { 807 | RXPromise* promise = [[RXPromiseD alloc] initWithDeallocHandler:deallocHandler]; 808 | return promise; 809 | } 810 | 811 | + (instancetype)promiseWithTask:(id(^)(void))task { 812 | assert(task); 813 | RXPromise* promise = [[self alloc] init]; 814 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 815 | [promise resolveWithResult:task()]; 816 | }); 817 | return promise; 818 | } 819 | 820 | 821 | + (instancetype)promiseWithQueue:(dispatch_queue_t)queue task:(id(^)(void))task { 822 | assert(queue); 823 | assert(task); 824 | RXPromise* promise = [[self alloc] init]; 825 | dispatch_async(queue, ^{ 826 | [promise resolveWithResult:task()]; 827 | }); 828 | return promise; 829 | } 830 | 831 | #pragma mark Initializer 832 | 833 | // 1. Designated Initializer 834 | - (instancetype)init { 835 | self = [super init]; 836 | DLogInfo(@"create: %p", (__bridge void*)self); 837 | return self; 838 | } 839 | 840 | // 2. Designated Initializer 841 | - (instancetype)initWithResult:(id)result { 842 | assert(![result isKindOfClass:[RXPromise class]]); 843 | DLogInfo(@"create: %p", (__bridge void*)self); 844 | self = [super init]; 845 | if (self) { 846 | _result = result; 847 | _state = [result isKindOfClass:[NSError class]] ? Rejected : Fulfilled; 848 | } 849 | return self; 850 | } 851 | 852 | #pragma mark Resolver 853 | 854 | - (void) resolveWithResult:(id)result { 855 | dispatch_barrier_async(Shared.sync_queue, ^{ 856 | [self synced_resolveWithResult:result]; 857 | }); 858 | } 859 | 860 | 861 | - (void) fulfillWithValue:(id)value { 862 | assert(![value isKindOfClass:[NSError class]]); 863 | if (dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id) { 864 | [self synced_fulfillWithValue:value]; 865 | } 866 | else { 867 | dispatch_barrier_async(Shared.sync_queue, ^{ 868 | [self synced_fulfillWithValue:value]; 869 | }); 870 | } 871 | } 872 | 873 | 874 | - (void) rejectWithReason:(id)reason { 875 | if (dispatch_get_specific(rxpromise::shared::QueueID) == rxpromise::shared::sync_queue_id) { 876 | [self synced_rejectWithReason:reason]; 877 | } 878 | else { 879 | dispatch_barrier_async(Shared.sync_queue, ^{ 880 | [self synced_rejectWithReason:reason]; 881 | }); 882 | } 883 | } 884 | 885 | 886 | 887 | 888 | 889 | @end -------------------------------------------------------------------------------- /Source/RXPromiseHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // RXPromise.h 3 | // 4 | // Copyright 2013 Andreas Grosam 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | #import 19 | 20 | 21 | /* 22 | 23 | A RXPromise object represents the eventual result of an asynchronous function 24 | or method. 25 | 26 | 27 | ## Synopsis 28 | 29 | See also [RXPromise(Deferred)](@ref RXPromise(Deferred)). 30 | 31 | @class RXPromise; 32 | 33 | typedef id (^promise_completionHandler_t)(id result); 34 | typedef id (^promise_errorHandler_t)(NSError* error); 35 | 36 | typedef RXPromise* (^then_block_t)(promise_completionHandler_t, promise_errorHandler_t); 37 | typedef RXPromise* (^then_on_block_t)(id, promise_completionHandler_t, promise_errorHandler_t); 38 | typedef RXPromise* (^then_on_main_block_t)(promise_completionHandler_t, promise_errorHandler_t); 39 | 40 | 41 | @interface RXPromise : NSObject 42 | 43 | @property (nonatomic, readonly) BOOL isPending; 44 | @property (nonatomic, readonly) BOOL isFulfilled; 45 | @property (nonatomic, readonly) BOOL isRejected; 46 | @property (nonatomic, readonly) BOOL isCancelled; 47 | 48 | @property (nonatomic, readonly) then_block_t then; 49 | @property (nonatomic, readonly) then_on_block_t thenOn; 50 | @property (nonatomic, readonly) then_on_main_block_t thenOnMain; 51 | 52 | @property (nonatomic, readonly) RXPromise* parent; 53 | @property (nonatomic, readonly) RXPromise* root; 54 | 55 | 56 | - (void) cancel; 57 | - (void) cancelWithReason:(id)reason; 58 | - (void) bind:(RXPromise*) other; 59 | - (id) get; 60 | - (id) getWithTimeout:(NSTimeInterval)timeout; 61 | - (void) wait; 62 | - (void) runLoopWait; 63 | - (RXPromise*) setTimeout:(NSTimeInterval)timeout; 64 | 65 | + (RXPromise*) promiseWithTask:(id(^)(void))task; 66 | + (RXPromise*) promiseWithQueue:(dispatch_queue_t)queue task:(id(^)(void))task; 67 | 68 | @end 69 | 70 | 71 | 72 | @interface RXPromise(Deferred) 73 | 74 | + (instancetype) promiseWithResult:(id)result; 75 | + (instancetype) promiseWithDeallocHandler:(void(^)())deallocHandler; 76 | 77 | - (id)init; 78 | 79 | - (void) fulfillWithValue:(id)result; 80 | - (void) rejectWithReason:(id)error; 81 | - (void) resolveWithResult:(id)result; 82 | 83 | @end 84 | 85 | 86 | 87 | 88 | ## Concurrency: 89 | 90 | RXPromises are thread safe and will not dead lock. It's safe to send them 91 | messages from any thread/queue and at any time. 92 | 93 | The handlers use an /e execution-context" where they are executed. The execution 94 | context is either explicit or implicit. If the execution context is nil or not 95 | specified, the handler will execute on a concurrent dispatch queue. Otherwise, 96 | the execution context can be specified with the `thenOn` property and can be a 97 | dispatch_queue, a NSThread, a NSOperationQueue or a NSManagedObjectContext. 98 | 99 | If the \p then property's block will be used to define the completion and error 100 | handler, the handlers will implicitly run on an _unspecified_ and _concurrent_ 101 | execution context. That is, once the promise is resolved the corresponding 102 | handler MAY execute on any thread and concurrently to any other handler. 103 | 104 | If the \p thenOn property's block will be used to define completion and error handler, 105 | the execution context will be explicitly defined through the first parameter 106 | _queue_ of the block, which is either a serial or concurrent _dispatch queue_. 107 | Once the promise is resolved, the handler is guaranteed to execute on the specified 108 | execution context. The execution context MAY be serial or concurrent. 109 | 110 | Without any other synchronization means, concurrent access to shared resources 111 | from within handlers is only guaranteed to be safe when they execute on the same 112 | and _serial_ execution context. 113 | 114 | The "execution context" for a dispatch queue is the target queue of that queue. 115 | The target queue is responsible for processing the completion or error block. 116 | 117 | 118 | It's safe to specify the same execution context where the \p then or \p thenOn 119 | property will be executed. 120 | 121 | 122 | 123 | ## Usage: 124 | 125 | 126 | ### An example for continuation: 127 | 128 | [self fetchUsersWithURL:url] 129 | .then(^id(id usersJSON){ 130 | return [foo parseJSON:usersJSON]; 131 | }, nil) 132 | .then(^id(id users){ 133 | return [self mergeIntoMOC:users]; 134 | }, nil) 135 | .thenOn(dispatch_get_main_queue(), nil, 136 | ^id(NSError* error) { 137 | // on main thread 138 | [self alertError:error]; 139 | return nil; 140 | }); 141 | 142 | 143 | ### Simultaneous Invocations: 144 | 145 | 146 | Perform authentication for a user, if that succeeded, simultaneously load profile 147 | and messages for that user, parse the JSON and create models. 148 | 149 | RXPromise* if_auth = [self.user authenticate]; 150 | 151 | if_auth 152 | .then(^id(id result){ return [self.user loadProfile]; }, nil) 153 | .then(^id(id result){ return [self parseJSON:result]; }, nil) 154 | .then(^id(id result){ return [self createProfileModel:result]; }, nil) 155 | }); 156 | 157 | if_auth 158 | .then(^id(id result){ return [self.user loadMessages]; }, nil) 159 | .then(^id(id result){ return [self parseJSON:result]; }, nil) 160 | .then(^id(id result){ return [self createMessagesModel:result]; }, nil) 161 | }); 162 | 163 | 164 | */ 165 | 166 | 167 | 168 | 169 | @class RXPromise; 170 | 171 | /*! 172 | @brief Type definition for the completion handler block. 173 | 174 | @discussion The completion handler will be invoked when the associated promise has 175 | been fulfilled. 176 | 177 | The block's return value will resolve the "returned promise" - that is, the promise 178 | returned from the "then" block - if there is any. The return value can be a promise 179 | or any other object and \c nil. Returning anything else than an \c NSError object 180 | signals success to the "returned promise", where returning a \c NSErro object will 181 | signal a failure the the "returned promise". 182 | 183 | @note The execution context is either the execution context specified 184 | when the handlers have been registered via property \p thenOn or it is 185 | unspecified when registred via \p then. 186 | 187 | @param result The result value set by the "asynchronous result provider" when it 188 | succeeded. 189 | 190 | @return An object or \c nil which resolves the "returned promise". 191 | 192 | */ 193 | typedef id (^promise_completionHandler_t)(id result); 194 | 195 | /*! 196 | @brief Type definition for the error handler block. 197 | 198 | @discussion The error handler will be invoked when the associated promise has been 199 | rejected or cancelled. 200 | 201 | @par The block's return value will resolve the "returned promise" - that is, the promise 202 | returned from the "then" block - if there is any. The return value can be a promise 203 | or any other object and \c nil. In most cases the error handler will return a 204 | \c NSError object in order to forward the error. However, a handler may also consider 205 | to signal success in particular cases, where it returns something else than an 206 | \c NSError object. 207 | 208 | @note The execution context is either the execution context specified 209 | when the handlers have been registered via property \p thenOn or it is 210 | unspecified when registred via \p then. 211 | 212 | @param error The value set by the "asynchronous result provider" when it failed. 213 | 214 | @return An object or \c nil which resolves the "returned promise". 215 | 216 | */ 217 | typedef id (^promise_errorHandler_t)(NSError* error); 218 | 219 | /*! 220 | @brief Type definition of the "then block". The "then block" is the return value 221 | of the property \p then. 222 | 223 | @discussion The "then block" has two parameters, the completion handler block and 224 | the error handler block. The handlers may be \c nil. 225 | 226 | @par The "then block" returns a promise, the "returned promise". When the parent 227 | promise will be resolved the corresponding handler (if not \c nil) will be invoked 228 | and its return value resolves the "returned promise" (if it exists). Otherwise, 229 | if the handler block is \c nil the "returned promise" (if it exists) will be resolved 230 | with the parent promise' result value. 231 | 232 | @par The handler executes on a \e concurrent unspecified execution context. 233 | */ 234 | typedef RXPromise* (^then_block_t)(promise_completionHandler_t, promise_errorHandler_t); 235 | 236 | /*! 237 | @brief Type definition of the "then_on block". The "then_on block" is the return 238 | value of the property \p thenOn. 239 | 240 | @discussion The "then_on block" has three parameters, the execution context, the 241 | completion handler block and the error handler block. The handlers may be \c nil. 242 | 243 | @par The "then block" returns a promise, the "returned promise". When the parent 244 | promise will be resolved the corresponding handler (if not \c nil) will be invoked 245 | and its return value resolves the "returned promise" (if it exists). Otherwise, 246 | if the handler block is \c nil the "returned promise" (if it exists) will be resolved 247 | with the parent promise' result value. 248 | */ 249 | typedef RXPromise* (^then_on_block_t)(id, 250 | promise_completionHandler_t, 251 | promise_errorHandler_t); 252 | 253 | /*! 254 | @brief Type definition of the "then_on_main block". The "then_on_main block" is the return 255 | value of the property \p thenOnMain. 256 | 257 | @discussion The "then_on_main block" has two parameters, the completion handler block 258 | and the error handler block. The handlers may be \c nil. 259 | 260 | @par The "then block" returns a promise, the "returned promise". When the parent 261 | promise will be resolved the corresponding handler (if not \c nil) will be invoked 262 | and its return value resolves the "returned promise" (if it exists). Otherwise, 263 | if the handler block is \c nil the "returned promise" (if it exists) will be resolved 264 | with the parent promise' result value. 265 | */ 266 | typedef RXPromise* (^then_on_main_block_t)(promise_completionHandler_t, promise_errorHandler_t); 267 | 268 | /*! 269 | @brief Type definition of the "catch_on block". The "catch_on block" is the return value 270 | of the property \p catchOn. 271 | 272 | @discussion The "catch_on block" has two parameters, the execution context and the error 273 | handler block. The handler may be \c nil. 274 | 275 | @par The "catch block" returns a promise, the "returned promise". When the parent 276 | promise will be resolved the corresponding handler (if not \c nil) will be invoked 277 | and its return value resolves the "returned promise" (if it exists). Otherwise, 278 | if the handler block is \c nil the "returned promise" (if it exists) will be resolved 279 | with the parent promise' result value. 280 | */ 281 | typedef RXPromise *(^catch_on_block_t)(id, promise_errorHandler_t errorHandler); 282 | 283 | /*! 284 | @brief Type definition of the "catch_on_main block". The "catch_on_main block" is the return 285 | value of the property \p catchOnMain. 286 | 287 | @discussion The "catch_on_main block" has one parameters, the error handler block. 288 | The handler may be \c nil. 289 | 290 | @par The "catch_on_main block" returns a promise, the "returned promise". When the parent 291 | promise will be resolved the error handler (if not \c nil) will be invoked if the promise 292 | is rejected and its return value resolves the "returned promise" (if it exists). Otherwise, 293 | if the handler block is \c nil the "returned promise" (if it exists) will be resolved 294 | with the parent promise' result value. 295 | */ 296 | typedef RXPromise* (^catch_on_main_block_t)(promise_errorHandler_t); 297 | 298 | /*! 299 | 300 | @brief A \p RXPromise object represents the eventual result of an asynchronous 301 | function or method. 302 | 303 | A \p RXPromise is a lightweight primitive which helps managing asynchronous patterns 304 | and make them easier to follow and understand. It also adds a few powerful features 305 | to asynchronous operations like \a continuation , \a grouping and \a cancellation 306 | 307 | 308 | @par \b Caution: 309 | 310 | A promise which has registered one or more handlers will not deallocate unless 311 | it is resolved and the handlers are executed. This implies that an asynchronous 312 | result provider MUST eventually resolve its promise. 313 | 314 | @par \b Concurrency: 315 | 316 | Concurrent access to shared resources is only guaranteed to be safe for accesses 317 | from within handlers whose promises belong to the same "promise tree". 318 | 319 | A "promise tree" is a set of promises which share the same root promise. 320 | 321 | 322 | @remarks Currently, it is guraranteed that concurrent access from within 323 | any handler from any promise to a shared resource is guaranteed to be safe. 324 | */ 325 | 326 | @interface RXPromise : NSObject 327 | 328 | 329 | 330 | /*! 331 | @brief Property \p then returns a block whose signature is 332 | @code 333 | RXPromise* (^)(promise_completionHandler_t onSuccess, 334 | promise_errorHandler_t onError) @endcode 335 | 336 | When this returned block is called it will register the completion handler and 337 | the error handler given as block parameters. 338 | 339 | @par The receiver will be retained and released only until after the receiver 340 | has been resolved. 341 | 342 | @par The block returns a new \c RXPromise, the "returned promise", whose result will become 343 | the return value of either handler that gets called when the receiver will be resolved. 344 | 345 | @par When the block is invoked and the receiver is already resolved, the corresponding 346 | handler will be immediately asynchronously scheduled for execution on the 347 | \e unspecified execution context. 348 | 349 | @par Parameter \p onSuccess and \p onError may be \c nil. 350 | 351 | @par The receiver can register zero or more handler (pairs) through clientes calling 352 | the block multiple times. 353 | 354 | @return Returns a block of type \p then_block_t. 355 | */ 356 | @property (nonatomic, readonly) then_block_t then; 357 | 358 | 359 | /*! 360 | @brief Property \p thenOn returns a block whose signature is 361 | @code 362 | RXPromise* (^)(id executionContext, 363 | promise_completionHandler_t onSuccess, 364 | promise_errorHandler_t onError) 365 | @endcode 366 | 367 | When the block is called it will register the completion handler \p onSuccess and 368 | the error handler \p onError. When the receiver will be fulfilled the completion handler 369 | will be executed on the specified execution context \p executionContext. When the 370 | receiver will be rejected the error handler will be called on the specified 371 | execution context \p executionContext. 372 | 373 | @par The receiver will be retained and released only until after the receiver has 374 | been resolved. 375 | 376 | @par The block returns a new \c RXPromise, the "returned promise", whose result will become 377 | the return value of either handler that gets called when the receiver will be resolved. 378 | 379 | @par When the block is invoked and the receiver is already resolved, the corresponding 380 | handler will be immediately asynchronously scheduled for execution on the specified \e execution-context. 381 | 382 | @par Parameter \p onSuccess and \p onError may be \c nil. 383 | 384 | @par Parameter \p executionContext is an "execution context" where blocks can be executed. This can 385 | be a \c dispatch_queue, a \c NSThread, a \c NSOperationQueue or a \c NSManagedObjectContext. 386 | \p executionContext can be \c nil which effectively is the same as when using the 387 | \c then_block_t block returned from property \p then. 388 | 389 | @par The receiver can register zero or more handler (pairs) through clients calling 390 | the block multiple times. 391 | 392 | @return Returns a block of type \c then_on_block_t. 393 | */ 394 | @property (nonatomic, readonly) then_on_block_t thenOn; 395 | 396 | 397 | 398 | /*! 399 | @brief Property \p thenOnMain returns a block whose signature is 400 | @code 401 | RXPromise* (^)(promise_completionHandler_t onSuccess, promise_errorHandler_t onError) 402 | @endcode 403 | 404 | When the block is called it will register the completion handler \p onSuccess and 405 | the error handler \p onError. When the receiver will be fulfilled the completion handler 406 | will be executed on the main thread. When the receiver will be rejected the error 407 | handler will be called and executed on the main thread. 408 | 409 | @par The receiver will be retained and released only until after the receiver has 410 | been resolved. 411 | 412 | @par The block returns a new \c RXPromise, the "returned promise", whose result will become 413 | the return value of either handler that gets called when the receiver will be resolved. 414 | 415 | @par When the block is invoked and the receiver is already resolved, the corresponding 416 | handler will be immediately asynchronously scheduled for execution on the main thread. 417 | 418 | @par Parameter \p onSuccess and \p onError may be \c nil. 419 | 420 | @par The receiver can register zero or more handler (pairs) through clients calling 421 | the block multiple times. 422 | 423 | @return Returns a block of type \c then_on_block_t. 424 | */ 425 | @property (nonatomic, readonly) then_on_main_block_t thenOnMain; 426 | 427 | /*! 428 | @brief Property \p catchOn returns a block whose signature is 429 | @code 430 | RXPromise* (^)(id executionContext, promise_errorHandler_t onError) @endcode 431 | 432 | When this returned block is called it will register the error handler 433 | given as the block parameter with the execution context set as specified. 434 | 435 | @par The receiver will be retained and released only until after the receiver 436 | has been resolved. 437 | 438 | @par The block returns a new \c RXPromise, the "returned promise", whose result will become 439 | the return value of either handler that gets called when the receiver will be resolved. 440 | 441 | @par When the block is invoked and the receiver is already resolved, the corresponding 442 | handler will be immediately asynchronously scheduled for the execution context. 443 | 444 | @par The receiver can register zero or more handlers through clients calling 445 | the block multiple times. 446 | 447 | @return Returns a block of type \p then_block_t. 448 | */ 449 | @property (nonatomic, readonly) catch_on_block_t catchOn; 450 | 451 | /*! 452 | @brief Property \p catchOnMain returns a block whose signature is 453 | @code 454 | RXPromise* (^)(promise_errorHandler_t onError) 455 | @endcode 456 | 457 | When the block is called it will register the error handler \p onError. 458 | When the receiver will be rejected the error handler will be called and 459 | executed on the main thread. 460 | 461 | @par The receiver will be retained and released only until after the receiver has 462 | been resolved. 463 | 464 | @par The block returns a new \c RXPromise, the "returned promise", whose result will become 465 | the return value of the handler if it is called. 466 | 467 | @par When the block is invoked and the receiver is already rejected, the error handler will 468 | be immediately asynchronously scheduled for execution on the main thread. 469 | 470 | @par Parameter \p onError may be \c nil. 471 | 472 | @par The receiver can register zero or more handlers through clients calling 473 | the block multiple times. 474 | 475 | @return Returns a block of type \c catch_on_main_block_t. 476 | */ 477 | @property (nonatomic, readonly) catch_on_main_block_t catchOnMain; 478 | 479 | 480 | 481 | /*! 482 | Returns \c YES if the receiveer is pending. 483 | */ 484 | @property (nonatomic, readonly) BOOL isPending; 485 | 486 | /*! 487 | Returns \c YES if the receiver is fulfilled. 488 | */ 489 | @property (nonatomic, readonly) BOOL isFulfilled; 490 | 491 | /*! 492 | Returns \c YES if the receiver is rejected. 493 | */ 494 | @property (nonatomic, readonly) BOOL isRejected; 495 | 496 | /*! 497 | Returns \c YES if the receiver is cancelled. 498 | */ 499 | @property (nonatomic, readonly) BOOL isCancelled; 500 | 501 | 502 | /*! 503 | Returns the parent promise - the promise which created 504 | the receiver. 505 | */ 506 | @property (nonatomic, readonly) RXPromise* parent; 507 | 508 | 509 | /*! 510 | Returns the root promise. 511 | */ 512 | @property (nonatomic, readonly) RXPromise* root; 513 | 514 | /*! 515 | Cancels the promise unless it is already resolved and then forwards the 516 | message to all children. 517 | */ 518 | - (void) cancel; 519 | 520 | /*! 521 | @brief Cancels the promise with the specfied reason unless it is already resolved and 522 | then forwards the message wto all children. 523 | 524 | @param reason The reason. If reason is not a \c NSError object, the receiver will 525 | create a \c NSError object whose demain is \@"RXPromise", the error code is -1000 526 | and the user dictionary contains an entry with key \c NSLocalizedFailureReason whose 527 | value becomes parameter reason. 528 | */ 529 | - (void) cancelWithReason:(id)reason; 530 | 531 | 532 | /*! 533 | Creates a resolver which rejects the receiver after the specified timeout value 534 | whose reason is a \c NSError object with domain \@"RXPromise" and code = -1001, 535 | unless it has been resolved before elsewhere. 536 | 537 | @param timeout The timeout in seconds. 538 | 539 | @return Returns the receiver. 540 | */ 541 | - (RXPromise*) setTimeout:(NSTimeInterval)timeout; 542 | 543 | 544 | /*! 545 | @brief Binds the receiver to the given promise @p other. 546 | 547 | @discussion The receiver will take in the state of the given promise @p other, and 548 | vice versa: The receiver will be fulfilled or rejected according its bound promise. If the 549 | receiver receives a \c cancel message, the bound promise will be sent a \c cancelWithReason: 550 | message with the receiver's reason.
551 | 552 | @attention A promise should not be bound to more than one other promise.

553 | 554 | 555 | @par Example: @code 556 | - (RXPromise*) doSomethingAsync { 557 | self.promise = [RXPromise new]; 558 | return self.promise; 559 | } 560 | 561 | - (void) handleEvent:(id)event { 562 | RXPromise* other = [self handleEvenAsync:event]; 563 | [self.promise bind:other]; 564 | } 565 | @endcode 566 | 567 | @param other The promise that will bind to the receiver. 568 | */ 569 | - (void) bind:(RXPromise*) other; 570 | 571 | 572 | /*! 573 | @brief Blocks the current thread until after the receiver has been resolved, and 574 | previously queued handlers have been finished. 575 | 576 | @note The method should be used for debugging and testing only. 577 | 578 | */ 579 | - (void) wait; 580 | 581 | 582 | /*! 583 | @brief Runs the current run loop until after the receiver has been resolved, 584 | and previously queued handlers have been finished. 585 | 586 | Prerequisite: The current thread MUST have a run loop with at least one event 587 | source. Otherwise, the behavior is undefined. 588 | 589 | @note The method should be used for debugging and testing only. 590 | */ 591 | - (void) runLoopWait; 592 | 593 | 594 | /*! 595 | Synchronously returns the value of the promise. 596 | 597 | Will block the current thread until after the promise has been resolved. 598 | 599 | @note The method should be used for debugging and testing only. 600 | 601 | @return Returns the _value_ of the receiver. 602 | */ 603 | - (id) get; 604 | 605 | 606 | /*! 607 | @brief Synchronously returns the value of the promise. 608 | 609 | The current thread will be blocked until after the promise has been resolved or the 610 | timeout has been expired. The method does not change the state of the receiver. 611 | 612 | @note The method should be used for debugging and testing only. 613 | 614 | @param timeout The timeout in seconds. 615 | 616 | @return If the timeout has not been expired, returns the value of the receiver. 617 | Otherwise, returns an \c NSError object whose domain equals \@"RXPromise" and whose 618 | code equals -1001. 619 | 620 | */ 621 | - (id) getWithTimeout:(NSTimeInterval)timeout; 622 | 623 | @end 624 | 625 | 626 | 627 | /*! 628 | 629 | See also: `RXPromise` interface. 630 | 631 | The "Deferred" interface is what the asynchronous result provider will see. 632 | It is responisble for creating and resolving the promise. 633 | 634 | */ 635 | @interface RXPromise(Deferred) 636 | 637 | 638 | /*! 639 | @brief Factory method which returns a new promise whose state is fulfilled or 640 | rejected depending on parameter \p result. 641 | 642 | @discussion If result is kind of \c NSError, the promise will be in the \a rejected 643 | state. Otherwise it will be in the \a fulfilled state. Parameter \p result MUST 644 | NOT be a \c RXPromise object. 645 | 646 | @param result The value which resolves the promise. This can be any object except a 647 | \c RXPromise object, or \c nil. 648 | */ 649 | + (instancetype) promiseWithResult:(id)result; 650 | 651 | 652 | /*! 653 | A convenient class method which returns a promise whose associated task is 654 | defined with block \p task. 655 | 656 | The block is asynchronously dispatched on a private queue. The task's return 657 | value will eventually resolve the returned promise. 658 | 659 | @param task The associated task to execute as a block. The return value of the 660 | block will resolve the returned promise. The return value SHALL be a \c NSError 661 | object in order to indicate a failure. _task_ MUST NOT be \c nil. 662 | 663 | @par Example: @code 664 | self.fetchUserPromise = 665 | [RXPromise promiseWithBlock:^id(void) { 666 | id result = [self doSomethingElaborate]; 667 | return result; 668 | }]; 669 | */ 670 | + (RXPromise *)promiseWithTask:(id(^)(void))task; 671 | 672 | 673 | /*! 674 | A convenient class method which returns a promise whose associated task is 675 | defined with block \p task. 676 | 677 | The block is asynchronously dispatched on the specified queue. The task's return 678 | value will eventually resolve the returned promise. 679 | 680 | @param queue The dispatch queue where the task will be executed. 681 | 682 | @param task The associated task to execute as a block. The return value of the 683 | block will resolve the returned promise. The return value SHALL be a \c NSError 684 | object in order to indicate a failure. _task_ MUST NOT be \c nil. 685 | 686 | @par Example: @code 687 | self.fetchUserPromise = [RXPromise promiseWithQueue:dispatch_get_main_queue() task:^id(void) { 688 | id result = [self doSomethingElaborate]; 689 | return result; 690 | }]; 691 | 692 | */ 693 | + (RXPromise *)promiseWithQueue:(dispatch_queue_t)queue task:(id(^)(void))task; 694 | 695 | 696 | 697 | /*! 698 | @brief Factory method which returns a new promise whose state is pending. 699 | 700 | @discussion Resolvers may wish to detect if there is still a "subscriber" listening for 701 | the eventual result without relying on the fact that the subscriber will cancel 702 | the promise if it whishes to abandon the interest in the result. Those resolvers 703 | would keep a week reference as opposed to a strong reference to their associated 704 | promise in order to resolve it. Thus a resolver can detect if there is no subscriber 705 | interested in the result anymore when its promise gets deallocated. Those resolvers 706 | use the block in order to get notified when this happens. Usually, the block 707 | would perform a cancellation of resolver's asynchronous task. 708 | 709 | @param deallocHandler a block which will be called when the promise will be 710 | deallocated. 711 | 712 | */ 713 | + (instancetype) promiseWithDeallocHandler:(void(^)())deallocHandler; 714 | 715 | 716 | 717 | 718 | /*! 719 | @brief Returns a new promise whose state is pending. 720 | 721 | Designated Initializer 722 | */ 723 | - (instancetype)init; 724 | 725 | 726 | /*! 727 | @brief Fulfilles the promise with specified value. 728 | 729 | If the promise is already resolved this method has no effect. 730 | 731 | @param value The result given from the asynchronous result provider. 732 | */ 733 | - (void) fulfillWithValue:(id)value; 734 | 735 | 736 | /*! 737 | @brief Rejects the promise with the specified reason. 738 | 739 | If the promise is already resolved this method has no effect. 740 | 741 | @param reason The reason. If parameter \p reason is not a \c NSError object, the 742 | receiver will create a \c NSError object whose demain is \@"RXPromise", the 743 | error code is -1000 and the user dictionary contains an entry with key 744 | \c NSLocalizedFailureReason whose value becomes parameter reason. 745 | */ 746 | - (void) rejectWithReason:(id)reason; 747 | 748 | 749 | /*! 750 | @brief Resolves the promise with the specified result. 751 | 752 | If parameter \p result is a promise, the receiver will "bind" to the given promise, which 753 | includes to forward cancellation from the receiver to the given promise. 754 | 755 | @param result The result given from the asynchronous result provider which can 756 | can be a promise , \c nil, an \c NSError object or any other object. 757 | */ 758 | - (void) resolveWithResult:(id)result; 759 | 760 | 761 | 762 | @end 763 | 764 | 765 | -------------------------------------------------------------------------------- /Source/RXSettledResult.h: -------------------------------------------------------------------------------- 1 | // 2 | // RXSettledResult.h 3 | // RXPromise Libraries 4 | // 5 | // Created by Luke Melia on 5/13/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface RXSettledResult : NSObject 12 | 13 | @property (nonatomic, assign, readonly, getter=isFulfilled) BOOL fulfilled; 14 | @property (nonatomic, assign, readonly, getter=isRejected) BOOL rejected; 15 | @property (readonly, nonatomic) id result; 16 | 17 | -(instancetype)initWithFulfilled:(BOOL)isFulfilled andResult:(id)valueOrReason; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Source/RXSettledResult.mm: -------------------------------------------------------------------------------- 1 | // 2 | // RXSettledResult.m 3 | // RXPromise Libraries 4 | // 5 | // Created by Luke Melia on 5/13/14. 6 | // 7 | // 8 | 9 | #import "RXSettledResult.h" 10 | 11 | @implementation RXSettledResult { 12 | BOOL _fulfilled; 13 | id _result; 14 | } 15 | 16 | -(instancetype)initWithFulfilled:(BOOL)isFulfilled andResult:(id)valueOrReason; 17 | { 18 | self = [super init]; 19 | if (self) { 20 | _fulfilled = isFulfilled; 21 | _result = valueOrReason; 22 | } 23 | return self; 24 | } 25 | 26 | -(BOOL) isFulfilled { 27 | return _fulfilled; 28 | } 29 | 30 | -(BOOL) isRejected { 31 | return !_fulfilled; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Source/utility/DLog.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLog.h 3 | // 4 | // Copyright 2013 Andreas Grosam 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | 19 | #ifndef DLOG_H 20 | #define DLOG_H 21 | 22 | #define DEBUG_LOGLEVEL_DEBUG 4 23 | #define DEBUG_LOGLEVEL_INFO 3 24 | #define DEBUG_LOGLEVEL_WARN 2 25 | #define DEBUG_LOGLEVEL_ERROR 1 26 | #define DEBUG_LOGLEVEL_NONE 0 27 | 28 | 29 | // DEBUG_LOG_MIN 30 | // If DEBUG_LOG and DEBUG_LOG_MIN is defined and DEBUG_LOG_MIN > DEBUG_LOG, 31 | // DEBUG_LOG will be set to DEBUG_LOG_MIN. 32 | // For example, if DEBUG_LOG_MIN is defined as DEBUG_LOGLEVEL_WARN and DEBUG_LOG 33 | // is defined as DEBUGG_LOG_ERROR, all warnings and errors will be logged. 34 | // 35 | // DEBUG_LOG_MAX 36 | // If DEBUG_LOG and DEBUG_LOG_MAX is defined and DEBUG_LOG > DEBUG_LOG_MAX 37 | // DEBUG_LOG will be set to DEBUG_LOG_MAX. 38 | // For example, if DEBUG_LOG is defined as DEBUG_LOGLEVEL_DEBUG and DEBUG_LOG_MAX 39 | // is defined as DEBUG_LOGLEVEL_ERROR, DEBUG_LOG will become DEBUG_LOGLEVEL_ERROR, 40 | // which only logs all errors (and messages with higher severity). 41 | 42 | 43 | #if !defined (DEBUG_LOG) 44 | #if defined (NDEBUG) 45 | #define DEBUG_LOG DEBUG_LOGLEVEL_ERROR 46 | #elif defined (DEBUG) 47 | #define DEBUG_LOG DEBUG_LOGLEVEL_DEBUG 48 | #warning DEBUG_LOG not defined - set to default: DEBUG_LOGLEVEL_DEBUG 49 | #else 50 | #define DEBUG_LOG DEBUG_LOGLEVEL_WARN 51 | #warning DEBUG_LOG not defined - set to default: DEBUG_LOGLEVEL_WARN 52 | #endif 53 | #else 54 | #if defined (DEBUG_LOG_MIN) && (DEBUG_LOG_MIN > DEBUG_LOG) 55 | #undef DEBUG_LOG 56 | #define DEBUG_LOG DEBUG_LOG_MIN 57 | #endif 58 | #if defined (DEBUG_LOG_MAX) && (DEBUG_LOG_MAX < DEBUG_LOG) 59 | #undef DEBUG_LOG 60 | #define DEBUG_LOG DEBUG_LOG_MAX 61 | #endif 62 | #endif 63 | 64 | #if defined (DEBUG_LOG) && (DEBUG_LOG >= DEBUG_LOGLEVEL_DEBUG) 65 | #define DLogDebug(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__]) 66 | #else 67 | #define DLogDebug(...) do { } while (0) 68 | #endif 69 | 70 | #if defined (DEBUG_LOG) && (DEBUG_LOG >= DEBUG_LOGLEVEL_INFO) 71 | #define DLogInfo(...) NSLog(@"INFO: %s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__]) 72 | #else 73 | #define DLogInfo(...) do { } while (0) 74 | #endif 75 | 76 | #if defined (DEBUG_LOG) && (DEBUG_LOG >= DEBUG_LOGLEVEL_WARN) 77 | #define DLogWarn(...) NSLog(@"WARNING: %s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__]) 78 | #else 79 | #define DLogWarn(...) do { } while (0) 80 | #endif 81 | 82 | #if !defined (DEBUG_LOG) || (DEBUG_LOG >= DEBUG_LOGLEVEL_ERROR) 83 | #define DLogError(...) NSLog(@"ERROR: %s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__]) 84 | #else 85 | #define DLogError(...) do { } while (0) 86 | #endif 87 | 88 | #define DLog(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__]) 89 | 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /Tests/DLog.h: -------------------------------------------------------------------------------- 1 | // 2 | // DLog.h 3 | // 4 | // Copyright 2013 Andreas Grosam 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | 19 | #ifndef DLOG_H 20 | #define DLOG_H 21 | 22 | #define DEBUG_LOGLEVEL_DEBUG 4 23 | #define DEBUG_LOGLEVEL_INFO 3 24 | #define DEBUG_LOGLEVEL_WARN 2 25 | #define DEBUG_LOGLEVEL_ERROR 1 26 | #define DEBUG_LOGLEVEL_NONE 0 27 | 28 | 29 | // DEBUG_LOG_MIN 30 | // If DEBUG_LOG and DEBUG_LOG_MIN is defined and DEBUG_LOG_MIN > DEBUG_LOG, 31 | // DEBUG_LOG will be set to DEBUG_LOG_MIN. 32 | // For example, if DEBUG_LOG_MIN is defined as DEBUG_LOGLEVEL_WARN and DEBUG_LOG 33 | // is defined as DEBUGG_LOG_ERROR, all warnings and errors will be logged. 34 | // 35 | // DEBUG_LOG_MAX 36 | // If DEBUG_LOG and DEBUG_LOG_MAX is defined and DEBUG_LOG > DEBUG_LOG_MAX 37 | // DEBUG_LOG will be set to DEBUG_LOG_MAX. 38 | // For example, if DEBUG_LOG is defined as DEBUG_LOGLEVEL_DEBUG and DEBUG_LOG_MAX 39 | // is defined as DEBUG_LOGLEVEL_ERROR, DEBUG_LOG will become DEBUG_LOGLEVEL_ERROR, 40 | // which only logs all errors (and messages with higher severity). 41 | 42 | 43 | #if !defined (DEBUG_LOG) 44 | #if defined (NDEBUG) 45 | #define DEBUG_LOG DEBUG_LOGLEVEL_ERROR 46 | #elif defined (DEBUG) 47 | #define DEBUG_LOG DEBUG_LOGLEVEL_DEBUG 48 | #warning DEBUG_LOG not defined - set to default: DEBUG_LOGLEVEL_DEBUG 49 | #else 50 | #define DEBUG_LOG DEBUG_LOGLEVEL_WARN 51 | #warning DEBUG_LOG not defined - set to default: DEBUG_LOGLEVEL_WARN 52 | #endif 53 | #else 54 | #if defined (DEBUG_LOG_MIN) && (DEBUG_LOG_MIN > DEBUG_LOG) 55 | #undef DEBUG_LOG 56 | #define DEBUG_LOG DEBUG_LOG_MIN 57 | #endif 58 | #if defined (DEBUG_LOG_MAX) && (DEBUG_LOG_MAX < DEBUG_LOG) 59 | #undef DEBUG_LOG 60 | #define DEBUG_LOG DEBUG_LOG_MAX 61 | #endif 62 | #endif 63 | 64 | #if defined (DEBUG_LOG) && (DEBUG_LOG >= DEBUG_LOGLEVEL_DEBUG) 65 | #define DLogDebug(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__]) 66 | #else 67 | #define DLogDebug(...) do { } while (0) 68 | #endif 69 | 70 | #if defined (DEBUG_LOG) && (DEBUG_LOG >= DEBUG_LOGLEVEL_INFO) 71 | #define DLogInfo(...) NSLog(@"INFO: %s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__]) 72 | #else 73 | #define DLogInfo(...) do { } while (0) 74 | #endif 75 | 76 | #if defined (DEBUG_LOG) && (DEBUG_LOG >= DEBUG_LOGLEVEL_WARN) 77 | #define DLogWarn(...) NSLog(@"WARNING: %s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__]) 78 | #else 79 | #define DLogWarn(...) do { } while (0) 80 | #endif 81 | 82 | #if !defined (DEBUG_LOG) || (DEBUG_LOG >= DEBUG_LOGLEVEL_ERROR) 83 | #define DLogError(...) NSLog(@"ERROR: %s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__]) 84 | #else 85 | #define DLogError(...) do { } while (0) 86 | #endif 87 | 88 | #define DLog(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__]) 89 | 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /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.5 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | --------------------------------------------------------------------------------