diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index cd68870..0000000 --- a/.codecov.yml +++ /dev/null @@ -1,6 +0,0 @@ -coverage: - status: - patch: on - -ignore: - - Tests/**/* diff --git a/.slather.yml b/.slather.yml new file mode 100644 index 0000000..4cf7de0 --- /dev/null +++ b/.slather.yml @@ -0,0 +1,5 @@ +coverage_service: coveralls +xcodeproj: Queuer.xcodeproj +scheme: Queuer iOS +ignore: + - Tests/* diff --git a/.swift-version b/.swift-version index 7d5c902..bf77d54 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.1 +4.2 diff --git a/.travis.yml b/.travis.yml index 92d3df6..1e63593 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,56 +14,60 @@ matrix: env: SPM="YES" - os: osx language: objective-c - osx_image: xcode9.4 + osx_image: xcode10 env: SPM="YES" - os: osx language: objective-c - osx_image: xcode9.4 - env: PROJ="Queuer.xcodeproj" DESTINATION="OS=11.4,name=iPhone X" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" LINT="NO" CODECOV="YES" + osx_image: xcode10 + env: PROJ="Queuer.xcodeproj" DESTINATION="OS=12.0,name=iPhone X" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" LINT="NO" COVERAGE="YES" - os: osx language: objective-c - osx_image: xcode9.4 - env: PROJ="Queuer.xcodeproj" DESTINATION="OS=11.1,name=iPhone 7 Plus" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" LINT="YES" CODECOV="NO" + osx_image: xcode10 + env: PROJ="Queuer.xcodeproj" DESTINATION="OS=11.4,name=iPhone 7 Plus" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" LINT="YES" COVERAGE="NO" - os: osx language: objective-c - osx_image: xcode9.4 - env: PROJ="Queuer.xcodeproj" DESTINATION="OS=10.3.1,name=iPhone 6" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" LINT="NO" CODECOV="NO" + osx_image: xcode10 + env: PROJ="Queuer.xcodeproj" DESTINATION="OS=10.3.1,name=iPhone 6" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" LINT="NO" COVERAGE="NO" - os: osx language: objective-c - osx_image: xcode9.4 - env: PROJ="Queuer.xcodeproj" DESTINATION="OS=9.0,name=iPhone 5s" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" LINT="NO" CODECOV="NO" + osx_image: xcode10 + env: PROJ="Queuer.xcodeproj" DESTINATION="OS=9.0,name=iPhone 5s" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" LINT="NO" COVERAGE="NO" - os: osx language: objective-c osx_image: xcode9.2 - env: PROJ="Queuer.xcodeproj" DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" LINT="NO" CODECOV="NO" + env: PROJ="Queuer.xcodeproj" DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="$IOS_SCHEME" RUN_TESTS="YES" LINT="NO" COVERAGE="NO" - os: osx language: objective-c - osx_image: xcode9.4 - env: PROJ="Queuer.xcodeproj" DESTINATION="OS=11.2,name=Apple TV 4K" SCHEME="$TVOS_SCHEME" RUN_TESTS="YES" LINT="NO" CODECOV="NO" + osx_image: xcode10 + env: PROJ="Queuer.xcodeproj" DESTINATION="OS=12.0,name=Apple TV 4K" SCHEME="$TVOS_SCHEME" RUN_TESTS="YES" LINT="NO" COVERAGE="NO" - os: osx language: objective-c - osx_image: xcode9.4 - env: PROJ="Queuer.xcodeproj" DESTINATION="OS=10.2,name=Apple TV 1080p" SCHEME="$TVOS_SCHEME" RUN_TESTS="YES" LINT="NO" CODECOV="NO" + osx_image: xcode10 + env: PROJ="Queuer.xcodeproj" DESTINATION="OS=11.2,name=Apple TV 4K" SCHEME="$TVOS_SCHEME" RUN_TESTS="YES" LINT="NO" COVERAGE="NO" - os: osx language: objective-c - osx_image: xcode9.4 - env: PROJ="Queuer.xcodeproj" DESTINATION="OS=9.0,name=Apple TV 1080p" SCHEME="$TVOS_SCHEME" RUN_TESTS="YES" LINT="NO" CODECOV="NO" + osx_image: xcode10 + env: PROJ="Queuer.xcodeproj" DESTINATION="OS=10.2,name=Apple TV 1080p" SCHEME="$TVOS_SCHEME" RUN_TESTS="YES" LINT="NO" COVERAGE="NO" - os: osx language: objective-c - osx_image: xcode9.4 - env: PROJ="Queuer.xcodeproj" DESTINATION="OS=4.2,name=Apple Watch Series 3 - 42mm" SCHEME="$WATCHOS_SCHEME" RUN_TESTS="NO" LINT="NO" CODECOV="NO" + osx_image: xcode10 + env: PROJ="Queuer.xcodeproj" DESTINATION="OS=9.0,name=Apple TV 1080p" SCHEME="$TVOS_SCHEME" RUN_TESTS="YES" LINT="NO" COVERAGE="NO" - os: osx language: objective-c - osx_image: xcode9.4 - env: PROJ="Queuer.xcodeproj" DESTINATION="OS=3.2,name=Apple Watch Series 2 - 42mm" SCHEME="$WATCHOS_SCHEME" RUN_TESTS="NO" LINT="NO" CODECOV="NO" + osx_image: xcode10 + env: PROJ="Queuer.xcodeproj" DESTINATION="OS=5.0,name=Apple Watch Series 4 - 44mm" SCHEME="$WATCHOS_SCHEME" RUN_TESTS="NO" LINT="NO" COVERAGE="NO" - os: osx language: objective-c - osx_image: xcode9.4 - env: PROJ="Queuer.xcodeproj" DESTINATION="OS=2.0,name=Apple Watch - 38mm" SCHEME="$WATCHOS_SCHEME" RUN_TESTS="NO" LINT="NO" CODECOV="NO" + osx_image: xcode10 + env: PROJ="Queuer.xcodeproj" DESTINATION="OS=4.2,name=Apple Watch Series 3 - 38mm" SCHEME="$WATCHOS_SCHEME" RUN_TESTS="NO" LINT="NO" COVERAGE="NO" - os: osx language: objective-c - osx_image: xcode9.4 - env: PROJ="Queuer.xcodeproj" DESTINATION="arch=x86_64" SCHEME="$MACOS_SCHEME" RUN_TESTS="YES" LINT="NO" CODECOV="NO" + osx_image: xcode10 + env: PROJ="Queuer.xcodeproj" DESTINATION="OS=3.2,name=Apple Watch Series 2 - 42mm" SCHEME="$WATCHOS_SCHEME" RUN_TESTS="NO" LINT="NO" COVERAGE="NO" + - os: osx + language: objective-c + osx_image: xcode10 + env: PROJ="Queuer.xcodeproj" DESTINATION="arch=x86_64" SCHEME="$MACOS_SCHEME" RUN_TESTS="YES" LINT="NO" COVERAGE="NO" before_install: - if [ "$TRAVIS_OS_NAME" == "osx" ]; then @@ -71,11 +75,12 @@ before_install: gem install cocoapods --pre --no-rdoc --no-ri --no-document; brew update; brew outdated carthage || brew upgrade carthage; + gem install slather; fi install: - if [ "$TRAVIS_OS_NAME" == "linux" ]; then - eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/02090c7ede5a637b76e6df1710e83cd0bbe7dcdf/swiftenv-install.sh)"; + eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"; fi - if [ "$TRAVIS_OS_NAME" == "osx" ]; then ./install_swiftlint.sh; @@ -87,8 +92,9 @@ script: - swift -version - if [ "$SPM" == "YES" ]; then - swift build; - swift test --parallel; + swift package clean; + ruby generate_linux_tests.rb; + swift test; fi - if [ "$TRAVIS_OS_NAME" == "osx" ]; then @@ -110,6 +116,6 @@ script: fi after_success: - - if [ "$CODECOV" == "YES" ]; then - bash <(curl -s https://codecov.io/bash) -X xcodeplist; + - if [ "$COVERAGE" == "YES" ]; then + slather; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ced009..c3d7b34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ All notable changes to this project will be documented in this file.
--- +### 2.x Releases +- `2.0.x` Releases - [2.0.0](#200---let-me-retry) + ### 1.x Releases - `1.3.x` Releases - [1.3.0](#130---open-everything) | [1.3.1](#131---swift-41-support) | [1.3.2](#132---linux-quality) - `1.2.x` Releases - [1.2.0](#120---swift-4-support) | [1.2.1](#121---unwanted-alert) @@ -16,13 +19,34 @@ All notable changes to this project will be documented in this file.
--- -## Develop +## [2.0.0](https://github.com/FabrizioBrancati/Queuer/releases/tag/2.0.0) - Let Me Retry +### XX XXX 2018 +### Added +- Added support to Xcode 10 and Swift 4.2 +- Added retry feature to `ConcurrentOperation` class [#10](https://github.com/FabrizioBrancati/BFKit-Swift/issues/10), more info on how to use it [here](https://github.com/FabrizioBrancati/Queuer#automatically-retry-an-operation) and [here](https://github.com/FabrizioBrancati/Queuer#manually-retry-an-operation) +- Added `addCompletionHandler(_:)` function to `Queuer` class +- Added a `Scheduler` class to better schedule your tasks + +### Changed +- Changed watchOS target to watchOS 3.0 instead of 2.0, thanks to an App Store submission rule [#11](https://github.com/FabrizioBrancati/BFKit-Swift/issues/11) +- Changed `executionBlock` of `ConcurrentOperation` to pass the `concurrentOperation` variable inside the closure to be able to use the retry feature. If you don't need it simply put `_ in` after the block creation: + ```swift + let concurrentOperation = ConcurrentOperation { _ in + /// Your task here + } + ``` + This also affects `SynchronousOperation` +- Changed from Codecov to Coveralls service for code coverage + ### Improved +- Improved `Semaphore` with timeout handling - Updated SwiftLint to 0.27.0 ### Removed - Removed Hound CI +Thanks to [@zykloman](https://github.com/zykloman) and [@debjitk](https://github.com/debjitk) for this release + --- ## [1.3.2](https://github.com/FabrizioBrancati/Queuer/releases/tag/1.3.2) - Linux Quality @@ -96,7 +120,7 @@ Thanks to [@BabyAzerty](https://github.com/BabyAzerty) for this release - Updated SwiftLint to 0.21.0 ### Fixed -- Now `ConcurrentOperation` is subclassable with `open` instead of `public` Access Control [#2](https://github.com/FabrizioBrancati/Queuer/issue/2) +- Now `ConcurrentOperation` is subclassable with `open` instead of `public` Access Control [#2](https://github.com/FabrizioBrancati/Queuer/issues/2) - Fixed tests that sometimes fails --- diff --git a/Package.swift b/Package.swift index c14ec86..5cd57f1 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.0 +// swift-tools-version:4.2 // // Package.swift // Queuer diff --git a/Queuer.podspec b/Queuer.podspec index 4477a21..eb86c90 100644 --- a/Queuer.podspec +++ b/Queuer.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'Queuer' s.module_name = 'Queuer' - s.version = '1.3.2' + s.version = '2.0.0' s.summary = 'Queuer is a queue manager, built on top of OperationQueue and Dispatch (aka GCD).' s.homepage = 'https://github.com/FabrizioBrancati/Queuer' s.screenshots = 'https://github.fabriziobrancati.com/queuer/resources/queuer-screenshot.png' @@ -11,12 +11,12 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/FabrizioBrancati/Queuer.git', :tag => s.version } s.documentation_url = 'https://github.fabriziobrancati.com/documentation/Queuer/' - s.swift_version = '4.1' + s.swift_version = '4.2' s.source_files = 'Sources/**/*.swift' s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.10' s.tvos.deployment_target = '9.0' - s.watchos.deployment_target = '2.0' + s.watchos.deployment_target = '3.0' end diff --git a/Queuer.xcodeproj/project.pbxproj b/Queuer.xcodeproj/project.pbxproj index 618e7f1..40dce84 100644 --- a/Queuer.xcodeproj/project.pbxproj +++ b/Queuer.xcodeproj/project.pbxproj @@ -8,6 +8,13 @@ /* Begin PBXBuildFile section */ 493F57291F21F32B009EC8FA /* Queuer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 493F57201F21F32B009EC8FA /* Queuer.framework */; }; + 4946BDF6214D586900FAFC84 /* Scheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4946BDF5214D586900FAFC84 /* Scheduler.swift */; }; + 4946BDF7214D586900FAFC84 /* Scheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4946BDF5214D586900FAFC84 /* Scheduler.swift */; }; + 4946BDF8214D586900FAFC84 /* Scheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4946BDF5214D586900FAFC84 /* Scheduler.swift */; }; + 4946BDF9214D586900FAFC84 /* Scheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4946BDF5214D586900FAFC84 /* Scheduler.swift */; }; + 4946BDFB214D5C0100FAFC84 /* SchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4946BDFA214D5C0100FAFC84 /* SchedulerTests.swift */; }; + 4946BDFC214D5C0100FAFC84 /* SchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4946BDFA214D5C0100FAFC84 /* SchedulerTests.swift */; }; + 4946BDFD214D5C0100FAFC84 /* SchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4946BDFA214D5C0100FAFC84 /* SchedulerTests.swift */; }; 4995D7431F21E19C00A34E6E /* Queuer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4995D7391F21E19B00A34E6E /* Queuer.framework */; }; 4995D76F1F21ED1C00A34E6E /* Queuer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4995D7661F21ED1B00A34E6E /* Queuer.framework */; }; 49B28E2C1F27EDCB00D0819A /* Queuer.h in Headers */ = {isa = PBXBuildFile; fileRef = 49C7BCC11F26938F00F4FFBC /* Queuer.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -15,47 +22,33 @@ 49B28E2E1F27EDCC00D0819A /* Queuer.h in Headers */ = {isa = PBXBuildFile; fileRef = 49C7BCC11F26938F00F4FFBC /* Queuer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 49C7BCCE1F26938F00F4FFBC /* ConcurrentOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBB1F26938F00F4FFBC /* ConcurrentOperation.swift */; }; 49C7BCCF1F26938F00F4FFBC /* Queuer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBC1F26938F00F4FFBC /* Queuer.swift */; }; - 49C7BCD01F26938F00F4FFBC /* RequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBD1F26938F00F4FFBC /* RequestOperation.swift */; }; 49C7BCD11F26938F00F4FFBC /* Semaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBE1F26938F00F4FFBC /* Semaphore.swift */; }; 49C7BCD21F26938F00F4FFBC /* SynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBF1F26938F00F4FFBC /* SynchronousOperation.swift */; }; - 49C7BCD31F26938F00F4FFBC /* URLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCC01F26938F00F4FFBC /* URLBuilder.swift */; }; 49C7BCD41F26938F00F4FFBC /* Queuer.h in Headers */ = {isa = PBXBuildFile; fileRef = 49C7BCC11F26938F00F4FFBC /* Queuer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 49C7BCD51F26939900F4FFBC /* ConcurrentOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBB1F26938F00F4FFBC /* ConcurrentOperation.swift */; }; 49C7BCD61F26939900F4FFBC /* Queuer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBC1F26938F00F4FFBC /* Queuer.swift */; }; - 49C7BCD71F26939900F4FFBC /* RequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBD1F26938F00F4FFBC /* RequestOperation.swift */; }; 49C7BCD81F26939900F4FFBC /* Semaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBE1F26938F00F4FFBC /* Semaphore.swift */; }; 49C7BCD91F26939900F4FFBC /* SynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBF1F26938F00F4FFBC /* SynchronousOperation.swift */; }; - 49C7BCDA1F26939900F4FFBC /* URLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCC01F26938F00F4FFBC /* URLBuilder.swift */; }; 49C7BCDB1F26939A00F4FFBC /* ConcurrentOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBB1F26938F00F4FFBC /* ConcurrentOperation.swift */; }; 49C7BCDC1F26939A00F4FFBC /* Queuer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBC1F26938F00F4FFBC /* Queuer.swift */; }; - 49C7BCDD1F26939A00F4FFBC /* RequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBD1F26938F00F4FFBC /* RequestOperation.swift */; }; 49C7BCDE1F26939A00F4FFBC /* Semaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBE1F26938F00F4FFBC /* Semaphore.swift */; }; 49C7BCDF1F26939A00F4FFBC /* SynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBF1F26938F00F4FFBC /* SynchronousOperation.swift */; }; - 49C7BCE01F26939A00F4FFBC /* URLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCC01F26938F00F4FFBC /* URLBuilder.swift */; }; 49C7BCE11F26939A00F4FFBC /* ConcurrentOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBB1F26938F00F4FFBC /* ConcurrentOperation.swift */; }; 49C7BCE21F26939A00F4FFBC /* Queuer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBC1F26938F00F4FFBC /* Queuer.swift */; }; - 49C7BCE31F26939A00F4FFBC /* RequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBD1F26938F00F4FFBC /* RequestOperation.swift */; }; 49C7BCE41F26939A00F4FFBC /* Semaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBE1F26938F00F4FFBC /* Semaphore.swift */; }; 49C7BCE51F26939A00F4FFBC /* SynchronousOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCBF1F26938F00F4FFBC /* SynchronousOperation.swift */; }; - 49C7BCE61F26939A00F4FFBC /* URLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCC01F26938F00F4FFBC /* URLBuilder.swift */; }; 49C7BCE71F26939F00F4FFBC /* ConcurrentOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCAF1F26938F00F4FFBC /* ConcurrentOperationTests.swift */; }; 49C7BCE81F26939F00F4FFBC /* QueuerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB01F26938F00F4FFBC /* QueuerTests.swift */; }; - 49C7BCE91F26939F00F4FFBC /* RequestOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB11F26938F00F4FFBC /* RequestOperationTests.swift */; }; 49C7BCEA1F26939F00F4FFBC /* SemaphoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB21F26938F00F4FFBC /* SemaphoreTests.swift */; }; 49C7BCEB1F26939F00F4FFBC /* SynchronousOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB31F26938F00F4FFBC /* SynchronousOperationTests.swift */; }; - 49C7BCEC1F26939F00F4FFBC /* URLBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB41F26938F00F4FFBC /* URLBuilderTests.swift */; }; 49C7BCED1F2693A000F4FFBC /* ConcurrentOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCAF1F26938F00F4FFBC /* ConcurrentOperationTests.swift */; }; 49C7BCEE1F2693A000F4FFBC /* QueuerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB01F26938F00F4FFBC /* QueuerTests.swift */; }; - 49C7BCEF1F2693A000F4FFBC /* RequestOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB11F26938F00F4FFBC /* RequestOperationTests.swift */; }; 49C7BCF01F2693A000F4FFBC /* SemaphoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB21F26938F00F4FFBC /* SemaphoreTests.swift */; }; 49C7BCF11F2693A000F4FFBC /* SynchronousOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB31F26938F00F4FFBC /* SynchronousOperationTests.swift */; }; - 49C7BCF21F2693A000F4FFBC /* URLBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB41F26938F00F4FFBC /* URLBuilderTests.swift */; }; 49C7BCF31F2693A100F4FFBC /* ConcurrentOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCAF1F26938F00F4FFBC /* ConcurrentOperationTests.swift */; }; 49C7BCF41F2693A100F4FFBC /* QueuerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB01F26938F00F4FFBC /* QueuerTests.swift */; }; - 49C7BCF51F2693A100F4FFBC /* RequestOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB11F26938F00F4FFBC /* RequestOperationTests.swift */; }; 49C7BCF61F2693A100F4FFBC /* SemaphoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB21F26938F00F4FFBC /* SemaphoreTests.swift */; }; 49C7BCF71F2693A100F4FFBC /* SynchronousOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB31F26938F00F4FFBC /* SynchronousOperationTests.swift */; }; - 49C7BCF81F2693A100F4FFBC /* URLBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C7BCB41F26938F00F4FFBC /* URLBuilderTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -85,6 +78,8 @@ /* Begin PBXFileReference section */ 493F57201F21F32B009EC8FA /* Queuer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Queuer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 493F57281F21F32B009EC8FA /* QueuerTests macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "QueuerTests macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4946BDF5214D586900FAFC84 /* Scheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scheduler.swift; sourceTree = ""; }; + 4946BDFA214D5C0100FAFC84 /* SchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchedulerTests.swift; sourceTree = ""; }; 4995D7391F21E19B00A34E6E /* Queuer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Queuer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4995D7421F21E19C00A34E6E /* QueuerTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "QueuerTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 4995D7581F21EA3F00A34E6E /* Queuer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Queuer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -93,20 +88,13 @@ 49C7BCAC1F26938F00F4FFBC /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49C7BCAF1F26938F00F4FFBC /* ConcurrentOperationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConcurrentOperationTests.swift; sourceTree = ""; }; 49C7BCB01F26938F00F4FFBC /* QueuerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueuerTests.swift; sourceTree = ""; }; - 49C7BCB11F26938F00F4FFBC /* RequestOperationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestOperationTests.swift; sourceTree = ""; }; 49C7BCB21F26938F00F4FFBC /* SemaphoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SemaphoreTests.swift; sourceTree = ""; }; 49C7BCB31F26938F00F4FFBC /* SynchronousOperationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronousOperationTests.swift; sourceTree = ""; }; - 49C7BCB41F26938F00F4FFBC /* URLBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLBuilderTests.swift; sourceTree = ""; }; - 49C7BCB61F26938F00F4FFBC /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; - 49C7BCB71F26938F00F4FFBC /* Info-macOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-macOS.plist"; sourceTree = ""; }; - 49C7BCB81F26938F00F4FFBC /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = ""; }; - 49C7BCB91F26938F00F4FFBC /* Info-watchOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-watchOS.plist"; sourceTree = ""; }; + 49C7BCB71F26938F00F4FFBC /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49C7BCBB1F26938F00F4FFBC /* ConcurrentOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConcurrentOperation.swift; sourceTree = ""; }; 49C7BCBC1F26938F00F4FFBC /* Queuer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Queuer.swift; sourceTree = ""; }; - 49C7BCBD1F26938F00F4FFBC /* RequestOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestOperation.swift; sourceTree = ""; }; 49C7BCBE1F26938F00F4FFBC /* Semaphore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Semaphore.swift; sourceTree = ""; }; 49C7BCBF1F26938F00F4FFBC /* SynchronousOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronousOperation.swift; sourceTree = ""; }; - 49C7BCC01F26938F00F4FFBC /* URLBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLBuilder.swift; sourceTree = ""; }; 49C7BCC11F26938F00F4FFBC /* Queuer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Queuer.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -203,10 +191,9 @@ children = ( 49C7BCAF1F26938F00F4FFBC /* ConcurrentOperationTests.swift */, 49C7BCB01F26938F00F4FFBC /* QueuerTests.swift */, - 49C7BCB11F26938F00F4FFBC /* RequestOperationTests.swift */, + 4946BDFA214D5C0100FAFC84 /* SchedulerTests.swift */, 49C7BCB21F26938F00F4FFBC /* SemaphoreTests.swift */, 49C7BCB31F26938F00F4FFBC /* SynchronousOperationTests.swift */, - 49C7BCB41F26938F00F4FFBC /* URLBuilderTests.swift */, ); path = QueuerTests; sourceTree = ""; @@ -214,10 +201,7 @@ 49C7BCB51F26938F00F4FFBC /* Sources */ = { isa = PBXGroup; children = ( - 49C7BCB61F26938F00F4FFBC /* Info-iOS.plist */, - 49C7BCB71F26938F00F4FFBC /* Info-macOS.plist */, - 49C7BCB81F26938F00F4FFBC /* Info-tvOS.plist */, - 49C7BCB91F26938F00F4FFBC /* Info-watchOS.plist */, + 49C7BCB71F26938F00F4FFBC /* Info.plist */, 49C7BCBA1F26938F00F4FFBC /* Queuer */, 49C7BCC11F26938F00F4FFBC /* Queuer.h */, ); @@ -229,10 +213,9 @@ children = ( 49C7BCBB1F26938F00F4FFBC /* ConcurrentOperation.swift */, 49C7BCBC1F26938F00F4FFBC /* Queuer.swift */, - 49C7BCBD1F26938F00F4FFBC /* RequestOperation.swift */, + 4946BDF5214D586900FAFC84 /* Scheduler.swift */, 49C7BCBE1F26938F00F4FFBC /* Semaphore.swift */, 49C7BCBF1F26938F00F4FFBC /* SynchronousOperation.swift */, - 49C7BCC01F26938F00F4FFBC /* URLBuilder.swift */, ); path = Queuer; sourceTree = ""; @@ -528,12 +511,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 49C7BCE31F26939A00F4FFBC /* RequestOperation.swift in Sources */, 49C7BCE21F26939A00F4FFBC /* Queuer.swift in Sources */, 49C7BCE41F26939A00F4FFBC /* Semaphore.swift in Sources */, 49C7BCE11F26939A00F4FFBC /* ConcurrentOperation.swift in Sources */, + 4946BDF7214D586900FAFC84 /* Scheduler.swift in Sources */, 49C7BCE51F26939A00F4FFBC /* SynchronousOperation.swift in Sources */, - 49C7BCE61F26939A00F4FFBC /* URLBuilder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -541,12 +523,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 49C7BCEF1F2693A000F4FFBC /* RequestOperationTests.swift in Sources */, 49C7BCEE1F2693A000F4FFBC /* QueuerTests.swift in Sources */, 49C7BCF01F2693A000F4FFBC /* SemaphoreTests.swift in Sources */, + 4946BDFC214D5C0100FAFC84 /* SchedulerTests.swift in Sources */, 49C7BCED1F2693A000F4FFBC /* ConcurrentOperationTests.swift in Sources */, 49C7BCF11F2693A000F4FFBC /* SynchronousOperationTests.swift in Sources */, - 49C7BCF21F2693A000F4FFBC /* URLBuilderTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -557,9 +538,8 @@ 49C7BCCE1F26938F00F4FFBC /* ConcurrentOperation.swift in Sources */, 49C7BCD21F26938F00F4FFBC /* SynchronousOperation.swift in Sources */, 49C7BCCF1F26938F00F4FFBC /* Queuer.swift in Sources */, - 49C7BCD31F26938F00F4FFBC /* URLBuilder.swift in Sources */, + 4946BDF6214D586900FAFC84 /* Scheduler.swift in Sources */, 49C7BCD11F26938F00F4FFBC /* Semaphore.swift in Sources */, - 49C7BCD01F26938F00F4FFBC /* RequestOperation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -567,12 +547,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 49C7BCE91F26939F00F4FFBC /* RequestOperationTests.swift in Sources */, 49C7BCE81F26939F00F4FFBC /* QueuerTests.swift in Sources */, 49C7BCEA1F26939F00F4FFBC /* SemaphoreTests.swift in Sources */, + 4946BDFB214D5C0100FAFC84 /* SchedulerTests.swift in Sources */, 49C7BCE71F26939F00F4FFBC /* ConcurrentOperationTests.swift in Sources */, 49C7BCEB1F26939F00F4FFBC /* SynchronousOperationTests.swift in Sources */, - 49C7BCEC1F26939F00F4FFBC /* URLBuilderTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -580,12 +559,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 49C7BCD71F26939900F4FFBC /* RequestOperation.swift in Sources */, 49C7BCD61F26939900F4FFBC /* Queuer.swift in Sources */, 49C7BCD81F26939900F4FFBC /* Semaphore.swift in Sources */, 49C7BCD51F26939900F4FFBC /* ConcurrentOperation.swift in Sources */, + 4946BDF9214D586900FAFC84 /* Scheduler.swift in Sources */, 49C7BCD91F26939900F4FFBC /* SynchronousOperation.swift in Sources */, - 49C7BCDA1F26939900F4FFBC /* URLBuilder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -593,12 +571,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 49C7BCDD1F26939A00F4FFBC /* RequestOperation.swift in Sources */, 49C7BCDC1F26939A00F4FFBC /* Queuer.swift in Sources */, 49C7BCDE1F26939A00F4FFBC /* Semaphore.swift in Sources */, 49C7BCDB1F26939A00F4FFBC /* ConcurrentOperation.swift in Sources */, + 4946BDF8214D586900FAFC84 /* Scheduler.swift in Sources */, 49C7BCDF1F26939A00F4FFBC /* SynchronousOperation.swift in Sources */, - 49C7BCE01F26939A00F4FFBC /* URLBuilder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -606,12 +583,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 49C7BCF51F2693A100F4FFBC /* RequestOperationTests.swift in Sources */, 49C7BCF41F2693A100F4FFBC /* QueuerTests.swift in Sources */, 49C7BCF61F2693A100F4FFBC /* SemaphoreTests.swift in Sources */, + 4946BDFD214D5C0100FAFC84 /* SchedulerTests.swift in Sources */, 49C7BCF31F2693A100F4FFBC /* ConcurrentOperationTests.swift in Sources */, 49C7BCF71F2693A100F4FFBC /* SynchronousOperationTests.swift in Sources */, - 49C7BCF81F2693A100F4FFBC /* URLBuilderTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -647,7 +623,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = "$(SRCROOT)/Sources/Info-macOS.plist"; + INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; @@ -669,7 +645,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = "$(SRCROOT)/Sources/Info-macOS.plist"; + INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; @@ -728,6 +704,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -749,14 +726,13 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1.3.2; + CURRENT_PROJECT_VERSION = 2.0.0; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -780,11 +756,11 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=100 -Xfrontend -warn-long-function-bodies=100"; PRODUCT_NAME = Queuer; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -809,6 +785,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -830,7 +807,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1.3.2; + CURRENT_PROJECT_VERSION = 2.0.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -854,10 +831,11 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=100 -Xfrontend -warn-long-function-bodies=100"; PRODUCT_NAME = Queuer; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -875,7 +853,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "$(SRCROOT)/Sources/Info-iOS.plist"; + INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.FabrizioBrancati.Queuer-iOS"; @@ -895,7 +873,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "$(SRCROOT)/Sources/Info-iOS.plist"; + INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.FabrizioBrancati.Queuer-iOS"; @@ -947,7 +925,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "$(SRCROOT)/Sources/Info-watchOS.plist"; + INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.FabrizioBrancati.Queuer-watchOS"; @@ -955,7 +933,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 2.0; + WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Debug; }; @@ -969,7 +947,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "$(SRCROOT)/Sources/Info-watchOS.plist"; + INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.FabrizioBrancati.Queuer-watchOS"; @@ -977,7 +955,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 2.0; + WATCHOS_DEPLOYMENT_TARGET = 3.0; }; name = Release; }; @@ -991,7 +969,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "$(SRCROOT)/Sources/Info-tvOS.plist"; + INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.FabrizioBrancati.Queuer-tvOS"; @@ -1013,7 +991,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "$(SRCROOT)/Sources/Info-tvOS.plist"; + INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.FabrizioBrancati.Queuer-tvOS"; diff --git a/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer iOS.xcscheme b/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer iOS.xcscheme index 5a9199e..be2f8d0 100644 --- a/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer iOS.xcscheme +++ b/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer iOS.xcscheme @@ -26,9 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> + codeCoverageEnabled = "YES" + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -57,7 +56,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer macOS.xcscheme b/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer macOS.xcscheme index f32be8e..394e377 100644 --- a/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer macOS.xcscheme +++ b/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer macOS.xcscheme @@ -26,9 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> + codeCoverageEnabled = "YES" + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -57,7 +56,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer tvOS.xcscheme b/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer tvOS.xcscheme index 2cdf53b..a0fc727 100644 --- a/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer tvOS.xcscheme +++ b/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer tvOS.xcscheme @@ -26,9 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> + codeCoverageEnabled = "YES" + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -57,7 +56,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer watchOS.xcscheme b/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer watchOS.xcscheme index 0624fe0..485d9c2 100644 --- a/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer watchOS.xcscheme +++ b/Queuer.xcodeproj/xcshareddata/xcschemes/Queuer watchOS.xcscheme @@ -26,9 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> + codeCoverageEnabled = "YES" + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -38,7 +37,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/README.md b/README.md index 2ea1cf9..4b9e05c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

[![Build Status](https://travis-ci.org/FabrizioBrancati/Queuer.svg?branch=master)](https://travis-ci.org/FabrizioBrancati/Queuer) -[![Codecov](https://codecov.io/gh/FabrizioBrancati/Queuer/branch/master/graph/badge.svg)](https://codecov.io/gh/FabrizioBrancati/Queuer) +[![Coverage Status](https://coveralls.io/repos/github/FabrizioBrancati/Queuer/badge.svg?branch=master)](https://coveralls.io/github/FabrizioBrancati/Queuer?branch=master) [![Documentation](https://github.fabriziobrancati.com/documentation/Queuer/badge.svg)](https://github.fabriziobrancati.com/documentation/Queuer/) [![codebeat badge](https://codebeat.co/badges/50844e60-f4f2-4f9f-a688-5ccc976b7c8c)](https://codebeat.co/projects/github-com-fabriziobrancati-queuer-master-9833cda0-af64-433d-a08a-cd0d50d6b579) [![Swift Package Manager Compatible](https://img.shields.io/badge/SPM-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) @@ -11,8 +11,8 @@ [![Version](https://img.shields.io/cocoapods/v/Queuer.svg?style=flat)][Documentation] [![License](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://github.com/FabrizioBrancati/Queuer/blob/master/LICENSE)
-[![Language](https://img.shields.io/badge/language-Swift%204.0%20%7C%204.1-orange.svg)](https://swift.org/) -[![Platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS%20%7C%20Linux-ffc713.svg)][Documentation] +[![Language](https://img.shields.io/badge/language-Swift%204.2-orange.svg)](https://swift.org/) +[![Platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS%20%7C%20Linux-cc9c00.svg)][Documentation] --- @@ -35,34 +35,34 @@ Features ======== Queuer is a queue manager, built on top of [OperationQueue](https://developer.apple.com/documentation/foundation/operationqueue) and [Dispatch](https://developer.apple.com/documentation/dispatch) (aka GCD).
-It allows you to create any synchronous and asynchronous task easily, with just a few lines. +It allows you to create any asynchronous and synchronous task easily, all managed by a queue, with just a few lines. Here is the list of all the features: -- [x] Works on all Swift compatible platforms (even Linux `*`) +- [x] Works on all Swift compatible platforms (even Linux) - [x] Easy to use - [x] Well documented (100% documented) -- [x] Well tested (currently 99% of code coverage) +- [x] Well tested (100% of code coverage) - [x] Create an operation block - [x] Create a single operation - [x] Create chained operations - [x] Manage a centralized queue - [x] Create unlimited queue - [x] Declare how many concurrent operation a queue can handle +- [x] Create semaphores +- [x] Create and handle schedules +- [x] Automatically or manually retry an operation - [ ] Ability to restore uncompleted operations -- [ ] Wrappers for other frameworks (like Alamofire, Moya, ecc) maybe with pod subspecs - -> `*` Currently, `URLSession.shared` property is not yet implemented on Linux. +- [ ] Throlling between each automatic operation retry Requirements ============ -| **Swift** | **Xcode** | **Queuer** | **iOS** | **macOS** | **tvOS** | **watchOS** | **Linux** | -|-----------|-----------|---------------|---------|-----------|----------|-------------|-----------| -| 3.1...3.2 | 8.3...9.0 | 1.0.0...1.1.0 | 8.0+ | 10.10 | 9.0 | 2.0+ | ![✓] `*` | -| 4.0 | 9.0...9.2 | 1.3.0 | 8.0+ | 10.10 | 9.0 | 2.0+ | ![✓] `*` | -| 4.1 | 9.3...9.4 | 1.3.1...1.3.2 | 8.0+ | 10.10 | 9.0 | 2.0+ | ![✓] `*` | - -> `*` Currently, `URLSession.shared` property is not yet implemented on Linux. +| **Swift** | **Xcode** | **Queuer** | **iOS** | **macOS** | **tvOS** | **watchOS** | **Linux** | +|-----------|-----------|---------------|---------|------------|-----------|-------------|-----------| +| 3.1...3.2 | 8.3...9.0 | 1.0.0...1.1.0 | 8.0+ | 10.10+ | 9.0+ | 2.0+ | ![✓] | +| 4.0 | 9.0...9.2 | 1.3.0 | 8.0+ | 10.10+ | 9.0+ | 2.0+ | ![✓] | +| 4.1 | 9.3...9.4 | 1.3.1...1.3.2 | 8.0+ | 10.10+ | 9.0+ | 2.0+ | ![✓] | +| 4.2 | 10.0 | 2.0.0 | 8.0+ | 10.10+ | 9.0+ | 3.0+ | ![✓] | Installing ========== @@ -131,7 +131,7 @@ See [Requirements](https://github.com/FabrizioBrancati/Queuer#requirements) sect - Create a **Package.swift** file in your **project directory** and write into: ```swift - // swift-tools-version:4.0 + // swift-tools-version:4.2 import PackageDescription let package = Package( @@ -155,6 +155,18 @@ See [Requirements](https://github.com/FabrizioBrancati/Queuer#requirements) sect Usage ===== +- [Shared Queuer](https://github.com/FabrizioBrancati/Queuer#shared-queuer) +- [Custom Queue](https://github.com/FabrizioBrancati/Queuer#custom-queue) +- [Create an Operation Block](https://github.com/FabrizioBrancati/Queuer#create-an-operation-block) +- [Chained Operations](https://github.com/FabrizioBrancati/Queuer#chained-operations) +- [Queue States](https://github.com/FabrizioBrancati/Queuer#queue-states) +- [Asynchronous Operation](https://github.com/FabrizioBrancati/Queuer#anynchronous-operation) +- [Synchronous Operation](https://github.com/FabrizioBrancati/Queuer#synchronous-operation) +- [Automatically Retry an Operation](https://github.com/FabrizioBrancati/Queuer#automatically-retry-an-operation) +- [Manually Retry an Operation](https://github.com/FabrizioBrancati/Queuer#manually-retry-an-operation) +- [Scheduler](https://github.com/FabrizioBrancati/Queuer#scheduler) +- [Semaphore](https://github.com/FabrizioBrancati/Queuer#semaphore) + ### Shared Queuer ```swift @@ -184,7 +196,7 @@ You have three methods to add an `Operation` block: - Creating a `ConcurrentOperation` with a block: ```swift - let concurrentOperation = ConcurrentOperation { + let concurrentOperation = ConcurrentOperation { _ in /// Your task here } queue.addOperation(concurrentOperation) @@ -192,7 +204,7 @@ You have three methods to add an `Operation` block: - Creating a `SynchronousOperation` with a block: ```swift - let synchronousOperation = SynchronousOperation { + let synchronousOperation = SynchronousOperation { _ in /// Your task here } queue.addOperation(concurrentOperation) @@ -204,10 +216,10 @@ You have three methods to add an `Operation` block: Chained Operations are operations that add a dependency each other.
They follow the given array order, for example: `[A, B, C] = A -> B -> C -> completionBlock`. ```swift -let concurrentOperation1 = ConcurrentOperation { +let concurrentOperation1 = ConcurrentOperation { _ in /// Your task 1 here } -let concurrentOperation2 = ConcurrentOperation { +let concurrentOperation2 = ConcurrentOperation { _ in /// Your task 2 here } queue.addChainedOperations([concurrentOperation1, concurrentOperation2]) { @@ -225,13 +237,13 @@ queue.addChainedOperations([concurrentOperation1, concurrentOperation2]) { queue.pause() ``` > By calling `pause()` you will not be sure that every operation will be paused. - If the Operation is already started it will not be on pause until it's a custom Operation that overrides `pause()` function or is a `RequestOperation`. + If the Operation is already started it will not be on pause until it's a custom Operation that overrides `pause()` function. - Resume queue: ```swift queue.resume() ``` - > To have a complete `pause` and `resume` states you must create a custom Operation that overrides `pause()` and `resume()` function or use a `RequestOperation`. + > To have a complete `pause` and `resume` states you must create a custom Operation that overrides `pause()` and `resume()` function. - Wait until all operations are finished: ```swift @@ -244,12 +256,11 @@ queue.addChainedOperations([concurrentOperation1, concurrentOperation2]) { It allows synchronous and asynchronous tasks, has a pause and resume states, can be easily added to a queue and can be created with a block. You can create your custom `ConcurrentOperation` by subclassing it.
-You must override `execute()` function and call the `finish()` function inside it, when the task has finished its job to notify the queue.
-Look at [RequestOperation.swift](https://github.com/FabrizioBrancati/Queuer/blob/master/Sources/Queuer/RequestOperation.swift) if you are looking for an example. +You must override `execute()` function and call the `finish()` function inside it, when the task has finished its job to notify the queue. For convenience it has an `init` function with a completion block: ```swift -let concurrentOperation = ConcurrentOperation { +let concurrentOperation = ConcurrentOperation { _ in /// Your task here } concurrentOperation.addToQueue(queue) @@ -266,12 +277,68 @@ There are three methods to create synchronous tasks or even queue: For convenience it has an `init` function with a completion block: ```swift -let synchronousOperation = SynchronousOperation { +let synchronousOperation = SynchronousOperation { _ in /// Your task here } synchronousOperation.addToQueue(queue) ``` +### Automatically Retry an Operation +An `Operation` is passed to every closure, with it you can set and handle the retry feature.
+By default the retry feature is disabled, to enable it simply set the `hasFailed` property to `true`. With `hasFailed` to `true` the `Operation` will retry until reaches `maximumRetries` property value. To let the `Operation` know when everything is ok, you must set `hasFailed` to `false`.
+With `currentAttempt` you can know at which attempt the `Operation` is. +```swift +let concurrentOperation = ConcurrentOperation { operation in + /// Your task here + if /* Failing */ { + operation.hasFailed = true + } else { + operation.hasFailed = false + } +} +``` + +### Manually Retry an Operation +It's even possible to manually retry an `Operation` when you think that the execution will be successful.
+An `Operation` is passed to every closure, with it you can set and handle the retry feature.
+By default the manual retry feature is disabled, to enable it simply set the `manualRetry` property to `true`, you must do this outside of the execution closure. You must also set `hasFailed` to `true` or `false` to let the `Operation` when is everything ok, like the automatic retry feature.
+To let the `Operation` retry your execution closure, you have to call the `retry()` function. If the `retry()` is not called, you may block the entire queue. Be sure to call it at least `maximumRetries` times, it is not a problem if you call `retry()` more times than is needed, your execution closure will not be executed more times than the `maximumRetries` value. +```swift +let concurrentOperation = ConcurrentOperation { operation in + /// Your task here + if /* Failing */ { + operation.hasFailed = true + } else { + operation.hasFailed = false + } +} +concurrentOperation.manualRetry = true +/// Later on your code +concurrentOperation.retry() +``` + +### Scheduler +A `Scheduler` is a struct that uses the GDC's `DispatchSourceTimer` to create a timer that can execute functions with a specified interval and quality of service. + +```swift +let schedule = Scheduler(deadline: .now(), repeating: .seconds(1)) { + /// Your task here +} +``` + +You can even create a `Scheduler` without the handler and set it later: +```swift +var schedule = Scheduler(deadline: .now(), repeating: .seconds(1)) +schedule.setHandler { + /// Your task here. +} +``` + +With `timer` property you can access to all `DispatchSourceTimer` properties and functions, like `cancel()`: +```swift +schedule.timer.cancel() +``` + ### Semaphore A `Semaphore` is a struct that uses the GCD's `DispatchSemaphore` to create a semaphore on the function and wait until it finish its job.
I recommend you to use a `defer { semaphore.continue() }` right after the `Semaphore` creation and `wait()` call. @@ -283,6 +350,11 @@ defer { semaphore.continue() } /// Your task here ``` +You can even set a custom timeout, default is `.distantFuture`: +```swift +semaphore.wait(DispatchTime(uptimeNanoseconds: 1_000_000_000)) +``` + It's more useful if used inside an asynchronous task: ```swift let concurrentOperation = ConcurrentOperation { @@ -293,40 +365,6 @@ concurrentOperation.addToQueue(queue) semaphore.wait() ``` -### Request Operation `*` -`RequestOperation` allows you to easily create a network request and add it to a queue: -```swift -let requestOperation: RequestOperation = RequestOperation(url: self.testAddress) { success, response, data, error in - -} -requestOperation.addToQueue(queue) -``` -Allowed parameters in `RequestOperation` `init` function: -- `url` is a `String` representing the request URL -- `query` is `Dictionary` representing the request query parameters to be added to the `url` with `?` and `&` characters -- `timeout` is the request timeout -- `method` is the request method, you can choose to one of: `connect`, `delete`, `get`, `head`, `options`, `patch`, `post` and `put` -- `cachePolicy` is the request cache policy, referrer to [CachePolicy documentation](https://developer.apple.com/documentation/foundation/nsurlrequest.cachepolicy) -- `headers` is a `Dictionary` representing the request headers -- `body` is a `Data` representing the request body -- `completionHandler` is the request response handler - -Response handler variables: -- `success` is a `Bool` indicating if the request was successful. - It's successful if its status is between 200 and 399, it wasn't cancelled and did't get any other network error. -- `respose` is an `HTTPURLResponse` instance. - It contains all the response headers and the status code. - May be `nil`. -- `data` is a `Data` instance with the request body. - You must convert, to a JSON or String in example, it in order to use. - May be `nil`. -- `error` is an `Error` instance with the request error. - May be `nil`. - -It can be `pause`d, `resume`d, `cancel`led and chained with other `Operation`s. - -> `*` Currently, `URLSession.shared` property is not yet implemented on Linux, also `RequestOperation` is currently deprecated and will be removed in Queuer 2. - Documentation ============= diff --git a/Sources/Info-iOS.plist b/Sources/Info-iOS.plist deleted file mode 100644 index 2a3d8ce..0000000 --- a/Sources/Info-iOS.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.3.2 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/Sources/Info-tvOS.plist b/Sources/Info-tvOS.plist deleted file mode 100644 index 0fb03af..0000000 --- a/Sources/Info-tvOS.plist +++ /dev/null @@ -1,28 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.3.2 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - UIRequiredDeviceCapabilities - - arm64 - - - diff --git a/Sources/Info-watchOS.plist b/Sources/Info-watchOS.plist deleted file mode 100644 index 2a3d8ce..0000000 --- a/Sources/Info-watchOS.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.3.2 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/Sources/Info-macOS.plist b/Sources/Info.plist similarity index 88% rename from Sources/Info-macOS.plist rename to Sources/Info.plist index 0d1adf9..73dbae2 100644 --- a/Sources/Info-macOS.plist +++ b/Sources/Info.plist @@ -15,12 +15,16 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.3.2 + 2.0.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright Copyright © 2017 - 2018 Fabrizio Brancati. NSPrincipalClass + UIRequiredDeviceCapabilities + + arm64 + diff --git a/Sources/Queuer/ConcurrentOperation.swift b/Sources/Queuer/ConcurrentOperation.swift index 2906a98..db1cc2f 100644 --- a/Sources/Queuer/ConcurrentOperation.swift +++ b/Sources/Queuer/ConcurrentOperation.swift @@ -26,26 +26,18 @@ import Foundation -/// It allows asynchronous tasks, has a pause and resume states, can be easily added to a queue and can be created with a block. +/// It allows asynchronous tasks, has a pause and resume states, +/// can be easily added to a queue and can be created with a block. open class ConcurrentOperation: Operation { - /// Operation's execution block. - public var executionBlock: (() -> Void)? + /// `Operation`'s execution block. + public var executionBlock: ((_ operation: ConcurrentOperation) -> Void)? - /// Creates the Operation with an execution block. - /// - /// - Parameter executionBlock: Execution block. - public init(executionBlock: (() -> Void)? = nil) { - super.init() - - self.executionBlock = executionBlock - } - - /// Set the Operation as asynchronous. + /// Set the `Operation` as asynchronous. override open var isAsynchronous: Bool { return true } - /// Set if the Operation is executing. + /// Set if the `Operation` is executing. private var _executing = false { willSet { willChangeValue(forKey: "isExecuting") @@ -55,12 +47,12 @@ open class ConcurrentOperation: Operation { } } - /// Set if the Operation is executing. + /// Set if the `Operation` is executing. override open var isExecuting: Bool { return _executing } - /// Set if the Operation is finished. + /// Set if the `Operation` is finished. private var _finished = false { willSet { willChangeValue(forKey: "isFinished") @@ -70,49 +62,102 @@ open class ConcurrentOperation: Operation { } } - /// Set if the Operation is finished. + /// Set if the `Operation` is finished. override open var isFinished: Bool { return _finished } - /// Start the Operation. + /// You should use `hasFailed` if you want the retry feature. + /// Set it to `true` if the `Operation` has failed, otherwise `false`. + /// Default is `false` to avoid retries. + open var hasFailed = false + + /// Maximum allowed retries. + /// Default are 3 retries. + open var maximumRetries = 3 + + /// Current retry attempt. + open private(set) var currentAttempt = 1 + + /// Allows for manual retries. + /// If set to `true`, `retry()` function must be manually called. + /// Default is `false` to automatically retry. + open var manualRetry = false + + /// Specify if the `Operation` should retry another time. + private var shouldRetry = true + + /// Creates the `Operation` with an execution block. + /// + /// - Parameter executionBlock: Execution block. + public init(executionBlock: ((_ operation: ConcurrentOperation) -> Void)? = nil) { + super.init() + + self.executionBlock = executionBlock + } + + /// Start the `Operation`. override open func start() { _executing = true execute() } - /// Execute the Operation. + /// Retry function. + /// It only works if `manualRetry` property has been set to `true`. + open func retry() { + if manualRetry, shouldRetry, let executionBlock = executionBlock { + executionBlock(self) + finish(hasFailed) + } + } + + /// Execute the `Operation`. /// If `executionBlock` is set, it will be executed and also `finish()` will be called. open func execute() { if let executionBlock = executionBlock { - executionBlock() - self.finish() + while shouldRetry, !manualRetry { + executionBlock(self) + finish(hasFailed) + } + + retry() } } - /// Notify the completion of async task and hence the completion of the operation. - /// Must be called when the Operation is finished. - open func finish() { - _executing = false - _finished = true + /// Notify the completion of async task and hence the completion of the `Operation`. + /// Must be called when the `Operation` is finished. + /// + /// - Parameter hasFailed: Set it to `true` if the `Operation` has failed, otherwise `false`. + open func finish(_ hasFailed: Bool) { + if !hasFailed || currentAttempt >= maximumRetries { + _executing = false + _finished = true + shouldRetry = false + } else { + currentAttempt += 1 + shouldRetry = true + } } - /// Pause the current Operation, if it's supported. + /// Pause the current `Operation`, if it's supported. /// Must be overridend by subclass to get a custom pause action. open func pause() {} - /// Resume the current Operation, if it's supported. + /// Resume the current `Operation`, if it's supported. /// Must be overridend by subclass to get a custom resume action. open func resume() {} - - /// Adds the Operation to `shared` Queuer. +} + +/// `ConcurrentOperation` extension with queue handling. +public extension ConcurrentOperation { + /// Adds the `Operation` to `shared` Queuer. public func addToSharedQueuer() { Queuer.shared.addOperation(self) } - /// Adds the Operation to the custom queue. + /// Adds the `Operation` to the custom queue. /// - /// - Parameter queue: Custom queue where the Operation will be added. + /// - Parameter queue: Custom queue where the `Operation` will be added. public func addToQueue(_ queue: Queuer) { queue.addOperation(self) } diff --git a/Sources/Queuer/Queuer.swift b/Sources/Queuer/Queuer.swift index 49796bc..0f4d151 100644 --- a/Sources/Queuer/Queuer.swift +++ b/Sources/Queuer/Queuer.swift @@ -31,20 +31,20 @@ public class Queuer { /// Shared Queuer. public static let shared = Queuer(name: "Queuer") - /// Queuer OperationQueue. + /// Queuer `OperationQueue`. public let queue = OperationQueue() - /// Total Operation count in queue. + /// Total `Operation` count in queue. public var operationCount: Int { return queue.operationCount } - /// Operations currently in queue. + /// `Operation`s currently in queue. public var operations: [Operation] { return queue.operations } - /// The default service level to apply to operations executed using the queue. + /// The default service level to apply to `Operation`s executed using the queue. public var qualityOfService: QualityOfService { get { return queue.qualityOfService @@ -61,7 +61,7 @@ public class Queuer { return !queue.isSuspended } - /// Define the max concurrent operation count. + /// Define the max concurrent `Operation`s count. public var maxConcurrentOperationCount: Int { get { return queue.maxConcurrentOperationCount @@ -75,37 +75,69 @@ public class Queuer { /// /// - Parameters: /// - name: Custom queue name. - /// - maxConcurrentOperationCount: The max concurrent operation count. - /// - qualityOfService: The default service level to apply to operations executed using the queue. + /// - maxConcurrentOperationCount: The max concurrent `Operation`s count. + /// - qualityOfService: The default service level to apply to `Operation`s executed using the queue. public init(name: String, maxConcurrentOperationCount: Int = Int.max, qualityOfService: QualityOfService = .default) { queue.name = name self.maxConcurrentOperationCount = maxConcurrentOperationCount self.qualityOfService = qualityOfService } - /// Add an Operation to be executed asynchronously. + /// Cancel all `Operation`s in queue. + public func cancelAll() { + queue.cancelAllOperations() + } + + /// Pause the queue. + public func pause() { + queue.isSuspended = true + + for operation in queue.operations where operation is ConcurrentOperation { + (operation as? ConcurrentOperation)?.pause() + } + } + + /// Resume the queue. + public func resume() { + queue.isSuspended = false + + for operation in queue.operations where operation is ConcurrentOperation { + (operation as? ConcurrentOperation)?.resume() + } + } + + /// Blocks the current thread until all of the receiver’s queued and executing + /// `Operation`s finish executing. + public func waitUntilAllOperationsAreFinished() { + queue.waitUntilAllOperationsAreFinished() + } +} + +/// `Queuer` extension with `Operation`s handling. +public extension Queuer { + /// Add an `Operation` to be executed asynchronously. /// /// - Parameter block: Block to be executed. public func addOperation(_ operation: @escaping () -> Void) { queue.addOperation(operation) } - /// Add an Operation to be executed asynchronously. + /// Add an `Operation` to be executed asynchronously. /// - /// - Parameter operation: Operation to be executed. + /// - Parameter operation: `Operation` to be executed. public func addOperation(_ operation: Operation) { queue.addOperation(operation) } - /// Add an Array of chained Operations. + /// Add an Array of chained `Operation`s. /// /// Example: /// /// [A, B, C] = A -> B -> C -> completionHandler. /// /// - Parameters: - /// - operations: Operations Array. - /// - completionHandler: Completion block to be exectuted when all Operations + /// - operations: `Operation`s Array. + /// - completionHandler: Completion block to be exectuted when all `Operation`s /// are finished. public func addChainedOperations(_ operations: [Operation], completionHandler: (() -> Void)? = nil) { for (index, operation) in operations.enumerated() { @@ -120,53 +152,31 @@ public class Queuer { return } - let completionOperation = BlockOperation(block: completionHandler) - if !operations.isEmpty { - completionOperation.addDependency(operations[operations.count - 1]) - } - addOperation(completionOperation) + addCompletionHandler(completionHandler) } - /// Add an Array of chained Operations. + /// Add an Array of chained `Operation`s. /// /// Example: /// /// [A, B, C] = A -> B -> C -> completionHandler. /// /// - Parameters: - /// - operations: Operations list. - /// - completionHandler: Completion block to be exectuted when all Operations + /// - operations: `Operation`s list. + /// - completionHandler: Completion block to be exectuted when all `Operation`s /// are finished. public func addChainedOperations(_ operations: Operation..., completionHandler: (() -> Void)? = nil) { addChainedOperations(operations, completionHandler: completionHandler) } - /// Cancel all Operations in queue. - public func cancelAll() { - queue.cancelAllOperations() - } - - /// Pause the queue. - public func pause() { - queue.isSuspended = true - - for operation in queue.operations where operation is ConcurrentOperation { - (operation as? ConcurrentOperation)?.pause() - } - } - - /// Resume the queue. - public func resume() { - queue.isSuspended = false - - for operation in queue.operations where operation is ConcurrentOperation { - (operation as? ConcurrentOperation)?.resume() + /// Add a completion block to the queue. + /// + /// - Parameter completionHandler: Completion handler to be executed as last `Operation`. + public func addCompletionHandler(_ completionHandler: @escaping () -> Void) { + let completionOperation = BlockOperation(block: completionHandler) + if let lastOperation = operations.last { + completionOperation.addDependency(lastOperation) } - } - - /// Blocks the current thread until all of the receiver’s queued and executing - /// operations finish executing. - public func waitUntilAllOperationsAreFinished() { - queue.waitUntilAllOperationsAreFinished() + addOperation(completionOperation) } } diff --git a/Sources/Queuer/RequestOperation.swift b/Sources/Queuer/RequestOperation.swift deleted file mode 100644 index 3fc7c40..0000000 --- a/Sources/Queuer/RequestOperation.swift +++ /dev/null @@ -1,230 +0,0 @@ -// -// RequestOperation.swift -// Queuer -// -// MIT License -// -// Copyright (c) 2017 - 2018 Fabrizio Brancati -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#if !os(Linux) - -import Foundation - -/// HTTP Method enum. -public enum HTTPMethod: String { - /// CONNECT method. - case connect = "CONNECT" - /// DELETE method. - case delete = "DELETE" - /// GET method. - case get = "GET" - /// HEAD method. - case head = "HEAD" - /// OPTIONS method. - case options = "OPTIONS" - /// PATCH method. - case patch = "PATCH" - /// POST method. - case post = "POST" - /// PUT method. - case put = "PUT" -} - -/// RequestOperation helps you to create network operation with an easy interface. -@available(*, deprecated: 1.3.2, message: "`RequestOperation` is deprecated and will be removed in Queuer 2.") -open class RequestOperation: ConcurrentOperation { - /// Custom HTTP errors. - public enum RequestError: Error { - /// URL doesn't exist. - case urlError - /// Operation has been cancelled. - case operationCancelled - } - - /// Request closure alias. - public typealias RequestClosure = (Bool, HTTPURLResponse?, Data?, Error?) -> Void - - /// Global cache policy for all request. - /// Also cache policy can be set per request. - public static var globalCachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy - - /// Request task. - public private(set) var task: URLSessionDataTask? - /// Request URL. - public private(set) var url: URL? - /// Request query. - public private(set) var query: String? - /// Request complete URL - public private(set) var completeURL: URL? - /// Request timeout. - public private(set) var timeout: TimeInterval = 30 - /// Request HTTP method. - public private(set) var method: HTTPMethod = .get - /// Request cache policy. - public private(set) var cachePolicy: URLRequest.CachePolicy = globalCachePolicy - /// Request headers. - public private(set) var headers: [String: String]? - /// Request body. - public private(set) var body: Data? - /// Request completionHandler. - public private(set) var completionHandler: RequestClosure? - - /// URLSession instance. - open var session: URLSession { - let configuration = URLSessionConfiguration.default - if #available(iOS 11, macOS 10.13, tvOS 11, watchOS 4, *) { - configuration.waitsForConnectivity = true - } - return URLSession(configuration: configuration) - } - - /// URLRequest instance. - private var request: URLRequest! // swiftlint:disable:this implicitly_unwrapped_optional - - /// Private init with executrion block. - /// You can't create a RequestOperation with only an execution block. - /// - /// - Parameter block: Execution block. - override internal init(executionBlock: (() -> Void)? = nil) { - super.init(executionBlock: nil) - } - - /// Creates a RequestOperation, ready to be added in a queue. - /// - /// - Parameters: - /// - url: Request URL String. - /// - query: Request query. Default is nil. - /// - timeout: Request timeout. Default is 30 seconds. - /// - method: Request HTTP method. Default is `.get`. - /// - cachePolicy: Request cache policy. Use static var `globalCachePolicy` - /// to set a global cache policy for all the RequestOperations. - /// - headers: Request headers. Defatult is nil. - /// - body: Request body. Default is nil. - /// - completionHandler: Request completion handler. Default is nil. - public init(url: String, query: [String: String]? = nil, timeout: TimeInterval = 30, method: HTTPMethod = .get, cachePolicy: URLRequest.CachePolicy = globalCachePolicy, headers: [String: String]? = nil, body: Data? = nil, completionHandler: RequestClosure? = nil) { - super.init() - - let query = URLBuilder.build(query: query) - - self.query = query - self.url = URL(string: url) - self.completeURL = URL(string: url + query) - self.timeout = timeout - self.method = method - self.cachePolicy = cachePolicy - self.headers = headers - self.body = body - self.completionHandler = completionHandler - } - - /// Executes the request operation asynchronously. - override open func execute() { - /// Check if the Operation has been cancelled. - guard !self.isCancelled else { - if let completionHandler = completionHandler { - completionHandler(false, nil, nil, RequestError.operationCancelled) - } - - /// Notify that the Operation has finished execution. - self.finish() - - return - } - - /// Check if the URL can be used. - guard let url = self.completeURL else { - if let completionHandler = completionHandler { - completionHandler(false, nil, nil, RequestError.urlError) - } - - /// Notify that the Operation has finished execution. - finish() - - return - } - - /// Creates the request. - request = URLRequest(url: url, cachePolicy: self.cachePolicy, timeoutInterval: self.timeout) - /// Set the HTTP method. - request.httpMethod = method.rawValue - /// Set the HTTP body. - if let body = body { - request.httpBody = body - } - /// Set all the HTTP headers. - if let headers = headers { - headers.forEach { request.addValue($1, forHTTPHeaderField: $0) } - } - - /// Create the task. - task = session.dataTask(with: request) { [weak self] data, response, error in - /// Check if the Operation has a completion handler, has an HTTP response - /// and has not been canceled. - if let strongSelf = self { - if let completionHandler = strongSelf.completionHandler { - if let httpResponse = response as? HTTPURLResponse { - var error: Error? = error - /// Set `success` to true if the HTTP status code - /// is greater or equal than 200 and less than 400 - /// and has not been cancelled. - let success: Bool = httpResponse.statusCode >= 200 && httpResponse.statusCode < 400 && !strongSelf.isCancelled - - /// Check again if the request has not been cancelled. - if strongSelf.isCancelled { - error = RequestError.operationCancelled - } - - completionHandler(success, httpResponse, data, error) - } else { - completionHandler(false, nil, data, error) - } - } - /// Notify that the Operation has finished execution. - strongSelf.finish() - } - } - /// Start the task. - task?.resume() - } - - /// Cancels the request operation. - override open func cancel() { - super.cancel() - - task?.cancel() - } - - /// Suspends the request operation. - override open func pause() { - super.pause() - - task?.suspend() - } - - /// Resumes the request operation. - override open func resume() { - super.resume() - - task?.resume() - } -} - -#endif diff --git a/Sources/Queuer/Scheduler.swift b/Sources/Queuer/Scheduler.swift new file mode 100644 index 0000000..12014e0 --- /dev/null +++ b/Sources/Queuer/Scheduler.swift @@ -0,0 +1,77 @@ +// +// Scheduler.swift +// Queuer +// +// MIT License +// +// Copyright (c) 2017 - 2018 Fabrizio Brancati +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Dispatch +import Foundation + +/// Scheduler struct, based on top of `DispatchSourceTimer`. +public struct Scheduler { + /// Schedule timer. + public private(set) var timer: DispatchSourceTimer + /// Schedule deadline. + public private(set) var deadline: DispatchTime + /// Schedule repeating interval. + public private(set) var repeating: DispatchTimeInterval + /// Schedule quality of service. + public private(set) var qualityOfService: DispatchQoS + /// Schedule handler. + public private(set) var handler: (() -> Void)? + + /// Create a schedule. + /// + /// - Parameters: + /// - deadline: Deadline. + /// - repeating: Repeating interval + /// - qualityOfService: Quality of service. + /// - handler: Closure handler. + public init(deadline: DispatchTime, repeating: DispatchTimeInterval, qualityOfService: DispatchQoS = .default, handler: (() -> Void)? = nil) { + self.deadline = deadline + self.repeating = repeating + self.qualityOfService = qualityOfService + self.handler = handler + + timer = DispatchSource.makeTimerSource() + timer.schedule(deadline: deadline, repeating: repeating) + if let handler = handler { + timer.setEventHandler(qos: qualityOfService) { + handler() + } + timer.resume() + } + } + + /// Set the handler after schedule creation. + /// + /// - Parameter handler: Closure handler. + public mutating func setHandler(_ handler: @escaping () -> Void) { + self.handler = handler + + timer.setEventHandler(qos: qualityOfService) { + handler() + } + timer.resume() + } +} diff --git a/Sources/Queuer/Semaphore.swift b/Sources/Queuer/Semaphore.swift index a31987c..cc9fe31 100644 --- a/Sources/Queuer/Semaphore.swift +++ b/Sources/Queuer/Semaphore.swift @@ -27,9 +27,9 @@ import Dispatch import Foundation -/// DispatchSemaphore struct wrapper. +/// `DispatchSemaphore` struct wrapper. public struct Semaphore { - /// Private DispatchSemaphore. + /// Private `DispatchSemaphore`. private let semaphore: DispatchSemaphore /// Creates new counting semaphore with an initial value. @@ -39,15 +39,18 @@ public struct Semaphore { /// to the value. /// /// - Parameter poolSize: The starting value for the semaphore. - /// Passing a value less than zero will cause NULL to be returned. + /// Passing a value less than zero will cause `NULL` to be returned. public init(poolSize: Int = 0) { semaphore = DispatchSemaphore(value: poolSize) } /// Wait for a `continue` function call. + /// + /// - Parameter timeout: The timeout `DispatchTime`, default is `.distantFuture`. + /// - Returns: Returns a `DispatchTimeoutResult`. @discardableResult - public func wait() -> DispatchTimeoutResult { - return semaphore.wait(timeout: .distantFuture) + public func wait(_ timeout: DispatchTime = .distantFuture) -> DispatchTimeoutResult { + return semaphore.wait(timeout: timeout) } /// This function returns non-zero if a thread is woken. Otherwise, zero is returned. diff --git a/Sources/Queuer/SynchronousOperation.swift b/Sources/Queuer/SynchronousOperation.swift index db89b70..6f51c82 100644 --- a/Sources/Queuer/SynchronousOperation.swift +++ b/Sources/Queuer/SynchronousOperation.swift @@ -28,28 +28,32 @@ import Foundation /// It allows synchronous tasks, has a pause and resume states, can be easily added to a queue and can be created with a block. public class SynchronousOperation: ConcurrentOperation { - /// Private semaphore instance. + /// Private `Semaphore` instance. private let semaphore = Semaphore() - /// Set the Operation as synchronous. + /// Set the `Operation` as synchronous. override public var isAsynchronous: Bool { return false } - /// Notify the completion of sync task and hence the completion of the operation. - /// Must be called when the Operation is finished. - override public func finish() { + /// Notify the completion of async task and hence the completion of the `Operation`. + /// Must be called when the `Operation` is finished. + /// + /// - Parameter hasFailed: Set it to `true` if the `Operation` has failed, otherwise `false`. + override public func finish(_ hasFailed: Bool) { + super.finish(hasFailed) + semaphore.continue() } - /// Advises the operation object that it should stop executing its task. + /// Advises the `Operation` object that it should stop executing its task. override public func cancel() { super.cancel() semaphore.continue() } - /// Execute the Operation. + /// Execute the `Operation`. /// If `executionBlock` is set, it will be executed and also `finish()` will be called. override public func execute() { super.execute() diff --git a/Tests/Info.plist b/Tests/Info.plist index df52bb5..60e89d2 100644 --- a/Tests/Info.plist +++ b/Tests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.2 + 2.0.0 CFBundleVersion 1 diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index fd29c7b..23b901b 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -23,16 +23,24 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// +// +// +// NOTE: This file was generated by generate_linux_tests.rb +// +// Do NOT edit this file directly as it will be regenerated automatically when needed. +// -#if os(Linux) -@testable import QueuerTests import XCTest -XCTMain([ - testCase(ConcurrentOperationTests.allTests), - testCase(QueuerTests.allTests), - testCase(URLBuilderTests.allTests), - testCase(SemaphoreTests.allTests), - testCase(SynchronousOperationTests.allTests) -]) +#if os(Linux) || os(FreeBSD) + @testable import QueuerTests + + XCTMain([ + testCase(ConcurrentOperationTests.allTests), + testCase(QueuerTests.allTests), + testCase(SchedulerTests.allTests), + testCase(SemaphoreTests.allTests), + testCase(SynchronousOperationTests.allTests) + ]) #endif diff --git a/Tests/QueuerTests/ConcurrentOperationTests+XCTest.swift b/Tests/QueuerTests/ConcurrentOperationTests+XCTest.swift new file mode 100644 index 0000000..b4025d9 --- /dev/null +++ b/Tests/QueuerTests/ConcurrentOperationTests+XCTest.swift @@ -0,0 +1,50 @@ +// +// ConcurrentOperationTests+XCTest.swift +// Queuer +// +// MIT License +// +// Copyright (c) 2017 - 2018 Fabrizio Brancati +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// +// NOTE: This file was generated by generate_linux_tests.rb +// +// Do NOT edit this file directly as it will be regenerated automatically when needed. +// + +import XCTest + +internal extension ConcurrentOperationTests { + internal static var allTests: [(String, (ConcurrentOperationTests) -> () throws -> Void)] { + return [ + ("testInitWithExecutionBlock", testInitWithExecutionBlock), + ("testIsAsynchronous", testIsAsynchronous), + ("testAddToSharedQueuer", testAddToSharedQueuer), + ("testAddToQueue", testAddToQueue), + ("testSimpleRetry", testSimpleRetry), + ("testChainedRetry", testChainedRetry), + ("testCanceledChainedRetry", testCanceledChainedRetry), + ("testChainedManualRetry", testChainedManualRetry), + ("testChainedWrongManualRetry", testChainedWrongManualRetry) + ] + } +} diff --git a/Tests/QueuerTests/ConcurrentOperationTests.swift b/Tests/QueuerTests/ConcurrentOperationTests.swift index 90a6a74..5ea45d9 100644 --- a/Tests/QueuerTests/ConcurrentOperationTests.swift +++ b/Tests/QueuerTests/ConcurrentOperationTests.swift @@ -28,19 +28,12 @@ import XCTest internal class ConcurrentOperationTests: XCTestCase { - internal static let allTests = [ - ("testInitWithExecutionBlock", testInitWithExecutionBlock), - ("testIsAsynchronous", testIsAsynchronous), - ("testAddToSharedQueuer", testAddToSharedQueuer), - ("testAddToQueue", testAddToQueue) - ] - internal func testInitWithExecutionBlock() { let queue = Queuer(name: "ConcurrentOperationTestInitWithExecutionBlock") let testExpectation = expectation(description: "Init With Execution Block") - let concurrentOperation = ConcurrentOperation { + let concurrentOperation = ConcurrentOperation { _ in testExpectation.fulfill() } concurrentOperation.addToQueue(queue) @@ -73,4 +66,142 @@ internal class ConcurrentOperationTests: XCTestCase { XCTAssertEqual(queue.operationCount, 1) XCTAssertEqual(queue.operations, [concurrentOperation]) } + + internal func testSimpleRetry() { + let queue = Queuer(name: "ConcurrentOperationTestSimpleRetry") + + let testExpectation = expectation(description: "Simple Retry") + + let concurrentOperation = ConcurrentOperation { operation in + operation.hasFailed = true + } + queue.addCompletionHandler { + testExpectation.fulfill() + } + concurrentOperation.addToQueue(queue) + + waitForExpectations(timeout: 5) { error in + XCTAssertNil(error) + XCTAssertTrue(concurrentOperation.hasFailed) + XCTAssertEqual(concurrentOperation.currentAttempt, 3) + } + } + + internal func testChainedRetry() { + let queue = Queuer(name: "ConcurrentOperationTestChainedRetry") + let testExpectation = expectation(description: "Chained Retry") + var order: [Int] = [] + + let concurrentOperation1 = ConcurrentOperation { operation in + Thread.sleep(forTimeInterval: 1) + order.append(0) + operation.hasFailed = true + } + let concurrentOperation2 = ConcurrentOperation { operation in + order.append(1) + operation.hasFailed = true + } + queue.addChainedOperations([concurrentOperation1, concurrentOperation2]) { + order.append(2) + testExpectation.fulfill() + } + + waitForExpectations(timeout: 5) { error in + XCTAssertNil(error) + XCTAssertEqual(order, [0, 0, 0, 1, 1, 1, 2]) + } + } + + internal func testCanceledChainedRetry() { + let queue = Queuer(name: "ConcurrentOperationTestCanceledChainedRetry") + let testExpectation = expectation(description: "Canceled Chained Retry") + var order: [Int] = [] + + let concurrentOperation1 = ConcurrentOperation { operation in + Thread.sleep(forTimeInterval: 1) + order.append(0) + operation.hasFailed = true + } + let concurrentOperation2 = ConcurrentOperation { operation in + operation.cancel() + guard !operation.isCancelled else { + return + } + order.append(1) + operation.hasFailed = true + } + queue.addChainedOperations([concurrentOperation1, concurrentOperation2]) { + order.append(2) + testExpectation.fulfill() + } + + waitForExpectations(timeout: 5) { error in + XCTAssertNil(error) + XCTAssertEqual(order, [0, 0, 0, 2]) + } + } + + internal func testChainedManualRetry() { + let queue = Queuer(name: "ConcurrentOperationTestChainedManualRetry") + let testExpectation = expectation(description: "Chained Manual Retry") + var order: [Int] = [] + + let concurrentOperation1 = ConcurrentOperation { operation in + order.append(0) + operation.hasFailed = true + } + concurrentOperation1.manualRetry = true + + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { + concurrentOperation1.retry() + } + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) { + concurrentOperation1.retry() + } + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { + concurrentOperation1.retry() + } + + let concurrentOperation2 = ConcurrentOperation { operation in + order.append(1) + operation.hasFailed = true + } + queue.addChainedOperations([concurrentOperation1, concurrentOperation2]) { + order.append(2) + testExpectation.fulfill() + } + + waitForExpectations(timeout: 5) { error in + XCTAssertNil(error) + XCTAssertEqual(order, [0, 0, 0, 1, 1, 1, 2]) + } + } + + internal func testChainedWrongManualRetry() { + let queue = Queuer(name: "ConcurrentOperationTestChainedWrongManualRetry") + let testExpectation = expectation(description: "Chained Wrong Manual Retry") + var order: [Int] = [] + + let concurrentOperation1 = ConcurrentOperation { operation in + order.append(0) + operation.hasFailed = true + } + concurrentOperation1.manualRetry = true + + let concurrentOperation2 = ConcurrentOperation { _ in + order.append(1) + } + queue.addChainedOperations([concurrentOperation1, concurrentOperation2]) { + order.append(2) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { + testExpectation.fulfill() + } + + waitForExpectations(timeout: 5) { error in + XCTAssertNil(error) + XCTAssertEqual(order, [0]) + } + } } diff --git a/Tests/QueuerTests/QueuerTests+XCTest.swift b/Tests/QueuerTests/QueuerTests+XCTest.swift new file mode 100644 index 0000000..bfe5de0 --- /dev/null +++ b/Tests/QueuerTests/QueuerTests+XCTest.swift @@ -0,0 +1,57 @@ +// +// QueuerTests+XCTest.swift +// Queuer +// +// MIT License +// +// Copyright (c) 2017 - 2018 Fabrizio Brancati +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// +// NOTE: This file was generated by generate_linux_tests.rb +// +// Do NOT edit this file directly as it will be regenerated automatically when needed. +// + +import XCTest + +internal extension QueuerTests { + internal static var allTests: [(String, (QueuerTests) -> () throws -> Void)] { + return [ + ("testOperationCount", testOperationCount), + ("testOperations", testOperations), + ("testMaxConcurrentOperationCount", testMaxConcurrentOperationCount), + ("testQualityOfService", testQualityOfService), + ("testInitWithNameMaxConcurrentOperationCount", testInitWithNameMaxConcurrentOperationCount), + ("testInitWithNameMaxConcurrentOperationCountQualityOfService", testInitWithNameMaxConcurrentOperationCountQualityOfService), + ("testAddOperationBlock", testAddOperationBlock), + ("testAddOperation", testAddOperation), + ("testAddOperations", testAddOperations), + ("testAddChainedOperations", testAddChainedOperations), + ("testAddChainedOperationsList", testAddChainedOperationsList), + ("testAddChainedOperationsEmpty", testAddChainedOperationsEmpty), + ("testAddChainedOperationsWithoutCompletion", testAddChainedOperationsWithoutCompletion), + ("testCancelAll", testCancelAll), + ("testPauseAndResume", testPauseAndResume), + ("testWaitUnitlAllOperationsAreFinished", testWaitUnitlAllOperationsAreFinished) + ] + } +} diff --git a/Tests/QueuerTests/QueuerTests.swift b/Tests/QueuerTests/QueuerTests.swift index 16e50ec..7976beb 100644 --- a/Tests/QueuerTests/QueuerTests.swift +++ b/Tests/QueuerTests/QueuerTests.swift @@ -28,34 +28,14 @@ import Dispatch @testable import Queuer import XCTest -// swiftlint:disable type_body_length internal class QueuerTests: XCTestCase { - internal static let allTests = [ - ("testOperationCount", testOperationCount), - ("testOperations", testOperations), - ("testMaxConcurrentOperationCount", testMaxConcurrentOperationCount), - ("testQualityOfService", testQualityOfService), - ("testInitWithNameMaxConcurrentOperationCount", testInitWithNameMaxConcurrentOperationCount), - ("testInitWithNameMaxConcurrentOperationCountQualityOfService", testInitWithNameMaxConcurrentOperationCountQualityOfService), - ("testAddOperationBlock", testAddOperationBlock), - ("testAddOperation", testAddOperation), - ("testAddOperations", testAddOperations), - ("testAddChainedOperations", testAddChainedOperations), - ("testAddChainedOperationsList", testAddChainedOperationsList), - ("testAddChainedOperationsEmpty", testAddChainedOperationsEmpty), - ("testAddChainedOperationsWithoutCompletion", testAddChainedOperationsWithoutCompletion), - ("testCancelAll", testCancelAll), - ("testPauseAndResume", testPauseAndResume), - ("testWaitUnitlAllOperationsAreFinished", testWaitUnitlAllOperationsAreFinished) - ] - internal func testOperationCount() { let queue = Queuer(name: "QueuerTestOperationCount") let testExpectation = expectation(description: "Operation Count") XCTAssertEqual(queue.operationCount, 0) - let concurrentOperation = ConcurrentOperation { + let concurrentOperation = ConcurrentOperation { _ in Thread.sleep(forTimeInterval: 2) testExpectation.fulfill() } @@ -72,7 +52,7 @@ internal class QueuerTests: XCTestCase { let queue = Queuer(name: "QueuerTestOperations") let testExpectation = expectation(description: "Operations") - let concurrentOperation = ConcurrentOperation { + let concurrentOperation = ConcurrentOperation { _ in Thread.sleep(forTimeInterval: 2) testExpectation.fulfill() } @@ -129,7 +109,6 @@ internal class QueuerTests: XCTestCase { waitForExpectations(timeout: 5) { error in XCTAssertNil(error) - XCTAssertEqual(queue.operationCount, 0) } } @@ -137,7 +116,7 @@ internal class QueuerTests: XCTestCase { let queue = Queuer(name: "QueuerTestAddOperation") let testExpectation = expectation(description: "Add Operation") - let concurrentOperation = ConcurrentOperation { + let concurrentOperation = ConcurrentOperation { _ in XCTAssertEqual(queue.operationCount, 1) testExpectation.fulfill() } @@ -154,17 +133,16 @@ internal class QueuerTests: XCTestCase { let testExpectation = expectation(description: "Add Operations") var check = 0 - let concurrentOperation1 = ConcurrentOperation { + let concurrentOperation1 = ConcurrentOperation { _ in check += 1 } - let concurrentOperation2 = ConcurrentOperation { + let concurrentOperation2 = ConcurrentOperation { _ in check += 1 } queue.addOperation(concurrentOperation1) queue.addOperation(concurrentOperation2) - let deadline = DispatchTime.now() + .seconds(2) - DispatchQueue.global(qos: .background).asyncAfter(deadline: deadline) { + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) { testExpectation.fulfill() } @@ -180,11 +158,11 @@ internal class QueuerTests: XCTestCase { let testExpectation = expectation(description: "Add Chained Operations") var order: [Int] = [] - let concurrentOperation1 = ConcurrentOperation { + let concurrentOperation1 = ConcurrentOperation { _ in Thread.sleep(forTimeInterval: 2) order.append(0) } - let concurrentOperation2 = ConcurrentOperation { + let concurrentOperation2 = ConcurrentOperation { _ in order.append(1) } queue.addChainedOperations([concurrentOperation1, concurrentOperation2]) { @@ -203,11 +181,11 @@ internal class QueuerTests: XCTestCase { let testExpectation = expectation(description: "Add Chained Operations List") var order: [Int] = [] - let concurrentOperation1 = ConcurrentOperation { + let concurrentOperation1 = ConcurrentOperation { _ in Thread.sleep(forTimeInterval: 2) order.append(0) } - let concurrentOperation2 = ConcurrentOperation { + let concurrentOperation2 = ConcurrentOperation { _ in order.append(1) } queue.addChainedOperations(concurrentOperation1, concurrentOperation2) { @@ -240,10 +218,10 @@ internal class QueuerTests: XCTestCase { let testExpectation = expectation(description: "Add Chained Operations Without Completion") var order: [Int] = [] - let concurrentOperation1 = ConcurrentOperation { + let concurrentOperation1 = ConcurrentOperation { _ in order.append(0) } - let concurrentOperation2 = ConcurrentOperation { + let concurrentOperation2 = ConcurrentOperation { _ in order.append(1) testExpectation.fulfill() } @@ -261,11 +239,11 @@ internal class QueuerTests: XCTestCase { let testExpectation = expectation(description: "Cancell All Operations") var order: [Int] = [] - let concurrentOperation1 = SynchronousOperation { + let concurrentOperation1 = SynchronousOperation { _ in Thread.sleep(forTimeInterval: 2) order.append(0) } - let concurrentOperation2 = SynchronousOperation { + let concurrentOperation2 = SynchronousOperation { _ in order.append(1) } queue.addChainedOperations([concurrentOperation1, concurrentOperation2]) { @@ -290,11 +268,11 @@ internal class QueuerTests: XCTestCase { let testExpectation = expectation(description: "Pause and Resume") var order: [Int] = [] - let concurrentOperation1 = ConcurrentOperation { + let concurrentOperation1 = ConcurrentOperation { _ in Thread.sleep(forTimeInterval: 2) order.append(0) } - let concurrentOperation2 = ConcurrentOperation { + let concurrentOperation2 = ConcurrentOperation { _ in order.append(1) } queue.addChainedOperations([concurrentOperation1, concurrentOperation2]) { @@ -322,11 +300,11 @@ internal class QueuerTests: XCTestCase { let testExpectation = expectation(description: "Wait Unitl All Operations Are Finished") var order: [Int] = [] - let concurrentOperation1 = ConcurrentOperation { + let concurrentOperation1 = ConcurrentOperation { _ in Thread.sleep(forTimeInterval: 2) order.append(0) } - let concurrentOperation2 = ConcurrentOperation { + let concurrentOperation2 = ConcurrentOperation { _ in order.append(1) } queue.addChainedOperations([concurrentOperation1, concurrentOperation2]) { diff --git a/Tests/QueuerTests/RequestOperationTests.swift b/Tests/QueuerTests/RequestOperationTests.swift deleted file mode 100644 index b0f5422..0000000 --- a/Tests/QueuerTests/RequestOperationTests.swift +++ /dev/null @@ -1,200 +0,0 @@ -// -// RequestOperationTests.swift -// Queuer -// -// MIT License -// -// Copyright (c) 2017 - 2018 Fabrizio Brancati -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#if !os(Linux) - -@testable import Queuer -import XCTest - -internal class RequestOperationTests: XCTestCase { - internal static let allTests = [ - ("testInitFull", testInitFull), - ("testExecute", testExecute), - ("testUnsupportedURL", testUnsupportedURL), - ("testWrongURL", testWrongURL), - ("testWithoutCompletionHandler", testWithoutCompletionHandler), - ("testCancel", testCancel), - ("testPauseAndResume", testPauseAndResume) - ] - - internal let testAddress: String = "https://google.com" - - override internal func setUp() { - super.setUp() - - RequestOperation.globalCachePolicy = .reloadIgnoringLocalCacheData - } - - internal func testInit() { - let queue = Queuer(name: "RequestOperationTestInit") - - let requestOperation = RequestOperation() - requestOperation.addToQueue(queue) - - XCTAssertNil(requestOperation.url?.absoluteString) - XCTAssertNil(requestOperation.query) - XCTAssertNil(requestOperation.completeURL) - XCTAssertEqual(requestOperation.timeout, 30) - XCTAssertEqual(requestOperation.method, .get) - XCTAssertEqual(requestOperation.headers ?? [:], [:]) - XCTAssertNil(requestOperation.body) - } - - internal func testInitFull() { - let queue = Queuer(name: "RequestOperationTestInitFull") - let testExpectation = expectation(description: "InitFull") - - let requestOperation = RequestOperation(url: self.testAddress, query: ["test": "test", "test2": "test2"], timeout: 30, method: .get, headers: ["test": "test", "test2": "test2"], body: Data()) { _, _, _, _ in - testExpectation.fulfill() - } - requestOperation.addToQueue(queue) - - XCTAssertEqual(requestOperation.url?.absoluteString, self.testAddress) - XCTAssertEqual(requestOperation.query, "?test=test&test2=test2") - XCTAssertEqual(requestOperation.completeURL, URL(string: self.testAddress + "?test=test&test2=test2")) - XCTAssertEqual(requestOperation.timeout, 30) - XCTAssertEqual(requestOperation.method, .get) - XCTAssertEqual(requestOperation.headers ?? [:], ["test": "test", "test2": "test2"]) - XCTAssertEqual(requestOperation.body, Data()) - - waitForExpectations(timeout: 5) { error in - XCTAssertNil(error) - } - } - - internal func testExecute() { - let queue = Queuer(name: "RequestOperationTestExecute") - let testExpectation = expectation(description: "Execute") - - let requestOperation = RequestOperation(url: self.testAddress) { success, _, _, error in - XCTAssertNil(error) - XCTAssertTrue(success) - testExpectation.fulfill() - } - requestOperation.addToQueue(queue) - - waitForExpectations(timeout: 5) { error in - XCTAssertNil(error) - } - } - - internal func testUnsupportedURL() { - let queue = Queuer(name: "RequestOperationTestUnsupportedURL") - let testExpectation = expectation(description: "Unsupported URL") - - let requestOperation = RequestOperation(url: "/path/to/something") { success, _, _, error in - XCTAssertTrue(error is URLError) - XCTAssertFalse(success) - testExpectation.fulfill() - } - requestOperation.addToQueue(queue) - - waitForExpectations(timeout: 5) { error in - XCTAssertNil(error) - } - } - - internal func testWrongURL() { - let queue = Queuer(name: "RequestOperationTestWrongURL") - let testExpectation = expectation(description: "Wrong URL") - - let requestOperation = RequestOperation(url: "👍") { success, _, _, error in - XCTAssertEqual(error as? RequestOperation.RequestError, RequestOperation.RequestError.urlError) - XCTAssertFalse(success) - testExpectation.fulfill() - } - requestOperation.addToQueue(queue) - - waitForExpectations(timeout: 5) { error in - XCTAssertNil(error) - } - } - - internal func testWithoutCompletionHandler() { - let queue = Queuer(name: "RequestOperationTestWithoutCompletionHandler") - let requestOperation = RequestOperation(url: self.testAddress) - requestOperation.addToQueue(queue) - - XCTAssertEqual(queue.operations, [requestOperation]) - XCTAssertEqual(queue.operationCount, 1) - } - - internal func testCancel() { - let queue = Queuer(name: "RequestOperationTestCancel") - let testExpectation = expectation(description: "Cancel") - - let requestOperation = RequestOperation(url: "http://fakehttpaddress.com/", cachePolicy: .reloadIgnoringLocalCacheData) { success, _, _, _ in - // Currently there is no easy way to check in time if the operation was cancelled before completion. - // XCTAssertEqual(error as? RequestOperation.RequestError, RequestOperation.RequestError.operationCancelled) - XCTAssertFalse(success) - testExpectation.fulfill() - } - requestOperation.addToQueue(queue) - - requestOperation.cancel() - - waitForExpectations(timeout: 5) { error in - XCTAssertNil(error) - } - } - - internal func testPauseAndResume() { - let queue = Queuer(name: "RequestOperationTestPauseAndResume") - let testExpectation = expectation(description: "Pause and Resume") - var order: [Int] = [] - - let requestOperation1 = RequestOperation(url: self.testAddress) { success, _, _, error in - XCTAssertNil(error) - XCTAssertTrue(success) - order.append(0) - } - let requestOperation2 = RequestOperation(url: self.testAddress) { success, _, _, error in - XCTAssertNil(error) - XCTAssertTrue(success) - order.append(1) - } - queue.addChainedOperations([requestOperation1, requestOperation2]) { - order.append(2) - } - - queue.pause() - testExpectation.fulfill() - - XCTAssertLessThanOrEqual(queue.operationCount, 3) - - waitForExpectations(timeout: 5) { error in - XCTAssertNil(error) - XCTAssertFalse(queue.isExecuting) - XCTAssertLessThanOrEqual(queue.operationCount, 3) - XCTAssertNotEqual(order, [0, 1, 2]) - - queue.resume() - XCTAssertTrue(queue.isExecuting) - } - } -} - -#endif diff --git a/Sources/Queuer/URLBuilder.swift b/Tests/QueuerTests/SchedulerTests+XCTest.swift similarity index 56% rename from Sources/Queuer/URLBuilder.swift rename to Tests/QueuerTests/SchedulerTests+XCTest.swift index c139701..c893436 100644 --- a/Sources/Queuer/URLBuilder.swift +++ b/Tests/QueuerTests/SchedulerTests+XCTest.swift @@ -1,5 +1,5 @@ // -// URLBuilder.swift +// SchedulerTests+XCTest.swift // Queuer // // MIT License @@ -23,28 +23,22 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// +// +// +// NOTE: This file was generated by generate_linux_tests.rb +// +// Do NOT edit this file directly as it will be regenerated automatically when needed. +// -import Foundation +import XCTest -/// URL builder struct. -internal enum URLBuilder { - /// Builds the query as a string, ready to be added in the URL. - /// - /// - Parameter query: Query dictionary. - /// - Returns: Returns the query as a String. - internal static func build(query: [String: String]?) -> String { - guard let query = query else { - return "" - } - - var finalQuery: String = "" - for (index, parameter) in query.enumerated() { - guard let parameterKey = parameter.key.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), let parameterValue = parameter.value.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { - continue - } - finalQuery += index == 0 ? "?" : "&" - finalQuery += parameterKey + "=" + parameterValue - } - return finalQuery +internal extension SchedulerTests { + internal static var allTests: [(String, (SchedulerTests) -> () throws -> Void)] { + return [ + ("testInitWithoutHandler", testInitWithoutHandler), + ("testInitWithHandler", testInitWithHandler), + ("testCancel", testCancel) + ] } } diff --git a/Tests/QueuerTests/SchedulerTests.swift b/Tests/QueuerTests/SchedulerTests.swift new file mode 100644 index 0000000..395e8c8 --- /dev/null +++ b/Tests/QueuerTests/SchedulerTests.swift @@ -0,0 +1,89 @@ +// +// SchedulerTests.swift +// Queuer +// +// MIT License +// +// Copyright (c) 2017 - 2018 Fabrizio Brancati +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Dispatch +@testable import Queuer +import XCTest + +internal class SchedulerTests: XCTestCase { + internal func testInitWithoutHandler() { + let testExpectation = expectation(description: "Init Without Handler") + var order: [Int] = [] + + var schedule = Scheduler(deadline: .now(), repeating: .seconds(1)) + schedule.setHandler { + order.append(0) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(3500)) { + testExpectation.fulfill() + } + + waitForExpectations(timeout: 5) { error in + XCTAssertNil(error) + XCTAssertEqual(order, [0 ,0 ,0, 0]) + } + } + + internal func testInitWithHandler() { + let testExpectation = expectation(description: "Init With Handler") + var order: [Int] = [] + + let schedule = Scheduler(deadline: .now(), repeating: .never) { + order.append(0) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(3500)) { + testExpectation.fulfill() + } + + waitForExpectations(timeout: 5) { error in + XCTAssertNil(error) + XCTAssertEqual(order, [0]) + schedule.timer.cancel() + } + } + + internal func testCancel() { + let testExpectation = expectation(description: "Init Without Handler") + var order: [Int] = [] + + var schedule = Scheduler(deadline: .now(), repeating: .seconds(1)) + schedule.setHandler { + order.append(0) + schedule.timer.cancel() + } + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(3500)) { + testExpectation.fulfill() + } + + waitForExpectations(timeout: 5) { error in + XCTAssertNil(error) + XCTAssertEqual(order, [0]) + } + } +} diff --git a/Tests/QueuerTests/SemaphoreTests+XCTest.swift b/Tests/QueuerTests/SemaphoreTests+XCTest.swift new file mode 100644 index 0000000..9e795ba --- /dev/null +++ b/Tests/QueuerTests/SemaphoreTests+XCTest.swift @@ -0,0 +1,43 @@ +// +// SemaphoreTests+XCTest.swift +// Queuer +// +// MIT License +// +// Copyright (c) 2017 - 2018 Fabrizio Brancati +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// +// NOTE: This file was generated by generate_linux_tests.rb +// +// Do NOT edit this file directly as it will be regenerated automatically when needed. +// + +import XCTest + +internal extension SemaphoreTests { + internal static var allTests: [(String, (SemaphoreTests) -> () throws -> Void)] { + return [ + ("testWithSemaphore", testWithSemaphore), + ("testWithoutSemaphore", testWithoutSemaphore) + ] + } +} diff --git a/Tests/QueuerTests/SemaphoreTests.swift b/Tests/QueuerTests/SemaphoreTests.swift index 82f08f6..038f186 100644 --- a/Tests/QueuerTests/SemaphoreTests.swift +++ b/Tests/QueuerTests/SemaphoreTests.swift @@ -28,18 +28,13 @@ import XCTest internal class SemaphoreTests: XCTestCase { - internal static let allTests = [ - ("testWithSemaphore", testWithSemaphore), - ("testWithoutSemaphore", testWithoutSemaphore) - ] - internal func testWithSemaphore() { let semaphore = Semaphore() let queue = Queuer(name: "SemaphoreTestWithSemaphore") let testExpectation = expectation(description: "With Semaphore") var testString = "" - let concurrentOperation = ConcurrentOperation { + let concurrentOperation = ConcurrentOperation { _ in Thread.sleep(forTimeInterval: 2) testString = "Tested" semaphore.continue() @@ -60,7 +55,7 @@ internal class SemaphoreTests: XCTestCase { let testExpectation = expectation(description: "Without Semaphore") var testString = "" - let concurrentOperation = ConcurrentOperation { + let concurrentOperation = ConcurrentOperation { _ in Thread.sleep(forTimeInterval: 2) testString = "Tested" testExpectation.fulfill() diff --git a/Tests/QueuerTests/SynchronousOperationTests+XCTest.swift b/Tests/QueuerTests/SynchronousOperationTests+XCTest.swift new file mode 100644 index 0000000..e8a4e19 --- /dev/null +++ b/Tests/QueuerTests/SynchronousOperationTests+XCTest.swift @@ -0,0 +1,45 @@ +// +// SynchronousOperationTests+XCTest.swift +// Queuer +// +// MIT License +// +// Copyright (c) 2017 - 2018 Fabrizio Brancati +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// +// NOTE: This file was generated by generate_linux_tests.rb +// +// Do NOT edit this file directly as it will be regenerated automatically when needed. +// + +import XCTest + +internal extension SynchronousOperationTests { + internal static var allTests: [(String, (SynchronousOperationTests) -> () throws -> Void)] { + return [ + ("testSynchronousOperation", testSynchronousOperation), + ("testSynchronousOperationOnSharedQueuer", testSynchronousOperationOnSharedQueuer), + ("testSynchronousOperationRetry", testSynchronousOperationRetry), + ("testCancel", testCancel) + ] + } +} diff --git a/Tests/QueuerTests/SynchronousOperationTests.swift b/Tests/QueuerTests/SynchronousOperationTests.swift index b0dcffa..6f5d688 100644 --- a/Tests/QueuerTests/SynchronousOperationTests.swift +++ b/Tests/QueuerTests/SynchronousOperationTests.swift @@ -29,21 +29,15 @@ import Dispatch import XCTest internal class SynchronousOperationTests: XCTestCase { - internal static let allTests = [ - ("testSynchronousOperation", testSynchronousOperation), - ("testSynchronousOperationOnSharedQueuer", testSynchronousOperationOnSharedQueuer), - ("testCancel", testCancel) - ] - internal func testSynchronousOperation() { let queue = Queuer(name: "SynchronousOperationTestSynchronousOperation") let testExpectation = expectation(description: "Synchronous Operation") var testString = "" - let synchronousOperation1 = SynchronousOperation { + let synchronousOperation1 = SynchronousOperation { _ in testString = "Tested1" } - let synchronousOperation2 = SynchronousOperation { + let synchronousOperation2 = SynchronousOperation { _ in Thread.sleep(forTimeInterval: 2) testString = "Tested2" @@ -65,10 +59,10 @@ internal class SynchronousOperationTests: XCTestCase { let testExpectation = expectation(description: "Synchronous Operation") var testString = "" - let synchronousOperation1 = SynchronousOperation { + let synchronousOperation1 = SynchronousOperation { _ in testString = "Tested1" } - let synchronousOperation2 = SynchronousOperation { + let synchronousOperation2 = SynchronousOperation { _ in Thread.sleep(forTimeInterval: 2) testString = "Tested2" @@ -86,6 +80,35 @@ internal class SynchronousOperationTests: XCTestCase { } } + internal func testSynchronousOperationRetry() { + let queue = Queuer(name: "SynchronousOperationTestRetry") + let testExpectation = expectation(description: "Synchronous Operation Retry") + var order: [Int] = [] + + let synchronousOperation1 = SynchronousOperation { operation in + Thread.sleep(forTimeInterval: 1) + order.append(0) + operation.hasFailed = true + + if operation.currentAttempt == 3 { + testExpectation.fulfill() + } + } + let synchronousOperation2 = SynchronousOperation { _ in + order.append(1) + } + synchronousOperation1.addToQueue(queue) + synchronousOperation2.addToQueue(queue) + + XCTAssertFalse(synchronousOperation1.isAsynchronous) + XCTAssertFalse(synchronousOperation2.isAsynchronous) + + waitForExpectations(timeout: 5) { error in + XCTAssertNil(error) + XCTAssertEqual(order, [1, 0, 0, 0]) + } + } + internal func testCancel() { let queue = Queuer(name: "SynchronousOperationTestCancel") queue.maxConcurrentOperationCount = 1 @@ -98,11 +121,11 @@ internal class SynchronousOperationTests: XCTestCase { testExpectation.fulfill() } - let synchronousOperation1 = SynchronousOperation { + let synchronousOperation1 = SynchronousOperation { _ in testString = "Tested1" Thread.sleep(forTimeInterval: 4) } - let synchronousOperation2 = SynchronousOperation { + let synchronousOperation2 = SynchronousOperation { _ in testString = "Tested2" } synchronousOperation1.addToQueue(queue) diff --git a/Tests/QueuerTests/URLBuilderTests.swift b/Tests/QueuerTests/URLBuilderTests.swift deleted file mode 100644 index d730d6d..0000000 --- a/Tests/QueuerTests/URLBuilderTests.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// URLBuilderTests.swift -// Queuer -// -// MIT License -// -// Copyright (c) 2017 - 2018 Fabrizio Brancati -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -@testable import Queuer -import XCTest - -internal class URLBuilderTests: XCTestCase { - internal static let allTests = [ - ("testWithoutParameters", testWithoutParameters), - ("testWithSingleParameter", testBuildURLWithSingleParameter), - ("testWithMultipleParameters", testWithMultipleParameters), - ("testWithEmojiCharctersInParameters", testWithEmojiCharctersInParameters) - //("testWithStrangeCharctersInParameters", testWithStrangeCharctersInParameters) - ] - - internal func testWithoutParameters() { - let query = URLBuilder.build(query: [:]) - - XCTAssertEqual(query, "") - } - - internal func testBuildURLWithSingleParameter() { - let query = URLBuilder.build(query: ["test": "test"]) - - XCTAssertEqual(query, "?test=test") - } - - internal func testWithMultipleParameters() { - let query = URLBuilder.build(query: ["test": "test", "test2": "test2"]) - - XCTAssert(query == "?test=test&test2=test2" || query == "?test2=test2&test=test") - } - - internal func testWithEmojiCharctersInParameters() { - let query = URLBuilder.build(query: ["test": "test", "test👍": "test👍"]) - - XCTAssert(query == "?test=test&test%F0%9F%91%8D=test%F0%9F%91%8D" || query == "?test%F0%9F%91%8D=test%F0%9F%91%8D&test=test") - } - - /// Thanks to [Stack Overflow](https://stackoverflow.com/a/33558934/4032046 ). - internal func testWithStrangeCharctersInParameters() { - guard let string = String(bytes: [0xD8, 0x00] as [UInt8], encoding: String.Encoding.utf16BigEndian) else { - XCTFail("`testWithStrangeCharctersInParameters` error") - return - } - - let query = URLBuilder.build(query: [string: "test", "test👍": string]) - XCTAssertEqual(query, "") - } -} diff --git a/generate_linux_tests.rb b/generate_linux_tests.rb new file mode 100644 index 0000000..8727017 --- /dev/null +++ b/generate_linux_tests.rb @@ -0,0 +1,245 @@ +#!/usr/bin/env ruby + +# +# process_test_files.rb +# +# Copyright 2016 Tony Stone +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Created by Tony Stone on 5/4/16. +# +require 'getoptlong' +require 'fileutils' +require 'pathname' + +include FileUtils + +# +# This ruby script will auto generate LinuxMain.swift and the +XCTest.swift extension files for Swift Package Manager on Linux platforms. +# +# See https://github.com/apple/swift-corelibs-xctest/blob/master/Documentation/Linux.md +# +def header(file_name) + string = <<-eos +// +// +// Queuer +// +// MIT License +// +// Copyright (c) 2017 - 2018 Fabrizio Brancati +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// +// NOTE: This file was generated by generate_linux_tests.rb +// +// Do NOT edit this file directly as it will be regenerated automatically when needed. +// + +import XCTest + eos + + string + .sub('', File.basename(file_name)) + .sub('', Time.now.to_s) +end + +def create_extension_file(file_name, classes) + extension_file = file_name.sub! '.swift', '+XCTest.swift' + print 'Creating file: ' + extension_file + "\n" + + File.open(extension_file, 'w') do |file| + file.write header(extension_file) + file.write "\n" + + for class_array in classes + file.write 'internal extension ' + class_array[0] + " {\n" + file.write ' internal static var allTests: [(String, (' + class_array[0] + ") -> () throws -> Void)] {\n" + file.write " return [\n" + + class_count = class_array[1].size + count = 0 + for func_name in class_array[1] + count += 1 + + file.write ' ("' + func_name + '", ' + func_name + (count == class_count ? ")\n" : "),\n") + end + + file.write " ]\n" + file.write " }\n" + file.write "}\n" + end + end +end + +def create_linux_main(tests_directory, all_test_sub_directories, files) + file_name = tests_directory + '/LinuxMain.swift' + print 'Creating file: ' + file_name + "\n" + + File.open(file_name, 'w') do |file| + file.write header(file_name) + file.write "\n" + + file.write "#if os(Linux) || os(FreeBSD)\n" + for test_sub_directory in all_test_sub_directories.sort { |x, y| x <=> y } + file.write ' @testable import ' + test_sub_directory + "\n" + end + file.write "\n" + file.write " XCTMain([\n" + + test_cases = [] + for classes in files + for class_array in classes + test_cases << class_array[0] + end + end + + cases_count = test_cases.size + count = 0 + for test_case in test_cases.sort { |x, y| x <=> y } + count += 1 + + file.write ' testCase(' + test_case + (count == cases_count ? ".allTests)\n" : ".allTests),\n") + end + file.write " ])\n" + file.write "#endif\n" + end +end + +def parse_source_file(file_name) + puts 'Parsing file: ' + file_name + "\n" + + classes = [] + current_class = nil + in_if_linux = false + in_else = false + ignore = false + + # + # Read the file line by line + # and parse to find the class + # names and func names + # + File.readlines(file_name).each do |line| + if in_if_linux + if /\#else/ =~ line + in_else = true + ignore = true + elsif /\#end/ =~ line + in_else = false + in_if_linux = false + ignore = false + end + elsif /\#if[ \t]+os\(Linux\)/ =~ line + in_if_linux = true + ignore = false + end + + next if ignore + # Match class or func + match = line[/class[ \t]+[a-zA-Z0-9_]*(?=[ \t]*:[ \t]*XCTestCase)|func[ \t]+test[a-zA-Z0-9_]*(?=[ \t]*\(\))/, 0] + if match + if match[/class/, 0] == 'class' + class_name = match.sub(/^class[ \t]+/, '') + # + # Create a new class / func structure + # and add it to the classes array. + # + current_class = [class_name, []] + classes << current_class + else # Must be a func + func_name = match.sub(/^func[ \t]+/, '') + # + # Add each func name the the class / func + # structure created above. + # + current_class[1] << func_name + end + end + end + classes +end + +# +# Main routine +# +# + +tests_directory = 'Tests' + +options = GetoptLong.new(['--tests-dir', GetoptLong::OPTIONAL_ARGUMENT]) +options.quiet = true + +begin + options.each do |option, value| + case option + when '--tests-dir' + tests_directory = value + end + end + rescue GetoptLong::InvalidOption +end + +all_test_sub_directories = [] +all_files = [] + +Dir[tests_directory + '/*'].each do |sub_directory| + next unless File.directory?(sub_directory) + directory_has_classes = false + Dir[sub_directory + '/*Test{s,}.swift'].each do |file_name| + next unless File.file? file_name + file_classes = parse_source_file(file_name) + + # + # If there are classes in the + # test source file, create an extension + # file for it. + # + next unless file_classes.count.positive? + create_extension_file(file_name, file_classes) + directory_has_classes = true + all_files << file_classes + end + + if directory_has_classes + all_test_sub_directories << Pathname.new(sub_directory).split.last.to_s + end +end + +# +# Last step is the create a LinuxMain.swift file that +# references all the classes and funcs in the source files. +# +if all_files.count.positive? + create_linux_main(tests_directory, all_test_sub_directories, all_files) +end +# eof diff --git a/jazzy.sh b/jazzy.sh index e49fa0d..c384069 100755 --- a/jazzy.sh +++ b/jazzy.sh @@ -2,7 +2,7 @@ # Creates documentation using Jazzy. -FRAMEWORK_VERSION=1.3.2 +FRAMEWORK_VERSION=2.0.0 jazzy \ --clean \