├── .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 | |[][Swift] |
14 | |[][MIT] |
15 | | |
16 | |[][CI] |
17 | |[][CocoaPods] |
18 | |[][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