├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md ├── actions │ └── set-versions │ │ ├── action.yml │ │ └── index.js └── workflows │ ├── ci.yml │ ├── docs.yml │ ├── release-pr.yml │ └── release-push.yml ├── .gitignore ├── .jazzy.yml ├── .swiftlint.yml ├── BNRDeferred.podspec ├── Configurations ├── Base.xcconfig ├── Debug.xcconfig ├── Framework.xcconfig ├── Release.xcconfig └── Tests.xcconfig ├── Deferred.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ ├── IDETemplateMacros.plist │ └── xcschemes │ └── Deferred.xcscheme ├── Documentation ├── Articles.md └── Guide │ ├── Basic Tasks.md │ ├── Getting Started.md │ ├── Mastering The Future Type.md │ ├── Migrating to Swift Concurrency.md │ └── Why Deferred?.md ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── CAtomics │ ├── Empty.c │ └── include │ │ ├── CAtomics.h │ │ └── module.modulemap ├── Deferred │ ├── Atomics.swift │ ├── Deferred.swift │ ├── DeferredQueue.swift │ ├── DeferredVariant.swift │ ├── Executor.swift │ ├── ExistentialFuture.swift │ ├── Future.swift │ ├── FutureAndThen.swift │ ├── FutureAsync.swift │ ├── FutureCollections.swift │ ├── FutureComposition.swift │ ├── FutureEveryMap.swift │ ├── FutureIgnore.swift │ ├── FutureMap.swift │ ├── FuturePeek.swift │ ├── FutureUpon.swift │ ├── Locking.swift │ ├── Promise.swift │ └── Protected.swift ├── Info.plist └── Task │ ├── Either.swift │ ├── ExistentialTask.swift │ ├── Progress+ExplicitComposition.swift │ ├── Progress+Future.swift │ ├── Task.swift │ ├── TaskAndThen.swift │ ├── TaskAsync.swift │ ├── TaskChain.swift │ ├── TaskCollections.swift │ ├── TaskComposition.swift │ ├── TaskEveryMap.swift │ ├── TaskFallback.swift │ ├── TaskIgnore.swift │ ├── TaskMap.swift │ ├── TaskPromise.swift │ ├── TaskRecovery.swift │ ├── TaskResult.swift │ └── TaskUpon.swift └── Tests ├── AllTestsCommon.swift ├── DeferredTests ├── AllTestsCommon.swift ├── DeferredTests.swift ├── ExistentialFutureTests.swift ├── FilledDeferredTests.swift ├── FutureAsyncTests.swift ├── FutureCustomExecutorTests.swift ├── FutureIgnoreTests.swift ├── FutureTests.swift ├── ObjectDeferredTests.swift ├── PerformanceTests.swift ├── ProtectedTests.swift └── SwiftBugTests.swift ├── Info.plist ├── LinuxMain.swift └── TaskTests ├── AllTestsCommon.swift ├── TaskAsyncTests.swift ├── TaskCompositionTests.swift ├── TaskComprehensiveTests.swift ├── TaskProgressTests.swift ├── TaskProtocolTests.swift ├── TaskResultTests.swift └── TaskTests.swift /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at cocoa-engineering@bignerdranch.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Pull requests and issues are very welcome! 2 | 3 | If you run into bugs, design problems, or have suggestions (basically, 4 | anything!), please create a GitHub issue. 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #### What's in this pull request? 4 | 5 | 6 | #### Testing 7 | 8 | 9 | #### API Changes 10 | 11 | -------------------------------------------------------------------------------- /.github/actions/set-versions/action.yml: -------------------------------------------------------------------------------- 1 | name: Set versions 2 | inputs: 3 | version: 4 | description: 'Full semantic version.' 5 | required: true 6 | version_optimistic: 7 | description: 'Major and minor digits (x.y.0) of semantic version.' 8 | required: true 9 | runs: 10 | using: 'node12' 11 | main: 'index.js' 12 | -------------------------------------------------------------------------------- /.github/actions/set-versions/index.js: -------------------------------------------------------------------------------- 1 | const { promises: fs } = require('fs') 2 | 3 | async function replace(path, regexp, replacement) { 4 | const text = await fs.readFile(path, 'utf-8') 5 | const replacedText = text.replace(regexp, replacement) 6 | await fs.writeFile(path, replacedText) 7 | } 8 | 9 | async function run() { 10 | try { 11 | const version = process.env.INPUT_VERSION 12 | if (!version) { 13 | throw new Error("Input required and not supplied: version") 14 | } 15 | 16 | const versionOptimistic = process.env.INPUT_VERSION_OPTIMISTIC 17 | if (!versionMajor) { 18 | throw new Error("Input required and not supplied: version_major") 19 | } 20 | 21 | await Promise.all([ 22 | replace('Configurations/Base.xcconfig', /^(CURRENT_PROJECT_VERSION +=).*$/gm, `$1 ${version}`), 23 | replace('BNRDeferred.podspec', /^(\s*\w+\.version\s*= )\S*$/gm, `$1"${version}"`), 24 | replace('Documentation/Guide/Getting Started.md', /^(```\ngithub "bignerdranch\/Deferred" ).*(\n```)$/gm, `$1~> ${versionOptimistic}$2`), 25 | replace('Documentation/Guide/Getting Started.md', /^(```ruby\npod 'BNRDeferred', ').*('\n```)$/gm, `$1~> ${versionOptimistic}$2`), 26 | replace('Documentation/Guide/Getting Started.md', /^(\s*\.package\(url: ".*", from: ).*(\),?)$/gm, `$1"${versionOptimistic}.0"$2`) 27 | ]) 28 | } catch (error) { 29 | process.exitCode = 1 30 | process.stdout.write(`::error::${error.message}\n`) 31 | } 32 | } 33 | 34 | run() 35 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches-ignore: 5 | - gh-pages 6 | jobs: 7 | test_iOS: 8 | name: Test iOS 9 | runs-on: macos-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Run xcodebuild 13 | env: 14 | SCHEME: Deferred 15 | DESTINATION: platform=iOS Simulator,name=iPhone 11,OS=latest 16 | run: xcodebuild -scheme "$SCHEME" -destination "$DESTINATION" -quiet test 17 | test_macOS: 18 | name: Test macOS 19 | runs-on: macos-latest 20 | steps: 21 | - uses: actions/checkout@v1 22 | - name: Run swift test 23 | run: swift test --parallel 24 | - name: Check build for playgrounds 25 | env: 26 | SCHEME: Deferred 27 | DESTINATION: 'generic/platform=iOS' 28 | run: xcodebuild -scheme "$SCHEME" -destination "$DESTINATION" SWIFT_ACTIVE_COMPILATION_CONDITIONS="XCODE FORCE_PLAYGROUND_COMPATIBILITY" -quiet build 29 | - name: Run pod lib lint 30 | run: pod lib lint --use-libraries --skip-tests --platforms=macos 31 | test_Linux: 32 | name: Test Linux 33 | runs-on: ubuntu-latest 34 | container: swift:5.2 35 | steps: 36 | - uses: actions/checkout@v1 37 | - name: Run swift test 38 | run: swift test --parallel --sanitize=thread 39 | - name: Run swiftlint 40 | uses: norio-nomura/action-swiftlint@3.1.0 41 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | on: 3 | push: 4 | branches-ignore: 5 | - gh-pages 6 | jobs: 7 | build: 8 | name: Generate docs 9 | runs-on: macOS-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v1 13 | - name: Gem cache 14 | uses: actions/cache@v1 15 | with: 16 | path: vendor/bundle 17 | key: gems-${{ runner.OS }}- 18 | - name: Run jazzy 19 | run: | 20 | bundle init 21 | bundle add jazzy --skip-install 22 | bundle install --path vendor/bundle --clean 23 | bundle exec jazzy --config .jazzy.yml 24 | chmod -R 0700 docs 25 | - name: Upload generated docs 26 | uses: actions/upload-artifact@v1 27 | with: 28 | name: Documentation 29 | path: docs 30 | deploy: 31 | name: Upload to GitHub Pages 32 | runs-on: ubuntu-latest 33 | needs: build 34 | if: github.ref == 'refs/heads/master' 35 | steps: 36 | - name: Download generated docs 37 | uses: actions/download-artifact@v1 38 | with: 39 | name: Documentation 40 | path: docs 41 | - uses: zwaldowski/git-commit-action@v1 42 | with: 43 | working_directory: docs 44 | commit_message: 'Publish from ${{ github.event_name }} to ${{ github.sha }}' 45 | - name: git push 46 | uses: ad-m/github-push-action@master 47 | with: 48 | github_token: ${{ secrets.GITHUB_TOKEN }} 49 | directory: docs 50 | branch: gh-pages 51 | force: true 52 | -------------------------------------------------------------------------------- /.github/workflows/release-pr.yml: -------------------------------------------------------------------------------- 1 | name: Release checks 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | types: 7 | - opened 8 | - labeled 9 | - unlabeled 10 | jobs: 11 | includes_version_bump: 12 | name: Labels include one of 'major', 'minor', or 'patch' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Check PR labels 16 | uses: zwaldowski/match-label-action@v1 17 | with: 18 | allowed: major,minor,patch 19 | -------------------------------------------------------------------------------- /.github/workflows/release-push.yml: -------------------------------------------------------------------------------- 1 | name: Release management 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | types: 7 | - closed 8 | jobs: 9 | create_release: 10 | name: Create release 11 | runs-on: macOS-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - id: bump 15 | name: Determine version increment 16 | uses: zwaldowski/match-label-action@v1 17 | with: 18 | allowed: major,minor,patch 19 | - id: next_version 20 | name: Determine incremented version 21 | uses: zwaldowski/semver-release-action@v1 22 | with: 23 | dry_run: true 24 | bump: ${{ steps.bump.outputs.match }} 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | - uses: ./.github/actions/set-versions 27 | with: 28 | version: ${{ steps.next_version.outputs.version }} 29 | version_optimistic: ${{ steps.next_version.outputs.version_optimistic }} 30 | - id: git_commit 31 | uses: zwaldowski/git-commit-action@v1 32 | with: 33 | commit_message: 'Bump version to ${{ steps.next_version.outputs.version }}' 34 | - uses: ad-m/github-push-action@master 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | - uses: zwaldowski/semver-release-action@v1 38 | with: 39 | github_token: ${{ secrets.GITHUB_TOKEN }} 40 | sha: ${{ steps.git_commit.outputs.sha }} 41 | - run: pod trunk push 42 | env: 43 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Build generated 2 | build/ 3 | DerivedData/ 4 | 5 | ## Various settings 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | 16 | ## Other 17 | *.moved-aside 18 | *.xccheckout 19 | *.xcscmblueprint 20 | 21 | # Swift Package Manager 22 | Packages/ 23 | .build/ 24 | 25 | # Jazzy 26 | docs/ 27 | -------------------------------------------------------------------------------- /.jazzy.yml: -------------------------------------------------------------------------------- 1 | # jazzy --config .jazzy.yml 2 | author: "Big Nerd Ranch" 3 | author_url: "https://bignerdranch.github.io" 4 | copyright: "© 2014-2020 [Big Nerd Ranch](//www.bignerdranch.com) under [MIT license](//github.com/bignerdranch/Deferred/blob/master/LICENSE)." 5 | custom_categories: 6 | - name: Articles 7 | children: 8 | - Why Deferred? 9 | - Getting Started 10 | - Basic Tasks 11 | - Mastering The Future Type 12 | - Migrating to Swift Concurrency 13 | - name: Futures and Promises 14 | children: 15 | - Deferred 16 | - FutureProtocol 17 | - PromiseProtocol 18 | - name: Transforming Futures 19 | children: 20 | - Future 21 | - Collection 22 | - Sequence 23 | - name: Controlling Where Callbacks Execute 24 | children: 25 | - Executor 26 | - DispatchQueue 27 | - OperationQueue 28 | - RunLoop 29 | - CFRunLoop 30 | - name: Futures Than Can Fail 31 | children: 32 | - TaskResult 33 | - Task 34 | - TaskProtocol 35 | - Either 36 | - name: Coordinating Access to Data 37 | children: 38 | - Protected 39 | - Locking 40 | - name: Coordination Primitives 41 | children: 42 | - NativeLock 43 | - POSIXReadWriteLock 44 | - DispatchSemaphore 45 | - NSLock 46 | documentation: Documentation/Guide/*.md 47 | abstract: Documentation/*.md 48 | github_url: "https://github.com/bignerdranch/Deferred" 49 | head: | 50 | 51 | 52 | 53 | 54 | hide_documentation_coverage: true 55 | module: Deferred 56 | root_url: "https://bignerdranch.github.io/Deferred/" 57 | theme: fullwidth 58 | use_safe_filenames: true 59 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - block_based_kvo 3 | - deployment_target 4 | - nesting 5 | - todo 6 | opt_in_rules: 7 | - empty_count 8 | - vertical_whitespace 9 | line_length: 220 10 | excluded: 11 | - vendor/bundle 12 | -------------------------------------------------------------------------------- /BNRDeferred.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 3 | s.name = "BNRDeferred" 4 | s.version = "4.1.0" 5 | s.summary = "Work with values that haven't been determined yet." 6 | 7 | s.description = <<-DESC 8 | Deferred is an asynchronous promise-style API that can be used as an 9 | alternative to the "block callback" pattern. It lets you work with values that 10 | haven't been determined yet, like an array that's coming later (one day!) from 11 | a web service call. It was originally inspired by OCaml's Deferred library. 12 | DESC 13 | 14 | s.homepage = "https://github.com/bignerdranch/Deferred" 15 | s.documentation_url = "https://bignerdranch.github.io/Deferred/" 16 | 17 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 18 | s.license = { :type => 'MIT', :file => 'LICENSE' } 19 | 20 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 21 | s.authors = {"Zachary Waldowski" => "zachary@bignerdranch.com", 22 | "Big Nerd Ranch" => nil} 23 | 24 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 25 | s.swift_version = "5.1" 26 | s.cocoapods_version = ">=1.1.0" 27 | s.ios.deployment_target = "10.0" 28 | s.osx.deployment_target = "10.12" 29 | s.watchos.deployment_target = "3.0" 30 | s.tvos.deployment_target = "10.0" 31 | 32 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 33 | s.source = { :git => "https://github.com/bignerdranch/Deferred.git", :tag => "#{s.version}" } 34 | 35 | # ――― Source Settings ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 36 | s.source_files = "Sources/**/*.swift" 37 | s.preserve_path = "Sources/CAtomics" 38 | s.module_name = "Deferred" 39 | 40 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 41 | s.pod_target_xcconfig = { "SWIFT_INCLUDE_PATHS": "$(PODS_TARGET_SRCROOT)/Sources/CAtomics/include" } 42 | 43 | end 44 | -------------------------------------------------------------------------------- /Configurations/Base.xcconfig: -------------------------------------------------------------------------------- 1 | // Build Options 2 | TOOLCHAINS = default 3 | 4 | // Architectures 5 | SDKROOT = iphoneos 6 | SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator 7 | 8 | // Deployment 9 | COPY_PHASE_STRIP = NO 10 | TARGETED_DEVICE_FAMILY = 1,2,3,4,6 11 | MACOSX_DEPLOYMENT_TARGET = 10.12 12 | IPHONEOS_DEPLOYMENT_TARGET = 10.0 13 | WATCHOS_DEPLOYMENT_TARGET = 3.0 14 | TVOS_DEPLOYMENT_TARGET = 10.0 15 | 16 | // Search Paths 17 | ALWAYS_SEARCH_USER_PATHS = NO 18 | 19 | // Signing 20 | CODE_SIGN_IDENTITY = 21 | DEVELOPMENT_TEAM = 22 | 23 | // Versioning 24 | CURRENT_PROJECT_VERSION = 4.1.0 25 | VERSION_INFO_PREFIX = 26 | VERSIONING_SYSTEM = apple-generic 27 | 28 | // C Compiler - Code Generation 29 | GCC_DYNAMIC_NO_PIC = NO 30 | GCC_NO_COMMON_BLOCKS = YES 31 | 32 | // C Compiler - Language 33 | GCC_C_LANGUAGE_STANDARD = gnu11 34 | 35 | // C Compiler - Language - Objective-C 36 | CLANG_ENABLE_OBJC_ARC = YES 37 | CLANG_ENABLE_OBJC_WEAK = YES 38 | 39 | // C Compiler - Preprocessing 40 | ENABLE_STRICT_OBJC_MSGSEND = YES 41 | 42 | // C Compiler - Warnings - All Languages 43 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES 44 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES 45 | CLANG_WARN_EMPTY_BODY = YES 46 | CLANG_WARN_BOOL_CONVERSION = YES 47 | CLANG_WARN_CONSTANT_CONVERSION = YES 48 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES 49 | CLANG_WARN_ENUM_CONVERSION = YES 50 | CLANG_WARN_INT_CONVERSION = YES 51 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES 52 | CLANG_WARN_INFINITE_RECURSION = YES 53 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR 54 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES 55 | CLANG_WARN_STRICT_PROTOTYPES = YES 56 | CLANG_WARN_COMMA = YES 57 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 58 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE 59 | CLANG_WARN_UNREACHABLE_CODE = YES 60 | GCC_WARN_UNUSED_FUNCTION = YES 61 | GCC_WARN_UNUSED_VARIABLE = YES 62 | 63 | // C Compiler - Warnings - C++ 64 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES 65 | CLANG_WARN_SUSPICIOUS_MOVE = YES 66 | 67 | // C Compiler - Warnings - Objective-C 68 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR 69 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 70 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES 71 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES 72 | GCC_WARN_UNDECLARED_SELECTOR = YES 73 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR 74 | 75 | // C Compiler - Warnings - Objective-C and ARC 76 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 77 | 78 | // Static Analyzer - Generic Issues 79 | CLANG_ANALYZER_NONNULL = YES 80 | 81 | // Static Analyzer - Issues - Apple APIs 82 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE 83 | 84 | // Swift Compiler - Version 85 | SWIFT_VERSION = 5.0 86 | -------------------------------------------------------------------------------- /Configurations/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Base.xcconfig" 2 | 3 | // Architectures 4 | ONLY_ACTIVE_ARCH = YES 5 | 6 | // Build Options 7 | DEBUG_INFORMATION_FORMAT = dwarf 8 | ENABLE_TESTABILITY = YES 9 | 10 | // C Compiler - Code Generation 11 | GCC_OPTIMIZATION_LEVEL = 0 12 | 13 | // Swift Compiler - Code Generation 14 | SWIFT_OPTIMIZATION_LEVEL = -Onone 15 | 16 | // Swift Compiler - Custom Flags 17 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG 18 | -------------------------------------------------------------------------------- /Configurations/Framework.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Base.xcconfig" 2 | 3 | // Build Options 4 | APPLICATION_EXTENSION_API_ONLY = YES 5 | 6 | // Deployment 7 | INSTALL_PATH = $(LOCAL_LIBRARY_DIR)/Frameworks 8 | SKIP_INSTALL = YES 9 | COMBINE_HIDPI_IMAGES[sdk=macosx*] = YES 10 | 11 | // Linking 12 | DYLIB_COMPATIBILITY_VERSION = 1 13 | DYLIB_CURRENT_VERSION = 1 14 | DYLIB_INSTALL_NAME_BASE = @rpath 15 | LD_RUNPATH_SEARCH_PATHS = '@executable_path/Frameworks' '@loader_path/Frameworks' 16 | LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = '@executable_path/../Frameworks' '@loader_path/Frameworks' 17 | 18 | // Packaging 19 | INFOPLIST_FILE = Sources/Info.plist 20 | PRODUCT_BUNDLE_IDENTIFIER = com.bignerdranch.$(TARGET_NAME) 21 | PRODUCT_NAME = Deferred 22 | 23 | // Swift Compiler - General 24 | SWIFT_INSTALL_OBJC_HEADER = NO 25 | 26 | // Swift Compiler - Search Paths 27 | SWIFT_INCLUDE_PATHS = Sources/CAtomics/include 28 | -------------------------------------------------------------------------------- /Configurations/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Base.xcconfig" 2 | 3 | // Build Options 4 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 5 | VALIDATE_PRODUCT = YES 6 | 7 | // C Compiler - Preprocessing 8 | ENABLE_NS_ASSERTIONS = NO 9 | 10 | // Swift Compiler - Code Generation 11 | SWIFT_OPTIMIZATION_LEVEL = -O 12 | SWIFT_COMPILATION_MODE = wholemodule 13 | -------------------------------------------------------------------------------- /Configurations/Tests.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Base.xcconfig" 2 | 3 | // Architectures 4 | SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator appletvos appletvsimulator 5 | 6 | // Build Options 7 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 8 | 9 | // Deployment (macOS) 10 | COMBINE_HIDPI_IMAGES = YES 11 | 12 | // Linking 13 | LD_RUNPATH_SEARCH_PATHS = '@executable_path/Frameworks' '@loader_path/Frameworks' 14 | LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = '@executable_path/../Frameworks' '@loader_path/../Frameworks' 15 | 16 | // Packaging 17 | INFOPLIST_FILE = Tests/Info.plist 18 | PRODUCT_BUNDLE_IDENTIFIER = com.bignerdranch.$(TARGET_NAME) 19 | PRODUCT_NAME = $(TARGET_NAME) 20 | -------------------------------------------------------------------------------- /Deferred.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Deferred.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Deferred.xcodeproj/xcshareddata/IDETemplateMacros.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILEHEADER 6 | 7 | // ___FILENAME___ 8 | // ___PRODUCTNAME___ 9 | // 10 | // Created by ___FULLUSERNAME___ on ___DATE___. 11 | // Copyright © ___YEAR___ ___ORGANIZATIONNAME___. Licensed under MIT. 12 | // 13 | 14 | 15 | -------------------------------------------------------------------------------- /Deferred.xcodeproj/xcshareddata/xcschemes/Deferred.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 38 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 64 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /Documentation/Articles.md: -------------------------------------------------------------------------------- 1 | # Intutition 2 | 3 | A `Deferred` is a value that might be unknown now but is expected to resolve to a definite `Value` at some time in the future. It is resolved by being "filled" with a `Value`. 4 | 5 | A `Deferred` represents a `Value`. If you just want to work with the eventual `Value`, use `upon`. If you want to produce an `OtherValue` future from a `Deferred`, check out `map(upon:transform:)` and `andThen(upon:start:)`. 6 | 7 | You can wait for an array of values to resolve with `Collection.allFilled()`, or just take the first one to resolve with `Sequence.firstFilled()`. (`allFilled` for just a handful of futures is so common, there's a few variations of `and` to save you the trouble of putting them in an array.) 8 | 9 | ## Gotcha: No Double-Stuffed `Deferred`s 10 | 11 | It's a no-op to `fill(with:)` an already-`fill`ed `Deferred`. Use `mustFill(with:)` if you want to treat this state as an error. 12 | -------------------------------------------------------------------------------- /Documentation/Guide/Basic Tasks.md: -------------------------------------------------------------------------------- 1 | ## Basic Tasks 2 | 3 | ### Vending a Future Value 4 | 5 | ```swift 6 | // Potentially long-running operation. 7 | func performOperation() -> Deferred { 8 | // 1. Create deferred. 9 | let deferred = Deferred() 10 | 11 | // 2. Kick off asynchronous code that will eventually… 12 | let queue: DispatchQueue = /* … */ 13 | queue.async { 14 | let result = computeResult() 15 | 16 | // 3. … fill the deferred in with its value 17 | deferred.fill(with: result) 18 | } 19 | 20 | // 4. Return the (currently still unfilled) deferred 21 | return deferred 22 | } 23 | ``` 24 | 25 | ### Taking Action when a Future Is Filled 26 | 27 | 28 | 29 | You can use the `upon(_:execute:)` method to run a function once the `Deferred` has been filled. `upon(_:execute:)` can be called multiple times, and the closures will be called in the order they were supplied to `upon(_:execute:)`. 30 | 31 | By default, `upon(_:)` will run the closures on a background concurrent GCD queue. You can change this by passing a different queue when using the full `upon(_:execute:)` method to specify a queue for the closure. 32 | 33 | ```swift 34 | let deferredResult = performOperation() 35 | 36 | deferredResult.upon { result in 37 | print("got \(result)") 38 | } 39 | ``` 40 | ### Peeking at the Current Value 41 | 42 | Use the `peek()` method to determine whether or not the `Deferred` is currently 43 | filled. 44 | 45 | ```swift 46 | let deferredResult = performOperation() 47 | 48 | if let result = deferredResult.peek() { 49 | print("filled with \(result)") 50 | } else { 51 | print("currently unfilled") 52 | } 53 | ``` 54 | 55 | ### Blocking on Fulfillment 56 | 57 | Use the `wait(until:)` method to wait for a `Deferred` to be filled, and return the value. 58 | 59 | ```swift 60 | // warning: Blocks the calling thread! 61 | let result: Int = performOperation().wait(until: .distantFuture)! 62 | ``` 63 | 64 | ### Sequencing Deferreds 65 | 66 | Monadic `map(upon:transform:)` and `andThen(upon:start:)` are available to chain `Deferred` results. For example, suppose you have a method that asynchronously reads a string, and you want to call `Int.init(_:)` on that string: 67 | 68 | ```swift 69 | // Producer 70 | func readString() -> Deferred { 71 | let deferredResult = Deferred() 72 | // call something async to fill deferredResult… 73 | return deferredResult 74 | } 75 | 76 | // Consumer 77 | let deferredInt: Future = readString().map(upon: .any()) { Int($0) } 78 | ``` 79 | 80 | ### Combining Deferreds 81 | 82 | There are three functions available for combining multiple `Deferred` instances: 83 | 84 | ```swift 85 | // MARK: and 86 | 87 | // `and` creates a new future that is filled once both inputs are available: 88 | let d1: Deferred = /* … */ 89 | let d2: Deferred = /* … */ 90 | let dBoth: Future<(Int, String)> = d1.and(d2) 91 | 92 | // MARK: allFilled 93 | 94 | // `allFilled` creates a new future that is filled once all inputs are available. 95 | // All of the input Deferreds must contain the same type. 96 | var deferreds: [Deferred] = [] 97 | for i in 0 ..< 10 { 98 | deferreds.append(/* … */) 99 | } 100 | 101 | // Once all 10 input deferreds are filled, the item at index `i` in the array passed to `upon` will contain the result of `deferreds[i]`. 102 | let allDeferreds: Future<[Int]> = deferreds.allFilled 103 | 104 | // MARK: firstFilled 105 | 106 | // `firstFilled ` creates a new future that is filled once any one of its inputs is available. 107 | // If multiple inputs become available simultaneously, no guarantee is made about which will be selected. 108 | // Once any one of the 10 inputs is filled, `anyDeferred` will be filled with that value. 109 | let anyDeferred: Future = deferreds. firstFilled() 110 | ``` 111 | 112 | ### Cancellation 113 | 114 | Cancellation gets pretty ugly with callbacks. 115 | You often have to fork a bunch of, "Wait, has this been cancelled?" 116 | checks throughout your code. 117 | 118 | With `Deferred`, it's nothing special: 119 | You resolve the `Deferred` to a default value, 120 | and all the work waiting for your `Deferred` to resolve 121 | is unchanged. It's still a `Value`, whatever its provenance. 122 | This is the power of regarding a `Deferred` as just a `Value` that 123 | hasn't quite been nailed down yet. 124 | 125 | That solves cancellation for consumers of the value. 126 | But generally you have some value-producer work you'd like to abort 127 | on cancellation, like stopping a web request that's in flight. 128 | To do this, the producer adds an `upon` closure to the `Deferred` 129 | before vending it to your API consumer. 130 | This closure is responsible for aborting the operation if needed. 131 | Now, if someone defaults the `Deferred` to some `Value`, 132 | the `upon` closure will run and cancel the in-flight operation. 133 | 134 | Let's look at cancelling our `fetchFriends(for:)` request: 135 | 136 | ```swift 137 | // MARK: - Client 138 | 139 | extension FriendsViewController { 140 | 141 | private var friends: Deferred? 142 | 143 | func refreshFriends() { 144 | let friends = fetchFriends(for: jimbob) 145 | friends.upon(.main) { friends in 146 | let names = friends.map { $0.name } 147 | dataSource.array = names 148 | tableView.reloadData() 149 | } 150 | 151 | // Stash the `Deferred` for defaulting later. 152 | self.friends = friends 153 | } 154 | 155 | func cancelFriends() { 156 | friends?.fill(with: []) 157 | } 158 | 159 | } 160 | 161 | // MARK: - Producer 162 | 163 | func fetchFriends(for user: User) -> Deferred<[Friend]> { 164 | let deferredFriends = Deferred<[Friend]>() 165 | let session: NSURLSession = /* … */ 166 | let request: NSURLRequest = /* … */ 167 | let task = session.dataTaskWithRequest(request) { data, response, error in 168 | let friends: [Friend] = parseFriends(data, response, error) 169 | // fillIfUnfulfilled since we might be racing with another producer 170 | // to fill this value 171 | deferredFriends.fillIfUnfulfilled(friends) 172 | } 173 | 174 | // arrange to cancel on fill 175 | deferredFriends.upon { [weak task] _ in 176 | task?.cancel() 177 | } 178 | 179 | // start the operation that will eventually resolve the deferred value 180 | task.resume() 181 | 182 | // finally, pass the deferred value to the caller 183 | return deferredFriends 184 | } 185 | ``` 186 | -------------------------------------------------------------------------------- /Documentation/Guide/Getting Started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Deferred is designed to be used as a framework on Apple platforms, which requires a minimum deployment target of iOS 10, macOS 10.12, watchOS 3.0, or tvOS 10.0. 4 | 5 | Linux is also supported. 6 | 7 | There are a few different options to install Deferred. 8 | 9 | ## Swift Packages in Xcode 10 | 11 | Use Xcode 11 or higher for Swift Packages support. See [Xcode Help: “Link a target to a package product”](https://help.apple.com/xcode/mac/11.4/index.html?localePath=en.lproj#/devb83d64851) or [WWDC 2019: “Adopting Swift Packages in Xcode”](https://developer.apple.com/videos/play/wwdc2019/408/) for details. At the “Choose Package Repository” window, enter the repository URL `https://github.com/bignerdranch/Deferred.git`. 12 | 13 | ### Troubleshooting Swift Packages (Xcode 11) 14 | 15 | If you incorporate Deferred into your project using Swift Packages in Xcode feature, you may find that your project's unit and/or UI testing targets fail to build with the following error: 16 | 17 | ```swift 18 | import Deferred // ERROR: missing required module 'Atomics' 19 | ``` 20 | 21 | This can happen either in Xcode builds or command line builds. This is a known Xcode bug that has received [repeated attention from Apple](https://github.com/apple/swift-nio/issues/1128), but is still [reproducible](https://github.com/apple/swift-nio/issues/1128#issuecomment-588483372), at least as of Xcode 11.3.1. 22 | 23 | If this happens to you, here are workarounds we have found that may help: 24 | 25 | 1. In your unit or UI test target, add the Deferred library to the linked libraries build phase, even if that test target doesn't use it directly. 26 | 27 | 2. *Sometimes Required in Addition to Remedy 1:* If you are also using an Xcode scheme that is pointed at a unit or UI test target, you must also make sure that the "Build" section of the scheme editor includes your application target in that list. By default, if you add a new Xcode scheme for, e.g., a UI test target, the application won't be included in that list. 28 | 29 | Both of these workarounds somehow manage to coax Xcode into including the necessary `-fmodule-map-file` compiler flag for application source files that contain `import Deferred` statements. 30 | 31 | ## Swift Package Manager 32 | 33 | Use Swift 5.1 or greater for [Swift Package Manager](https://swift.org/package-manager/) support. 34 | 35 | Add us to your `Package.swift`: 36 | 37 | ```swift 38 | // swift-tools-version:5.1 39 | import PackageDescription 40 | 41 | let package = Package( 42 | name: "My Extremely Nerdy App", 43 | dependencies: [ 44 | .package(url: "https://github.com/bignerdranch/Deferred.git", from: "4.1.0"), 45 | ] 46 | ) 47 | ``` 48 | 49 | ## CocoaPods 50 | 51 | [CocoaPods](https://cocoapods.org) is a popular, Ruby-inspired Cocoa package manager. 52 | 53 | Add the following to your [Podfile](http://guides.cocoapods.org/using/the-podfile.html): 54 | 55 | ```ruby 56 | pod 'BNRDeferred', '~> 4.0' 57 | ``` 58 | 59 | You will also need to make sure you're opting into using frameworks: 60 | 61 | ```ruby 62 | use_frameworks! 63 | ``` 64 | 65 | Then run `pod install`. 66 | 67 | ## Carthage 68 | 69 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized, hands-off package manager built in Swift. 70 | 71 | Add the following to your Cartfile: 72 | 73 | ```cartfile 74 | github "bignerdranch/Deferred" ~> 4.0 75 | ``` 76 | 77 | Then run `carthage update`. 78 | 79 | Follow the current instructions in [Carthage's README][https://github.com/Carthage/Carthage/blob/master/README.md] for up-to-date installation instructions. 80 | -------------------------------------------------------------------------------- /Documentation/Guide/Mastering The Future Type.md: -------------------------------------------------------------------------------- 1 | ## Mastering The Future Type 2 | 3 | Deferred is designed to scale with the fundamentals you see above. Large applications can be built using just `Deferred` and its `upon` and `fill` methods. 4 | 5 | ### Read-Only Views 6 | 7 | It sometimes just doesn't make *sense* to be able to `fill` something; if you have a `Deferred` wrapping `UIApplication`'s push notification token, what does it mean if someone in your codebase calls `fill` on it? 8 | 9 | You may have noticed that anybody can call `upon` on a `Deferred` type; this is fundamental. But the same is true of `fill`, and this may be a liability as different pieces of code interact with each other. How can we make it **read-only**? 10 | 11 | For this reason, Deferred is split into `FutureProtocol` and `PromiseProtocol`, both protocols the `Deferred` type conforms to. You can think of these as the "reading" and "writing" sides of a deferred value; a future can only be `upon`ed, and a promise can only be `fill`ed. 12 | 13 | Deferred also provides the `Future` type, a wrapper for anything that's a `PromiseProtocol` much like the Swift `Any` types. You can use it protectively to make a `Deferred` read-only. Reconsider the example from above: 14 | 15 | ```swift 16 | extension FriendsViewController { 17 | 18 | // `FriendsViewController` is the only of the `Deferred` in its 19 | // `Promise` role, and can use it as it pleases. 20 | private var friends: Deferred? 21 | 22 | // Now this method can vend a `Future` and not worry about the 23 | // rules of accessing its private `Deferred`. 24 | func refreshFriends() -> Future<[Friend]> { 25 | let friends = fetchFriends(for: jimbob) 26 | friends.upon(.main) { friends in 27 | let names = friends.map { $0.name } 28 | dataSource.array = names 29 | tableView.reloadData() 30 | } 31 | 32 | /* Stash the `Deferred` for defaulting later. */ 33 | self.friends = friends 34 | 35 | return Future(friends) 36 | } 37 | 38 | func cancelFriends() { 39 | friends?.fill(with: []) 40 | } 41 | 42 | } 43 | ``` 44 | 45 | Use of the `Future` type isn't only defensive, it encapsulates and hides implementation details. 46 | 47 | ```swift 48 | extension FriendsStore { 49 | 50 | // dependency, injected later on 51 | var context: NSManagedObjectContext? 52 | 53 | func getLocalFriends(for user: User) -> Future<[Friend]> { 54 | guard let context = context else { 55 | // a future can be created with an immediate value, allowing the benefits 56 | // of Deferred's design even if values are available already (consider a 57 | // stub object, for instance). 58 | return Future([]) 59 | } 60 | 61 | let predicate: NSPredicate = /* … */ 62 | 63 | return Friend.findAll(matching: predicate, inContext: context) 64 | } 65 | 66 | } 67 | ``` 68 | 69 | ### Other Patterns 70 | 71 | As a codebase or team using Deferred gets larger, it may become important to reduce repetition and noise. 72 | 73 | Deferred's abstractions can be extended using protocols. [`FutureProtocol`](http://bignerdranch.github.io/Deferred/Protocols/FutureProtocol.html) gives you all the power of the `Deferred` type on anything you build. 74 | 75 | An example algorithm, included in Deferred, is the `IgnoringFuture`. Simply call `ignored()` to create a future that gets "filled" with `Void`: 76 | 77 | ```swift 78 | func whenFriendsAreLoaded() -> IgnoringFuture { 79 | return self.deferredFriends.ignored() 80 | } 81 | ``` 82 | 83 | This method erases the `Value` of the `Deferred` without the boilerplate of creating a new `Deferred` and having to wait on an `upon`. 84 | 85 | The [`Executor`](http://bignerdranch.github.io/Deferred/Protocols/Executor.html) protocol allows changing the behavior of an `upon` call, and any derived algorithm such as `map` or `andThen`. If your app isn't using a `DispatchQueue` directly, this allows you to adapt other asynchronous mechanisms like `NSManagedObjectContext.performBlock(_:)` for Deferred. 86 | -------------------------------------------------------------------------------- /Documentation/Guide/Migrating to Swift Concurrency.md: -------------------------------------------------------------------------------- 1 | # Cheat Sheet: Migrating to Swift Concurrency 2 | 3 | Swift Concurrency debuted in Swift 5.5 and Xcode 13.0 for code targeting macOS 12, iOS 15, tvOS 15, and watchOS 8. 4 | In Swift 5.5.2 and Xcode 13.2, Swift Concurrency became back-deployable to macOS 10.15, iOS 13, tvOS 13, or watchOS 6. 5 | Language-level concurrency is robust, safe, and efficient. 6 | It obviates the need for a separate framework like Deferred. 7 | 8 | ## Basics 9 | 10 | || Deferred | Swift Concurrency 11 | -|-|- 12 | Deployment Targets | macOS 10.12, iOS 10, tvOS 10, watchOS 3 | macOS 10.15, iOS 13, tvOS 13, or watchOS 6 | 13 | Platforms Supported | macOS, iOS, tvOS, watchOS, Linux | macOS, iOS, tvOS, watchOS, Linux, others 14 | Maintained by | Big Nerd Ranch, open source community | Apple, Swift community 15 | 16 | ## Core Components 17 | 18 | Deferred | Swift Concurrency 19 | -|- 20 | `func someMethod() -> Future` | `func someMethod() async -> Value` 21 | `func someMethod() -> Task` | `func someMethod() async throws -> Value` 22 | `func someMethod() -> Deferred` | `func someMethod() async -> Value`[^1] 23 | `someMethod().upon(_:execute:)` | `try someMethod()` 24 | `someMethod().uponSuccess(on:execute:)` | `try await someMethod()` 25 | `Future` | `Task` 26 | `Task` | `Task` 27 | `Deferred` | `CheckedContinuation` 28 | `Task.Promise` | `CheckedContinuation` 29 | `Protected` | `actor` types 30 | 31 | ### Disambiguating `Task` 32 | 33 | As you migrate code to Swift Concurrency, you may have files that use both Swift Concurrency and Deferred at the same time. In those files, the `Task` type from Deferred may be ambiguous with the `Task` type from Swift. 34 | 35 | Work around this with a `typealias`: 36 | 37 | ```swift 38 | /// A way to avoid conflicts between `_Concurrency.Task` and `Deferred.Task`. 39 | /// Wherever there is not a conflict, `Task` should be used instead. 40 | typealias AsyncTask = _Concurrency.Task 41 | ``` 42 | 43 | ## Methods 44 | 45 | Deferred | Swift Concurrency 46 | -|- 47 | `Task(_:onCancel:)` | `withTaskCancellationHandler(operation:onCancel:)` 48 | `Task.cancel()` | `Task.cancel()` 49 | `map(upon:transform:)` | Use the result of an `await` 50 | `andThen(upon:start:)` | Use multiple `await` statements 51 | `repeat(upon:count:continuingIf:to:)` | Use `await` in a `for` loop 52 | `recover(upon:substituting:)` | Use `catch` in an `async` method 53 | `fallback(upon:to:)` | Use `catch` in an `async` method 54 | `someMethod().ignored()` | `_ = await someMethod()` 55 | `Sequence.firstFilled()` | `withTaskGroup(of:returning:body:)`[^2] 56 | `Collection.allFilled()` | `withTaskGroup(of:returning:body:)`[^3] 57 | `Collection.allSucceeded()` | `withThrowingTaskGroup(of:returning:body:)`[^4] 58 | 59 | ## Additional Conveniences 60 | 61 | Deferred | Swift Concurrency 62 | -|- 63 | `someMethod().upon(.main)` | `@MainActor` | Annotate a closure, method, class, struct, enum, or protocol with `@MainActor`. 64 | `DispatchQueue.any()` | `Task { }` 65 | `Future.async(upon:flags:execute:)` | `Task(priority: ...) { }` 66 | 67 | [^1]: Depends on usage. If a caller needs to fulfill the value separately, use a signature like `func someMethod() async -> (Value) -> Void`. 68 | [^2]: `await group.next()` 69 | [^3]: `for await in group` 70 | [^4]: `for try await in group` 71 | -------------------------------------------------------------------------------- /Documentation/Guide/Why Deferred?.md: -------------------------------------------------------------------------------- 1 | ## Why Deferred? 2 | 3 | If you're a computer, people are really slow. And networks are REALLY slow. 4 | 5 | ### Async Programming with Callbacks Is Bad News 6 | 7 | The standard solution to this in Cocoaland is writing async methods with 8 | a callback, either to a delegate or a completion block. 9 | 10 | Async programming with callbacks rapidly descends into callback hell: 11 | you're transforming your program into what amounts to a series of 12 | gotos. This is hard to understand, hard to reason about, and hard to 13 | debug. 14 | 15 | ### How Deferred is Different 16 | 17 | When you're writing synchronous code, 18 | you call a function, you get a return value, 19 | you work with that return value: 20 | 21 | ```swift 22 | let friends = getFriends(forUser: jimbob) 23 | let names = friends.map { $0.name } 24 | dataSource.array = names 25 | tableView.reloadData() 26 | ``` 27 | 28 | Deferred enables this comfortable linear flow for async programming: 29 | 30 | ```swift 31 | let friends = fetchFriends(forUser: jimbob) 32 | friends.upon(.main) { friends in 33 | let names = friends.map { $0.name } 34 | dataSource.array = names 35 | tableView.reloadData() 36 | } 37 | ``` 38 | 39 | A `Deferred` is a `Value` whose value is unknown till some future time. 40 | The method passed to `upon` gets run once the value becomes known. 41 | 42 | ### More Than Just a Callback 43 | 44 | You might be thinking, "Wait! That's just a callback!" You got me: `upon` is indeed a callback: the closure gets the `Value` that the `Deferred` was determined to represent and can work with it. 45 | 46 | Where `Deferred` shines is the situations where callbacks make you 47 | want to run screaming. Like, say, chaining async calls, or fork–join 48 | of calls. What if getting a friend's name also required a remote call? 49 | We'd want to do this: 50 | 51 | - Fetch jimbob's friends 52 | - For each friend, fetch their name 53 | - Once you have all the friends' names, now update the data source 54 | and reload the table view. 55 | 56 | This gets really messy with callbacks. But with `Deferred`, it's just: 57 | 58 | ```swift 59 | // Let's use type annotations to make it easier to see what's going on here. 60 | let friends: Deferred<[Friend]> = fetchFriends(for: jimbob) 61 | let names: Future<[Name]> = friends.andThen(upon: .global()) { (friends: [Friend]) -> Future<[Name]> in 62 | // fork: get an array of not-yet-determined names 63 | let names: [Deferred] = friends.map { AsynchronousCall(.Name, friend: $0) } 64 | 65 | // get a not-yet-determined array of now-determined names 66 | return names.allFilled() 67 | } 68 | 69 | names.upon(.main) { (names: [Name]) in 70 | // names has been determined - use it! 71 | dataSource.array = names 72 | tableView.reloadData() 73 | } 74 | ``` 75 | 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-2018 John Gallagher, Zachary Waldowski, and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | // 4 | // Package.swift 5 | // Deferred 6 | // 7 | // Created by Zachary Waldowski on 12/7/15. 8 | // Copyright © 2015-2020 Big Nerd Ranch. Licensed under MIT. 9 | // 10 | 11 | import PackageDescription 12 | 13 | let package = Package( 14 | name: "Deferred", 15 | platforms: [ 16 | .macOS(.v10_12), .iOS(.v10), .watchOS(.v3), .tvOS(.v10) 17 | ], 18 | products: [ 19 | .library(name: "Deferred", targets: [ "Deferred", "Task" ]) 20 | ], 21 | targets: [ 22 | .target( 23 | name: "Deferred", 24 | dependencies: [ "CAtomics" ]), 25 | .testTarget( 26 | name: "DeferredTests", 27 | dependencies: [ "Deferred" ], 28 | exclude: [ "Tests/AllTestsCommon.swift" ]), 29 | .target( 30 | name: "Task", 31 | dependencies: [ "Deferred", "CAtomics" ]), 32 | .testTarget( 33 | name: "TaskTests", 34 | dependencies: [ "Deferred", "Task" ], 35 | exclude: [ "Tests/AllTestsCommon.swift" ]), 36 | .target(name: "CAtomics") 37 | ]) 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deferred 2 | 3 | > **Warning** 4 | > 5 | > Deferred is deprecated in favor of Swift Concurrency. For migration hints, see [Migrating to Swift Concurrency](https://github.com/bignerdranch/Deferred/tree/develop/Documentation/Guide/Migrating%20to%20Swift%20Concurrency.md). 6 | 7 | Deferred let you work with values that haven't been determined yet, like an array that's coming later (one day!) from a web service call. It was originally inspired by [OCaml's Deferred](https://ocaml.janestreet.com/ocaml-core/111.25.00/doc/async_kernel/#Deferred) library. 8 | 9 | Deferred is a "futures library", probably like ones you've already heard about. Where Deferred aims to be different is by providing a small, efficient API that's easily adopted in our many [consulting projects](https://www.bignerdranch.com/work). 10 | 11 | | **Vital Statistics** | 12 | |:-------------------------------------------------------------------------------------------------------| 13 | |[![Requires Swift 5.1 or greater](https://img.shields.io/badge/swift-5.1%2B-EF5138.svg)][Swift] | 14 | |[![Under MIT License](https://img.shields.io/badge/license-MIT-blue.svg)][MIT] | 15 | |![Multiplatform](https://img.shields.io/badge/platforms-macOS,_iOS,_watchOS,_tvOS,_Linux-lightgrey.svg) | 16 | |[![Circle CI](https://img.shields.io/circleci/build/gh/bignerdranch/Deferred.svg)][CI] | 17 | |[![CocoaPods](https://img.shields.io/cocoapods/v/BNRDeferred.svg)][CocoaPods] | 18 | |[![Swift Package Manager](https://img.shields.io/badge/swiftpm-supported-orange.svg)][SwiftPM] | 19 | 20 | [Swift]: https://swift.org 21 | [MIT]: https://github.com/bignerdranch/Deferred/blob/master/LICENSE 22 | [CI]: https://circleci.com/gh/bignerdranch/Deferred 23 | [CocoaPods]: https://cocoapods.org/pods/BNRDeferred 24 | [SwiftPM]: https://github.com/apple/swift-package-manager 25 | 26 | ## Don't Panic 27 | 28 | See Deferred's comprehensive programming guide and documentation at the [Deferred Reference](https://bignerdranch.github.io/Deferred/). 29 | 30 | If you have a question not answered by the guide or the module comments, please open an issue! 31 | 32 | 33 | ## Need Some Help? 34 | 35 | Deferred, as an open source project, is free to use and always will be. [Big Nerd Ranch](https://bignerdranch.com/) offers help for Deferred, Code Audits, and general mobile app design/development services. Email us at [sales@bignerdranch.com](mailto:sales@bignerdranch.com) to get in touch with us for more details. 36 | -------------------------------------------------------------------------------- /Sources/CAtomics/Empty.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bignerdranch/Deferred/c82b9ef73132ac88a768b9e2fdbab7765346c61d/Sources/CAtomics/Empty.c -------------------------------------------------------------------------------- /Sources/CAtomics/include/CAtomics.h: -------------------------------------------------------------------------------- 1 | // 2 | // CAtomics.h 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 12/7/15. 6 | // Copyright © 2015-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | // Each of the types defined in this file have specific usage requirements, the 9 | // precise semantics of which are defined in the C11 standard. In Swift, they 10 | // are tricky to use correctly because of writeback semantics. 11 | // 12 | // It is best to use the below methods directly on C pointers 13 | // (`UnsafeMutablePointer>`) that are known to 14 | // point directly to the memory where the value is stored. 15 | // 16 | // The shared memory that you are accessing much be located inside a heap 17 | // allocation, such as a Swift class instance property, a `ManagedBuffer`, 18 | // a pointer to an `Array` element, etc.] 19 | // 20 | // If the above conditions are not met, the code may still compile, but still 21 | // cause races due to Swift writeback or other undefined behavior. 22 | 23 | #ifndef __BNR_DEFERRED_ATOMIC_SHIMS__ 24 | #define __BNR_DEFERRED_ATOMIC_SHIMS__ 25 | 26 | #if !__has_include() 27 | #error An implementation of threading primitives is not available on this platform. Please open an issue with the Deferred project. 28 | #endif 29 | 30 | #include 31 | #include 32 | 33 | #define BNR_ATOMIC_INLINE static inline __attribute__((always_inline)) 34 | #define BNR_ATOMIC_OVERLOAD __attribute__((overloadable)) 35 | #define BNR_ATOMIC_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) 36 | 37 | #if !defined(SWIFT_ENUM) 38 | #define SWIFT_ENUM(_type, _name) enum _name _name##_t; enum _name 39 | #endif 40 | 41 | // Swift looks for enums declared using a particular macro named SWIFT_ENUM 42 | #define BNR_ATOMIC_ENUM(_type, _name) SWIFT_ENUM(_type, _name) 43 | 44 | typedef BNR_ATOMIC_ENUM(int, bnr_atomic_memory_order) { 45 | bnr_atomic_memory_order_relaxed = memory_order_relaxed, 46 | bnr_atomic_memory_order_acquire = memory_order_acquire, 47 | bnr_atomic_memory_order_release = memory_order_release, 48 | bnr_atomic_memory_order_acq_rel = memory_order_acq_rel, 49 | bnr_atomic_memory_order_seq_cst = memory_order_seq_cst 50 | }; 51 | 52 | typedef const void *_Nullable volatile *_Nonnull bnr_atomic_ptr_t; 53 | 54 | BNR_ATOMIC_INLINE BNR_ATOMIC_WARN_UNUSED_RESULT BNR_ATOMIC_OVERLOAD 55 | const void *_Nullable bnr_atomic_load(bnr_atomic_ptr_t target, bnr_atomic_memory_order_t order) { 56 | return atomic_load_explicit((const void *_Atomic *)target, order); 57 | } 58 | 59 | BNR_ATOMIC_INLINE BNR_ATOMIC_WARN_UNUSED_RESULT 60 | const void *_Nullable bnr_atomic_exchange(bnr_atomic_ptr_t target, const void *_Nullable desired, bnr_atomic_memory_order_t order) { 61 | return atomic_exchange_explicit((const void *_Atomic *)target, desired, order); 62 | } 63 | 64 | BNR_ATOMIC_INLINE BNR_ATOMIC_WARN_UNUSED_RESULT 65 | bool bnr_atomic_compare_and_swap(bnr_atomic_ptr_t target, const void *_Nullable expected, const void *_Nullable desired, bnr_atomic_memory_order_t order, bnr_atomic_memory_order_t failureOrder) { 66 | return atomic_compare_exchange_strong_explicit((const void *_Atomic *)target, &expected, desired, order, failureOrder); 67 | } 68 | 69 | typedef volatile bool *_Nonnull bnr_atomic_flag_t; 70 | 71 | BNR_ATOMIC_INLINE BNR_ATOMIC_WARN_UNUSED_RESULT BNR_ATOMIC_OVERLOAD 72 | bool bnr_atomic_load(bnr_atomic_flag_t target, bnr_atomic_memory_order_t order) { 73 | return atomic_load_explicit((atomic_bool *)target, order); 74 | } 75 | 76 | BNR_ATOMIC_INLINE BNR_ATOMIC_OVERLOAD 77 | void bnr_atomic_store(bnr_atomic_flag_t target, bool desired, bnr_atomic_memory_order_t order) { 78 | atomic_store_explicit((atomic_bool *)target, desired, order); 79 | } 80 | 81 | #undef SWIFT_ENUM 82 | 83 | #endif // __BNR_DEFERRED_ATOMIC_SHIMS__ 84 | -------------------------------------------------------------------------------- /Sources/CAtomics/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module CAtomics { 2 | header "CAtomics.h" 3 | } 4 | -------------------------------------------------------------------------------- /Sources/Deferred/Atomics.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Atomics.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 2/22/18. 6 | // Copyright © 2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | // swiftlint:disable type_name 10 | // swiftlint:disable identifier_name 11 | 12 | #if canImport(Darwin) 13 | import Darwin 14 | #elseif canImport(Glibc) 15 | import Glibc 16 | #endif 17 | 18 | #if SWIFT_PACKAGE || (canImport(CAtomics) && !FORCE_PLAYGROUND_COMPATIBILITY) 19 | @_implementationOnly import CAtomics 20 | #elseif canImport(Darwin) 21 | #warning("Using fallback implementation for Swift Playgrounds. This is unsafe for use in production. Check your build setup.") 22 | 23 | typealias bnr_atomic_memory_order_t = memory_order 24 | 25 | extension bnr_atomic_memory_order_t { 26 | static let relaxed = memory_order_relaxed 27 | static let acquire = memory_order_acquire 28 | static let release = memory_order_release 29 | static let acq_rel = memory_order_acq_rel 30 | static let seq_cst = memory_order_seq_cst 31 | } 32 | 33 | private extension Optional where Wrapped == UnsafeMutableRawPointer { 34 | func symbol(named name: String, of _: T.Type = T.self, file: StaticString = #file, line: UInt = #line) -> T { 35 | assert("\(T.self)".hasPrefix("@convention(c)"), "Type must be a C symbol", file: file, line: line) 36 | guard let symbol = dlsym(self, name) else { preconditionFailure(String(cString: dlerror()), file: file, line: line) } 37 | return unsafeBitCast(symbol, to: T.self) 38 | } 39 | } 40 | 41 | /// Follows the routines in Apple's libc, defined by: 42 | /// http://llvm.org/docs/Atomics.html#libcalls-atomic 43 | private struct DarwinAtomics { 44 | let load: @convention(c) (Int, UnsafeMutableRawPointer, UnsafeMutableRawPointer, bnr_atomic_memory_order_t) -> Void 45 | let store: @convention(c) (Int, UnsafeMutableRawPointer, UnsafeMutableRawPointer, bnr_atomic_memory_order_t) -> Void 46 | let exchange: @convention(c) (Int, UnsafeMutableRawPointer, UnsafeMutableRawPointer, UnsafeMutableRawPointer, bnr_atomic_memory_order_t) -> Void 47 | let compareExchange: @convention(c) (Int, UnsafeMutableRawPointer, UnsafeMutableRawPointer, UnsafeMutableRawPointer, bnr_atomic_memory_order_t, bnr_atomic_memory_order_t) -> Bool 48 | 49 | static let shared: DarwinAtomics = { 50 | let library = UnsafeMutableRawPointer(bitPattern: -2) // RTLD_DEFAULT 51 | return DarwinAtomics( 52 | load: library.symbol(named: "__atomic_load"), 53 | store: library.symbol(named: "__atomic_store"), 54 | exchange: library.symbol(named: "__atomic_exchange"), 55 | compareExchange: library.symbol(named: "__atomic_compare_exchange")) 56 | }() 57 | } 58 | 59 | typealias bnr_atomic_ptr_t = UnsafeMutablePointer 60 | 61 | func bnr_atomic_load(_ target: bnr_atomic_ptr_t, _ order: bnr_atomic_memory_order_t) -> UnsafeRawPointer? { 62 | var result: UnsafeRawPointer? 63 | DarwinAtomics.shared.load(MemoryLayout.size, target, &result, order) 64 | return result 65 | } 66 | 67 | func bnr_atomic_exchange(_ target: bnr_atomic_ptr_t, _ desired: UnsafeRawPointer?, _ order: bnr_atomic_memory_order_t) -> UnsafeRawPointer? { 68 | var new = desired 69 | var old: UnsafeRawPointer? 70 | DarwinAtomics.shared.exchange(MemoryLayout.size, target, &new, &old, order) 71 | return old 72 | } 73 | 74 | func bnr_atomic_compare_and_swap(_ target: bnr_atomic_ptr_t, _ expected: UnsafeRawPointer?, _ desired: UnsafeRawPointer?, _ order: bnr_atomic_memory_order_t, _ failureOrder: bnr_atomic_memory_order_t) -> Bool { 75 | var expected = expected 76 | var desired = desired 77 | return DarwinAtomics.shared.compareExchange(MemoryLayout.size, target, &expected, &desired, order, failureOrder) 78 | } 79 | 80 | typealias bnr_atomic_flag_t = UnsafeMutablePointer 81 | 82 | func bnr_atomic_load(_ target: bnr_atomic_flag_t, _ order: bnr_atomic_memory_order_t) -> Bool { 83 | var result: Bool = false 84 | DarwinAtomics.shared.load(MemoryLayout.size, target, &result, order) 85 | return result 86 | } 87 | 88 | func bnr_atomic_store(_ target: bnr_atomic_flag_t, _ desired: Bool, _ order: bnr_atomic_memory_order_t) { 89 | var desired = desired 90 | DarwinAtomics.shared.store(MemoryLayout.size, target, &desired, order) 91 | } 92 | #else 93 | #error("An implementation of threading primitives is not available on this platform. Please open an issue with the Deferred project.") 94 | #endif 95 | 96 | func bnr_atomic_load(_ target: UnsafeMutablePointer, _ order: bnr_atomic_memory_order_t) -> T? { 97 | let rawTarget = UnsafeMutableRawPointer(target).assumingMemoryBound(to: UnsafeRawPointer?.self) 98 | guard let opaqueResult = bnr_atomic_load(rawTarget, order) else { return nil } 99 | return Unmanaged.fromOpaque(opaqueResult).takeUnretainedValue() 100 | } 101 | 102 | @discardableResult 103 | func bnr_atomic_store(_ target: UnsafeMutablePointer, _ desired: T?, _ order: bnr_atomic_memory_order_t) -> T? { 104 | let rawTarget = UnsafeMutableRawPointer(target).assumingMemoryBound(to: UnsafeRawPointer?.self) 105 | let opaqueDesired: UnsafeMutableRawPointer? 106 | if let desired = desired { 107 | opaqueDesired = Unmanaged.passRetained(desired).toOpaque() 108 | } else { 109 | opaqueDesired = nil 110 | } 111 | guard let opaquePrevious = bnr_atomic_exchange(rawTarget, opaqueDesired, order) else { return nil } 112 | return Unmanaged.fromOpaque(opaquePrevious).takeRetainedValue() 113 | } 114 | 115 | func bnr_atomic_initialize_once(_ target: UnsafeMutablePointer, _ desired: T) -> Bool { 116 | let rawTarget = UnsafeMutableRawPointer(target).assumingMemoryBound(to: UnsafeRawPointer?.self) 117 | let retainedDesired = Unmanaged.passRetained(desired) 118 | let wonRace = bnr_atomic_compare_and_swap(rawTarget, nil, retainedDesired.toOpaque(), .acq_rel, .acquire) 119 | if !wonRace { 120 | retainedDesired.release() 121 | } 122 | return wonRace 123 | } 124 | 125 | func bnr_atomic_load_and_wait(_ target: UnsafeMutablePointer) -> T { 126 | let rawTarget = UnsafeMutableRawPointer(target).assumingMemoryBound(to: UnsafeRawPointer?.self) 127 | var opaqueResult = bnr_atomic_load(rawTarget, .acquire) 128 | while opaqueResult == nil { 129 | #if canImport(Darwin) 130 | pthread_yield_np() 131 | #elseif canImport(Glibc) 132 | sched_yield() 133 | #endif 134 | opaqueResult = bnr_atomic_load(rawTarget, .relaxed) 135 | } 136 | return Unmanaged.fromOpaque(opaqueResult!).takeUnretainedValue() 137 | } 138 | 139 | @discardableResult 140 | func bnr_atomic_initialize_once(_ target: UnsafeMutablePointer, _ handler: () -> Void) -> Bool { 141 | guard !bnr_atomic_load(target, .acquire) else { return false } 142 | handler() 143 | bnr_atomic_store(target, true, .release) 144 | return true 145 | } 146 | -------------------------------------------------------------------------------- /Sources/Deferred/Deferred.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Deferred.swift 3 | // Deferred 4 | // 5 | // Created by John Gallagher on 7/19/14. 6 | // Copyright © 2014-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import Dispatch 10 | 11 | /// A value that may become determined (or "filled") at some point in the 12 | /// future. Once determined, it cannot change. 13 | /// 14 | /// You may subscribe to be notified once the value becomes determined. 15 | /// 16 | /// Handlers and their captures are strongly referenced until: 17 | /// - they are executed when the value is determined 18 | /// - the last copy to this type escapes without the value becoming determined 19 | /// 20 | /// If the value never becomes determined, a handler submitted to it will never 21 | /// be executed. 22 | public struct Deferred { 23 | /// The primary storage, initialized with a value once-and-only-once (at 24 | /// init or later). 25 | private let variant: Variant 26 | 27 | public init() { 28 | variant = Variant() 29 | } 30 | 31 | /// Creates an instance resolved with `value`. 32 | public init(filledWith value: Value) { 33 | variant = Variant(for: value) 34 | } 35 | } 36 | 37 | extension Deferred: FutureProtocol { 38 | /// An enqueued handler. 39 | struct Continuation { 40 | let target: Executor? 41 | let handler: (Value) -> Void 42 | } 43 | 44 | public func upon(_ executor: Executor, execute body: @escaping(Value) -> Void) { 45 | let continuation = Continuation(target: executor, handler: body) 46 | variant.notify(continuation) 47 | } 48 | 49 | public func peek() -> Value? { 50 | return variant.load() 51 | } 52 | 53 | public func wait(until time: DispatchTime) -> Value? { 54 | let semaphore = DispatchSemaphore(value: 0) 55 | var result: Value? 56 | 57 | let continuation = Continuation(target: nil) { (value) in 58 | result = value 59 | semaphore.signal() 60 | } 61 | 62 | variant.notify(continuation) 63 | 64 | guard case .success = semaphore.wait(timeout: time) else { return nil } 65 | return result 66 | } 67 | } 68 | 69 | extension Deferred.Continuation { 70 | /// A continuation can be submitted to its passed-in executor or executed 71 | /// in the current context. 72 | func execute(with value: Value) { 73 | target?.submit { [handler] in 74 | handler(value) 75 | } ?? handler(value) 76 | } 77 | } 78 | 79 | extension Deferred { 80 | /// Determines the promise with `value`. 81 | /// 82 | /// Filling a deferred value should usually be attempted only once. 83 | /// 84 | /// - returns: Whether the promise was fulfilled with `value`. 85 | @discardableResult 86 | public func fill(with value: Value) -> Bool { 87 | return variant.store(value) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/Deferred/DeferredQueue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeferredQueue.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 2/22/18. 6 | // Copyright © 2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | extension Deferred { 10 | /// Heap storage acting as a linked list node of continuations. 11 | /// 12 | /// The use of `ManagedBuffer` ensures aligned and heap-allocated addresses 13 | /// for the storage. The storage is tail-allocated with a reference to the 14 | /// next node. 15 | final class Node: ManagedBuffer { 16 | static func create(with continuation: Continuation) -> Node { 17 | let storage = super.create(minimumCapacity: 1, makingHeaderWith: { _ in nil }) 18 | 19 | storage.withUnsafeMutablePointers { (_, pointerToContinuation) in 20 | pointerToContinuation.initialize(to: continuation) 21 | } 22 | 23 | return unsafeDowncast(storage, to: Node.self) 24 | } 25 | 26 | deinit { 27 | _ = withUnsafeMutablePointers { (_, pointerToContinuation) in 28 | pointerToContinuation.deinitialize(count: 1) 29 | } 30 | } 31 | } 32 | 33 | /// A singly-linked list of continuations to be submitted after fill. 34 | /// 35 | /// A multi-producer, single-consumer atomic queue a la `DispatchGroup`: 36 | /// . 37 | struct Queue { 38 | fileprivate(set) var head: Node? 39 | fileprivate(set) var tail: Node? 40 | } 41 | } 42 | 43 | private extension Deferred.Node { 44 | /// The next node in the linked list. 45 | /// 46 | /// - warning: To alleviate data races, the next node is loaded 47 | /// unconditionally. `self` must have been checked not to be the tail. 48 | var next: Deferred.Node { 49 | get { 50 | return withUnsafeMutablePointers { (target, _) in 51 | bnr_atomic_load_and_wait(target) 52 | } 53 | } 54 | set { 55 | _ = withUnsafeMutablePointers { (target, _) in 56 | bnr_atomic_store(target, newValue, .relaxed) 57 | } 58 | } 59 | } 60 | 61 | func execute(with value: Value) { 62 | withUnsafeMutablePointers { (_, pointerToContinuation) in 63 | pointerToContinuation.pointee.execute(with: value) 64 | } 65 | } 66 | } 67 | 68 | extension Deferred { 69 | static func drain(from target: UnsafeMutablePointer, continuingWith value: Value) { 70 | var head = bnr_atomic_store(&target.pointee.head, nil, .relaxed) 71 | let tail = head != nil ? bnr_atomic_store(&target.pointee.tail, nil, .release) : nil 72 | 73 | while let current = head { 74 | head = current !== tail ? current.next : nil 75 | current.execute(with: value) 76 | } 77 | } 78 | 79 | static func push(_ continuation: Continuation, to target: UnsafeMutablePointer) -> Bool { 80 | let node = Node.create(with: continuation) 81 | 82 | if let tail = bnr_atomic_store(&target.pointee.tail, node, .release) { 83 | tail.next = node 84 | return false 85 | } 86 | 87 | _ = bnr_atomic_store(&target.pointee.head, node, .seq_cst) 88 | return true 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/Deferred/DeferredVariant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeferredVariant.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 2/22/18. 6 | // Copyright © 2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if SWIFT_PACKAGE || (canImport(CAtomics) && !FORCE_PLAYGROUND_COMPATIBILITY) 10 | @_implementationOnly import CAtomics 11 | #endif 12 | 13 | extension Deferred { 14 | /// Deferred's storage. It, lock-free but thread-safe, can be initialized 15 | /// with a value once and only once. 16 | /// 17 | /// An underlying implementation is chosen at init. The variants that start 18 | /// unfilled use `ManagedBuffer` to guarantee aligned and heap-allocated 19 | /// addresses for atomic access, and are tail-allocated with space for the 20 | /// callbacks queue. 21 | /// 22 | /// - note: **Q:** Why not just stored properties? Aren't you overthinking 23 | /// it? **A:** We want raw memory because Swift reserves the right to 24 | /// lay out properties opaquely. To that end, the initial store done 25 | /// during `init` counts as unsafe access to TSAN. 26 | enum Variant { 27 | case object(ObjectVariant) 28 | case native(NativeVariant) 29 | indirect case filled(Value) 30 | } 31 | 32 | /// Heap storage that is initialized once and only once from `nil` to a 33 | /// reference. See `Deferred.Variant` for more details. 34 | final class ObjectVariant: ManagedBuffer { 35 | fileprivate static func create() -> ObjectVariant { 36 | let storage = super.create(minimumCapacity: 1, makingHeaderWith: { _ in Queue() }) 37 | 38 | storage.withUnsafeMutablePointers { (_, pointerToValue) in 39 | pointerToValue.initialize(to: nil) 40 | } 41 | 42 | return unsafeDowncast(storage, to: ObjectVariant.self) 43 | } 44 | 45 | deinit { 46 | withUnsafeMutablePointers { (_, pointerToValue) in 47 | _ = pointerToValue.deinitialize(count: 1) 48 | } 49 | } 50 | } 51 | 52 | /// Heap storage that is initialized once and only once using a flag. 53 | /// See `Deferred.Variant` for more details. 54 | final class NativeVariant: ManagedBuffer { 55 | fileprivate static func create() -> NativeVariant { 56 | let storage = super.create(minimumCapacity: 1, makingHeaderWith: { _ in NativeHeader() }) 57 | return unsafeDowncast(storage, to: NativeVariant.self) 58 | } 59 | 60 | deinit { 61 | withUnsafeMutablePointers { (pointerToHeader, pointerToValue) in 62 | if pointerToHeader.pointee.isInitialized { 63 | pointerToValue.deinitialize(count: 1) 64 | } 65 | } 66 | } 67 | } 68 | 69 | /// The tail-allocated header used for `NativeStorage`. 70 | struct NativeHeader { 71 | fileprivate var isInitialized = false 72 | fileprivate var queue = Queue() 73 | } 74 | } 75 | 76 | extension Deferred.Variant { 77 | init() { 78 | if Value.self is AnyObject.Type { 79 | self = .object(.create()) 80 | } else { 81 | self = .native(.create()) 82 | } 83 | } 84 | 85 | init(for value: Value) { 86 | self = .filled(value) 87 | } 88 | } 89 | 90 | extension Deferred.Variant { 91 | /// Adds the `continuation` to the queue. If filled, drain the queue to 92 | /// execute it immediately. 93 | func notify(_ continuation: Deferred.Continuation) { 94 | switch self { 95 | case .object(let storage): 96 | storage.withUnsafeMutablePointers { (pointerToQueue, pointerToValue) in 97 | guard Deferred.push(continuation, to: pointerToQueue), 98 | let existingValue = unsafeBitCast(bnr_atomic_load(pointerToValue, .seq_cst), to: Value?.self) else { return } 99 | Deferred.drain(from: pointerToQueue, continuingWith: existingValue) 100 | } 101 | case .native(let storage): 102 | storage.withUnsafeMutablePointers { (pointerToHeader, pointerToValue) in 103 | guard Deferred.push(continuation, to: &pointerToHeader.pointee.queue), 104 | bnr_atomic_load(&pointerToHeader.pointee.isInitialized, .seq_cst) else { return } 105 | Deferred.drain(from: &pointerToHeader.pointee.queue, continuingWith: pointerToValue.pointee) 106 | } 107 | case .filled(let value): 108 | continuation.execute(with: value) 109 | } 110 | } 111 | 112 | func load() -> Value? { 113 | switch self { 114 | case .object(let storage): 115 | return storage.withUnsafeMutablePointers { (_, pointerToValue) in 116 | unsafeBitCast(bnr_atomic_load(pointerToValue, .relaxed), to: Value?.self) 117 | } 118 | case .native(let storage): 119 | return storage.withUnsafeMutablePointers { (pointerToHeader, pointerToValue) in 120 | bnr_atomic_load(&pointerToHeader.pointee.isInitialized, .relaxed) ? pointerToValue.pointee : nil 121 | } 122 | case .filled(let value): 123 | return value 124 | } 125 | } 126 | 127 | func store(_ value: Value) -> Bool { 128 | switch self { 129 | case .object(let storage): 130 | return storage.withUnsafeMutablePointers { (pointerToHeader, pointerToValue) -> Bool in 131 | guard bnr_atomic_initialize_once(pointerToValue, unsafeBitCast(value, to: AnyObject.self)) else { return false } 132 | Deferred.drain(from: pointerToHeader, continuingWith: value) 133 | return true 134 | } 135 | case .native(let storage): 136 | return storage.withUnsafeMutablePointers { (pointerToHeader, pointerToValue) -> Bool in 137 | guard bnr_atomic_initialize_once(&pointerToHeader.pointee.isInitialized, { pointerToValue.initialize(to: value) }) else { return false } 138 | Deferred.drain(from: &pointerToHeader.pointee.queue, continuingWith: value) 139 | return true 140 | } 141 | case .filled: 142 | return false 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Sources/Deferred/Executor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Executor.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 3/29/16. 6 | // Copyright © 2014-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import Dispatch 10 | import Foundation 11 | #if !os(macOS) && !os(iOS) && !os(tvOS) && !os(watchOS) 12 | import CoreFoundation 13 | #endif 14 | 15 | /// An executor calls closures submitted to it, typically in first-in, first-out 16 | /// order on some other thread. An executor may also model locks or atomicity. 17 | /// 18 | /// Throughout the Deferred module, `upon` methods (or parameters to methods 19 | /// built around `upon`, such as `map`) are overloaded to take an `Executor` 20 | /// as well as the standard `DispatchQueue`. 21 | /// 22 | /// A custom executor is a customization point into the asynchronous semantics 23 | /// of a future, and may be important for ensuring the thread safety of an 24 | /// `upon` closure. 25 | /// 26 | /// For instance, the concurrency model of Apple's Core Data framework requires 27 | /// that objects be accessed from other threads using the `perform(_:)` 28 | /// method, and not just thread isolation. Here, we connect that to Deferred: 29 | /// 30 | /// extension NSManagedObjectContext: Executor { 31 | /// 32 | /// func submit(body: @escaping() -> Void) { 33 | /// perform(body) 34 | /// } 35 | /// 36 | /// } 37 | /// 38 | /// And use it like you would a dispatch queue, with `upon`: 39 | /// 40 | /// let context: NSManagedObjectContext = ... 41 | /// let personJSON: Future = ... 42 | /// let person: Future = personJSON.map(upon: context) { json in 43 | /// Person(json: json, inContext: context) 44 | /// } 45 | /// 46 | public protocol Executor: AnyObject { 47 | /// Execute the `body` closure. 48 | func submit(_ body: @escaping() -> Void) 49 | 50 | /// Execute the `workItem`. 51 | func submit(_ workItem: DispatchWorkItem) 52 | } 53 | 54 | extension Executor { 55 | /// By default, submits the closure contents of the work item. 56 | public func submit(_ workItem: DispatchWorkItem) { 57 | submit(workItem.perform) 58 | } 59 | } 60 | 61 | /// Dispatch queues invoke function bodies submitted to them serially in FIFO 62 | /// order. A queue will only invoke one-at-a-time, but independent queues may 63 | /// each invoke concurrently with respect to each other. 64 | extension DispatchQueue: Executor { 65 | /// A generic catch-all dispatch queue, for when you just want to throw some 66 | /// work onto the concurrent pile. As an alternative to the `.utility` QoS 67 | /// global queue, work dispatched onto this queue on platforms with support 68 | /// for QoS will match the QoS of the caller. 69 | public static func any() -> DispatchQueue { 70 | // The technique is described and used in Core Foundation: 71 | // http://opensource.apple.com/source/CF/CF-1153.18/CFInternal.h 72 | // https://github.com/apple/swift-corelibs-foundation/blob/master/CoreFoundation/Base.subproj/CFInternal.h#L869-L889 73 | let qosClass: DispatchQoS.QoSClass 74 | #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) 75 | qosClass = DispatchQoS.QoSClass(rawValue: qos_class_self()) ?? .utility 76 | #else 77 | qosClass = .utility 78 | #endif 79 | return .global(qos: qosClass) 80 | } 81 | 82 | public func submit(_ body: @escaping() -> Void) { 83 | async(execute: body) 84 | } 85 | 86 | public func submit(_ workItem: DispatchWorkItem) { 87 | async(execute: workItem) 88 | } 89 | } 90 | 91 | /// An operation queue manages a number of operation objects, making high 92 | /// level features like cancellation and dependencies simple. 93 | /// 94 | /// As an `Executor`, `upon` closures are enqueued as non-cancellable 95 | /// operations. This is ideal for regulating the call relative to other 96 | /// operations in the queue. 97 | extension OperationQueue: Executor { 98 | public func submit(_ body: @escaping() -> Void) { 99 | addOperation(body) 100 | } 101 | } 102 | 103 | /// A run loop processes events on a thread, and is a fundamental construct in 104 | /// Cocoa applications. 105 | /// 106 | /// As an `Executor`, submitted functions are invoked on the next iteration 107 | /// of the run loop. 108 | extension CFRunLoop: Executor { 109 | public func submit(_ body: @escaping() -> Void) { 110 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 111 | CFRunLoopPerformBlock(self, CFRunLoopMode.defaultMode.rawValue, body) 112 | #else 113 | CFRunLoopPerformBlock(self, kCFRunLoopDefaultMode, body) 114 | #endif 115 | CFRunLoopWakeUp(self) 116 | } 117 | } 118 | 119 | /// A run loop processes events on a thread, and is a fundamental construct in 120 | /// Cocoa applications. 121 | /// 122 | /// As an `Executor`, submitted functions are invoked on the next iteration 123 | /// of the run loop. 124 | extension RunLoop: Executor { 125 | public func submit(_ body: @escaping() -> Void) { 126 | perform(body) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Sources/Deferred/ExistentialFuture.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExistentialFuture.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 8/29/15. 6 | // Copyright © 2014-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import Dispatch 10 | 11 | /* 12 | The types in this file provide an implementation of type erasure for `FutureProtocol`. 13 | The techniques were derived from experimenting with `AnySequence` and `Mirror` 14 | in a playground, the following post, and the Swift standard library: 15 | - https://realm.io/news/type-erased-wrappers-in-swift/ 16 | - https://github.com/apple/swift/blob/master/stdlib/public/core/ExistentialCollection.swift.gyb 17 | */ 18 | 19 | // Abstract class that fake-conforms to `FutureProtocol` for use by `Future`. 20 | private class Box { 21 | func upon(_: Executor, execute _: @escaping(Value) -> Void) { 22 | fatalError() 23 | } 24 | 25 | func peek() -> Value? { 26 | fatalError() 27 | } 28 | 29 | func wait(until _: DispatchTime) -> Value? { 30 | fatalError() 31 | } 32 | } 33 | 34 | // Concrete future wrapper given an instance of a `FutureProtocol`. 35 | private final class ForwardedTo: Box { 36 | let base: Future 37 | init(base: Future) { 38 | self.base = base 39 | } 40 | 41 | override func upon(_ executor: Executor, execute body: @escaping(Future.Value) -> Void) { 42 | return base.upon(executor, execute: body) 43 | } 44 | 45 | override func peek() -> Future.Value? { 46 | return base.peek() 47 | } 48 | 49 | override func wait(until time: DispatchTime) -> Future.Value? { 50 | return base.wait(until: time) 51 | } 52 | } 53 | 54 | // Concrete future wrapper for an always-filled future. 55 | private final class Always: Box { 56 | let value: Value 57 | init(value: Value) { 58 | self.value = value 59 | } 60 | 61 | override func upon(_ executor: Executor, execute body: @escaping(Value) -> Void) { 62 | executor.submit { [value] in 63 | body(value) 64 | } 65 | } 66 | 67 | override func peek() -> Value? { 68 | return value 69 | } 70 | 71 | override func wait(until _: DispatchTime) -> Value? { 72 | return value 73 | } 74 | } 75 | 76 | // Concrete future wrapper that will never get filled. 77 | private final class Never: Box { 78 | override init() {} 79 | 80 | override func upon(_: Executor, execute _: @escaping(Value) -> Void) {} 81 | 82 | override func peek() -> Value? { 83 | return nil 84 | } 85 | 86 | override func wait(until _: DispatchTime) -> Value? { 87 | return nil 88 | } 89 | } 90 | 91 | /// A type-erased wrapper over any future. 92 | /// 93 | /// Forwards operations to an arbitrary underlying future having the same 94 | /// `Value` type, hiding the specifics of the underlying `FutureProtocol`. 95 | /// 96 | /// This type may be used to: 97 | /// 98 | /// - Prevent clients from coupling to the specific kind of `FutureProtocol` your 99 | /// implementation is currently using. 100 | /// - Publicly expose only the `FutureProtocol` aspect of a deferred value, 101 | /// ensuring that only your implementation can fill the deferred value. 102 | public struct Future: FutureProtocol { 103 | private let box: Box 104 | 105 | /// Create a future whose `upon(_:execute:)` methods forward to `base`. 106 | public init(_ wrapped: Wrapped) where Wrapped.Value == Value { 107 | if let future = wrapped as? Future { 108 | self.box = future.box 109 | } else { 110 | self.box = ForwardedTo(base: wrapped) 111 | } 112 | } 113 | 114 | /// Wrap and forward future as if it were always filled with `value`. 115 | public init(value: Value) { 116 | self.box = Always(value: value) 117 | } 118 | 119 | private init(never: ()) { 120 | self.box = Never() 121 | } 122 | 123 | /// Create a future that will never get fulfilled. 124 | public static var never: Future { 125 | return Future(never: ()) 126 | } 127 | 128 | /// Create a future having the same underlying future as `other`. 129 | public init(_ future: Future) { 130 | self.box = future.box 131 | } 132 | 133 | public func upon(_ executor: Executor, execute body: @escaping(Value) -> Void) { 134 | return box.upon(executor, execute: body) 135 | } 136 | 137 | public func peek() -> Value? { 138 | return box.peek() 139 | } 140 | 141 | public func wait(until time: DispatchTime) -> Value? { 142 | return box.wait(until: time) 143 | } 144 | } 145 | 146 | public extension FutureProtocol { 147 | /// Wraps this future with a type eraser. 148 | /// 149 | /// Use `eraseToFuture()` to expose an instance of `Future` across an API boundary, rather than this future's actual type. 150 | @inlinable 151 | func eraseToFuture() -> Future { 152 | return Future(self) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Sources/Deferred/Future.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Future.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 8/29/15. 6 | // Copyright © 2014-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import Dispatch 10 | 11 | /// A future models reading a value which may become available at some point. 12 | /// 13 | /// A `FutureProtocol` may be preferable to an architecture using completion 14 | /// handlers; separating the mechanism for handling the completion from the call 15 | /// that began it leads to a more readable code flow. 16 | /// 17 | /// A future is primarily useful as a joining mechanism for asynchronous 18 | /// operations. Though the protocol requires a synchronous accessor, its use is 19 | /// not recommended outside of testing. `upon` is preferred for nearly all access: 20 | /// 21 | /// myFuture.upon(.main) { value in 22 | /// print("I now have the value: \(value)") 23 | /// } 24 | /// 25 | /// `FutureProtocol` makes no requirement on conforming types regarding thread-safe 26 | /// access, though ideally all members of the future could be called from any 27 | /// thread. 28 | /// 29 | public protocol FutureProtocol: CustomDebugStringConvertible, CustomReflectable { 30 | /// A type that represents the result of some asynchronous operation. 31 | associatedtype Value 32 | 33 | /// Call some `body` closure once the value is determined. 34 | /// 35 | /// If the value is determined, the closure should be submitted to the 36 | /// `executor` immediately. 37 | func upon(_ executor: Executor, execute body: @escaping(Value) -> Void) 38 | 39 | /// Checks for and returns a determined value. 40 | /// 41 | /// An implementation should use a "best effort" to return this value and 42 | /// not unnecessarily block in order to to return. 43 | /// 44 | /// - returns: The determined value, if already filled, or `nil`. 45 | func peek() -> Value? 46 | 47 | /// Waits synchronously for the value to become determined. 48 | /// 49 | /// If the value is already determined, the call returns immediately with 50 | /// the value. 51 | /// 52 | /// - parameter time: A deadline for the value to be determined. 53 | /// - returns: The determined value, if filled within the timeout, or `nil`. 54 | func wait(until time: DispatchTime) -> Value? 55 | } 56 | 57 | // MARK: - Default implementations 58 | 59 | extension FutureProtocol { 60 | /// A textual representation of this instance, suitable for debugging. 61 | public var debugDescription: String { 62 | var ret = "" 63 | ret.append(contentsOf: "\(Self.self)".prefix(while: { $0 != "<" })) 64 | ret.append("(") 65 | switch peek() { 66 | case _? where Value.self == Void.self: 67 | ret.append("filled") 68 | case let value?: 69 | debugPrint(value, terminator: "", to: &ret) 70 | case nil: 71 | ret.append("not filled") 72 | } 73 | ret.append(")") 74 | return ret 75 | } 76 | 77 | /// Return the `Mirror` for `self`. 78 | public var customMirror: Mirror { 79 | let child: Mirror.Child 80 | switch peek() { 81 | case let value? where Value.self != Void.self: 82 | child = (label: "value", value: value) 83 | case let other: 84 | child = (label: "isFilled", value: other != nil) 85 | } 86 | return Mirror(self, children: CollectionOfOne(child), displayStyle: .optional, ancestorRepresentation: .suppressed) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/Deferred/FutureAndThen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureAndThen.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 4/2/16. 6 | // Copyright © 2014-2016 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | extension FutureProtocol { 10 | /// Begins another asynchronous operation by passing the deferred value to 11 | /// `requestNextValue` once it becomes determined. 12 | /// 13 | /// `andThen` is similar to `map`, but `requestNextValue` returns another 14 | /// future instead of an immediate value. Use `andThen` when you want 15 | /// the reciever to feed into another asynchronous operation. You might hear 16 | /// this referred to as "chaining" or "binding". 17 | public func andThen(upon executor: PreferredExecutor, start requestNextValue: @escaping(Value) -> NewFuture) -> Future { 18 | return andThen(upon: executor as Executor, start: requestNextValue) 19 | } 20 | 21 | /// Begins another asynchronous operation by passing the deferred value to 22 | /// `requestNextValue` once it becomes determined. 23 | /// 24 | /// `andThen` is similar to `map`, but `requestNextValue` returns another 25 | /// future instead of an immediate value. Use `andThen` when you want 26 | /// the reciever to feed into another asynchronous operation. You might hear 27 | /// this referred to as "chaining" or "binding". 28 | /// 29 | /// - note: It is important to keep in mind the thread safety of the 30 | /// `requestNextValue` closure. Creating a new asynchronous task typically 31 | /// involves state. Ensure the function is compatible with `executor`. 32 | /// 33 | /// - parameter executor: Context to execute the transformation on. 34 | /// - parameter requestNextValue: Start a new operation with the future value. 35 | /// - returns: The new deferred value returned by the `transform`. 36 | public func andThen(upon executor: Executor, start requestNextValue: @escaping(Value) -> NewFuture) -> Future { 37 | let deferred = Deferred() 38 | upon(executor) { 39 | requestNextValue($0).upon(executor) { 40 | deferred.fill(with: $0) 41 | } 42 | } 43 | return Future(deferred) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Deferred/FutureAsync.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureAsync.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 6/3/18. 6 | // Copyright © 2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import Dispatch 10 | 11 | extension Future { 12 | /// Captures the value of asynchronously executing `work` on `queue`. 13 | /// 14 | /// - parameter queue: A dispatch queue to perform the `work` on. 15 | /// - parameter flags: Options controlling how the `work` is executed with 16 | /// respect to system resources. 17 | /// - parameter work: A function body that calculates and returns the 18 | /// fulfilled value for the future. 19 | public static func async(upon queue: DispatchQueue = .any(), flags: DispatchWorkItemFlags = [], execute work: @escaping() -> Value) -> Future { 20 | let deferred = Deferred() 21 | 22 | queue.async(flags: flags) { 23 | deferred.fill(with: work()) 24 | } 25 | 26 | return Future(deferred) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Deferred/FutureCollections.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureCollections.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 8/29/15. 6 | // Copyright © 2014-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import Dispatch 10 | 11 | extension Sequence where Iterator.Element: FutureProtocol { 12 | /// Chooses the future that is determined first from `self`. 13 | public func firstFilled() -> Future { 14 | let combined = Deferred() 15 | for future in self { 16 | future.upon(DispatchQueue.global(qos: .utility)) { 17 | combined.fill(with: $0) 18 | } 19 | } 20 | return Future(combined) 21 | } 22 | } 23 | 24 | private struct AllFilledFuture: FutureProtocol { 25 | let combined = Deferred<[Element]>() 26 | 27 | init(base: Base) where Base.Iterator.Element: FutureProtocol, Base.Iterator.Element.Value == Element { 28 | let array = Array(base) 29 | guard !array.isEmpty else { 30 | combined.fill(with: []) 31 | return 32 | } 33 | 34 | let group = DispatchGroup() 35 | let queue = DispatchQueue.global(qos: .utility) 36 | 37 | for future in array { 38 | group.enter() 39 | future.upon(queue) { [group] _ in 40 | group.leave() 41 | } 42 | } 43 | 44 | group.notify(queue: queue) { [combined] in 45 | // Expect each to be filled right now. 46 | // swiftlint:disable:next force_unwrapping 47 | combined.fill(with: array.map({ $0.peek()! })) 48 | } 49 | } 50 | 51 | func upon(_ executor: Executor, execute body: @escaping([Element]) -> Void) { 52 | combined.upon(executor, execute: body) 53 | } 54 | 55 | func wait(until time: DispatchTime) -> [Element]? { 56 | return combined.wait(until: time) 57 | } 58 | } 59 | 60 | extension Collection where Iterator.Element: FutureProtocol { 61 | /// Composes a number of futures into a single deferred array. 62 | public func allFilled() -> Future<[Iterator.Element.Value]> { 63 | return Future(AllFilledFuture(base: self)) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Deferred/FutureComposition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureComposition.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 4/2/16. 6 | // Copyright © 2014-2016 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import Dispatch 10 | 11 | // swiftlint:disable force_cast 12 | // swiftlint:disable function_parameter_count 13 | // swiftlint:disable large_tuple 14 | // swiftlint:disable line_length 15 | // We darn well know what unholiness we are pulling 16 | 17 | extension FutureProtocol { 18 | private func toAny() -> Future { 19 | return every { $0 } 20 | } 21 | 22 | /// Returns a value that becomes determined after both the callee and the 23 | /// given future become determined. 24 | /// 25 | /// - see: SequenceType.allFilled() 26 | public func and(_ one: Other1) -> Future<(Value, Other1.Value)> { 27 | return [ toAny(), one.toAny() ].allFilled().every { (array) in 28 | let zero = array[0] as! Value 29 | let one = array[1] as! Other1.Value 30 | return (zero, one) 31 | } 32 | } 33 | 34 | /// Returns a value that becomes determined after the callee and both other 35 | /// futures become determined. 36 | /// 37 | /// - see: SequenceType.allFilled() 38 | public func and(_ one: Other1, _ two: Other2) -> Future<(Value, Other1.Value, Other2.Value)> { 39 | return [ toAny(), one.toAny(), two.toAny() ].allFilled().every { (array) in 40 | let zero = array[0] as! Value 41 | let one = array[1] as! Other1.Value 42 | let two = array[2] as! Other2.Value 43 | return (zero, one, two) 44 | } 45 | } 46 | 47 | /// Returns a value that becomes determined after the callee and all other 48 | /// futures become determined. 49 | /// 50 | /// - see: SequenceType.allFilled() 51 | public func and(_ one: Other1, _ two: Other2, _ three: Other3) -> Future<(Value, Other1.Value, Other2.Value, Other3.Value)> { 52 | return [ toAny(), one.toAny(), two.toAny(), three.toAny() ].allFilled().every { (array) in 53 | let zero = array[0] as! Value 54 | let one = array[1] as! Other1.Value 55 | let two = array[2] as! Other2.Value 56 | let three = array[3] as! Other3.Value 57 | return (zero, one, two, three) 58 | } 59 | } 60 | 61 | /// Returns a value that becomes determined after the callee and all other 62 | /// futures become determined. 63 | /// 64 | /// - see: SequenceType.allFilled() 65 | public func and(_ one: Other1, _ two: Other2, _ three: Other3, _ four: Other4) -> Future<(Value, Other1.Value, Other2.Value, Other3.Value, Other4.Value)> { 66 | return [ toAny(), one.toAny(), two.toAny(), three.toAny(), four.toAny() ].allFilled().every { (array) in 67 | let zero = array[0] as! Value 68 | let one = array[1] as! Other1.Value 69 | let two = array[2] as! Other2.Value 70 | let three = array[3] as! Other3.Value 71 | let four = array[4] as! Other4.Value 72 | return (zero, one, two, three, four) 73 | } 74 | } 75 | 76 | /// Returns a value that becomes determined after the callee and all other 77 | /// futures become determined. 78 | /// 79 | /// - see: SequenceType.allFilled() 80 | public func and(_ one: Other1, _ two: Other2, _ three: Other3, _ four: Other4, _ five: Other5) -> Future<(Value, Other1.Value, Other2.Value, Other3.Value, Other4.Value, Other5.Value)> { 81 | return [ toAny(), one.toAny(), two.toAny(), three.toAny(), four.toAny(), five.toAny() ].allFilled().every { (array) in 82 | let zero = array[0] as! Value 83 | let one = array[1] as! Other1.Value 84 | let two = array[2] as! Other2.Value 85 | let three = array[3] as! Other3.Value 86 | let four = array[4] as! Other4.Value 87 | let five = array[5] as! Other5.Value 88 | return (zero, one, two, three, four, five) 89 | } 90 | } 91 | 92 | /// Returns a value that becomes determined after the callee and all other 93 | /// futures become determined. 94 | /// 95 | /// - see: SequenceType.allFilled() 96 | public func and(_ one: Other1, _ two: Other2, _ three: Other3, _ four: Other4, _ five: Other5, _ six: Other6) -> Future<(Value, Other1.Value, Other2.Value, Other3.Value, Other4.Value, Other5.Value, Other6.Value)> { 97 | return [ toAny(), one.toAny(), two.toAny(), three.toAny(), four.toAny(), five.toAny(), six.toAny() ].allFilled().every { (array) in 98 | let zero = array[0] as! Value 99 | let one = array[1] as! Other1.Value 100 | let two = array[2] as! Other2.Value 101 | let three = array[3] as! Other3.Value 102 | let four = array[4] as! Other4.Value 103 | let five = array[5] as! Other5.Value 104 | let six = array[6] as! Other6.Value 105 | return (zero, one, two, three, four, five, six) 106 | } 107 | } 108 | 109 | /// Returns a value that becomes determined after the callee and all other 110 | /// futures become determined. 111 | /// 112 | /// - see: SequenceType.allFilled() 113 | public func and(_ one: Other1, _ two: Other2, _ three: Other3, _ four: Other4, _ five: Other5, _ six: Other6, _ seven: Other7) -> Future<(Value, Other1.Value, Other2.Value, Other3.Value, Other4.Value, Other5.Value, Other6.Value, Other7.Value)> { 114 | return [ toAny(), one.toAny(), two.toAny(), three.toAny(), four.toAny(), five.toAny(), six.toAny(), seven.toAny() ].allFilled().every { (array) in 115 | let zero = array[0] as! Value 116 | let one = array[1] as! Other1.Value 117 | let two = array[2] as! Other2.Value 118 | let three = array[3] as! Other3.Value 119 | let four = array[4] as! Other4.Value 120 | let five = array[5] as! Other5.Value 121 | let six = array[6] as! Other6.Value 122 | let seven = array[7] as! Other7.Value 123 | return (zero, one, two, three, four, five, six, seven) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Sources/Deferred/FutureEveryMap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureEveryMap.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 10/27/16. 6 | // Copyright © 2016-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import Dispatch 10 | 11 | /// A `FutureProtocol` whose determined element is that of a `Base` future passed 12 | /// through a transform function returning `NewValue`. This value is computed 13 | /// each time it is read through a call to `upon(queue:body:)`. 14 | private struct LazyMapFuture: FutureProtocol { 15 | let base: Base 16 | let transform: (Base.Value) -> NewValue 17 | fileprivate init(_ base: Base, transform: @escaping(Base.Value) -> NewValue) { 18 | self.base = base 19 | self.transform = transform 20 | } 21 | 22 | func upon(_ executor: Executor, execute body: @escaping(NewValue) -> Void) { 23 | return base.upon(executor) { [transform] in 24 | body(transform($0)) 25 | } 26 | } 27 | 28 | func peek() -> NewValue? { 29 | return base.peek().map(transform) 30 | } 31 | 32 | func wait(until time: DispatchTime) -> NewValue? { 33 | return base.wait(until: time).map(transform) 34 | } 35 | } 36 | 37 | extension FutureProtocol { 38 | /// Returns a future that transparently performs the `eachUseTransform` 39 | /// while reusing the original future. 40 | /// 41 | /// The `upon(_:execute:)` and `wait(until:)` methods of the returned future 42 | /// forward to `self`, wrapping access to the underlying value by the 43 | /// transform. 44 | /// 45 | /// Though producing similar results, this method does not work like `map`, 46 | /// which eagerly evaluates the transform to create a new future with new 47 | /// storage. This is not suitable for simple transforms, such as unboxing 48 | /// or conversion. 49 | /// 50 | /// - see: map(upon:transform:) 51 | public func every(per eachUseTransform: @escaping(Value) -> NewValue) -> Future { 52 | return Future(LazyMapFuture(self, transform: eachUseTransform)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Deferred/FutureIgnore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureIgnore.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 9/3/15. 6 | // Copyright © 2014-2016 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | extension FutureProtocol { 10 | /// Returns a future that ignores the result of this future. 11 | /// 12 | /// This is semantically identical to the following: 13 | /// 14 | /// myFuture.map { _ in } 15 | /// 16 | /// But may behave more efficiently. 17 | /// 18 | /// - see: map(upon:transform:) 19 | public func ignored() -> Future { 20 | return every { _ in } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Deferred/FutureMap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureMap.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 4/2/16. 6 | // Copyright © 2014-2016 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | extension FutureProtocol { 10 | /// Returns a future containing the result of mapping `transform` over the 11 | /// deferred value. 12 | public func map(upon executor: PreferredExecutor, transform: @escaping(Value) -> NewValue) -> Future { 13 | return map(upon: executor as Executor, transform: transform) 14 | } 15 | 16 | /// Returns a future containing the result of mapping `transform` over the 17 | /// deferred value. 18 | /// 19 | /// `map` submits the `transform` to the `executor` once the future's value 20 | /// is determined. 21 | /// 22 | /// - parameter executor: Context to execute the transformation on. 23 | /// - parameter transform: Creates something using the deferred value. 24 | /// - returns: A new future that is filled once the receiver is determined. 25 | public func map(upon executor: Executor, transform: @escaping(Value) -> NewValue) -> Future { 26 | let deferred = Deferred() 27 | upon(executor) { 28 | deferred.fill(with: transform($0)) 29 | } 30 | return Future(deferred) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Deferred/FuturePeek.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FuturePeek.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 11/6/15. 6 | // Copyright © 2015-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | extension FutureProtocol { 10 | /// By default, calls `wait` with no delay. 11 | public func peek() -> Value? { 12 | return wait(until: .now()) 13 | } 14 | 15 | /// Checks for a fulfilled future value. 16 | public var isFilled: Bool { 17 | return peek() != nil 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Deferred/FutureUpon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureUpon.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 4/2/16. 6 | // Copyright © 2014-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import Dispatch 10 | 11 | /// The natural executor for use with Futures; a policy of the framework to 12 | /// allow for shorthand syntax with `Future.upon(_:execute:)` and others. 13 | public typealias PreferredExecutor = DispatchQueue 14 | 15 | extension FutureProtocol { 16 | /// The executor to use as a default argument to `upon` methods on `Future`. 17 | /// 18 | /// Don't provide a default parameter using this declaration unless doing 19 | /// so is unambiguous. For instance, `map` and `andThen` once had a default 20 | /// executor, but users found it unclear where the handlers executed. 21 | public static var defaultUponExecutor: PreferredExecutor { 22 | return DispatchQueue.any() 23 | } 24 | 25 | /// Call some `body` closure in the background once the value is determined. 26 | /// 27 | /// If the value is determined, the closure will be enqueued immediately, 28 | /// but this call is always asynchronous. 29 | public func upon(_ executor: PreferredExecutor = Self.defaultUponExecutor, execute body: @escaping(Value) -> Void) { 30 | upon(executor as Executor, execute: body) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Deferred/Locking.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Locking.swift 3 | // Deferred 4 | // 5 | // Created by John Gallagher on 7/17/14. 6 | // Copyright © 2014-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import Dispatch 10 | import Foundation 11 | 12 | /// A type that mutually excludes execution of code such that only one unit of 13 | /// code is running at any given time. An implementing type may choose to have 14 | /// readers-writer semantics, such that many readers can read at once, or lock 15 | /// around all reads and writes the same way. 16 | public protocol Locking { 17 | /// Call `body` with a reading lock. 18 | /// 19 | /// If the implementing type models a readers-writer lock, this function may 20 | /// behave differently to `withWriteLock(_:)`. 21 | /// 22 | /// - parameter body: A function that reads a value while locked. 23 | /// - returns: The value returned from the given function. 24 | func withReadLock(_ body: () throws -> Return) rethrows -> Return 25 | 26 | /// Attempt to call `body` with a reading lock. 27 | /// 28 | /// If the lock cannot immediately be taken, return `nil` instead of 29 | /// executing `body`. 30 | /// 31 | /// - returns: The value returned from the given function, or `nil`. 32 | /// - see: withReadLock(_:) 33 | func withAttemptedReadLock(_ body: () throws -> Return) rethrows -> Return? 34 | 35 | /// Call `body` with a writing lock. 36 | /// 37 | /// If the implementing type models a readers-writer lock, this function may 38 | /// behave differently to `withReadLock(_:)`. 39 | /// 40 | /// - parameter body: A function that writes a value while locked, then returns some value. 41 | /// - returns: The value returned from the given function. 42 | func withWriteLock(_ body: () throws -> Return) rethrows -> Return 43 | } 44 | 45 | extension Locking { 46 | public func withWriteLock(_ body: () throws -> Return) rethrows -> Return { 47 | return try withReadLock(body) 48 | } 49 | } 50 | 51 | /// A variant lock backed by a platform type that attempts to allow waiters to 52 | /// block efficiently on contention. This locking type behaves the same for both 53 | /// read and write locks. 54 | /// 55 | /// - On Apple platforms (iOS, macOS, tvOS, watchOS, or better), this efficiency is a guarantee. 56 | /// - On Linux, BSD, or Android, waiters perform comparably to a kernel lock 57 | /// under contention. 58 | public final class NativeLock: Locking { 59 | #if canImport(os) 60 | private let lock = UnsafeMutablePointer.allocate(capacity: 1) 61 | #else 62 | private let lock = UnsafeMutablePointer.allocate(capacity: 1) 63 | #endif 64 | 65 | /// Creates a standard platform lock. 66 | public init() { 67 | #if canImport(os) 68 | lock.initialize(to: os_unfair_lock()) 69 | #else 70 | lock.initialize(to: pthread_mutex_t()) 71 | pthread_mutex_init(lock, nil) 72 | #endif 73 | } 74 | 75 | deinit { 76 | #if !canImport(os) 77 | pthread_mutex_destroy(lock) 78 | #endif 79 | lock.deinitialize(count: 1) 80 | lock.deallocate() 81 | } 82 | 83 | public func withReadLock(_ body: () throws -> Return) rethrows -> Return { 84 | #if canImport(os) 85 | os_unfair_lock_lock(lock) 86 | defer { 87 | os_unfair_lock_unlock(lock) 88 | } 89 | #else 90 | pthread_mutex_lock(lock) 91 | defer { 92 | pthread_mutex_unlock(lock) 93 | } 94 | #endif 95 | return try body() 96 | } 97 | 98 | public func withAttemptedReadLock(_ body: () throws -> Return) rethrows -> Return? { 99 | #if canImport(os) 100 | guard os_unfair_lock_trylock(lock) else { return nil } 101 | defer { 102 | os_unfair_lock_unlock(lock) 103 | } 104 | #else 105 | guard pthread_mutex_trylock(lock) == 0 else { return nil } 106 | defer { 107 | pthread_mutex_unlock(lock) 108 | } 109 | #endif 110 | return try body() 111 | } 112 | } 113 | 114 | /// A readers-writer lock provided by the platform implementation of the 115 | /// POSIX Threads standard. Read more: https://en.wikipedia.org/wiki/POSIX_Threads 116 | public final class POSIXReadWriteLock: Locking { 117 | private let lock: UnsafeMutablePointer 118 | 119 | /// Create the standard platform lock. 120 | public init() { 121 | lock = UnsafeMutablePointer.allocate(capacity: 1) 122 | lock.initialize(to: pthread_rwlock_t()) 123 | pthread_rwlock_init(lock, nil) 124 | } 125 | 126 | deinit { 127 | pthread_rwlock_destroy(lock) 128 | lock.deinitialize(count: 1) 129 | lock.deallocate() 130 | } 131 | 132 | public func withReadLock(_ body: () throws -> Return) rethrows -> Return { 133 | pthread_rwlock_rdlock(lock) 134 | defer { 135 | pthread_rwlock_unlock(lock) 136 | } 137 | return try body() 138 | } 139 | 140 | public func withAttemptedReadLock(_ body: () throws -> Return) rethrows -> Return? { 141 | guard pthread_rwlock_tryrdlock(lock) == 0 else { return nil } 142 | defer { 143 | pthread_rwlock_unlock(lock) 144 | } 145 | return try body() 146 | } 147 | 148 | public func withWriteLock(_ body: () throws -> Return) rethrows -> Return { 149 | pthread_rwlock_wrlock(lock) 150 | defer { 151 | pthread_rwlock_unlock(lock) 152 | } 153 | return try body() 154 | } 155 | } 156 | 157 | /// A locking construct using a counting semaphore from Grand Central Dispatch. 158 | /// This locking type behaves the same for both read and write locks. 159 | /// 160 | /// The semaphore lock performs comparably to a spinlock under little lock 161 | /// contention, and comparably to a platform lock under contention. 162 | extension DispatchSemaphore: Locking { 163 | public func withReadLock(_ body: () throws -> Return) rethrows -> Return { 164 | _ = wait(timeout: .distantFuture) 165 | defer { 166 | signal() 167 | } 168 | return try body() 169 | } 170 | 171 | public func withAttemptedReadLock(_ body: () throws -> Return) rethrows -> Return? { 172 | guard case .success = wait(timeout: .now()) else { return nil } 173 | defer { 174 | signal() 175 | } 176 | return try body() 177 | } 178 | } 179 | 180 | /// A lock object from the Foundation Kit used to coordinate the operation of 181 | /// multiple threads of execution within the same application. 182 | extension NSLock: Locking { 183 | public func withReadLock(_ body: () throws -> Return) rethrows -> Return { 184 | lock() 185 | defer { 186 | unlock() 187 | } 188 | return try body() 189 | } 190 | 191 | public func withAttemptedReadLock(_ body: () throws -> Return) rethrows -> Return? { 192 | guard `try`() else { return nil } 193 | defer { 194 | unlock() 195 | } 196 | return try body() 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /Sources/Deferred/Promise.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 8/29/15. 6 | // Copyright © 2014-2016 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | extension Deferred { 10 | /// Determines the deferred `value`. 11 | /// 12 | /// Filling a deferred value should usually be attempted only once. A 13 | /// user may choose to enforce this. 14 | /// 15 | /// * In playgrounds and unoptimized builds (the default for a "Debug" 16 | /// configuration) where the deferred value is already filled, program 17 | /// execution will be stopped in a debuggable state. 18 | /// 19 | /// * In optimized builds (the default for a "Release" configuration) where 20 | /// the deferred value is already filled, stop program execution. 21 | /// 22 | /// * In unchecked builds, filling a deferred value that is already filled 23 | /// is a serious programming error. The optimizer may assume that it is 24 | /// not possible. 25 | @inlinable 26 | public func mustFill(with value: Value, file: StaticString = #file, line: UInt = #line) { 27 | precondition(fill(with: value), "Cannot fill an already-filled \(type(of: self)) using \(#function)", file: file, line: line) 28 | } 29 | } 30 | 31 | extension Deferred where Value == Void { 32 | /// Determines the promised event. 33 | /// 34 | /// Filling a deferred event should usually be attempted only once. 35 | /// 36 | /// - returns: Whether the promise was fulfilled. 37 | @discardableResult 38 | @inlinable 39 | func fill() -> Bool { 40 | return fill(with: ()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Deferred/Protected.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Protected.swift 3 | // Deferred 4 | // 5 | // Created by John Gallagher on 7/17/14. 6 | // Copyright © 2014-2016 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | /// A protected value only allows access from within a locking statement. This 10 | /// prevents accidental unsafe access when thread safety is desired. 11 | public final class Protected { 12 | @usableFromInline 13 | internal final var lock: Locking 14 | @usableFromInline 15 | internal final var unsafeValue: T 16 | 17 | /// Creates a protected `value` with a type implementing a `lock`. 18 | public init(initialValue value: T, lock: Locking = NativeLock()) { 19 | self.unsafeValue = value 20 | self.lock = lock 21 | } 22 | 23 | /// Give read access to the item within `body`. 24 | /// - parameter body: A function that reads from the contained item. 25 | /// - returns: The value returned from the given function. 26 | @inlinable 27 | public func withReadLock(_ body: (T) throws -> Return) rethrows -> Return { 28 | return try lock.withReadLock { 29 | try body(unsafeValue) 30 | } 31 | } 32 | 33 | /// Give write access to the item within the given function. 34 | /// - parameter body: A function that writes to the contained item, and returns some value. 35 | /// - returns: The value returned from the given function. 36 | @inlinable 37 | public func withWriteLock(_ body: (inout T) throws -> Return) rethrows -> Return { 38 | return try lock.withWriteLock { 39 | try body(&unsafeValue) 40 | } 41 | } 42 | } 43 | 44 | extension Protected: CustomDebugStringConvertible, CustomReflectable { 45 | public var debugDescription: String { 46 | var ret = "Protected(" 47 | if lock.withAttemptedReadLock({ 48 | debugPrint(unsafeValue, terminator: "", to: &ret) 49 | }) == nil { 50 | ret.append("locked") 51 | } 52 | ret.append(")") 53 | return ret 54 | } 55 | 56 | public var customMirror: Mirror { 57 | let child: Mirror.Child = lock.withAttemptedReadLock { 58 | (label: "value", value: unsafeValue) 59 | } ?? (label: "isLocked", value: true) 60 | return Mirror(self, children: CollectionOfOne(child), displayStyle: .optional) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2014-2018 Big Nerd Ranch. Licensed under MIT. 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/Task/Either.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Either.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 12/9/15. 6 | // Copyright © 2014-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | /// A type that can exclusively represent one of two values. 10 | /// 11 | /// By design, an either is symmetrical and treats its variants the same. 12 | /// For representing the most common case of success and failures, prefer 13 | /// a result type like `Result`. 14 | /// 15 | /// This protocol describes a minimal interface for representing a result type 16 | /// to overcome limitations with Swift. It is expected that it will be removed 17 | /// completely at some later point. 18 | @available(swift, deprecated: 100000) 19 | public protocol Either { 20 | /// One of the two possible results. 21 | /// 22 | /// By convention, one side indicates a failure through Swift `Error`. 23 | /// 24 | /// A `typealias` instead of an `associatedtype` to avoid breaking 25 | /// compatibility when what we actually want becomes representable. See also 26 | /// . 27 | typealias Left = Error 28 | 29 | /// One of the two possible results. 30 | /// 31 | /// By convention, one side indicates the output of some operation. 32 | associatedtype Right 33 | 34 | /// Creates an instance by evaluating a throwing `body`, capturing its 35 | /// returned value as a right bias, or the thrown error as a left bias. 36 | init(catching body: () throws -> Right) 37 | 38 | /// Returns the right-biased value as a throwing expression. 39 | /// 40 | /// Use this method to retrieve the value of this instance if it is 41 | /// right-biased or to throw the error if it is left-biased. 42 | func get() throws -> Right 43 | } 44 | 45 | extension Either { 46 | @inlinable 47 | init(left: Left) { 48 | self.init { throw left } 49 | } 50 | 51 | @inlinable 52 | init(right: Right) { 53 | self.init { right } 54 | } 55 | 56 | @inlinable 57 | init(from other: Other) where Other: Either, Other.Right == Right { 58 | self.init(catching: other.get) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/Task/Progress+ExplicitComposition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Progress+ExplicitComposition.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 11/12/18. 6 | // Copyright © 2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 10 | import Foundation 11 | 12 | /// A progress object whose attributes reflect that of an external progress 13 | /// tree. 14 | @objc(BNRTaskProxyProgress) 15 | private final class ProxyProgress: Progress { 16 | 17 | @objc dynamic let observee: Progress 18 | let token = Observation() 19 | 20 | /// Creates the progress observer for reflecting the state of `observee`. 21 | /// 22 | /// Given a `parent` of `Progress.current()`, attach the proxy for implicit 23 | /// composition. 24 | init(parent: Progress?, referencing observee: Progress) { 25 | self.observee = observee 26 | super.init(parent: parent) 27 | // The units for this type are percents. 28 | totalUnitCount = 1000 29 | token.observer = self 30 | token.activate(observing: observee) 31 | } 32 | 33 | deinit { 34 | token.invalidate(observing: observee) 35 | } 36 | 37 | // MARK: - Derived values 38 | 39 | override func cancel() { 40 | observee.cancel() 41 | } 42 | 43 | override func pause() { 44 | observee.pause() 45 | } 46 | 47 | override func resume() { 48 | observee.resume() 49 | } 50 | 51 | @objc static let keyPathsForValuesAffectingUserInfo: Set = [ 52 | #keyPath(observee.userInfo) 53 | ] 54 | 55 | override var userInfo: [ProgressUserInfoKey: Any] { 56 | return observee.userInfo 57 | } 58 | 59 | override func setUserInfoObject(_ objectOrNil: Any?, forKey key: ProgressUserInfoKey) { 60 | observee.setUserInfoObject(objectOrNil, forKey: key) 61 | } 62 | 63 | // MARK: - KVO babysitting 64 | 65 | func inheritFraction() { 66 | // Mirror `isIndeterminate`, otherwise reflect the percent to 1 decimal. 67 | completedUnitCount = observee.isIndeterminate ? -1 : Int64(observee.fractionCompleted * 1000) 68 | } 69 | 70 | func inheritAttribute(forKeyPath keyPath: String) { 71 | setValue(observee.value(forKeyPath: keyPath), forKeyPath: keyPath) 72 | } 73 | 74 | func inheritCancelled() { 75 | if observee.isCancelled { 76 | super.cancel() 77 | } 78 | } 79 | 80 | func inheritPaused() { 81 | if observee.isPaused { 82 | super.pause() 83 | } else { 84 | super.resume() 85 | } 86 | } 87 | 88 | /// A side-table object to weakify the progress observer and prevent 89 | /// delivery of notifications after deinit. 90 | final class Observation: NSObject { 91 | static let allKeyPaths = [ 92 | #keyPath(Progress.fractionCompleted), 93 | #keyPath(Progress.isIndeterminate), 94 | #keyPath(Progress.localizedDescription), 95 | #keyPath(Progress.localizedAdditionalDescription), 96 | #keyPath(Progress.isCancellable), 97 | #keyPath(Progress.isPausable), 98 | #keyPath(Progress.kind), 99 | #keyPath(Progress.isCancelled), 100 | #keyPath(Progress.isPaused) 101 | ] 102 | 103 | weak var observer: ProxyProgress? 104 | 105 | func activate(observing observee: Progress) { 106 | objc_setAssociatedObject(observee, Unmanaged.passUnretained(self).toOpaque(), self, .OBJC_ASSOCIATION_RETAIN) 107 | 108 | for key in Observation.allKeyPaths { 109 | observee.addObserver(self, forKeyPath: key, options: .initial, context: nil) 110 | } 111 | } 112 | 113 | func invalidate(observing observee: Progress) { 114 | for key in Observation.allKeyPaths { 115 | observee.removeObserver(self, forKeyPath: key, context: nil) 116 | } 117 | 118 | objc_setAssociatedObject(observee, Unmanaged.passUnretained(self).toOpaque(), nil, .OBJC_ASSOCIATION_ASSIGN) 119 | } 120 | 121 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { 122 | guard let keyPath = keyPath, let observer = observer else { return } 123 | switch keyPath { 124 | case #keyPath(Progress.fractionCompleted), #keyPath(Progress.isIndeterminate): 125 | observer.inheritFraction() 126 | case #keyPath(Progress.isCancelled): 127 | observer.inheritCancelled() 128 | case #keyPath(Progress.isPaused): 129 | observer.inheritPaused() 130 | default: 131 | observer.inheritAttribute(forKeyPath: keyPath) 132 | } 133 | } 134 | } 135 | } 136 | 137 | extension Progress { 138 | /// Adds an external progress tree as a child of this progress tree. 139 | /// 140 | /// This method has a similar effect to 141 | /// `Progress.addChild(_:withPendingUnitCount:)`, where `pendingUnitCount` 142 | /// becomes represented by whatever units `child` represents. 143 | /// 144 | /// This method may be useful if `child` cannot be known to already have no 145 | /// parent. 146 | func addProxiedChild(_ child: Progress, withPendingUnitCount pendingUnitCount: Int64) { 147 | let child = ProxyProgress(parent: nil, referencing: child) 148 | addChild(child, withPendingUnitCount: pendingUnitCount) 149 | } 150 | } 151 | #endif 152 | -------------------------------------------------------------------------------- /Sources/Task/Progress+Future.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Progress+Future.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 11/12/18. 6 | // Copyright © 2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 10 | import Foundation 11 | #if SWIFT_PACKAGE 12 | import Deferred 13 | #endif 14 | 15 | private final class ProgressCompletionExecutor: Executor { 16 | 17 | static let shared = ProgressCompletionExecutor() 18 | 19 | func submit(_ body: @escaping () -> Void) { 20 | body() 21 | } 22 | 23 | } 24 | 25 | extension Progress { 26 | private static let didTaskGenerateKey = ProgressUserInfoKey("_BNRTaskIsGenerated") 27 | 28 | /// Returns a progress that is indeterminate until `wrapped` is fulfilled, 29 | /// then finishes at 100%. 30 | static func basicProgress(for wrapped: Wrapped, uponCancel cancellation: (() -> Void)?) -> Progress { 31 | let child = Progress(parent: nil, userInfo: [ 32 | Progress.didTaskGenerateKey: true 33 | ]) 34 | 35 | if wrapped.isFilled { 36 | // No work to be done; already finished. 37 | child.completedUnitCount = 1 38 | } else { 39 | // Start as indeterminate. 40 | child.completedUnitCount = -1 41 | 42 | // Become determinate and completed upon fill. 43 | wrapped.upon(ProgressCompletionExecutor.shared) { _ in 44 | child.completedUnitCount = 1 45 | } 46 | } 47 | 48 | child.cancellationHandler = cancellation 49 | child.isCancellable = cancellation != nil 50 | return child 51 | } 52 | 53 | /// `true` for wrappers created by `Progress.basicProgress(for:uponCancel:)`. 54 | var wasGeneratedByTask: Bool { 55 | return userInfo[Progress.didTaskGenerateKey] as? Bool == true 56 | } 57 | 58 | /// Synthesizes a simple progress object based on the fulfillment of 59 | /// `wrapped`. If the future is not already fulfilled, the 60 | /// `pendingUnitCount` of `self` is assigned upon fulfillment. Otherwise, 61 | /// the `pendingUnitCount` becomes complete immediately. 62 | func monitorCompletion(of wrapped: Wrapped, uponCancel cancellation: (() -> Void)? = nil, withPendingUnitCount pendingUnitCount: Int64) { 63 | let child = Progress.basicProgress(for: wrapped, uponCancel: cancellation) 64 | addChild(child, withPendingUnitCount: pendingUnitCount) 65 | } 66 | } 67 | #endif 68 | -------------------------------------------------------------------------------- /Sources/Task/Task.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Task.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 9/16/18. 6 | // Copyright © 2018-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if SWIFT_PACKAGE 10 | import Deferred 11 | #endif 12 | 13 | /// An interface describing a thread-safe way for interacting with the result 14 | /// of some work that may either succeed or fail at some point in the future. 15 | /// 16 | /// A "task" is a superset of a future where the asynchronously-determined value 17 | /// represents 1 or more exclusive states. These states can be abstracted over 18 | /// by extensions on the task protocol. The future value is almost always the 19 | /// `TaskResult` type, but many types conforming to `TaskProtocol` may exist. 20 | /// 21 | /// - seealso: `FutureProtocol` 22 | public protocol TaskProtocol: FutureProtocol where Value: Either { 23 | /// A type that represents the success of some asynchronous work. 24 | typealias Success = Value.Right 25 | 26 | /// A type that represents the failure of some asynchronous work. 27 | typealias Failure = Error 28 | 29 | /// Call some `body` closure if the task successfully completes. 30 | /// 31 | /// - parameter executor: A context for handling the `body`. 32 | /// - parameter body: A closure to be invoked when the result is determined. 33 | /// * parameter value: The determined success value. 34 | /// - seealso: `FutureProtocol.upon(_:execute:)` 35 | func uponSuccess(on executor: Executor, execute body: @escaping(_ value: Success) -> Void) 36 | 37 | /// Call some `body` closure if the task fails. 38 | /// 39 | /// - parameter executor: A context for handling the `body`. 40 | /// - parameter body: A closure to be invoked when the result is determined. 41 | /// * parameter error: The determined failure value. 42 | /// - seealso: `FutureProtocol.upon(_:execute:)` 43 | func uponFailure(on executor: Executor, execute body: @escaping(_ error: Failure) -> Void) 44 | 45 | /// Attempt to cancel the underlying work. 46 | /// 47 | /// An implementation should be a "best effort". By default, no cancellation 48 | /// is supported, and this method does nothing. 49 | /// 50 | /// There are several situations in which a valid implementation may not 51 | /// actually cancel: 52 | /// * The work has already completed. 53 | /// * The work has entered an uncancelable state. 54 | /// * An underlying task is not cancellable. 55 | func cancel() 56 | } 57 | 58 | // MARK: - Default implementation 59 | 60 | public extension TaskProtocol { 61 | func cancel() {} 62 | } 63 | 64 | // MARK: - Conditional conformances 65 | 66 | extension Future: TaskProtocol where Value: Either { 67 | /// Create a future having the same underlying task as `other`. 68 | public init(resultFrom wrapped: Wrapped) where Wrapped.Success == Success { 69 | self = wrapped as? Future ?? wrapped.every(per: Value.init) 70 | } 71 | 72 | /// Create a future having the same underlying task as `other`. 73 | public init(succeedsFrom wrapped: Wrapped) where Wrapped.Value == Success { 74 | self = wrapped.every(per: Value.init(right:)) 75 | } 76 | 77 | /// Creates a future that is immediately filled with the result of calling `body` in the current context. 78 | @inlinable 79 | public init(catching body: () throws -> Success) { 80 | self.init(value: Value(catching: body)) 81 | } 82 | 83 | /// Creates an future having already filled successfully with `value`. 84 | @inlinable 85 | public init(success value: Success) { 86 | self.init(value: Value(right: value)) 87 | } 88 | 89 | /// Creates an future having already failed with `error`. 90 | @inlinable 91 | public init(failure error: Failure) { 92 | self.init(value: Value(left: error)) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/Task/TaskAndThen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskAndThen.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 10/27/15. 6 | // Copyright © 2015-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if SWIFT_PACKAGE 10 | import Deferred 11 | #endif 12 | import Dispatch 13 | 14 | extension TaskProtocol { 15 | /// Begins another task by passing the result of the task to `startNextTask` 16 | /// once it completes successfully. 17 | /// 18 | /// On Apple platforms, chaining a task contributes a unit of progress to 19 | /// the root task. A root task is the earliest task in a chain of tasks. If 20 | /// `startNextTask` runs and returns a task that itself reports progress, 21 | /// that progress will also contribute to the chain's overall progress. 22 | /// 23 | /// Cancelling the resulting task will attempt to cancel both the receiving 24 | /// task and the created task. 25 | public func andThen(upon executor: PreferredExecutor, start startNextTask: @escaping(Success) throws -> NewTask) -> Task { 26 | return andThen(upon: executor as Executor, start: startNextTask) 27 | } 28 | 29 | /// Begins another task by passing the result of the task to `startNextTask` 30 | /// once it completes successfully. 31 | /// 32 | /// On Apple platforms, chaining a task contributes a unit of progress to 33 | /// the root task. A root task is the earliest task in a chain of tasks. If 34 | /// `startNextTask` runs and returns a task that itself reports progress, 35 | /// that progress will also contribute to the chain's overall progress. 36 | /// 37 | /// Cancelling the resulting task will attempt to cancel both the receiving 38 | /// task and the created task. 39 | /// 40 | /// - note: It is important to keep in mind the thread safety of the 41 | /// `startNextTask` closure. `andThen` submits `startNextTask` to `executor` 42 | /// once the task completes successfully. 43 | /// - see: FutureProtocol.andThen(upon:start:) 44 | public func andThen(upon executor: Executor, start startNextTask: @escaping(Success) throws -> NewTask) -> Task { 45 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 46 | let chain = TaskChain(andThenFrom: self) 47 | #else 48 | let cancellationToken = Deferred() 49 | #endif 50 | 51 | let future: Future = andThen(upon: executor) { (result) -> Future in 52 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 53 | chain.beginAndThen() 54 | #endif 55 | 56 | do { 57 | let value = try result.get() 58 | let newTask = try startNextTask(value) 59 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 60 | chain.commitAndThen(with: newTask) 61 | #else 62 | cancellationToken.upon(DispatchQueue.any(), execute: newTask.cancel) 63 | #endif 64 | return Future(newTask) 65 | } catch { 66 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 67 | chain.flushAndThen() 68 | #endif 69 | return Future(failure: error) 70 | } 71 | } 72 | 73 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 74 | return Task(future, progress: chain.effectiveProgress) 75 | #else 76 | return Task(future) { 77 | cancellationToken.fill(with: ()) 78 | } 79 | #endif 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/Task/TaskAsync.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskAsync.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 7/14/15. 6 | // Copyright © 2015-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if SWIFT_PACKAGE 10 | import Deferred 11 | #endif 12 | import Dispatch 13 | 14 | extension Task { 15 | /// Captures the result of asynchronously executing `work` on `queue`. 16 | /// 17 | /// Canceling the returned task will not race with the contents of `work`. 18 | /// Once it begins to run, canceling will have no effect. 19 | /// 20 | /// - parameter queue: A dispatch queue to perform the `work` on. 21 | /// - parameter flags: Options controlling how the `work` is executed with 22 | /// respect to system resources. 23 | /// - parameter produceError: Upon cancellation, this value is used to 24 | /// preemptively fail the task. 25 | /// - parameter body: A function body that either calculates and returns the 26 | /// success value for the task or throws to indicate failure. 27 | public static func async(upon queue: DispatchQueue = .any(), flags: DispatchWorkItemFlags = [], onCancel makeError: @autoclosure @escaping() -> Failure, execute work: @escaping() throws -> Success) -> Task { 28 | let deferred = Deferred() 29 | let semaphore = DispatchSemaphore(value: 1) 30 | 31 | queue.async(flags: flags) { 32 | guard case .success = semaphore.wait(timeout: .now()) else { return } 33 | defer { semaphore.signal() } 34 | 35 | deferred.fill(with: Result(catching: work)) 36 | } 37 | 38 | return Task(deferred) { 39 | guard case .success = semaphore.wait(timeout: .now()) else { return } 40 | defer { semaphore.signal() } 41 | 42 | deferred.fail(with: makeError()) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Task/TaskCollections.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskCollections.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 11/18/15. 6 | // Copyright © 2015-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if SWIFT_PACKAGE 10 | import Deferred 11 | #endif 12 | import Dispatch 13 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 14 | import Foundation 15 | #endif 16 | 17 | private struct AllFilled: TaskProtocol { 18 | let group = DispatchGroup() 19 | let combined = Deferred.Result>() 20 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 21 | let progress = Progress() 22 | #else 23 | let cancellations: [() -> Void] 24 | #endif 25 | 26 | init(_ base: Base, mappingBy transform: @escaping([Base.Element]) -> Success) where Base.Element: TaskProtocol { 27 | let array = Array(base) 28 | let queue = DispatchQueue.global(qos: .utility) 29 | 30 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 31 | progress.totalUnitCount = numericCast(array.count) 32 | #else 33 | self.cancellations = array.map { $0.cancel } 34 | #endif 35 | 36 | for future in array { 37 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 38 | if let task = future as? Task { 39 | progress.addProxiedChild(task.progress, withPendingUnitCount: 1) 40 | } else { 41 | progress.monitorCompletion(of: future, withPendingUnitCount: 1) 42 | } 43 | #endif 44 | 45 | group.enter() 46 | future.upon(queue) { [combined, group] (result) in 47 | do { 48 | _ = try result.get() 49 | } catch { 50 | combined.fail(with: error) 51 | } 52 | 53 | group.leave() 54 | } 55 | } 56 | 57 | group.notify(queue: queue) { [combined] in 58 | combined.succeed(with: transform(array)) 59 | } 60 | } 61 | 62 | func upon(_ executor: Executor, execute body: @escaping(Task.Result) -> Void) { 63 | combined.upon(executor, execute: body) 64 | } 65 | 66 | func peek() -> Task.Result? { 67 | return combined.peek() 68 | } 69 | 70 | func wait(until time: DispatchTime) -> Task.Result? { 71 | return combined.wait(until: time) 72 | } 73 | 74 | func cancel() { 75 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 76 | progress.cancel() 77 | #else 78 | for cancellation in cancellations { 79 | cancellation() 80 | } 81 | #endif 82 | } 83 | } 84 | 85 | extension Collection where Element: TaskProtocol { 86 | /// Compose a number of tasks into a single array. 87 | /// 88 | /// If any of the contained tasks fail, the returned task will be determined 89 | /// with that failure. Otherwise, once all operations succeed, the returned 90 | /// task will be fulfilled by combining the values. 91 | public func allSucceeded() -> Task<[Element.Success]> { 92 | guard !isEmpty else { 93 | return Task(success: []) 94 | } 95 | 96 | let wrapper = AllFilled(self) { (array) -> [Element.Success] in 97 | // Expect each to be filled but not successful right now. 98 | // swiftlint:disable:next force_unwrapping 99 | return array.compactMap { try? $0.peek()!.get() } 100 | } 101 | 102 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 103 | return Task(wrapper, progress: wrapper.progress) 104 | #else 105 | return Task(wrapper, uponCancel: wrapper.cancel) 106 | #endif 107 | } 108 | } 109 | 110 | extension Collection where Element: TaskProtocol, Element.Success == Void { 111 | /// Compose a number of tasks into a single array. 112 | /// 113 | /// If any of the contained tasks fail, the returned task will be determined 114 | /// with that failure. Otherwise, once all operations succeed, the returned 115 | /// task will be determined a success. 116 | public func allSucceeded() -> Task { 117 | guard !isEmpty else { 118 | return Task(success: ()) 119 | } 120 | 121 | let wrapper = AllFilled(self) { _ in () } 122 | 123 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 124 | return Task(wrapper, progress: wrapper.progress) 125 | #else 126 | return Task(wrapper, uponCancel: wrapper.cancel) 127 | #endif 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Sources/Task/TaskComposition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskComposition.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 2/11/20. 6 | // Copyright © 2020 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | // swiftlint:disable force_cast 10 | // swiftlint:disable function_parameter_count 11 | // swiftlint:disable identifier_name 12 | // swiftlint:disable large_tuple 13 | 14 | public extension TaskProtocol { 15 | private func toAny() -> Task { 16 | return everySuccess { $0 as Any } 17 | } 18 | 19 | /// Returns a value that becomes determined after both the callee and the 20 | /// given task complete. 21 | /// 22 | /// - see: `Collection.allSucceeded()` 23 | func andSuccess( 24 | of a: A 25 | ) -> Task<(Success, A.Success)> { 26 | return [ toAny(), a.toAny() ].allSucceeded().everySuccess { (array) in 27 | ( 28 | array[0] as! Success, 29 | array[1] as! A.Success 30 | ) 31 | } 32 | } 33 | 34 | /// Returns a value that becomes determined after the callee and both 35 | /// other tasks complete. 36 | /// 37 | /// - see: `Collection.allSucceeded()` 38 | func andSuccess( 39 | of a: A, _ b: B 40 | ) -> Task<(Success, A.Success, B.Success)> { 41 | return [ toAny(), a.toAny(), b.toAny() ].allSucceeded().everySuccess { (array) in 42 | ( 43 | array[0] as! Success, 44 | array[1] as! A.Success, 45 | array[2] as! B.Success 46 | ) 47 | } 48 | } 49 | 50 | /// Returns a value that becomes determined after the callee and all 51 | /// other tasks complete. 52 | /// 53 | /// - see: `Collection.allSucceeded()` 54 | func andSuccess( 55 | of a: A, _ b: B, _ c: C 56 | ) -> Task<(Success, A.Success, B.Success, C.Success)> { 57 | return [ toAny(), a.toAny(), b.toAny(), c.toAny() ].allSucceeded().everySuccess { (array) in 58 | ( 59 | array[0] as! Success, 60 | array[1] as! A.Success, 61 | array[2] as! B.Success, 62 | array[3] as! C.Success 63 | ) 64 | } 65 | } 66 | 67 | /// Returns a value that becomes determined after the callee and all 68 | /// other tasks complete. 69 | /// 70 | /// - see: `Collection.allSucceeded()` 71 | func andSuccess( 72 | of a: A, _ b: B, _ c: C, _ d: D 73 | ) -> Task<(Success, A.Success, B.Success, C.Success, D.Success)> { 74 | return [ toAny(), a.toAny(), b.toAny(), c.toAny(), d.toAny() ].allSucceeded().everySuccess { (array) in 75 | ( 76 | array[0] as! Success, 77 | array[1] as! A.Success, 78 | array[2] as! B.Success, 79 | array[3] as! C.Success, 80 | array[4] as! D.Success 81 | ) 82 | } 83 | } 84 | 85 | /// Returns a value that becomes determined after the callee and all 86 | /// other tasks complete. 87 | /// 88 | /// - see: `Collection.allSucceeded()` 89 | func andSuccess( 90 | of a: A, _ b: B, _ c: C, _ d: D, _ e: E 91 | ) -> Task<(Success, A.Success, B.Success, C.Success, D.Success, E.Success)> { 92 | return [ toAny(), a.toAny(), b.toAny(), c.toAny(), d.toAny(), e.toAny() ].allSucceeded().everySuccess { (array) in 93 | ( 94 | array[0] as! Success, 95 | array[1] as! A.Success, 96 | array[2] as! B.Success, 97 | array[3] as! C.Success, 98 | array[4] as! D.Success, 99 | array[5] as! E.Success 100 | ) 101 | } 102 | } 103 | 104 | /// Returns a value that becomes determined after the callee and all 105 | /// other tasks complete. 106 | /// 107 | /// - see: `Collection.allSucceeded()` 108 | func andSuccess( 109 | of a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F 110 | ) -> Task<(Success, A.Success, B.Success, C.Success, D.Success, E.Success, F.Success)> { 111 | return [ toAny(), a.toAny(), b.toAny(), c.toAny(), d.toAny(), e.toAny(), f.toAny() ].allSucceeded().everySuccess { (array) in 112 | ( 113 | array[0] as! Success, 114 | array[1] as! A.Success, 115 | array[2] as! B.Success, 116 | array[3] as! C.Success, 117 | array[4] as! D.Success, 118 | array[5] as! E.Success, 119 | array[6] as! F.Success 120 | ) 121 | } 122 | } 123 | 124 | /// Returns a value that becomes determined after the callee and all 125 | /// other tasks complete. 126 | /// 127 | /// - see: `Collection.allSucceeded()` 128 | func andSuccess( 129 | of a: A, _ b: B, _ c: C, _ d: D, _ e: E, _ f: F, _ g: G 130 | ) -> Task<(Success, A.Success, B.Success, C.Success, D.Success, E.Success, F.Success, G.Success)> { 131 | return [ toAny(), a.toAny(), b.toAny(), c.toAny(), d.toAny(), e.toAny(), f.toAny(), g.toAny() ].allSucceeded().everySuccess { (array) in 132 | ( 133 | array[0] as! Success, 134 | array[1] as! A.Success, 135 | array[2] as! B.Success, 136 | array[3] as! C.Success, 137 | array[4] as! D.Success, 138 | array[5] as! E.Success, 139 | array[6] as! F.Success, 140 | array[7] as! G.Success 141 | ) 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Sources/Task/TaskEveryMap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskEveryMap.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 2/11/20. 6 | // Copyright © 2020 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import Dispatch 10 | 11 | extension TaskProtocol { 12 | /// Returns a task that transparently performs the `eachUseTransform` 13 | /// using the original task. 14 | /// 15 | /// The `upon(_:execute:)`, `wait(until:)`, `cancel()`, etc. methods 16 | /// of the returned task forward to `self`, wrapping access to the underlying value 17 | /// by the transform. 18 | /// 19 | /// Though this method has a similar signature to `map`, it works differently by 20 | /// evaluating the `eachSuccessTransform` in whatever context the result 21 | /// of `self` is, without any guarantee of thread safety. Use this method to perform 22 | /// trivial code, such as unwrapping an optional. 23 | /// 24 | /// - see: `map(upon:transform:)` 25 | public func everySuccess(per eachSuccessTransform: @escaping(Success) -> NewSuccess) -> Task { 26 | let wrapped = every { 27 | Result(from: $0) 28 | .map(eachSuccessTransform) 29 | } 30 | 31 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 32 | if let progress = (self as? Task)?.progress { 33 | return Task(wrapped, progress: progress) 34 | } 35 | #endif 36 | 37 | return Task(wrapped, uponCancel: cancel) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Task/TaskFallback.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskFallback.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 3/28/17. 6 | // Copyright © 2017-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if SWIFT_PACKAGE 10 | import Deferred 11 | #endif 12 | 13 | extension TaskProtocol { 14 | /// Begins another task in the case of the failure of `self` by calling 15 | /// `restartTask` with the error. 16 | /// 17 | /// On Apple platforms, retrying a task contributes a unit of progress to 18 | /// the root task. A root task is the earliest task in a chain of tasks. If 19 | /// `restartTask` runs and returns a task that itself reports progress, that 20 | /// progress will also contribute to the chain's overall progress. 21 | /// 22 | /// Cancelling the resulting task will attempt to cancel both the receiving 23 | /// task and the created task. 24 | public func fallback(upon executor: PreferredExecutor, to restartTask: @escaping(Failure) throws -> NewTask) -> Task where NewTask.Success == Success { 25 | return fallback(upon: executor as Executor, to: restartTask) 26 | } 27 | 28 | /// Begins another task in the case of the failure of `self` by calling 29 | /// `restartTask` with the error. 30 | /// 31 | /// On Apple platforms, retrying a task contributes a unit of progress to 32 | /// the root task. A root task is the earliest task in a chain of tasks. If 33 | /// `restartTask` runs and returns a task that itself reports progress, that 34 | /// progress will also contribute to the chain's overall progress. 35 | /// 36 | /// Cancelling the resulting task will attempt to cancel both the receiving 37 | /// task and the created task. 38 | /// 39 | /// - note: It is important to keep in mind the thread safety of the 40 | /// `restartTask` closure. `fallback` submits `restartTask` to `executor` 41 | /// once the task fails. 42 | /// - see: FutureProtocol.andThen(upon:start:) 43 | public func fallback(upon executor: Executor, to restartTask: @escaping(Failure) throws -> NewTask) -> Task where NewTask.Success == Success { 44 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 45 | let chain = TaskChain(andThenFrom: self) 46 | #else 47 | let cancellationToken = Deferred() 48 | #endif 49 | 50 | let future: Future = andThen(upon: executor) { (result) -> Future in 51 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 52 | chain.beginAndThen() 53 | #endif 54 | 55 | do { 56 | let value = try result.get() 57 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 58 | chain.flushAndThen() 59 | #endif 60 | return Future(success: value) 61 | } catch { 62 | do { 63 | let newTask = try restartTask(error) 64 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 65 | chain.commitAndThen(with: newTask) 66 | #else 67 | cancellationToken.upon(execute: newTask.cancel) 68 | #endif 69 | return Future(newTask) 70 | } catch { 71 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 72 | chain.flushAndThen() 73 | #endif 74 | return Future(failure: error) 75 | } 76 | } 77 | } 78 | 79 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 80 | return Task(future, progress: chain.effectiveProgress) 81 | #else 82 | return Task(future) { 83 | cancellationToken.fill(with: ()) 84 | } 85 | #endif 86 | } 87 | 88 | /// Begin a task immediately by calling `startTask`, then if it fails retry 89 | /// up to the `numberOfAttempts`. 90 | public static func `repeat`( 91 | upon preferredExecutor: PreferredExecutor, 92 | count numberOfAttempts: Int = 3, 93 | continuingIf shouldRetry: @escaping(Failure) -> Bool = { _ in return true }, 94 | to startTask: @escaping() throws -> Task 95 | ) -> Task { 96 | return self.repeat(upon: preferredExecutor as Executor, count: numberOfAttempts, continuingIf: shouldRetry, to: startTask) 97 | } 98 | 99 | /// Begin a task immediately by calling `startTask`, then if it fails retry 100 | /// by calling `startTask` up to the `numberOfAttempts`. 101 | /// 102 | /// If `numberOfAttempts` is less than 1, `startTask` will still be invoked 103 | /// regardless. 104 | /// 105 | /// If provided, `shouldRetry` will be submitted to the `executor` to check 106 | /// whether the work can be retried for a given failure. Without a retry 107 | /// predicate, the attempt will always continue. 108 | /// 109 | /// - note: It is important to keep in mind the thread safety of the 110 | /// `startTask` closure. It is initially called in the current execution 111 | /// context, then submitted to `executor` for any failures. 112 | public static func `repeat`( 113 | upon executor: Executor, 114 | count numberOfAttempts: Int = 3, 115 | continuingIf shouldRetry: @escaping(Failure) -> Bool = { _ in return true }, 116 | to startTask: @escaping() throws -> Task 117 | ) -> Task { 118 | var lastFailedTask: Task 119 | do { 120 | lastFailedTask = try startTask() 121 | } catch { 122 | return Task(failure: error) 123 | } 124 | 125 | for _ in 0 ..< max(numberOfAttempts, 0) { 126 | lastFailedTask = lastFailedTask.fallback(upon: executor) { (error) -> Task in 127 | guard shouldRetry(error) else { 128 | throw error 129 | } 130 | 131 | return try startTask() 132 | } 133 | } 134 | 135 | return lastFailedTask 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/Task/TaskIgnore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskIgnore.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 4/15/16. 6 | // Copyright © 2015-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if SWIFT_PACKAGE 10 | import Deferred 11 | #endif 12 | 13 | extension TaskProtocol { 14 | /// Returns a task that ignores the successful completion of this task. 15 | /// 16 | /// This is semantically identical to the following: 17 | /// 18 | /// myTask.map { _ in } 19 | /// 20 | /// But behaves more efficiently. 21 | /// 22 | /// The resulting task is cancellable in the same way the receiving task is. 23 | /// 24 | /// - see: map(transform:) 25 | public func ignored() -> Task { 26 | return everySuccess { _ in } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Task/TaskMap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskMap.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 10/27/15. 6 | // Copyright © 2015-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if SWIFT_PACKAGE 10 | import Deferred 11 | #endif 12 | 13 | extension TaskProtocol { 14 | /// Returns a `Task` containing the result of mapping `transform` over the 15 | /// successful task's value. 16 | /// 17 | /// On Apple platforms, mapping a task reports its progress to the root 18 | /// task. A root task is the earliest task in a chain of tasks. During 19 | /// execution of `transform`, an additional progress object created 20 | /// using the current parent will also contribute to the chain's progress. 21 | /// 22 | /// The resulting task is cancellable in the same way the receiving task is. 23 | public func map(upon queue: PreferredExecutor, transform: @escaping(Success) throws -> NewSuccess) -> Task { 24 | return map(upon: queue as Executor, transform: transform) 25 | } 26 | 27 | /// Returns a `Task` containing the result of mapping `transform` over the 28 | /// successful task's value. 29 | /// 30 | /// The `transform` is submitted to the `executor` once the task completes. 31 | /// 32 | /// On Apple platforms, mapping a task reports its progress to the root 33 | /// task. A root task is the earliest task in a chain of tasks. During 34 | /// execution of `transform`, an additional progress object created 35 | /// using the current parent will also contribute to the chain's progress. 36 | /// 37 | /// The resulting task is cancellable in the same way the receiving task is. 38 | /// 39 | /// - see: FutureProtocol.map(upon:transform:) 40 | public func map(upon executor: Executor, transform: @escaping(Success) throws -> NewSuccess) -> Task { 41 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 42 | let chain = TaskChain(mapFrom: self) 43 | #endif 44 | 45 | let future: Future = map(upon: executor) { (result) -> Task.Result in 46 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 47 | chain.beginMap() 48 | defer { chain.commitMap() } 49 | #endif 50 | 51 | return Task.Result { 52 | try transform(result.get()) 53 | } 54 | } 55 | 56 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 57 | return Task(future, progress: chain.effectiveProgress) 58 | #else 59 | return Task(future, uponCancel: cancel) 60 | #endif 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Task/TaskPromise.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskPromise.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 10/27/15. 6 | // Copyright © 2015-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if SWIFT_PACKAGE 10 | import Deferred 11 | #endif 12 | 13 | extension Deferred: TaskProtocol where Value: Either { 14 | /// Completes the task with a successful `value`, or a thrown error. 15 | /// 16 | /// If `value` represents an expression that may fail by throwing, the 17 | /// task will implicitly catch the failure as the result. 18 | /// 19 | /// Fulfilling this deferred value should usually be attempted only once. 20 | /// 21 | /// - seealso: `Deferred.fill(with:)` 22 | @discardableResult 23 | public func succeed(with value: Success) -> Bool { 24 | return fill(with: Value(right: value)) 25 | } 26 | 27 | /// Completes the task with a failed `error`. 28 | /// 29 | /// - see: fill(with:) 30 | @discardableResult 31 | public func fail(with error: Failure) -> Bool { 32 | return fill(with: Value(left: error)) 33 | } 34 | } 35 | 36 | extension Deferred where Value: Either, Value.Right == Void { 37 | /// Completes the task with a success. 38 | /// 39 | /// Fulfilling this deferred value should usually be attempted only once. 40 | /// 41 | /// - seealso: `Deferred.succeed(with:)` 42 | @discardableResult 43 | public func succeed() -> Bool { 44 | return fill(with: Value(right: ())) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/Task/TaskRecovery.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskRecovery.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 10/27/15. 6 | // Copyright © 2015-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if SWIFT_PACKAGE 10 | import Deferred 11 | #endif 12 | 13 | extension TaskProtocol { 14 | /// Returns a `Task` containing the result of mapping `substitution` over 15 | /// the failed task's error. 16 | /// 17 | /// On Apple platforms, recovering from a failed task reports its progress 18 | /// to the root task. A root task is the earliest task in a chain of tasks. 19 | /// During execution of `transform`, an additional progress object created 20 | /// using the current parent will also contribute to the chain's progress. 21 | /// 22 | /// The resulting task is cancellable in the same way the receiving task is. 23 | public func recover(upon executor: PreferredExecutor, substituting substitution: @escaping(Failure) throws -> Success) -> Task { 24 | return recover(upon: executor as Executor, substituting: substitution) 25 | } 26 | 27 | /// Returns a `Task` containing the result of mapping `substitution` over 28 | /// the failed task's error. 29 | /// 30 | /// `recover` submits the `substitution` to the `executor` once the task 31 | /// fails. 32 | /// 33 | /// On Apple platforms, recovering from a failed task reports its progress 34 | /// to the root task. A root task is the earliest task in a chain of tasks. 35 | /// During execution of `transform`, an additional progress object created 36 | /// using the current parent will also contribute to the chain's progress. 37 | /// 38 | /// The resulting task is cancellable in the same way the receiving task is. 39 | /// 40 | /// - see: FutureProtocol.map(upon:transform:) 41 | public func recover(upon executor: Executor, substituting substitution: @escaping(Failure) throws -> Success) -> Task { 42 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 43 | let chain = TaskChain(andThenFrom: self) 44 | #endif 45 | 46 | let future: Future = map(upon: executor) { (result) -> Task.Result in 47 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 48 | chain.beginMap() 49 | defer { chain.commitMap() } 50 | #endif 51 | 52 | return Task.Result { 53 | do { 54 | return try result.get() 55 | } catch { 56 | return try substitution(error) 57 | } 58 | } 59 | } 60 | 61 | #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) 62 | return Task(future, progress: chain.effectiveProgress) 63 | #else 64 | return Task(future, uponCancel: cancel) 65 | #endif 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/Task/TaskResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskResult.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 12/9/15. 6 | // Copyright © 2014-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | // MARK: Compatibility with Protocol Extensions 10 | 11 | extension Result: Either where Failure == Error {} 12 | 13 | // MARK: - Initializers 14 | 15 | private enum TaskResultInitializerError: Error { 16 | case invalidInput 17 | } 18 | 19 | extension Result where Failure == Error { 20 | /// Create an exclusive success/failure state derived from two optionals, 21 | /// in the style of Cocoa completion handlers. 22 | public init(value: Success?, error: Failure?) { 23 | switch (value, error) { 24 | case (let value?, _): 25 | // Ignore error if value is non-nil 26 | self = .success(value) 27 | case (nil, let error?): 28 | self = .failure(error) 29 | case (nil, nil): 30 | self = .failure(TaskResultInitializerError.invalidInput) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Task/TaskUpon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskUpon.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 12/26/15. 6 | // Copyright © 2015-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | #if SWIFT_PACKAGE 10 | import Deferred 11 | #endif 12 | 13 | extension TaskProtocol { 14 | /// Call some `body` closure if the task successfully completes. 15 | /// 16 | /// - seealso: `TaskProtocol.uponSuccess(on:execute:)` 17 | /// - see: `FutureProtocol.upon(_:execute:)` 18 | public func uponSuccess(on executor: PreferredExecutor = Self.defaultUponExecutor, execute body: @escaping(_ value: Success) -> Void) { 19 | uponSuccess(on: executor as Executor, execute: body) 20 | } 21 | 22 | public func uponSuccess(on executor: Executor, execute body: @escaping(_ value: Success) -> Void) { 23 | upon(executor) { (result) in 24 | do { 25 | try body(result.get()) 26 | } catch {} 27 | } 28 | } 29 | 30 | /// Call some `body` closure if the task fails. 31 | /// 32 | /// - seealso: `TaskProtocol.uponFailure(on:execute:)` 33 | /// - seealso: `FutureProtocol.upon(_:execute:)` 34 | public func uponFailure(on executor: PreferredExecutor = Self.defaultUponExecutor, execute body: @escaping(_ error: Failure) -> Void) { 35 | uponFailure(on: executor as Executor, execute: body) 36 | } 37 | 38 | public func uponFailure(on executor: Executor, execute body: @escaping(_ error: Failure) -> Void) { 39 | upon(executor) { result in 40 | do { 41 | _ = try result.get() 42 | } catch { 43 | body(error) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tests/AllTestsCommon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AllTestsCommon.swift 3 | // DeferredTests 4 | // 5 | // Created by Zachary Waldowski on 6/10/15. 6 | // Copyright © 2014-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | import Dispatch 11 | import Deferred 12 | 13 | enum TestError: Error, CustomStringConvertible, CustomDebugStringConvertible { 14 | case first 15 | case second 16 | case third 17 | case fourth 18 | 19 | var description: String { 20 | switch self { 21 | case .first: 22 | return "first" 23 | case .second: 24 | return "second" 25 | case .third: 26 | return "third" 27 | case .fourth: 28 | return "fourth" 29 | } 30 | } 31 | 32 | var debugDescription: String { 33 | switch self { 34 | case .first: 35 | return "TestError.first" 36 | case .second: 37 | return "TestError.second" 38 | case .third: 39 | return "TestError.third" 40 | case .fourth: 41 | return "TestError.fourth" 42 | } 43 | } 44 | } 45 | 46 | // MARK: - 47 | 48 | extension XCTestCase { 49 | func expectation(deallocationOf object: AnyObject) -> XCTestExpectation { 50 | return expectation(for: NSPredicate(block: { [weak object] (_, _) -> Bool in 51 | object == nil 52 | }), evaluatedWith: NSNull(), handler: nil) 53 | } 54 | 55 | var shortTimeoutInverted: TimeInterval { 56 | return 0.5 57 | } 58 | 59 | var shortTimeout: TimeInterval { 60 | return 3 61 | } 62 | 63 | var longTimeout: TimeInterval { 64 | return 10 65 | } 66 | 67 | func afterShortDelay(upon queue: DispatchQueue = .global(), execute body: @escaping() -> Void) { 68 | queue.asyncAfter(deadline: .now() + 0.3, execute: body) 69 | } 70 | } 71 | 72 | extension FutureProtocol { 73 | /// Waits for the value to become determined, then returns it. 74 | /// 75 | /// This is equivalent to unwrapping the value of calling 76 | /// `wait(until: .distantFuture)`, but may be more efficient. 77 | /// 78 | /// This getter will unnecessarily block execution. It might be useful for 79 | /// testing, but otherwise it should be strictly avoided. 80 | /// 81 | /// - returns: The determined value. 82 | var value: Value { 83 | return wait(until: .distantFuture).unsafelyUnwrapped 84 | } 85 | 86 | func shortWait() -> Value? { 87 | return wait(until: .now() + 0.05) 88 | } 89 | } 90 | 91 | // MARK: - 92 | 93 | private class CountingExecutor: Executor { 94 | let submitCount = Protected(initialValue: 0) 95 | 96 | init() {} 97 | 98 | func submit(_ body: @escaping() -> Void) { 99 | submitCount.withWriteLock { $0 += 1 } 100 | body() 101 | } 102 | } 103 | 104 | class CustomExecutorTestCase: XCTestCase { 105 | private let _customExecutor = CountingExecutor() 106 | 107 | final var customExecutor: Executor { 108 | return _customExecutor 109 | } 110 | 111 | final func expectationThatCustomExecutor(isCalledAtLeast times: Int) -> XCTestExpectation { 112 | return expectation(for: NSPredicate(block: { (sself, _) -> Bool in 113 | guard let sself = sself as? CustomExecutorTestCase else { return false } 114 | return sself._customExecutor.submitCount.withReadLock({ $0 >= times }) 115 | }), evaluatedWith: self) 116 | } 117 | 118 | final let customQueue = DispatchQueue(label: "com.bignerdranch.DeferredTests") 119 | 120 | final func expectCustomQueueToBeEmpty() -> XCTestExpectation { 121 | let expect = expectation(description: "queue is empty") 122 | customQueue.async(flags: .barrier) { 123 | expect.fulfill() 124 | } 125 | return expect 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Tests/DeferredTests/AllTestsCommon.swift: -------------------------------------------------------------------------------- 1 | ../AllTestsCommon.swift -------------------------------------------------------------------------------- /Tests/DeferredTests/ExistentialFutureTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExistentialFutureTests.swift 3 | // DeferredTests 4 | // 5 | // Created by Zachary Waldowski on 9/3/15. 6 | // Copyright © 2014-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | 11 | import Deferred 12 | 13 | class ExistentialFutureTests: XCTestCase { 14 | static let allTests: [(String, (ExistentialFutureTests) -> () throws -> Void)] = [ 15 | ("testPeekWhenFilled", testPeekWhenFilled), 16 | ("testPeekWhenUnfilled", testPeekWhenUnfilled), 17 | ("testAnyWaitWithTimeout", testAnyWaitWithTimeout), 18 | ("testFilledAnyFutureUpon", testFilledAnyFutureUpon), 19 | ("testUnfilledAnyUponCalledWhenFilled", testUnfilledAnyUponCalledWhenFilled), 20 | ("testFillAndIsFilledPostcondition", testFillAndIsFilledPostcondition), 21 | ("testDebugDescriptionUnfilled", testDebugDescriptionUnfilled), 22 | ("testDebugDescriptionFilled", testDebugDescriptionFilled), 23 | ("testDebugDescriptionFilledWhenValueIsVoid", testDebugDescriptionFilledWhenValueIsVoid), 24 | ("testReflectionUnfilled", testReflectionUnfilled), 25 | ("testReflectionFilled", testReflectionFilled), 26 | ("testReflectionFilledWhenValueIsVoid", testReflectionFilledWhenValueIsVoid) 27 | ] 28 | 29 | var anyFuture: Future! 30 | 31 | override func tearDown() { 32 | anyFuture = nil 33 | 34 | super.tearDown() 35 | } 36 | 37 | func testPeekWhenFilled() { 38 | anyFuture = Future(value: 42) 39 | let peek = anyFuture.peek() 40 | XCTAssertNotNil(peek) 41 | } 42 | 43 | func testPeekWhenUnfilled() { 44 | anyFuture = Future.never 45 | let peek = anyFuture.peek() 46 | XCTAssertNil(peek) 47 | } 48 | 49 | func testAnyWaitWithTimeout() { 50 | let deferred = Deferred() 51 | anyFuture = deferred.eraseToFuture() 52 | 53 | let expect = expectation(description: "value blocks while unfilled") 54 | afterShortDelay { 55 | deferred.fill(with: 42) 56 | expect.fulfill() 57 | } 58 | 59 | XCTAssertNil(anyFuture.shortWait()) 60 | 61 | wait(for: [ expect ], timeout: shortTimeout) 62 | } 63 | 64 | func testFilledAnyFutureUpon() { 65 | let future = Future(value: 1) 66 | let allExpectations = (0 ..< 10).map { (iteration) -> XCTestExpectation in 67 | let expect = expectation(description: "upon block #\(iteration) called with correct value") 68 | future.upon { value in 69 | XCTAssertEqual(value, 1) 70 | expect.fulfill() 71 | } 72 | return expect 73 | } 74 | wait(for: allExpectations, timeout: longTimeout) 75 | } 76 | 77 | func testUnfilledAnyUponCalledWhenFilled() { 78 | let deferred = Deferred() 79 | anyFuture = deferred.eraseToFuture() 80 | 81 | let allExpectations = (0 ..< 10).map { (iteration) -> XCTestExpectation in 82 | let expect = expectation(description: "upon block #\(iteration) not called while deferred is unfilled") 83 | anyFuture.upon { value in 84 | XCTAssertEqual(value, 1) 85 | XCTAssertEqual(deferred.value, value) 86 | expect.fulfill() 87 | } 88 | return expect 89 | } 90 | 91 | deferred.fill(with: 1) 92 | wait(for: allExpectations, timeout: longTimeout) 93 | } 94 | 95 | func testFillAndIsFilledPostcondition() { 96 | let deferred = Deferred() 97 | anyFuture = deferred.eraseToFuture() 98 | XCTAssertNil(anyFuture.peek()) 99 | XCTAssertFalse(anyFuture.isFilled) 100 | deferred.fill(with: 42) 101 | XCTAssertNotNil(anyFuture.peek()) 102 | XCTAssertTrue(anyFuture.isFilled) 103 | } 104 | 105 | func testDebugDescriptionUnfilled() { 106 | let future = Future.never 107 | XCTAssertEqual("\(future)", "Future(not filled)") 108 | } 109 | 110 | func testDebugDescriptionFilled() { 111 | let future = Future(value: 42) 112 | XCTAssertEqual("\(future)", "Future(42)") 113 | } 114 | 115 | func testDebugDescriptionFilledWhenValueIsVoid() { 116 | let future = Future(value: ()) 117 | XCTAssertEqual("\(future)", "Future(filled)") 118 | } 119 | 120 | func testReflectionUnfilled() { 121 | let future = Future.never 122 | 123 | let magicMirror = Mirror(reflecting: future) 124 | XCTAssertEqual(magicMirror.displayStyle, .optional) 125 | XCTAssertNil(magicMirror.superclassMirror) 126 | XCTAssertEqual(magicMirror.descendant("isFilled") as? Bool, false) 127 | } 128 | 129 | func testReflectionFilled() { 130 | let future = Future(value: 42) 131 | 132 | let magicMirror = Mirror(reflecting: future) 133 | XCTAssertEqual(magicMirror.displayStyle, .optional) 134 | XCTAssertNil(magicMirror.superclassMirror) 135 | XCTAssertEqual(magicMirror.descendant(0) as? Int, 42) 136 | } 137 | 138 | func testReflectionFilledWhenValueIsVoid() { 139 | let future = Future(value: ()) 140 | 141 | let magicMirror = Mirror(reflecting: future) 142 | XCTAssertEqual(magicMirror.displayStyle, .optional) 143 | XCTAssertNil(magicMirror.superclassMirror) 144 | XCTAssertEqual(magicMirror.descendant("isFilled") as? Bool, true) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Tests/DeferredTests/FilledDeferredTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilledDeferredTests.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 4/30/18. 6 | // Copyright © 2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | import Dispatch 11 | import Deferred 12 | 13 | class FilledDeferredTests: XCTestCase { 14 | static let allTests: [(String, (FilledDeferredTests) -> () throws -> Void)] = [ 15 | ("testPeek", testPeek), 16 | ("testWaitWithTimeout", testWaitWithTimeout), 17 | ("testValue", testValue), 18 | ("testCannotFillMultipleTimes", testCannotFillMultipleTimes), 19 | ("testIsFilled", testIsFilled), 20 | ("testUpon", testUpon), 21 | ("testUponMainQueueCalled", testUponMainQueueCalled), 22 | ("testConcurrentUpon", testConcurrentUpon), 23 | ("testAllCopiesOfADeferredValueRepresentTheSameDeferredValue", testAllCopiesOfADeferredValueRepresentTheSameDeferredValue), 24 | ("testDeferredOptionalBehavesCorrectly", testDeferredOptionalBehavesCorrectly), 25 | ("testIsFilledCanBeCalledMultipleTimesWhenFilled", testIsFilledCanBeCalledMultipleTimesWhenFilled), 26 | ("testDebugDescription", testDebugDescription), 27 | ("testDebugDescriptionWhenValueIsVoid", testDebugDescriptionWhenValueIsVoid), 28 | ("testReflection", testReflection), 29 | ("testReflectionFilledWhenValueIsVoid", testReflectionFilledWhenValueIsVoid) 30 | ] 31 | 32 | func testPeek() { 33 | let filled = Deferred(filledWith: 1) 34 | XCTAssertEqual(filled.peek(), 1) 35 | } 36 | 37 | func testWaitWithTimeout() { 38 | let filled = Deferred(filledWith: 42) 39 | XCTAssertNotNil(filled.shortWait()) 40 | } 41 | 42 | func testValue() { 43 | let filled = Deferred(filledWith: 2) 44 | XCTAssertEqual(filled.value, 2) 45 | } 46 | 47 | func testCannotFillMultipleTimes() { 48 | let filled = Deferred(filledWith: 1) 49 | XCTAssertFalse(filled.fill(with: 2)) 50 | XCTAssertEqual(filled.value, 1) 51 | } 52 | 53 | func testIsFilled() { 54 | let filled = Deferred(filledWith: 1) 55 | XCTAssertTrue(filled.isFilled) 56 | } 57 | 58 | func testUpon() { 59 | let filled = Deferred(filledWith: 1) 60 | 61 | let allExpectations = (0 ..< 10).map { (iteration) -> XCTestExpectation in 62 | let expect = expectation(description: "upon block #\(iteration) not called while deferred is unfilled") 63 | filled.upon { value in 64 | XCTAssertEqual(value, 1) 65 | expect.fulfill() 66 | } 67 | return expect 68 | } 69 | 70 | wait(for: allExpectations, timeout: longTimeout) 71 | } 72 | 73 | func testUponMainQueueCalled() { 74 | let filled = Deferred(filledWith: 1) 75 | 76 | let expect = expectation(description: "upon block called on main queue") 77 | filled.upon(.main) { value in 78 | XCTAssert(Thread.isMainThread) 79 | XCTAssertEqual(value, 1) 80 | expect.fulfill() 81 | } 82 | 83 | wait(for: [ expect ], timeout: shortTimeout) 84 | } 85 | 86 | func testConcurrentUpon() { 87 | let filled = Deferred(filledWith: 1) 88 | let queue = DispatchQueue.global() 89 | 90 | // spin up a bunch of these in parallel... 91 | let allExpectations = (0 ..< 32).map { (iteration) -> XCTestExpectation in 92 | let expect = expectation(description: "upon block \(iteration)") 93 | queue.async { 94 | filled.upon { _ in 95 | expect.fulfill() 96 | } 97 | } 98 | return expect 99 | } 100 | 101 | // ... and make sure all our upon blocks were called (i.e., the write lock protected access) 102 | wait(for: allExpectations, timeout: longTimeout) 103 | } 104 | 105 | /// Deferred values behave as values: All copies reflect the same value. 106 | /// The wrinkle of course is that the value might not be observable till a later 107 | /// date. 108 | func testAllCopiesOfADeferredValueRepresentTheSameDeferredValue() { 109 | let anyValue = 42 110 | 111 | let parent = Deferred(filledWith: anyValue) 112 | let child1 = parent 113 | let child2 = parent 114 | let allDeferreds = [parent, child1, child2] 115 | 116 | let expectedValues = Array(repeating: anyValue, count: allDeferreds.count) 117 | 118 | let expect = expectation(description: "filling any copy fulfills all") 119 | allDeferreds.allFilled().upon { (allValues) in 120 | XCTAssertEqual(allValues, expectedValues, "all deferreds are the same value") 121 | expect.fulfill() 122 | } 123 | 124 | wait(for: [ expect ], timeout: shortTimeout) 125 | } 126 | 127 | func testDeferredOptionalBehavesCorrectly() { 128 | let filled = Deferred(filledWith: nil) 129 | 130 | let beforeExpect = expectation(description: "already filled with nil optional") 131 | filled.upon { (value) in 132 | XCTAssertNil(value) 133 | beforeExpect.fulfill() 134 | } 135 | 136 | XCTAssertFalse(filled.fill(with: 42)) 137 | 138 | let afterExpect = expectation(description: "stays filled with same optional") 139 | filled.upon { (value) in 140 | XCTAssertNil(value) 141 | afterExpect.fulfill() 142 | } 143 | 144 | wait(for: [ beforeExpect, afterExpect ], timeout: shortTimeout) 145 | } 146 | 147 | func testIsFilledCanBeCalledMultipleTimesWhenFilled() { 148 | let filled = Deferred(filledWith: 42) 149 | 150 | for _ in 0 ..< 5 { 151 | XCTAssertTrue(filled.isFilled) 152 | } 153 | } 154 | 155 | func testDebugDescription() { 156 | let filled = Deferred(filledWith: 42) 157 | 158 | XCTAssertEqual("\(filled)", "Deferred(42)") 159 | } 160 | 161 | func testDebugDescriptionWhenValueIsVoid() { 162 | let filled = Deferred(filledWith: ()) 163 | 164 | XCTAssertEqual("\(filled)", "Deferred(filled)") 165 | } 166 | 167 | func testReflection() { 168 | let filled = Deferred(filledWith: 42) 169 | 170 | let magicMirror = Mirror(reflecting: filled) 171 | XCTAssertEqual(magicMirror.displayStyle, .optional) 172 | XCTAssertNil(magicMirror.superclassMirror) 173 | XCTAssertEqual(magicMirror.descendant(0) as? Int, 42) 174 | } 175 | 176 | func testReflectionFilledWhenValueIsVoid() { 177 | let filled = Deferred(filledWith: ()) 178 | 179 | let magicMirror = Mirror(reflecting: filled) 180 | XCTAssertEqual(magicMirror.displayStyle, .optional) 181 | XCTAssertNil(magicMirror.superclassMirror) 182 | XCTAssertEqual(magicMirror.descendant("isFilled") as? Bool, true) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /Tests/DeferredTests/FutureAsyncTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureAsyncTests.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 6/3/18. 6 | // Copyright © 2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | import Dispatch 11 | import Deferred 12 | 13 | class FutureAsyncTests: XCTestCase { 14 | static let allTests: [(String, (FutureAsyncTests) -> () throws -> Void)] = [ 15 | ("testThatPeekingBeforeStartingReturnsNil", testThatPeekingBeforeStartingReturnsNil) 16 | ] 17 | 18 | private var queue: DispatchQueue! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | 23 | queue = DispatchQueue(label: "FutureAsyncTests") 24 | queue.suspend() 25 | } 26 | 27 | override func tearDown() { 28 | queue = nil 29 | 30 | super.tearDown() 31 | } 32 | 33 | func testThatPeekingBeforeStartingReturnsNil() { 34 | let future = Future.async(upon: queue) { 1 } 35 | 36 | XCTAssertNil(future.peek()) 37 | 38 | queue.resume() 39 | 40 | let expect = expectation(description: "future fulfils") 41 | future.upon(queue) { (result) in 42 | XCTAssertEqual(result, 1) 43 | expect.fulfill() 44 | } 45 | 46 | wait(for: [ expect ], timeout: shortTimeout) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/DeferredTests/FutureCustomExecutorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureCustomExecutorTests.swift 3 | // DeferredTests 4 | // 5 | // Created by Zachary Waldowski on 4/10/16. 6 | // Copyright © 2016-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | 11 | import Deferred 12 | 13 | class FutureCustomExecutorTests: CustomExecutorTestCase { 14 | static let allTests: [(String, (FutureCustomExecutorTests) -> () throws -> Void)] = [ 15 | ("testUpon", testUpon), 16 | ("testMap", testMap), 17 | ("testAndThen", testAndThen) 18 | ] 19 | 20 | func testUpon() { 21 | let deferred = Deferred() 22 | 23 | let expect = expectation(description: "upon block called when deferred is filled") 24 | deferred.upon(customExecutor) { _ in 25 | expect.fulfill() 26 | } 27 | 28 | deferred.fill(with: ()) 29 | 30 | wait(for: [ 31 | expect, 32 | expectationThatCustomExecutor(isCalledAtLeast: 1) 33 | ], timeout: shortTimeout) 34 | } 35 | 36 | func testMap() { 37 | let marker = Deferred() 38 | let testValue = 42 39 | let mapped = marker.map(upon: customExecutor) { _ in testValue } 40 | 41 | let expect = expectation(description: "upon block called when deferred is filled") 42 | mapped.upon(customExecutor) { 43 | XCTAssertEqual($0, testValue) 44 | expect.fulfill() 45 | } 46 | 47 | marker.fill(with: ()) 48 | 49 | wait(for: [ 50 | expect, 51 | expectationThatCustomExecutor(isCalledAtLeast: 2) 52 | ], timeout: shortTimeout) 53 | } 54 | 55 | // Should this be promoted to an initializer on Future? 56 | private func delay(_ value: @autoclosure @escaping() -> Value) -> Future { 57 | let deferred = Deferred() 58 | afterShortDelay { 59 | deferred.fill(with: value()) 60 | } 61 | return Future(deferred) 62 | } 63 | 64 | func testAndThen() { 65 | let marker = Deferred() 66 | let testValue = 42 67 | let flattened = marker.andThen(upon: customExecutor) { _ in self.delay(testValue) } 68 | 69 | let expect = expectation(description: "upon block called when deferred is filled") 70 | flattened.upon(customExecutor) { 71 | XCTAssertEqual($0, testValue) 72 | expect.fulfill() 73 | } 74 | 75 | marker.fill(with: ()) 76 | 77 | wait(for: [ 78 | expect, 79 | expectationThatCustomExecutor(isCalledAtLeast: 3) 80 | ], timeout: shortTimeout) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/DeferredTests/FutureIgnoreTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureIgnoreTests.swift 3 | // DeferredTests 4 | // 5 | // Created by Zachary Waldowski on 9/3/15. 6 | // Copyright © 2014-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | 11 | import Deferred 12 | 13 | class FutureIgnoreTests: XCTestCase { 14 | static let allTests: [(String, (FutureIgnoreTests) -> () throws -> Void)] = [ 15 | ("testWaitWithTimeout", testWaitWithTimeout), 16 | ("testIgnoredUponCalledWhenFilled", testIgnoredUponCalledWhenFilled) 17 | ] 18 | 19 | var future: Future! 20 | 21 | override func tearDown() { 22 | future = nil 23 | 24 | super.tearDown() 25 | } 26 | 27 | func testWaitWithTimeout() { 28 | let deferred = Deferred() 29 | future = deferred.ignored() 30 | 31 | let expect = expectation(description: "value blocks while unfilled") 32 | afterShortDelay { 33 | deferred.fill(with: 42) 34 | expect.fulfill() 35 | } 36 | 37 | XCTAssertNil(future.shortWait()) 38 | 39 | wait(for: [ expect ], timeout: shortTimeout) 40 | } 41 | 42 | func testIgnoredUponCalledWhenFilled() { 43 | let deferred = Deferred() 44 | future = deferred.ignored() 45 | 46 | let allExpectations = (0 ..< 10).map { (iteration) -> XCTestExpectation in 47 | let expect = expectation(description: "upon block \(iteration) not called while deferred is unfilled") 48 | future.upon { _ in 49 | XCTAssertEqual(deferred.value, 1) 50 | expect.fulfill() 51 | } 52 | return expect 53 | } 54 | 55 | deferred.fill(with: 1) 56 | wait(for: allExpectations, timeout: longTimeout) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/DeferredTests/FutureTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FutureTests.swift 3 | // DeferredTests 4 | // 5 | // Created by Zachary Waldowski on 9/24/16. 6 | // Copyright © 2016-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | import Dispatch 11 | import Deferred 12 | 13 | class FutureTests: XCTestCase { 14 | static let allTests: [(String, (FutureTests) -> () throws -> Void)] = [ 15 | ("testAnd", testAnd), 16 | ("testAllFilled", testAllFilled), 17 | ("testAllFilledEmptyCollection", testAllFilledEmptyCollection), 18 | ("testFirstFilled", testFirstFilled) 19 | ] 20 | 21 | func testAnd() { 22 | let toBeCombined1 = Deferred() 23 | let toBeCombined2 = Deferred() 24 | let combined = toBeCombined1.and(toBeCombined2) 25 | 26 | let expect = expectation(description: "paired deferred should be filled") 27 | combined.upon(.main) { (value) in 28 | XCTAssertEqual(value.0, 1) 29 | XCTAssertEqual(value.1, "foo") 30 | expect.fulfill() 31 | } 32 | 33 | XCTAssertFalse(combined.isFilled) 34 | toBeCombined1.fill(with: 1) 35 | 36 | XCTAssertFalse(combined.isFilled) 37 | toBeCombined2.fill(with: "foo") 38 | 39 | wait(for: [ expect ], timeout: shortTimeout) 40 | } 41 | 42 | func testAllFilled() { 43 | var toBeCombined = [Deferred]() 44 | 45 | for _ in 0 ..< 10 { 46 | toBeCombined.append(Deferred()) 47 | } 48 | 49 | let combined = toBeCombined.allFilled() 50 | let outerExpect = expectation(description: "all results filled in") 51 | let innerExpect = expectation(description: "paired deferred should be filled") 52 | 53 | // skip first 54 | for (value, toFill) in toBeCombined.enumerated().dropFirst() { 55 | toFill.fill(with: value) 56 | } 57 | 58 | self.afterShortDelay { 59 | XCTAssertFalse(combined.isFilled) // unfilled because d[0] is still unfilled 60 | toBeCombined[0].fill(with: 0) 61 | 62 | self.afterShortDelay { 63 | XCTAssertTrue(combined.value == [Int](0 ..< toBeCombined.count)) 64 | innerExpect.fulfill() 65 | } 66 | outerExpect.fulfill() 67 | } 68 | 69 | wait(for: [ outerExpect, innerExpect ], timeout: shortTimeout) 70 | } 71 | 72 | func testAllFilledEmptyCollection() { 73 | let deferred = EmptyCollection>().allFilled() 74 | XCTAssert(deferred.isFilled) 75 | } 76 | 77 | func testFirstFilled() { 78 | let allDeferreds = (0 ..< 10).map { _ in Deferred() } 79 | let winner = allDeferreds.firstFilled() 80 | 81 | allDeferreds[3].fill(with: 3) 82 | 83 | let outerExpect = expectation(description: "any is filled") 84 | let innerExpect = expectation(description: "any is not changed") 85 | 86 | self.afterShortDelay { 87 | XCTAssertEqual(winner.value, 3) 88 | 89 | allDeferreds[4].fill(with: 4) 90 | 91 | self.afterShortDelay { 92 | XCTAssertEqual(winner.value, 3) 93 | innerExpect.fulfill() 94 | } 95 | 96 | outerExpect.fulfill() 97 | } 98 | 99 | wait(for: [ outerExpect, innerExpect ], timeout: shortTimeout) 100 | } 101 | 102 | func testEveryMapTransformerIsCalledMultipleTimes() { 103 | let deferred = Deferred(filledWith: 1) 104 | 105 | let everyExpectation = XCTestExpectation(description: "every is called each time the result is needed") 106 | everyExpectation.expectedFulfillmentCount = 4 107 | 108 | let doubled = deferred.every { (value) -> Int in 109 | everyExpectation.fulfill() 110 | return value * 2 111 | } 112 | 113 | let uponExpection = XCTestExpectation(description: "upon is called when filled") 114 | uponExpection.expectedFulfillmentCount = 4 115 | 116 | for _ in 0 ..< 4 { 117 | doubled.upon { (value) in 118 | XCTAssertEqual(value, 2) 119 | uponExpection.fulfill() 120 | } 121 | } 122 | 123 | wait(for: [ everyExpectation, uponExpection ], timeout: shortTimeout) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Tests/DeferredTests/PerformanceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Dispatch 3 | import Deferred 4 | 5 | class PerformanceTests: XCTestCase { 6 | 7 | private let iterationCount = 10_000 8 | 9 | // MARK: - GCD 10 | 11 | func testDispatchAsyncOnSerialQueue() { 12 | let queue = DispatchQueue(label: #function, qos: .userInitiated) 13 | let group = DispatchGroup() 14 | 15 | measure { 16 | for _ in 0 ..< iterationCount { 17 | group.enter() 18 | queue.async { 19 | group.leave() 20 | } 21 | } 22 | 23 | group.wait() 24 | } 25 | } 26 | 27 | func testDoubleDispatchAsyncOnSerialQueue() { 28 | let queue = DispatchQueue(label: #function, qos: .userInitiated) 29 | let group = DispatchGroup() 30 | 31 | measure { 32 | for _ in 0 ..< iterationCount { 33 | group.enter() 34 | queue.async { 35 | queue.async { 36 | group.leave() 37 | } 38 | } 39 | } 40 | 41 | group.wait() 42 | } 43 | } 44 | 45 | func testTripleDispatchAsyncOnSerialQueue() { 46 | let queue = DispatchQueue(label: #function, qos: .userInitiated) 47 | let group = DispatchGroup() 48 | 49 | measure { 50 | for _ in 0 ..< iterationCount { 51 | group.enter() 52 | queue.async { 53 | queue.async { 54 | queue.async { 55 | group.leave() 56 | } 57 | } 58 | } 59 | } 60 | 61 | group.wait() 62 | } 63 | } 64 | 65 | func testDispatchAsyncOnConcurrentQueue() { 66 | let queue = DispatchQueue(label: #function, qos: .userInitiated, attributes: .concurrent) 67 | let group = DispatchGroup() 68 | func noop() {} 69 | 70 | measure { 71 | for _ in 0 ..< iterationCount { 72 | queue.async(group: group, execute: noop) 73 | } 74 | 75 | XCTAssertEqual(group.wait(timeout: .now() + 1), .success) 76 | } 77 | } 78 | 79 | // MARK: - Deferred 80 | 81 | func testUponToSerialQueue() { 82 | let queue = DispatchQueue(label: #function, qos: .userInitiated) 83 | let group = DispatchGroup() 84 | 85 | measure { 86 | let deferred = Deferred() 87 | 88 | for _ in 0 ..< iterationCount { 89 | group.enter() 90 | deferred.upon(queue) { _ in 91 | group.leave() 92 | } 93 | } 94 | 95 | deferred.fill(with: true) 96 | group.wait() 97 | } 98 | } 99 | 100 | func testDoubleUponToSerialQueue() { 101 | let queue = DispatchQueue(label: #function, qos: .userInitiated) 102 | let group = DispatchGroup() 103 | 104 | measure { 105 | let deferred = Deferred() 106 | 107 | for _ in 0 ..< iterationCount { 108 | group.enter() 109 | deferred.upon(queue) { _ in 110 | deferred.upon(queue) { _ in 111 | group.leave() 112 | } 113 | } 114 | } 115 | 116 | deferred.fill(with: true) 117 | group.wait() 118 | } 119 | } 120 | 121 | func testTripleUponSerialQueue() { 122 | let queue = DispatchQueue(label: #function, qos: .userInitiated) 123 | let group = DispatchGroup() 124 | 125 | measure { 126 | let deferred = Deferred() 127 | 128 | for _ in 0 ..< iterationCount { 129 | group.enter() 130 | deferred.upon(queue) { _ in 131 | deferred.upon(queue) { _ in 132 | deferred.upon(queue) { _ in 133 | group.leave() 134 | } 135 | } 136 | } 137 | } 138 | 139 | deferred.fill(with: true) 140 | group.wait() 141 | } 142 | } 143 | 144 | func testFillWithUponToConcurrentQueue() { 145 | let queue = DispatchQueue(label: #function, qos: .userInitiated, attributes: .concurrent) 146 | let group = DispatchGroup() 147 | var deferreds = [Deferred]() 148 | 149 | let metrics = PerformanceTests.defaultPerformanceMetrics 150 | measureMetrics(metrics, automaticallyStartMeasuring: false) { 151 | deferreds.removeAll(keepingCapacity: true) 152 | 153 | for _ in 0 ..< iterationCount { 154 | let deferred = Deferred() 155 | group.enter() 156 | deferred.upon(queue) { _ in 157 | group.leave() 158 | } 159 | deferreds.append(deferred) 160 | } 161 | 162 | startMeasuring() 163 | for deferred in deferreds { 164 | deferred.fill(with: true) 165 | } 166 | 167 | XCTAssertEqual(group.wait(timeout: .now() + 1), .success) 168 | stopMeasuring() 169 | } 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /Tests/DeferredTests/ProtectedTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProtectedTests.swift 3 | // DeferredTests 4 | // 5 | // Created by John Gallagher on 7/19/14. 6 | // Copyright © 2014-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | import Dispatch 11 | import Deferred 12 | 13 | class ProtectedTests: XCTestCase { 14 | static let allTests: [(String, (ProtectedTests) -> () throws -> Void)] = [ 15 | ("testConcurrentReadingWriting", testConcurrentReadingWriting), 16 | ("testDebugDescription", testDebugDescription), 17 | ("testDebugDescriptionWhenLocked", testDebugDescriptionWhenLocked), 18 | ("testReflection", testReflection), 19 | ("testReflectionWhenLocked", testReflectionWhenLocked), 20 | ("testPerformanceSingleThreadRead", testPerformanceSingleThreadRead), 21 | ("testPerformanceSingleThreadWrite", testPerformanceSingleThreadWrite), 22 | ("testPerformance90PercentReads4ThreadsLock", testPerformance90PercentReads4ThreadsLock) 23 | ] 24 | 25 | func makeLock() -> Locking { 26 | return NativeLock() 27 | } 28 | 29 | var lock: Locking! 30 | var protected: Protected<(Date?, [Int])>! 31 | let queue = DispatchQueue(label: "ProtectedTests", attributes: .concurrent) 32 | 33 | override func setUp() { 34 | super.setUp() 35 | 36 | lock = makeLock() 37 | protected = Protected(initialValue: (nil, []), lock: lock) 38 | } 39 | 40 | override func tearDown() { 41 | protected = nil 42 | lock = nil 43 | 44 | super.tearDown() 45 | } 46 | 47 | func testConcurrentReadingWriting() { 48 | var lastWriterDate: Date? 49 | var allExpectations = [XCTestExpectation]() 50 | 51 | func startReader(forIteration iteration: Int) -> XCTestExpectation { 52 | let expect = expectation(description: "reader \(iteration)") 53 | queue.async { 54 | self.protected.withReadLock { (arg) in 55 | let (date, items) = arg 56 | if items.isEmpty && date == nil { 57 | // OK - we're before the writer has added items 58 | } else if items.count == 5 && date == lastWriterDate { 59 | // OK - we're after the writer has added items 60 | } else { 61 | XCTFail("invalid count (\(items.count)) or date (\(String(describing: date)))") 62 | } 63 | } 64 | expect.fulfill() 65 | } 66 | return expect 67 | } 68 | 69 | allExpectations += (0 ..< 64).map(startReader) 70 | 71 | let expectWrite = expectation(description: "writer") 72 | queue.async { 73 | self.protected.withWriteLock { dateItemsTuple -> Void in 74 | for iteration in 0 ..< 5 { 75 | dateItemsTuple.0 = Date() 76 | dateItemsTuple.1.append(iteration) 77 | Thread.sleep(forTimeInterval: 0.1) 78 | } 79 | lastWriterDate = dateItemsTuple.0 80 | } 81 | expectWrite.fulfill() 82 | } 83 | allExpectations.append(expectWrite) 84 | 85 | allExpectations += (64 ..< 128).map(startReader) 86 | 87 | wait(for: allExpectations, timeout: longTimeout) 88 | } 89 | 90 | func testDebugDescription() { 91 | let protected = Protected(initialValue: 42, lock: lock) 92 | XCTAssertEqual("\(protected)", "Protected(42)") 93 | } 94 | 95 | func testDebugDescriptionWhenLocked() { 96 | let protected = Protected(initialValue: 42, lock: lock) 97 | 98 | lock.withWriteLock { 99 | XCTAssertEqual("\(protected)", "Protected(locked)") 100 | } 101 | } 102 | 103 | func testReflection() { 104 | let protected = Protected(initialValue: 42, lock: lock) 105 | 106 | let magicMirror = Mirror(reflecting: protected) 107 | XCTAssertEqual(magicMirror.displayStyle, .optional) 108 | XCTAssertNil(magicMirror.superclassMirror) 109 | XCTAssertEqual(magicMirror.descendant(0) as? Int, 42) 110 | } 111 | 112 | func testReflectionWhenLocked() { 113 | let protected = Protected(initialValue: 42, lock: lock) 114 | 115 | lock.withWriteLock { 116 | let magicMirror = Mirror(reflecting: protected) 117 | XCTAssertEqual(magicMirror.displayStyle, .optional) 118 | XCTAssertNil(magicMirror.superclassMirror) 119 | XCTAssertEqual(magicMirror.descendant("isLocked") as? Bool, true) 120 | } 121 | } 122 | 123 | func testPerformanceSingleThreadRead() { 124 | let iterations = 250_000 125 | func doNothing() {} 126 | 127 | measure { 128 | for _ in 0 ..< iterations { 129 | lock.withReadLock(doNothing) 130 | } 131 | } 132 | } 133 | 134 | func testPerformanceSingleThreadWrite() { 135 | let iterations = 250_000 136 | func doNothing() {} 137 | 138 | measure { 139 | for _ in 0 ..< iterations { 140 | lock.withWriteLock(doNothing) 141 | } 142 | } 143 | } 144 | 145 | func testPerformance90PercentReads4ThreadsLock() { 146 | let iterations = 5000 147 | let numberOfThreads = max(ProcessInfo.processInfo.processorCount, 2) 148 | let group = DispatchGroup() 149 | func doNothing() {} 150 | 151 | measure { 152 | for _ in 0 ..< numberOfThreads { 153 | queue.async(group: group) { 154 | for iteration in 0 ..< iterations { 155 | if (iteration % 10) == 0 { 156 | self.lock.withWriteLock(doNothing) 157 | } else { 158 | self.lock.withReadLock(doNothing) 159 | } 160 | } 161 | } 162 | } 163 | 164 | group.wait() 165 | } 166 | } 167 | } 168 | 169 | class ProtectedTestsUsingDispatchSemaphore: ProtectedTests { 170 | override func makeLock() -> Locking { 171 | return DispatchSemaphore(value: 1) 172 | } 173 | } 174 | 175 | class ProtectedTestsUsingPOSIXReadWriteLock: ProtectedTests { 176 | override func makeLock() -> Locking { 177 | return POSIXReadWriteLock() 178 | } 179 | } 180 | 181 | class ProtectedTestsUsingNSLock: ProtectedTests { 182 | override func makeLock() -> Locking { 183 | return NSLock() 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /Tests/DeferredTests/SwiftBugTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftBugTests.swift 3 | // DeferredTests 4 | // 5 | // Created by Zachary Waldowski on 11/16/16. 6 | // Copyright © 2016-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | 11 | import Deferred 12 | 13 | class SwiftBugTests: XCTestCase { 14 | static let allTests: [(String, (SwiftBugTests) -> () throws -> Void)] = [ 15 | ("testIdAsAnyDictionaryDowncast", testIdAsAnyDictionaryDowncast), 16 | ("testIdAsAnyArrayDowncast", testIdAsAnyArrayDowncast) 17 | ] 18 | 19 | private enum SomeMultipayloadEnum: Hashable { 20 | case one 21 | case two(String) 22 | case three(Double) 23 | } 24 | 25 | // #150: In Swift 3.0 ..< 3.0.1, Swift collections have some trouble round- 26 | // tripping through id-as-Any using `as?`. (SR-????) 27 | func testIdAsAnyDictionaryDowncast() { 28 | let deferred = Deferred<[SomeMultipayloadEnum: SomeMultipayloadEnum]>() 29 | 30 | let keys: [SomeMultipayloadEnum] = [ .one, .two("foo"), .three(42) ] 31 | 32 | let toBeFilledWith: [SomeMultipayloadEnum: SomeMultipayloadEnum] = [ 33 | keys[0]: .two("one"), 34 | keys[1]: .two("two"), 35 | keys[2]: .two("three") 36 | ] 37 | 38 | let expect = expectation(description: "upon is called with correct values") 39 | deferred.upon { (dict) in 40 | XCTAssertEqual(dict, toBeFilledWith) 41 | expect.fulfill() 42 | } 43 | 44 | deferred.fill(with: toBeFilledWith) 45 | wait(for: [ expect ], timeout: shortTimeout) 46 | } 47 | 48 | // Variant of #150: In Swift 3.0 ..< 3.0.1, Swift collections have some 49 | // trouble round- tripping through id-as-Any using `as!`. (SR-2490) 50 | func testIdAsAnyArrayDowncast() { 51 | let deferred = Deferred<[SomeMultipayloadEnum]>() 52 | 53 | let toBeFilledWith: [SomeMultipayloadEnum] = [ .one, .two("foo"), .three(42) ] 54 | 55 | let expect = expectation(description: "upon is called with correct values") 56 | deferred.upon { (array) in 57 | XCTAssertEqual(array.count, toBeFilledWith.count) 58 | expect.fulfill() 59 | } 60 | 61 | deferred.fill(with: toBeFilledWith) 62 | wait(for: [ expect ], timeout: shortTimeout) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LinuxMain.swift 3 | // DeferredTests 4 | // 5 | // Created by Zachary Waldowski on 9/21/16. 6 | // Copyright © 2016 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | @testable import DeferredTests 11 | @testable import TaskTests 12 | 13 | XCTMain([ 14 | testCase(DeferredTests.allTests), 15 | testCase(ExistentialFutureTests.allTests), 16 | testCase(FilledDeferredTests.allTests), 17 | testCase(FutureCustomExecutorTests.allTests), 18 | testCase(FutureIgnoreTests.allTests), 19 | testCase(FutureTests.allTests), 20 | testCase(ObjectDeferredTests.allTests), 21 | testCase(ProtectedTests.allTests), 22 | testCase(ProtectedTestsUsingDispatchSemaphore.allTests), 23 | testCase(ProtectedTestsUsingPOSIXReadWriteLock.allTests), 24 | testCase(ProtectedTestsUsingNSLock.allTests), 25 | testCase(SwiftBugTests.allTests), 26 | 27 | testCase(TaskComprehensiveTests.allTests), 28 | testCase(TaskResultTests.allTests), 29 | testCase(TaskTests.allTests), 30 | testCase(TaskAsyncTests.allTests) 31 | ]) 32 | -------------------------------------------------------------------------------- /Tests/TaskTests/AllTestsCommon.swift: -------------------------------------------------------------------------------- 1 | ../AllTestsCommon.swift -------------------------------------------------------------------------------- /Tests/TaskTests/TaskAsyncTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskAsyncTests.swift 3 | // DeferredTests 4 | // 5 | // Created by John Gallagher on 7/15/15. 6 | // Copyright © 2014-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | import Dispatch 11 | 12 | #if SWIFT_PACKAGE 13 | import Deferred 14 | import Task 15 | #else 16 | import Deferred 17 | #endif 18 | 19 | class TaskAsyncTests: XCTestCase { 20 | static let allTests: [(String, (TaskAsyncTests) -> () throws -> Void)] = [ 21 | ("testThatCancellingATaskAfterItStartsRunningIsANoop", testThatCancellingATaskAfterItStartsRunningIsANoop), 22 | ("testThatCancellingBeforeATaskStartsProducesTheCancellationError", testThatCancellingBeforeATaskStartsProducesTheCancellationError) 23 | ] 24 | 25 | private var queue: DispatchQueue! 26 | 27 | override func setUp() { 28 | super.setUp() 29 | 30 | queue = DispatchQueue(label: "BlockCancellationTestsQueue") 31 | } 32 | 33 | override func tearDown() { 34 | queue = nil 35 | 36 | super.tearDown() 37 | } 38 | 39 | func testThatCancellingATaskAfterItStartsRunningIsANoop() { 40 | let startSemaphore = DispatchSemaphore(value: 0) 41 | let finishSemaphore = DispatchSemaphore(value: 0) 42 | 43 | let task = Task.async(upon: queue, onCancel: TestError.first) { 44 | startSemaphore.signal() 45 | XCTAssertEqual(finishSemaphore.wait(timeout: .distantFuture), .success) 46 | return 1 47 | } 48 | 49 | XCTAssertEqual(startSemaphore.wait(timeout: .distantFuture), .success) 50 | task.cancel() 51 | finishSemaphore.signal() 52 | 53 | let expect = expectation(description: "task completed") 54 | task.uponSuccess(on: .main) { (value) in 55 | XCTAssertEqual(value, 1) 56 | expect.fulfill() 57 | } 58 | 59 | wait(for: [ expect ], timeout: shortTimeout) 60 | } 61 | 62 | func testThatCancellingBeforeATaskStartsProducesTheCancellationError() { 63 | let semaphore = DispatchSemaphore(value: 0) 64 | 65 | // send a block into queue to keep it blocked while we submit our real test task 66 | queue.async { 67 | _ = semaphore.wait(timeout: .distantFuture) 68 | } 69 | 70 | let task = Task.async(upon: queue, onCancel: TestError.second) { 1 } 71 | 72 | task.cancel() 73 | 74 | let expect = expectation(description: "task completed") 75 | task.uponFailure(on: .main) { (error) in 76 | XCTAssertEqual(error as? TestError, .second) 77 | expect.fulfill() 78 | } 79 | 80 | wait(for: [ expect ], timeout: shortTimeout) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/TaskTests/TaskComprehensiveTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskComprehensiveTests.swift 3 | // DeferredTests 4 | // 5 | // Created by Pierluigi Cifani on 29/10/2016. 6 | // Copyright © 2016-2018 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | // The following test case aggressively exercises a few features of Task and 9 | // Deferred. It is not a unit test, per se, but has reliably uncovered several 10 | // full-stack threading problems in the framework, and is included in the suite 11 | // as a smoke test of sorts. 12 | // 13 | 14 | import XCTest 15 | import Dispatch 16 | 17 | #if SWIFT_PACKAGE 18 | import Deferred 19 | import Task 20 | #else 21 | import Deferred 22 | #endif 23 | 24 | class TaskComprehensiveTests: XCTestCase { 25 | static let allTests: [(String, (TaskComprehensiveTests) -> () throws -> Void)] = [ 26 | ("testThatSeveralIterationsRunCorrectly", testThatSeveralIterationsRunCorrectly), 27 | ("testThatCancellingATaskPropagatesTheCancellation", testThatCancellingATaskPropagatesTheCancellation) 28 | ] 29 | 30 | func testThatCancellingATaskPropagatesTheCancellation() { 31 | let semaphore = DispatchSemaphore(value: 0) 32 | 33 | let task1 = TaskProducer.produceTask() 34 | let task2 = TaskProducer.produceTask() 35 | let task = [task1, task2].allSucceeded() 36 | task.uponFailure(on: .any()) { (error) in 37 | XCTAssertEqual(error as? TaskProducerError, .userCancelled) 38 | semaphore.signal() 39 | } 40 | task.cancel() 41 | _ = semaphore.wait(timeout: .now() + .seconds(5)) 42 | } 43 | 44 | func testThatSeveralIterationsRunCorrectly() { 45 | let semaphore = DispatchSemaphore(value: 0) 46 | let numberOfIterations = 20 47 | 48 | for _ in 0 ..< numberOfIterations { 49 | let task = TaskProducer.produceTask() 50 | task.upon(.any()) { _ in 51 | semaphore.signal() 52 | } 53 | XCTAssertEqual(semaphore.wait(timeout: .now() + .seconds(5)), .success) 54 | } 55 | } 56 | } 57 | 58 | // MARK: - Fixtures 59 | 60 | private enum TaskProducerError: Error { 61 | case unknown 62 | case userCancelled 63 | } 64 | 65 | private final class TaskProducer { 66 | 67 | typealias SyncHandler = (Error?) -> Void 68 | 69 | static func produceTask() -> Task { 70 | return (0 ..< 5).map { 71 | syncFolder(folderID: String($0)) 72 | }.allSucceeded() 73 | } 74 | 75 | static func sync(items: [Item]) -> [Task<()>] { 76 | return items.map { (item) in 77 | // swiftlint:disable force_cast 78 | if item.isFolder { 79 | return self.syncFolder(folder: item as! Folder) 80 | } else { 81 | return self.sync(file: item as! File) 82 | } 83 | // swiftlint:enable force_cast 84 | } 85 | } 86 | 87 | static private func syncFolder(folderID: String) -> Task<()> { 88 | return fetchFolderInfo(folderID: folderID).andThen(upon: DispatchQueue.any()) { (items) in 89 | return sync(items: items).allSucceeded() 90 | } 91 | } 92 | 93 | static private func syncFolder(folder: Folder) -> Task<()> { 94 | return syncFolder(folderID: folder.modelID) 95 | } 96 | 97 | static private func sync(file: File) -> Task<()> { 98 | let deferred = Deferred.Result>() 99 | 100 | DispatchQueue.global().asyncAfter(deadline: .now() + 0.3) { 101 | deferred.fill(with: .success(())) 102 | } 103 | 104 | return Task(deferred, uponCancel: { 105 | deferred.fill(with: .failure(TaskProducerError.userCancelled)) 106 | }) 107 | } 108 | 109 | static private func fetchFolderInfo(folderID: String) -> Task<[Item]> { 110 | let deferred = Deferred.Result>() 111 | 112 | DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { 113 | let items = (0 ..< 25).map { _ in File() } 114 | deferred.fill(with: .success((items))) 115 | } 116 | 117 | return Task(deferred, uponCancel: { 118 | deferred.fill(with: .failure(TaskProducerError.userCancelled)) 119 | }) 120 | } 121 | } 122 | 123 | // MARK: - Models 124 | 125 | private protocol Item { 126 | var modelID: String { get } 127 | var isFolder: Bool { get } 128 | var isFile: Bool { get } 129 | } 130 | 131 | private struct File: Item { 132 | var modelID: String = UUID().uuidString 133 | let isFolder: Bool = false 134 | let isFile: Bool = true 135 | } 136 | 137 | private struct Folder: Item { 138 | var modelID: String = UUID().uuidString 139 | let isFolder: Bool = true 140 | let isFile: Bool = false 141 | } 142 | -------------------------------------------------------------------------------- /Tests/TaskTests/TaskProtocolTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskProtocolTests.swift 3 | // Deferred 4 | // 5 | // Created by Zachary Waldowski on 9/26/18. 6 | // Copyright © 2018-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | 11 | #if SWIFT_PACKAGE 12 | import Deferred 13 | import Task 14 | #else 15 | import Deferred 16 | #endif 17 | 18 | class TaskProtocolTests: XCTestCase { 19 | 20 | func testConditionalFutureInitAmbiguity() { 21 | // This is a compiler-time check only. 22 | typealias Result = Task.Result 23 | let deferred = Deferred() 24 | _ = Future(deferred) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Tests/TaskTests/TaskResultTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskResultTests.swift 3 | // DeferredTests 4 | // 5 | // Created by Zachary Waldowski on 2/7/15. 6 | // Copyright © 2014-2019 Big Nerd Ranch. Licensed under MIT. 7 | // 8 | 9 | import XCTest 10 | 11 | import Deferred 12 | #if SWIFT_PACKAGE 13 | import Task 14 | #endif 15 | 16 | class TaskResultTests: XCTestCase { 17 | static let allTests: [(String, (TaskResultTests) -> () throws -> Void)] = [ 18 | ("testCocoaBlockInitSuccess", testCocoaBlockInitSuccess), 19 | ("testCocoaBlockInitFailure", testCocoaBlockInitFailure), 20 | ("testCocoaBlockInitInvalid", testCocoaBlockInitInvalid) 21 | ] 22 | 23 | // MARK: - Initializers 24 | 25 | func testCocoaBlockInitSuccess() { 26 | let intSuccesss = Result(value: 42, error: nil) 27 | XCTAssertEqual(try intSuccesss.get(), 42) 28 | 29 | let stringSuccess = Result(value: "foo", error: nil) 30 | XCTAssertEqual(try stringSuccess.get(), "foo") 31 | 32 | let voidSuccess = Result(value: (), error: nil) 33 | XCTAssertNoThrow(try voidSuccess.get()) 34 | } 35 | 36 | func testCocoaBlockInitFailure() { 37 | let intFailure = Result(value: nil, error: TestError.first) 38 | XCTAssertThrowsError(try intFailure.get()) { 39 | XCTAssertEqual($0 as? TestError, .first) 40 | } 41 | 42 | let stringFailure = Result(value: nil, error: TestError.second) 43 | XCTAssertThrowsError(try stringFailure.get()) { 44 | XCTAssertEqual($0 as? TestError, .second) 45 | } 46 | 47 | let voidFailure = Result(value: nil, error: TestError.third) 48 | XCTAssertThrowsError(try voidFailure.get()) { 49 | XCTAssertEqual($0 as? TestError, .third) 50 | } 51 | } 52 | 53 | func testCocoaBlockInitInvalid() { 54 | let intFailure = Result(value: nil, error: nil) 55 | XCTAssertThrowsError(try intFailure.get()) { 56 | XCTAssertFalse($0 is TestError) 57 | } 58 | 59 | let stringFailure = Result(value: nil, error: nil) 60 | XCTAssertThrowsError(try stringFailure.get()) { 61 | XCTAssertFalse($0 is TestError) 62 | } 63 | 64 | let voidFailure = Result(value: nil, error: nil) 65 | XCTAssertThrowsError(try voidFailure.get()) { 66 | XCTAssertFalse($0 is TestError) 67 | } 68 | } 69 | 70 | } 71 | --------------------------------------------------------------------------------