├── .gitignore ├── .swift-version ├── .travis.yml ├── Assets ├── Banner.png └── Banner.psd ├── Brisk.podspec ├── Brisk.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── Brisk.xcscheme ├── Brisk.xcworkspace └── contents.xcworkspacedata ├── Brisk ├── Brisk.h ├── BriskAsync2Sync.swift ├── BriskDispatch.swift ├── BriskGate.swift ├── BriskLock.swift ├── BriskRaise.swift ├── BriskSync2Async.swift ├── Info.plist └── Swift2 │ └── BriskGCD_Swift2.swift ├── BriskTests ├── BriskTests.swift ├── Info.plist └── Spin.swift ├── CHANGELOG.md ├── CONTRIBUTORS.md ├── LICENSE ├── Package.swift ├── README.md └── README_SWIFT2.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8.3 3 | branches: 4 | only: 5 | - master 6 | env: 7 | - LC_CTYPE=en_US.UTF-8 LANG=en_US.UTF-8 8 | before_install: 9 | - SIMULATOR_ID=$(xcrun instruments -s | grep -o "iPhone 6S (10.3) \[.*\]" | grep -o "\[.*\]" | sed "s/^\[\(.*\)\]$/\1/") 10 | - gem install xcpretty -N 11 | script: 12 | - echo $SIMULATOR_ID 13 | - open -b com.apple.iphonesimulator --args -CurrentDeviceUDID $SIMULATOR_ID 14 | - set -o pipefail 15 | - xcodebuild -project Brisk.xcodeproj -scheme "Brisk" -sdk "iphonesimulator10.3" 16 | -destination "OS=10.3,name=iPhone 6S" ONLY_ACTIVE_ARCH=NO test | xcpretty -c 17 | - pod lib lint --quick 18 | -------------------------------------------------------------------------------- /Assets/Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmfieldman/Brisk/8db0c17f525d1f4959e665a2e26039064f021d06/Assets/Banner.png -------------------------------------------------------------------------------- /Assets/Banner.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmfieldman/Brisk/8db0c17f525d1f4959e665a2e26039064f021d06/Assets/Banner.psd -------------------------------------------------------------------------------- /Brisk.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "Brisk" 4 | s.version = "3.1.1" 5 | s.summary = "Concise concurrency manipulation for Swift" 6 | 7 | s.description = <<-DESC 8 | Concise concurrency manipulation for Swift: completionHandler+>>() 9 | DESC 10 | 11 | s.homepage = "https://github.com/jmfieldman/Brisk" 12 | s.license = { :type => "MIT", :file => "LICENSE" } 13 | s.author = { "Jason Fieldman" => "jason@fieldman.org" } 14 | s.social_media_url = 'http://fieldman.org' 15 | 16 | s.ios.deployment_target = "8.0" 17 | s.osx.deployment_target = "10.10" 18 | s.tvos.deployment_target = "9.0" 19 | s.watchos.deployment_target = "2.0" 20 | 21 | s.source = { :git => "https://github.com/jmfieldman/Brisk.git", :tag => "#{s.version}" } 22 | s.source_files = "Brisk/*.swift" 23 | 24 | s.requires_arc = true 25 | 26 | s.default_subspec = 'Core' 27 | 28 | s.subspec 'Core' do |ss| 29 | ss.source_files = "Brisk/*.swift" 30 | end 31 | 32 | s.subspec 'Swift2' do |ss| 33 | ss.source_files = "Brisk/Swift2/*.swift" 34 | ss.dependency 'Brisk/Core' 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /Brisk.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 35057D721D60B930004BBF6D /* BriskRaise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35057D711D60B930004BBF6D /* BriskRaise.swift */; }; 11 | 35057D741D60BCF9004BBF6D /* BriskGate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35057D731D60BCF9004BBF6D /* BriskGate.swift */; }; 12 | 356AFC631D5E618700D18479 /* Brisk.h in Headers */ = {isa = PBXBuildFile; fileRef = 356AFC621D5E618700D18479 /* Brisk.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 356AFC6A1D5E618700D18479 /* Brisk.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 356AFC5F1D5E618700D18479 /* Brisk.framework */; }; 14 | 356AFC6F1D5E618700D18479 /* BriskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 356AFC6E1D5E618700D18479 /* BriskTests.swift */; }; 15 | 356AFC7C1D5E623E00D18479 /* BriskLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 356AFC7B1D5E623E00D18479 /* BriskLock.swift */; }; 16 | 356AFC7E1D5E63C900D18479 /* BriskSync2Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 356AFC7D1D5E63C900D18479 /* BriskSync2Async.swift */; }; 17 | 356AFC801D5E63D100D18479 /* BriskAsync2Sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 356AFC7F1D5E63D100D18479 /* BriskAsync2Sync.swift */; }; 18 | 356AFC821D5E8CAC00D18479 /* Spin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 356AFC811D5E8CAC00D18479 /* Spin.swift */; }; 19 | 3585ABB41D8624E10063299C /* BriskGCD_Swift2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3585ABB31D8624E10063299C /* BriskGCD_Swift2.swift */; }; 20 | 3585ABB61D86265E0063299C /* BriskDispatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3585ABB51D86265E0063299C /* BriskDispatch.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 356AFC6B1D5E618700D18479 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 356AFC561D5E618700D18479 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 356AFC5E1D5E618700D18479; 29 | remoteInfo = Brisk; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 35057D711D60B930004BBF6D /* BriskRaise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BriskRaise.swift; sourceTree = ""; }; 35 | 35057D731D60BCF9004BBF6D /* BriskGate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BriskGate.swift; sourceTree = ""; }; 36 | 35057D761D60EE0B004BBF6D /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = SOURCE_ROOT; }; 37 | 35057D781D60EE81004BBF6D /* Brisk.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Brisk.podspec; sourceTree = SOURCE_ROOT; }; 38 | 35057D7A1D60EFEE004BBF6D /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = SOURCE_ROOT; }; 39 | 35057D7B1D60EFEE004BBF6D /* CONTRIBUTORS.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTORS.md; sourceTree = SOURCE_ROOT; }; 40 | 35057D7E1D6132A8004BBF6D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; 41 | 356AFC5F1D5E618700D18479 /* Brisk.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Brisk.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 356AFC621D5E618700D18479 /* Brisk.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Brisk.h; sourceTree = ""; }; 43 | 356AFC641D5E618700D18479 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | 356AFC691D5E618700D18479 /* BriskTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BriskTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 356AFC6E1D5E618700D18479 /* BriskTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BriskTests.swift; sourceTree = ""; }; 46 | 356AFC701D5E618700D18479 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | 356AFC7B1D5E623E00D18479 /* BriskLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BriskLock.swift; sourceTree = ""; }; 48 | 356AFC7D1D5E63C900D18479 /* BriskSync2Async.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BriskSync2Async.swift; sourceTree = ""; }; 49 | 356AFC7F1D5E63D100D18479 /* BriskAsync2Sync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BriskAsync2Sync.swift; sourceTree = ""; }; 50 | 356AFC811D5E8CAC00D18479 /* Spin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Spin.swift; sourceTree = ""; }; 51 | 3585AB7C1D845A550063299C /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = SOURCE_ROOT; }; 52 | 3585ABB01D85FE8F0063299C /* README_SWIFT2.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README_SWIFT2.md; sourceTree = SOURCE_ROOT; }; 53 | 3585ABB31D8624E10063299C /* BriskGCD_Swift2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BriskGCD_Swift2.swift; sourceTree = ""; }; 54 | 3585ABB51D86265E0063299C /* BriskDispatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BriskDispatch.swift; sourceTree = ""; }; 55 | /* End PBXFileReference section */ 56 | 57 | /* Begin PBXFrameworksBuildPhase section */ 58 | 356AFC5B1D5E618700D18479 /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | 356AFC661D5E618700D18479 /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | 356AFC6A1D5E618700D18479 /* Brisk.framework in Frameworks */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | 356AFC551D5E618700D18479 = { 77 | isa = PBXGroup; 78 | children = ( 79 | 356AFC611D5E618700D18479 /* Brisk */, 80 | 356AFC6D1D5E618700D18479 /* BriskTests */, 81 | 356AFC601D5E618700D18479 /* Products */, 82 | ); 83 | sourceTree = ""; 84 | usesTabs = 0; 85 | }; 86 | 356AFC601D5E618700D18479 /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 356AFC5F1D5E618700D18479 /* Brisk.framework */, 90 | 356AFC691D5E618700D18479 /* BriskTests.xctest */, 91 | ); 92 | name = Products; 93 | sourceTree = ""; 94 | }; 95 | 356AFC611D5E618700D18479 /* Brisk */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 3585AB7C1D845A550063299C /* .travis.yml */, 99 | 35057D7E1D6132A8004BBF6D /* README.md */, 100 | 3585ABB01D85FE8F0063299C /* README_SWIFT2.md */, 101 | 35057D7A1D60EFEE004BBF6D /* CHANGELOG.md */, 102 | 35057D7B1D60EFEE004BBF6D /* CONTRIBUTORS.md */, 103 | 35057D781D60EE81004BBF6D /* Brisk.podspec */, 104 | 35057D761D60EE0B004BBF6D /* LICENSE */, 105 | 356AFC621D5E618700D18479 /* Brisk.h */, 106 | 356AFC641D5E618700D18479 /* Info.plist */, 107 | 356AFC7B1D5E623E00D18479 /* BriskLock.swift */, 108 | 356AFC7D1D5E63C900D18479 /* BriskSync2Async.swift */, 109 | 356AFC7F1D5E63D100D18479 /* BriskAsync2Sync.swift */, 110 | 3585ABB51D86265E0063299C /* BriskDispatch.swift */, 111 | 35057D711D60B930004BBF6D /* BriskRaise.swift */, 112 | 35057D731D60BCF9004BBF6D /* BriskGate.swift */, 113 | 3585ABB21D8624E10063299C /* Swift2 */, 114 | ); 115 | path = Brisk; 116 | sourceTree = ""; 117 | }; 118 | 356AFC6D1D5E618700D18479 /* BriskTests */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 356AFC6E1D5E618700D18479 /* BriskTests.swift */, 122 | 356AFC811D5E8CAC00D18479 /* Spin.swift */, 123 | 356AFC701D5E618700D18479 /* Info.plist */, 124 | ); 125 | path = BriskTests; 126 | sourceTree = ""; 127 | }; 128 | 3585ABB21D8624E10063299C /* Swift2 */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 3585ABB31D8624E10063299C /* BriskGCD_Swift2.swift */, 132 | ); 133 | path = Swift2; 134 | sourceTree = ""; 135 | }; 136 | /* End PBXGroup section */ 137 | 138 | /* Begin PBXHeadersBuildPhase section */ 139 | 356AFC5C1D5E618700D18479 /* Headers */ = { 140 | isa = PBXHeadersBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 356AFC631D5E618700D18479 /* Brisk.h in Headers */, 144 | ); 145 | runOnlyForDeploymentPostprocessing = 0; 146 | }; 147 | /* End PBXHeadersBuildPhase section */ 148 | 149 | /* Begin PBXNativeTarget section */ 150 | 356AFC5E1D5E618700D18479 /* Brisk */ = { 151 | isa = PBXNativeTarget; 152 | buildConfigurationList = 356AFC731D5E618700D18479 /* Build configuration list for PBXNativeTarget "Brisk" */; 153 | buildPhases = ( 154 | 356AFC5A1D5E618700D18479 /* Sources */, 155 | 356AFC5B1D5E618700D18479 /* Frameworks */, 156 | 356AFC5C1D5E618700D18479 /* Headers */, 157 | 356AFC5D1D5E618700D18479 /* Resources */, 158 | ); 159 | buildRules = ( 160 | ); 161 | dependencies = ( 162 | ); 163 | name = Brisk; 164 | productName = Brisk; 165 | productReference = 356AFC5F1D5E618700D18479 /* Brisk.framework */; 166 | productType = "com.apple.product-type.framework"; 167 | }; 168 | 356AFC681D5E618700D18479 /* BriskTests */ = { 169 | isa = PBXNativeTarget; 170 | buildConfigurationList = 356AFC761D5E618700D18479 /* Build configuration list for PBXNativeTarget "BriskTests" */; 171 | buildPhases = ( 172 | 356AFC651D5E618700D18479 /* Sources */, 173 | 356AFC661D5E618700D18479 /* Frameworks */, 174 | 356AFC671D5E618700D18479 /* Resources */, 175 | ); 176 | buildRules = ( 177 | ); 178 | dependencies = ( 179 | 356AFC6C1D5E618700D18479 /* PBXTargetDependency */, 180 | ); 181 | name = BriskTests; 182 | productName = BriskTests; 183 | productReference = 356AFC691D5E618700D18479 /* BriskTests.xctest */; 184 | productType = "com.apple.product-type.bundle.unit-test"; 185 | }; 186 | /* End PBXNativeTarget section */ 187 | 188 | /* Begin PBXProject section */ 189 | 356AFC561D5E618700D18479 /* Project object */ = { 190 | isa = PBXProject; 191 | attributes = { 192 | LastSwiftUpdateCheck = 0730; 193 | LastUpgradeCheck = 0800; 194 | ORGANIZATIONNAME = "Jason Fieldman"; 195 | TargetAttributes = { 196 | 356AFC5E1D5E618700D18479 = { 197 | CreatedOnToolsVersion = 7.3; 198 | LastSwiftMigration = 0800; 199 | }; 200 | 356AFC681D5E618700D18479 = { 201 | CreatedOnToolsVersion = 7.3; 202 | LastSwiftMigration = 0800; 203 | }; 204 | }; 205 | }; 206 | buildConfigurationList = 356AFC591D5E618700D18479 /* Build configuration list for PBXProject "Brisk" */; 207 | compatibilityVersion = "Xcode 3.2"; 208 | developmentRegion = English; 209 | hasScannedForEncodings = 0; 210 | knownRegions = ( 211 | en, 212 | ); 213 | mainGroup = 356AFC551D5E618700D18479; 214 | productRefGroup = 356AFC601D5E618700D18479 /* Products */; 215 | projectDirPath = ""; 216 | projectRoot = ""; 217 | targets = ( 218 | 356AFC5E1D5E618700D18479 /* Brisk */, 219 | 356AFC681D5E618700D18479 /* BriskTests */, 220 | ); 221 | }; 222 | /* End PBXProject section */ 223 | 224 | /* Begin PBXResourcesBuildPhase section */ 225 | 356AFC5D1D5E618700D18479 /* Resources */ = { 226 | isa = PBXResourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | }; 232 | 356AFC671D5E618700D18479 /* Resources */ = { 233 | isa = PBXResourcesBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | /* End PBXResourcesBuildPhase section */ 240 | 241 | /* Begin PBXSourcesBuildPhase section */ 242 | 356AFC5A1D5E618700D18479 /* Sources */ = { 243 | isa = PBXSourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | 356AFC801D5E63D100D18479 /* BriskAsync2Sync.swift in Sources */, 247 | 35057D721D60B930004BBF6D /* BriskRaise.swift in Sources */, 248 | 35057D741D60BCF9004BBF6D /* BriskGate.swift in Sources */, 249 | 3585ABB61D86265E0063299C /* BriskDispatch.swift in Sources */, 250 | 3585ABB41D8624E10063299C /* BriskGCD_Swift2.swift in Sources */, 251 | 356AFC7E1D5E63C900D18479 /* BriskSync2Async.swift in Sources */, 252 | 356AFC7C1D5E623E00D18479 /* BriskLock.swift in Sources */, 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | 356AFC651D5E618700D18479 /* Sources */ = { 257 | isa = PBXSourcesBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | 356AFC821D5E8CAC00D18479 /* Spin.swift in Sources */, 261 | 356AFC6F1D5E618700D18479 /* BriskTests.swift in Sources */, 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | }; 265 | /* End PBXSourcesBuildPhase section */ 266 | 267 | /* Begin PBXTargetDependency section */ 268 | 356AFC6C1D5E618700D18479 /* PBXTargetDependency */ = { 269 | isa = PBXTargetDependency; 270 | target = 356AFC5E1D5E618700D18479 /* Brisk */; 271 | targetProxy = 356AFC6B1D5E618700D18479 /* PBXContainerItemProxy */; 272 | }; 273 | /* End PBXTargetDependency section */ 274 | 275 | /* Begin XCBuildConfiguration section */ 276 | 356AFC711D5E618700D18479 /* Debug */ = { 277 | isa = XCBuildConfiguration; 278 | buildSettings = { 279 | ALWAYS_SEARCH_USER_PATHS = NO; 280 | CLANG_ANALYZER_NONNULL = YES; 281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 282 | CLANG_CXX_LIBRARY = "libc++"; 283 | CLANG_ENABLE_MODULES = YES; 284 | CLANG_ENABLE_OBJC_ARC = YES; 285 | CLANG_WARN_BOOL_CONVERSION = YES; 286 | CLANG_WARN_CONSTANT_CONVERSION = YES; 287 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 288 | CLANG_WARN_EMPTY_BODY = YES; 289 | CLANG_WARN_ENUM_CONVERSION = YES; 290 | CLANG_WARN_INFINITE_RECURSION = YES; 291 | CLANG_WARN_INT_CONVERSION = YES; 292 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 293 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 294 | CLANG_WARN_UNREACHABLE_CODE = YES; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 297 | COPY_PHASE_STRIP = NO; 298 | CURRENT_PROJECT_VERSION = 1; 299 | DEBUG_INFORMATION_FORMAT = dwarf; 300 | ENABLE_STRICT_OBJC_MSGSEND = YES; 301 | ENABLE_TESTABILITY = YES; 302 | GCC_C_LANGUAGE_STANDARD = gnu99; 303 | GCC_DYNAMIC_NO_PIC = NO; 304 | GCC_NO_COMMON_BLOCKS = YES; 305 | GCC_OPTIMIZATION_LEVEL = 0; 306 | GCC_PREPROCESSOR_DEFINITIONS = ( 307 | "DEBUG=1", 308 | "$(inherited)", 309 | ); 310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 312 | GCC_WARN_UNDECLARED_SELECTOR = YES; 313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 314 | GCC_WARN_UNUSED_FUNCTION = YES; 315 | GCC_WARN_UNUSED_VARIABLE = YES; 316 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 317 | MTL_ENABLE_DEBUG_INFO = YES; 318 | ONLY_ACTIVE_ARCH = YES; 319 | SDKROOT = iphoneos; 320 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 321 | SWIFT_VERSION = 3.0; 322 | TARGETED_DEVICE_FAMILY = "1,2"; 323 | VERSIONING_SYSTEM = "apple-generic"; 324 | VERSION_INFO_PREFIX = ""; 325 | }; 326 | name = Debug; 327 | }; 328 | 356AFC721D5E618700D18479 /* Release */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ALWAYS_SEARCH_USER_PATHS = NO; 332 | CLANG_ANALYZER_NONNULL = YES; 333 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 334 | CLANG_CXX_LIBRARY = "libc++"; 335 | CLANG_ENABLE_MODULES = YES; 336 | CLANG_ENABLE_OBJC_ARC = YES; 337 | CLANG_WARN_BOOL_CONVERSION = YES; 338 | CLANG_WARN_CONSTANT_CONVERSION = YES; 339 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 340 | CLANG_WARN_EMPTY_BODY = YES; 341 | CLANG_WARN_ENUM_CONVERSION = YES; 342 | CLANG_WARN_INFINITE_RECURSION = YES; 343 | CLANG_WARN_INT_CONVERSION = YES; 344 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 345 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 346 | CLANG_WARN_UNREACHABLE_CODE = YES; 347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 349 | COPY_PHASE_STRIP = NO; 350 | CURRENT_PROJECT_VERSION = 1; 351 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 352 | ENABLE_NS_ASSERTIONS = NO; 353 | ENABLE_STRICT_OBJC_MSGSEND = YES; 354 | GCC_C_LANGUAGE_STANDARD = gnu99; 355 | GCC_NO_COMMON_BLOCKS = YES; 356 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 357 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 358 | GCC_WARN_UNDECLARED_SELECTOR = YES; 359 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 360 | GCC_WARN_UNUSED_FUNCTION = YES; 361 | GCC_WARN_UNUSED_VARIABLE = YES; 362 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 363 | MTL_ENABLE_DEBUG_INFO = NO; 364 | SDKROOT = iphoneos; 365 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 366 | SWIFT_VERSION = 3.0; 367 | TARGETED_DEVICE_FAMILY = "1,2"; 368 | VALIDATE_PRODUCT = YES; 369 | VERSIONING_SYSTEM = "apple-generic"; 370 | VERSION_INFO_PREFIX = ""; 371 | }; 372 | name = Release; 373 | }; 374 | 356AFC741D5E618700D18479 /* Debug */ = { 375 | isa = XCBuildConfiguration; 376 | buildSettings = { 377 | CLANG_ENABLE_MODULES = YES; 378 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 379 | DEFINES_MODULE = YES; 380 | DYLIB_COMPATIBILITY_VERSION = 1; 381 | DYLIB_CURRENT_VERSION = 1; 382 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 383 | INFOPLIST_FILE = Brisk/Info.plist; 384 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 385 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 386 | PRODUCT_BUNDLE_IDENTIFIER = org.fieldman.Brisk; 387 | PRODUCT_NAME = "$(TARGET_NAME)"; 388 | SKIP_INSTALL = YES; 389 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 390 | SWIFT_VERSION = 3.0; 391 | }; 392 | name = Debug; 393 | }; 394 | 356AFC751D5E618700D18479 /* Release */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | CLANG_ENABLE_MODULES = YES; 398 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 399 | DEFINES_MODULE = YES; 400 | DYLIB_COMPATIBILITY_VERSION = 1; 401 | DYLIB_CURRENT_VERSION = 1; 402 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 403 | INFOPLIST_FILE = Brisk/Info.plist; 404 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 405 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 406 | PRODUCT_BUNDLE_IDENTIFIER = org.fieldman.Brisk; 407 | PRODUCT_NAME = "$(TARGET_NAME)"; 408 | SKIP_INSTALL = YES; 409 | SWIFT_VERSION = 3.0; 410 | }; 411 | name = Release; 412 | }; 413 | 356AFC771D5E618700D18479 /* Debug */ = { 414 | isa = XCBuildConfiguration; 415 | buildSettings = { 416 | INFOPLIST_FILE = BriskTests/Info.plist; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 418 | PRODUCT_BUNDLE_IDENTIFIER = org.fieldman.BriskTests; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SWIFT_VERSION = 3.0; 421 | }; 422 | name = Debug; 423 | }; 424 | 356AFC781D5E618700D18479 /* Release */ = { 425 | isa = XCBuildConfiguration; 426 | buildSettings = { 427 | INFOPLIST_FILE = BriskTests/Info.plist; 428 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 429 | PRODUCT_BUNDLE_IDENTIFIER = org.fieldman.BriskTests; 430 | PRODUCT_NAME = "$(TARGET_NAME)"; 431 | SWIFT_VERSION = 3.0; 432 | }; 433 | name = Release; 434 | }; 435 | /* End XCBuildConfiguration section */ 436 | 437 | /* Begin XCConfigurationList section */ 438 | 356AFC591D5E618700D18479 /* Build configuration list for PBXProject "Brisk" */ = { 439 | isa = XCConfigurationList; 440 | buildConfigurations = ( 441 | 356AFC711D5E618700D18479 /* Debug */, 442 | 356AFC721D5E618700D18479 /* Release */, 443 | ); 444 | defaultConfigurationIsVisible = 0; 445 | defaultConfigurationName = Release; 446 | }; 447 | 356AFC731D5E618700D18479 /* Build configuration list for PBXNativeTarget "Brisk" */ = { 448 | isa = XCConfigurationList; 449 | buildConfigurations = ( 450 | 356AFC741D5E618700D18479 /* Debug */, 451 | 356AFC751D5E618700D18479 /* Release */, 452 | ); 453 | defaultConfigurationIsVisible = 0; 454 | defaultConfigurationName = Release; 455 | }; 456 | 356AFC761D5E618700D18479 /* Build configuration list for PBXNativeTarget "BriskTests" */ = { 457 | isa = XCConfigurationList; 458 | buildConfigurations = ( 459 | 356AFC771D5E618700D18479 /* Debug */, 460 | 356AFC781D5E618700D18479 /* Release */, 461 | ); 462 | defaultConfigurationIsVisible = 0; 463 | defaultConfigurationName = Release; 464 | }; 465 | /* End XCConfigurationList section */ 466 | }; 467 | rootObject = 356AFC561D5E618700D18479 /* Project object */; 468 | } 469 | -------------------------------------------------------------------------------- /Brisk.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Brisk.xcodeproj/xcshareddata/xcschemes/Brisk.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Brisk.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Brisk/Brisk.h: -------------------------------------------------------------------------------- 1 | // 2 | // Brisk.h 3 | // Brisk 4 | // 5 | // Copyright (c) 2016-Present Jason Fieldman - https://github.com/jmfieldman/Brisk 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | #import 27 | 28 | //! Project version number for Brisk. 29 | FOUNDATION_EXPORT double BriskVersionNumber; 30 | 31 | //! Project version string for Brisk. 32 | FOUNDATION_EXPORT const unsigned char BriskVersionString[]; 33 | 34 | // In this header, you should import all the public headers of your framework using statements like #import 35 | 36 | 37 | -------------------------------------------------------------------------------- /Brisk/BriskAsync2Sync.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BriskAsync2Sync.swift 3 | // Brisk 4 | // 5 | // Copyright (c) 2016-Present Jason Fieldman - https://github.com/jmfieldman/Brisk 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | // let .. = <<-{ func(i, handler: $0) } call func on current queue 29 | // let .. = <<~{ func(i, handler: $0) } call func on bg queue 30 | // let .. = <<+{ func(i, handler: $0) } call func on main queue 31 | // let .. = <<~myQueue ~~~ { func(i, handler: $0) } call func on specified queue 32 | 33 | prefix operator <<- 34 | prefix operator <<~ 35 | prefix operator <<+ 36 | 37 | /* -- old precendence = 95 -- */ 38 | precedencegroup QueueRedirectionPrecendence { 39 | higherThan: AssignmentPrecedence 40 | lowerThan: TernaryPrecedence 41 | } 42 | 43 | infix operator ~~~ : QueueRedirectionPrecendence 44 | 45 | 46 | /// Returns the queue this prefix is applied to. This is used to prettify the 47 | /// syntax: 48 | /// 49 | /// - e.g.: let x = <<~myQueue ~~~ { func(i, handler: $0) } 50 | @inline(__always) public prefix func <<~(q: DispatchQueue) -> DispatchQueue { 51 | return q 52 | } 53 | 54 | /// Executes the attached operation synchronously on the current queue 55 | /// and waits for it to complete. Returns the result of the callback handler that 56 | /// $0 was attached to. 57 | /// 58 | /// - e.g.: ```let x = <<-{ func(i, callback: $0)``` } 59 | public prefix func <<-(operation: (_ callbackHandler: @escaping (_ param: O) -> ()) -> ()) -> O { 60 | 61 | // Our gating mechanism 62 | let gate = BriskGate() 63 | 64 | // This value will eventually hold the response from the async function 65 | var handledResponse: O? 66 | 67 | let theHandler: (_ p: O) -> () = { responseFromCallback in 68 | handledResponse = responseFromCallback 69 | gate.signal() 70 | } 71 | 72 | operation(theHandler) 73 | gate.wait() 74 | 75 | // It's ok to use ! -- theoretically we are garanteed that handledResponse 76 | // has been set by this point (inside theHandler) 77 | return handledResponse! 78 | } 79 | 80 | /// This protects against optional functions being placed inside a sync-to-async block. 81 | public prefix func <<-(operation: @escaping (_ callbackHandler: @escaping (_ param: O) -> ()) -> Void?) -> O { 82 | fatalError("You cannot put an optional call inside of a brisk sync-to-async block, since it must be guaranteed to call and return.") 83 | } 84 | 85 | 86 | /// Using a generic handler for the non-noescape versions 87 | @inline(__always) private func processAsync2Sync(_ operation: @escaping (_ callbackHandler: @escaping (_ param: O) -> ()) -> (), 88 | queue: DispatchQueue) -> O { 89 | 90 | // Our gating mechanism 91 | let gate = BriskGate() 92 | 93 | // This value will eventually hold the response from the async function 94 | var handledResponse: O? 95 | 96 | let theHandler: (_ p: O) -> () = { responseFromCallback in 97 | handledResponse = responseFromCallback 98 | gate.signal() 99 | } 100 | 101 | queue.async { 102 | operation(theHandler) 103 | } 104 | 105 | gate.wait() 106 | 107 | // It's ok to use ! -- theoretically we are garanteed that handledResponse 108 | // has been set by this point (inside theHandler) 109 | return handledResponse! 110 | } 111 | 112 | 113 | /// Executes the attached operation on the general concurrent background queue 114 | /// and waits for it to complete. Returns the result of the callback handler that 115 | /// $0 was attached to. 116 | /// 117 | /// - e.g.: ```let x = <<~{ func(i, callback: $0)``` } 118 | public prefix func <<~(operation: @escaping (_ callbackHandler: @escaping (_ param: O) -> ()) -> ()) -> O { 119 | return processAsync2Sync(operation, queue: backgroundQueue) 120 | } 121 | 122 | /// This protects against optional functions being placed inside a sync-to-async block. 123 | public prefix func <<~(operation: @escaping (_ callbackHandler: @escaping (_ param: O) -> ()) -> Void?) -> O { 124 | fatalError("You cannot put an optional call inside of a brisk sync-to-async block, since it must be guaranteed to call and return.") 125 | } 126 | 127 | /// Executes the attached operation on the main queue 128 | /// and waits for it to complete. Returns the result of the callback handler that 129 | /// $0 was attached to. 130 | /// 131 | /// - e.g.: ```let x = <<+{ func(i, callback: $0)``` } 132 | public prefix func <<+(operation: @escaping (_ callbackHandler: @escaping (_ param: O) -> ()) -> ()) -> O { 133 | return processAsync2Sync(operation, queue: mainQueue) 134 | } 135 | 136 | /// This protects against optional functions being placed inside a sync-to-async block. 137 | public prefix func <<+(operation: @escaping (_ callbackHandler: @escaping (_ param: O) -> ()) -> Void?) -> O { 138 | fatalError("You cannot put an optional call inside of a brisk sync-to-async block, since it must be guaranteed to call and return.") 139 | } 140 | 141 | 142 | /// Executes the attached operation on the supplied queue from the left side 143 | /// and waits for it to complete. Returns the result of the callback handler that 144 | /// $0 was attached to. 145 | /// 146 | /// - e.g.: ```let x = <<~myQueue ~~~ { func(i, callback: $0)``` } 147 | public func ~~~(lhs: DispatchQueue, rhs: @escaping (_ callbackHandler: @escaping (_ param: O) -> ()) -> ()) -> O { 148 | return processAsync2Sync(rhs, queue: lhs) 149 | } 150 | 151 | /// This protects against optional functions being placed inside a sync-to-async block. 152 | public func ~~~(lhs: DispatchQueue, rhs: @escaping (_ callbackHandler: @escaping (_ param: O) -> ()) -> Void?) -> O { 153 | fatalError("You cannot put an optional call inside of a brisk sync-to-async block, since it must be guaranteed to call and return.") 154 | } 155 | 156 | 157 | -------------------------------------------------------------------------------- /Brisk/BriskDispatch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BriskDispatch.swift 3 | // Brisk 4 | // 5 | // Copyright (c) 2016-Present Jason Fieldman - https://github.com/jmfieldman/Brisk 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | 29 | // These are defined in the main library so that they can be accessed in the unit tests 30 | internal let mainQueue = DispatchQueue.main 31 | internal let backgroundQueue = DispatchQueue.global(qos: .default) 32 | 33 | 34 | public protocol QuickDispatchTimeInterval { 35 | /// Returns the receiver as a DispatchTimeInterval 36 | func asDispatchTimeInterval() -> DispatchTimeInterval 37 | } 38 | 39 | 40 | extension Double: QuickDispatchTimeInterval { 41 | public func asDispatchTimeInterval() -> DispatchTimeInterval { 42 | return DispatchTimeInterval.nanoseconds(Int(self * Double(NSEC_PER_SEC))) 43 | } 44 | } 45 | 46 | extension Float: QuickDispatchTimeInterval { 47 | public func asDispatchTimeInterval() -> DispatchTimeInterval { 48 | return DispatchTimeInterval.nanoseconds(Int(Double(self) * Double(NSEC_PER_SEC))) 49 | } 50 | } 51 | 52 | extension Int: QuickDispatchTimeInterval { 53 | public func asDispatchTimeInterval() -> DispatchTimeInterval { 54 | return DispatchTimeInterval.seconds(self) 55 | } 56 | } 57 | 58 | 59 | // Data Structures for DispatchQueue.once 60 | private var operationTimerForId: [String : DispatchSourceTimer] = [:] 61 | private var operationTimerLock: NSRecursiveLock = NSRecursiveLock() 62 | 63 | 64 | public extension DispatchQueue { 65 | 66 | /// Dispatch a block asynchronously on the receiving queue after a period of time. This method 67 | /// takes parameters that allow more straightforward and readable code. 68 | /// The DispatchSourceTimer is returned for reference, but can be ignored. 69 | /// 70 | /// - parameter after: The number of seconds before the block is triggered. 71 | /// - parameter leeway: The leeway, in seconds, for the timer. This is optional and 72 | /// will use the default if unspecified. 73 | /// - parameter qos: The qos to use for the executing block. 74 | /// - parameter flags: The DispatchWorkItemFlags for the executing block. 75 | /// - parameter execute: The block to run after the specified time on the receiving queue. 76 | @discardableResult public func async(after seconds: Double, 77 | leeway: QuickDispatchTimeInterval? = nil, 78 | qos: DispatchQoS = .default, 79 | flags: DispatchWorkItemFlags = [], 80 | execute block: @escaping () -> Void) -> DispatchSourceTimer { 81 | 82 | let timer = DispatchSource.makeTimerSource(flags: [], queue: self) 83 | timer.setEventHandler(qos: qos, flags: flags) { 84 | timer.setEventHandler(handler: nil) 85 | block() 86 | } 87 | 88 | timer.setCancelHandler { [weak timer] in 89 | timer?.setEventHandler(handler: nil) 90 | } 91 | 92 | if let leeway = leeway { 93 | timer.scheduleOneshot(deadline: DispatchTime.now() + seconds, leeway: leeway.asDispatchTimeInterval()) 94 | } else { 95 | timer.scheduleOneshot(deadline: DispatchTime.now() + seconds) 96 | } 97 | 98 | timer.resume() 99 | return timer 100 | } 101 | 102 | 103 | 104 | /// Dispatch a block asynchronously on the receiving queue after a period of time. This method 105 | /// takes parameters that allow more straightforward and readable code. 106 | /// The DispatchSourceTimer is returned for reference, but can be ignored. 107 | /// 108 | /// - parameter after: The number of seconds before the block is triggered. 109 | /// - parameter leeway: The leeway, in seconds, for the timer. This is optional and 110 | /// will use the default if unspecified. 111 | /// - parameter execute: The item to execute after the specified time on the receiving queue. 112 | @discardableResult public func async(after seconds: Double, 113 | leeway: QuickDispatchTimeInterval? = nil, 114 | execute item: DispatchWorkItem) -> DispatchSourceTimer { 115 | 116 | let timer = DispatchSource.makeTimerSource(flags: [], queue: self) 117 | timer.setEventHandler { 118 | timer.setEventHandler(handler: nil) 119 | item.perform() 120 | } 121 | 122 | timer.setCancelHandler { [weak timer] in 123 | timer?.setEventHandler(handler: nil) 124 | } 125 | 126 | if let leeway = leeway { 127 | timer.scheduleOneshot(deadline: DispatchTime.now() + seconds, leeway: leeway.asDispatchTimeInterval()) 128 | } else { 129 | timer.scheduleOneshot(deadline: DispatchTime.now() + seconds) 130 | } 131 | 132 | timer.resume() 133 | return timer 134 | } 135 | 136 | 137 | 138 | /// Dispatch a block asynchronously on the receiving queue at a specific date. This method 139 | /// takes parameters that allow more straightforward and readable code. 140 | /// The DispatchSourceTimer is returned for reference, but can be ignored. 141 | /// 142 | /// - parameter at: The date to trigger the block. If the date is before the current time 143 | /// it is triggered immediately. 144 | /// - parameter leeway: The leeway, in seconds, for the timer. This is optional and 145 | /// will use the default if unspecified. 146 | /// - parameter qos: The qos to use for the executing block. 147 | /// - parameter flags: The DispatchWorkItemFlags for the executing block. 148 | /// - parameter execute: The block to run after the specified time on the receiving queue. 149 | @discardableResult public func async(at date: NSDate, 150 | leeway: QuickDispatchTimeInterval? = nil, 151 | qos: DispatchQoS = .default, 152 | flags: DispatchWorkItemFlags = [], 153 | execute block: @escaping () -> Void) -> DispatchSourceTimer { 154 | 155 | let timer = DispatchSource.makeTimerSource(flags: [], queue: self) 156 | timer.setEventHandler(qos: qos, flags: flags) { 157 | timer.setEventHandler(handler: nil) 158 | block() 159 | } 160 | 161 | timer.setCancelHandler { [weak timer] in 162 | timer?.setEventHandler(handler: nil) 163 | } 164 | 165 | let timeInterval = max(date.timeIntervalSinceNow, 0) 166 | 167 | if let leeway = leeway { 168 | timer.scheduleOneshot(wallDeadline: DispatchWallTime.now() + timeInterval, leeway: leeway.asDispatchTimeInterval()) 169 | } else { 170 | timer.scheduleOneshot(wallDeadline: DispatchWallTime.now() + timeInterval) 171 | } 172 | 173 | timer.resume() 174 | return timer 175 | } 176 | 177 | 178 | 179 | /// Dispatch a block asynchronously on the receiving queue at a specific date. This method 180 | /// takes parameters that allow more straightforward and readable code. 181 | /// The DispatchSourceTimer is returned for reference, but can be ignored. 182 | /// 183 | /// - parameter at: The date to trigger the block. If the date is before the current time 184 | /// it is triggered immediately. 185 | /// - parameter leeway: The leeway, in seconds, for the timer. This is optional and 186 | /// will use the default if unspecified. 187 | /// - parameter execute: The item to execute after the specified time on the receiving queue. 188 | @discardableResult public func async(at date: NSDate, 189 | leeway: QuickDispatchTimeInterval? = nil, 190 | execute item: DispatchWorkItem) -> DispatchSourceTimer { 191 | 192 | let timer = DispatchSource.makeTimerSource(flags: [], queue: self) 193 | timer.setEventHandler { 194 | timer.setEventHandler(handler: nil) 195 | item.perform() 196 | } 197 | 198 | timer.setCancelHandler { [weak timer] in 199 | timer?.setEventHandler(handler: nil) 200 | } 201 | 202 | let deadline = DispatchWallTime.now() + max(date.timeIntervalSinceNow, 0) 203 | 204 | if let leeway = leeway { 205 | timer.scheduleOneshot(wallDeadline: deadline, leeway: leeway.asDispatchTimeInterval()) 206 | } else { 207 | timer.scheduleOneshot(wallDeadline: deadline) 208 | } 209 | 210 | timer.resume() 211 | return timer 212 | } 213 | 214 | 215 | 216 | /// Dispatch a block asynchronously on the receiving queue at a specified rate. 217 | /// The DispatchSourceTimer is returned for reference, but can be ignored. This 218 | /// version of the function takes a block with no arguments. It is considered 219 | /// a fatal error to pass both startingIn and startingAt parameters. If neither 220 | /// startingIn or startingAt are specified, the repetition will start after 221 | /// one interval. 222 | /// 223 | /// - parameter every: The interval to execute the block. 224 | /// - parameter startingIn: The number of seconds to begin the repetition. 225 | /// - parameter startingAt: The date at which to start the repetition. If the date is 226 | /// in the past it will start immediately. 227 | /// - parameter leeway: The leeway, in seconds, for the timer. This is optional and 228 | /// will use the default if unspecified. 229 | /// - parameter qos: The qos to use for the executing block. 230 | /// - parameter flags: The DispatchWorkItemFlags for the executing block. 231 | /// - parameter execute: The block to run after the specified time on the receiving queue. 232 | @discardableResult public func async(every interval: Double, 233 | startingIn: Double? = nil, 234 | startingAt: NSDate? = nil, 235 | leeway: QuickDispatchTimeInterval? = nil, 236 | qos: DispatchQoS = .default, 237 | flags: DispatchWorkItemFlags = [], 238 | execute block: @escaping () -> Void) -> DispatchSourceTimer { 239 | 240 | let timer = DispatchSource.makeTimerSource(flags: [], queue: self) 241 | timer.setEventHandler(qos: qos, flags: flags) { 242 | _ = timer.isCancelled 243 | block() 244 | } 245 | 246 | timer.setCancelHandler { [weak timer] in 247 | timer?.setEventHandler(handler: nil) 248 | } 249 | 250 | guard startingIn == nil || startingAt == nil else { 251 | Brisk.brisk_raise("It is considered a fatal error to pass both startingIn and startingAt") 252 | } 253 | 254 | if let startingAt = startingAt { 255 | let deadline = DispatchWallTime.now() + max(startingAt.timeIntervalSinceNow, 0) 256 | 257 | if let leeway = leeway { 258 | timer.scheduleRepeating(wallDeadline: deadline, interval: interval, leeway: leeway.asDispatchTimeInterval()) 259 | } else { 260 | timer.scheduleRepeating(wallDeadline: deadline, interval: interval) 261 | } 262 | } else { 263 | let deadline = DispatchTime.now() + (startingIn ?? interval) 264 | 265 | if let leeway = leeway { 266 | timer.scheduleRepeating(deadline: deadline, interval: interval, leeway: leeway.asDispatchTimeInterval()) 267 | } else { 268 | timer.scheduleRepeating(deadline: deadline, interval: interval) 269 | } 270 | } 271 | 272 | timer.resume() 273 | return timer 274 | } 275 | 276 | 277 | 278 | /// Dispatch a block asynchronously on the receiving queue at a specified rate. 279 | /// The DispatchSourceTimer is returned for reference, but can be ignored. It is considered 280 | /// a fatal error to pass both startingIn and startingAt parameters. If neither 281 | /// startingIn or startingAt are specified, the repetition will start after 282 | /// one interval. 283 | /// 284 | /// This version of the function takes a block with the repeating timer as an argument. 285 | /// You can use this parameter to cancel the repetition from inside the block. 286 | /// 287 | /// - parameter every: The interval to execute the block. 288 | /// - parameter startingIn: The number of seconds to begin the repetition. 289 | /// - parameter startingAt: The date at which to start the repetition. If the date is 290 | /// in the past it will start immediately. 291 | /// - parameter leeway: The leeway, in seconds, for the timer. This is optional and 292 | /// will use the default if unspecified. 293 | /// - parameter qos: The qos to use for the executing block. 294 | /// - parameter flags: The DispatchWorkItemFlags for the executing block. 295 | /// - parameter execute: The block to run after the specified time on the receiving queue. 296 | @discardableResult public func async(every interval: Double, 297 | startingIn: Double? = nil, 298 | startingAt: NSDate? = nil, 299 | leeway: QuickDispatchTimeInterval? = nil, 300 | qos: DispatchQoS = .default, 301 | flags: DispatchWorkItemFlags = [], 302 | execute block: @escaping (_ timer: DispatchSourceTimer) -> Void) -> DispatchSourceTimer { 303 | 304 | let timer = DispatchSource.makeTimerSource(flags: [], queue: self) 305 | timer.setEventHandler(qos: qos, flags: flags) { 306 | block(timer) 307 | } 308 | 309 | timer.setCancelHandler { [weak timer] in 310 | timer?.setEventHandler(handler: nil) 311 | } 312 | 313 | guard startingIn == nil || startingAt == nil else { 314 | Brisk.brisk_raise("It is considered a fatal error to pass both startingIn and startingAt") 315 | } 316 | 317 | if let startingAt = startingAt { 318 | let deadline = DispatchWallTime.now() + max(startingAt.timeIntervalSinceNow, 0) 319 | 320 | if let leeway = leeway { 321 | timer.scheduleRepeating(wallDeadline: deadline, interval: interval, leeway: leeway.asDispatchTimeInterval()) 322 | } else { 323 | timer.scheduleRepeating(wallDeadline: deadline, interval: interval) 324 | } 325 | } else { 326 | let deadline = DispatchTime.now() + (startingIn ?? interval) 327 | 328 | if let leeway = leeway { 329 | timer.scheduleRepeating(deadline: deadline, interval: interval, leeway: leeway.asDispatchTimeInterval()) 330 | } else { 331 | timer.scheduleRepeating(deadline: deadline, interval: interval) 332 | } 333 | } 334 | 335 | timer.resume() 336 | return timer 337 | } 338 | 339 | 340 | 341 | /// Dispatch a block asynchronously on the receiving queue at a specified rate. 342 | /// The DispatchSourceTimer is returned for reference, but can be ignored. This 343 | /// version of the function takes a block with no arguments. It is considered 344 | /// a fatal error to pass both startingIn and startingAt parameters. If neither 345 | /// startingIn or startingAt are specified, the repetition will start after 346 | /// one interval. 347 | /// 348 | /// - parameter every: The interval to execute the block. 349 | /// - parameter startingIn: The number of seconds to begin the repetition. 350 | /// - parameter startingAt: The date at which to start the repetition. If the date is 351 | /// in the past it will start immediately. 352 | /// - parameter leeway: The leeway, in seconds, for the timer. This is optional and 353 | /// will use the default if unspecified. 354 | /// - parameter qos: The qos to use for the executing block. 355 | /// - parameter flags: The DispatchWorkItemFlags for the executing block. 356 | /// - parameter execute: The block to run after the specified time on the receiving queue. 357 | @discardableResult public func async(every interval: Double, 358 | startingIn: Double? = nil, 359 | startingAt: NSDate? = nil, 360 | leeway: QuickDispatchTimeInterval? = nil, 361 | execute item: DispatchWorkItem) -> DispatchSourceTimer { 362 | 363 | let timer = DispatchSource.makeTimerSource(flags: [], queue: self) 364 | timer.setEventHandler { 365 | _ = timer.isCancelled 366 | item.perform() 367 | } 368 | 369 | timer.setCancelHandler { [weak timer] in 370 | timer?.setEventHandler(handler: nil) 371 | } 372 | 373 | guard startingIn == nil || startingAt == nil else { 374 | Brisk.brisk_raise("It is considered a fatal error to pass both startingIn and startingAt") 375 | } 376 | 377 | if let startingAt = startingAt { 378 | let deadline = DispatchWallTime.now() + max(startingAt.timeIntervalSinceNow, 0) 379 | 380 | if let leeway = leeway { 381 | timer.scheduleRepeating(wallDeadline: deadline, interval: interval, leeway: leeway.asDispatchTimeInterval()) 382 | } else { 383 | timer.scheduleRepeating(wallDeadline: deadline, interval: interval) 384 | } 385 | } else { 386 | let deadline = DispatchTime.now() + (startingIn ?? interval) 387 | 388 | if let leeway = leeway { 389 | timer.scheduleRepeating(deadline: deadline, interval: interval, leeway: leeway.asDispatchTimeInterval()) 390 | } else { 391 | timer.scheduleRepeating(deadline: deadline, interval: interval) 392 | } 393 | } 394 | 395 | timer.resume() 396 | return timer 397 | } 398 | 399 | 400 | 401 | /// Dispatch a block asynchronously on the receiving queue once per operationId, 402 | /// no matter how many times this request is made. This is convenient way to 403 | /// coalesce many disparate triggers into a single finalizing block (e.g. saving 404 | /// a database to disk after many simultaneous async updates) 405 | /// 406 | /// Each time the function is called with an operationId that corresponds to a 407 | /// timer that hasn't triggered, the previous timer is canceled in favor of the 408 | /// new one. 409 | /// 410 | /// When a timer eventually triggers for an operationId, that operationId is cleared 411 | /// and is no longer associated with a timer. 412 | /// 413 | /// It is considered a fatal error to pass both after and at parameters. 414 | /// If neither after or at is specified, the operation is scheduled to run asap. 415 | /// 416 | /// - parameter operationId: The ID of the operation to execute. 417 | /// - parameter leeway: The leeway, in seconds, for the timer. This is optional and 418 | /// will use the default if unspecified. 419 | /// - parameter startingIn: The number of seconds to begin the repetition. 420 | /// - parameter startingAt: The date at which to start the repetition. If the date is 421 | /// in the past it will start immediately. 422 | /// - parameter qos: The qos to use for the executing block. 423 | /// - parameter flags: The DispatchWorkItemFlags for the executing block. 424 | /// - parameter execute: The block to run after the specified time on the receiving queue. 425 | @discardableResult public func once(operationId: String, 426 | after interval: Double? = nil, 427 | at date: NSDate? = nil, 428 | leeway: QuickDispatchTimeInterval? = nil, 429 | qos: DispatchQoS = .default, 430 | flags: DispatchWorkItemFlags = [], 431 | execute block: @escaping () -> Void) -> DispatchSourceTimer { 432 | 433 | let timer = DispatchSource.makeTimerSource(flags: [], queue: self) 434 | timer.setEventHandler(qos: qos, flags: flags) { 435 | operationTimerLock.lock() 436 | if let curTimer = operationTimerForId[operationId], curTimer === timer { 437 | operationTimerForId[operationId] = nil 438 | } 439 | operationTimerLock.unlock() 440 | block() 441 | } 442 | 443 | guard interval == nil || date == nil else { 444 | Brisk.brisk_raise("It is considered a fatal error to pass both 'after' and 'at'") 445 | } 446 | 447 | if let date = date { 448 | let deadline = DispatchWallTime.now() + max(date.timeIntervalSinceNow, 0) 449 | 450 | if let leeway = leeway { 451 | timer.scheduleOneshot(wallDeadline: deadline, leeway: leeway.asDispatchTimeInterval()) 452 | } else { 453 | timer.scheduleOneshot(wallDeadline: deadline) 454 | } 455 | } else { 456 | let deadline = DispatchTime.now() + (interval ?? 0) 457 | 458 | if let leeway = leeway { 459 | timer.scheduleOneshot(deadline: deadline, leeway: leeway.asDispatchTimeInterval()) 460 | } else { 461 | timer.scheduleOneshot(deadline: deadline) 462 | } 463 | } 464 | 465 | operationTimerLock.lock() 466 | if let existingTimer = operationTimerForId[operationId], !existingTimer.isCancelled { 467 | existingTimer.cancel() 468 | } 469 | operationTimerForId[operationId] = timer 470 | timer.resume() 471 | operationTimerLock.unlock() 472 | 473 | return timer 474 | } 475 | } 476 | 477 | 478 | -------------------------------------------------------------------------------- /Brisk/BriskGate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BriskGate.swift 3 | // Brisk 4 | // 5 | // Copyright (c) 2016-Present Jason Fieldman - https://github.com/jmfieldman/Brisk 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | 29 | // BriskGate is an intelligent semaphore mechanism that can 30 | // perform waits from the main thread without freezing the 31 | // application (though it does so inefficiently). 32 | 33 | internal class BriskGate { 34 | 35 | var isMain: Bool 36 | var group: DispatchGroup? = nil 37 | var finished: Bool = false 38 | 39 | init() { 40 | isMain = Thread.current.isMainThread 41 | if !isMain { 42 | group = DispatchGroup() 43 | group!.enter() 44 | } 45 | } 46 | 47 | func signal() { 48 | finished = true 49 | if !isMain { 50 | group!.leave() 51 | } 52 | } 53 | 54 | func wait() { 55 | if isMain { 56 | while !finished { 57 | RunLoop.current.run(mode: RunLoopMode.defaultRunLoopMode, before: Date(timeIntervalSinceNow: 0.1)) 58 | } 59 | } else { 60 | _ = group!.wait(timeout: DispatchTime.distantFuture) 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Brisk/BriskLock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BriskLock.swift 3 | // Brisk 4 | // 5 | // Copyright (c) 2016-Present Jason Fieldman - https://github.com/jmfieldman/Brisk 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | 29 | 30 | 31 | // MARK: - Concurrency Synchronization 32 | 33 | 34 | /// Perform a block synchronized on a NSLocking object. 35 | /// 36 | /// - parameter lock: The NSLocking object to use. 37 | /// - parameter block: The block to perform. 38 | @inline(__always) public func synchronized(_ lock: NSLocking, block: () -> ()) { 39 | lock.lock() 40 | block() 41 | lock.unlock() 42 | } 43 | 44 | 45 | /// Perform a block synchronized on a NSLocking object. The block returns a value. 46 | /// 47 | /// - parameter lock: The NSLocking object to use. 48 | /// - parameter block: The block to perform. 49 | @inline(__always) public func synchronized(_ lock: NSLocking, block: () -> T) -> T { 50 | lock.lock() 51 | let r = block() 52 | lock.unlock() 53 | return r 54 | } 55 | 56 | 57 | /// Perform a block synchronized on a NSLocking object. The block returns an optional value. 58 | /// 59 | /// - parameter lock: The NSLocking object to use. 60 | /// - parameter block: The block to perform. 61 | @inline(__always) public func synchronized(_ lock: NSLocking, block: () -> T?) -> T? { 62 | lock.lock() 63 | let r = block() 64 | lock.unlock() 65 | return r 66 | } 67 | 68 | 69 | // For the universal synchronization 70 | private var universalLock: NSRecursiveLock = NSRecursiveLock() 71 | 72 | 73 | /// Perform a block synchronized on the global static spin lock. 74 | /// 75 | /// - parameter block: The block to perform. 76 | public func synchronized(_ block: () -> ()) { 77 | universalLock.lock() 78 | block() 79 | universalLock.unlock() 80 | } 81 | 82 | 83 | /// Perform a block synchronized on the global static spin lock. The block returns a value. 84 | /// 85 | /// - parameter block: The block to perform. 86 | public func synchronized(_ block: () -> T) -> T { 87 | universalLock.lock() 88 | let r = block() 89 | universalLock.unlock() 90 | return r 91 | } 92 | 93 | 94 | /// Perform a block synchronized on the global static spin lock. The block returns an optional value. 95 | /// 96 | /// - parameter block: The block to perform. 97 | public func synchronized(_ block: () -> T?) -> T? { 98 | universalLock.lock() 99 | let r = block() 100 | universalLock.unlock() 101 | return r 102 | } 103 | 104 | 105 | -------------------------------------------------------------------------------- /Brisk/BriskRaise.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BriskRaise.swift 3 | // Brisk 4 | // 5 | // Copyright (c) 2016-Present Jason Fieldman - https://github.com/jmfieldman/Brisk 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | internal func brisk_raise(_ reason: String) -> Never { 29 | NSException(name: NSExceptionName(rawValue: "Brisk Exception"), reason: reason, userInfo: nil).raise() 30 | fatalError("Brisk usage exceptions are fatal errors.") 31 | } 32 | -------------------------------------------------------------------------------- /Brisk/BriskSync2Async.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BriskSync2Async.swift 3 | // Brisk 4 | // 5 | // Copyright (c) 2016-Present Jason Fieldman - https://github.com/jmfieldman/Brisk 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | 29 | 30 | 31 | // MARK: - Routing Object 32 | 33 | 34 | public class __BriskRoutingObj { 35 | 36 | // ---------- Properties ---------- 37 | 38 | // The dispatch group used in various synchronizing routines 39 | fileprivate let dispatchGroup = DispatchGroup() 40 | 41 | // This is the actual function that we are routing 42 | fileprivate let wrappedFunction: (I) -> O 43 | 44 | // If we are routing the response, this catches the value 45 | fileprivate var response: O? = nil 46 | 47 | // This is the queue that the function will be executed on 48 | fileprivate var opQueue: DispatchQueue? = nil 49 | 50 | // This is the queue that the handler will execute on (if needed) 51 | fileprivate var handlerQueue: DispatchQueue? = nil 52 | 53 | // The lock used to synchronize various accesses 54 | fileprivate var lock: NSLock = NSLock() 55 | 56 | // Is this routing object available to perform its operation? 57 | // The routing objects may only perform their operations once, they should 58 | // NOT be retained and called a second time. 59 | fileprivate var operated: Bool = false 60 | 61 | 62 | 63 | // ---------- Init ------------ 64 | 65 | // Instantiate ourselves with a function 66 | public init(function: @escaping (I) -> O, defaultOpQueue: DispatchQueue? = nil) { 67 | wrappedFunction = function 68 | opQueue = defaultOpQueue 69 | } 70 | 71 | 72 | 73 | // ---------- Queue Adjustments ------------- 74 | 75 | /// Returns the current routing object set to execute its 76 | /// function on the main queue 77 | public var main: __BriskRoutingObj { 78 | self.opQueue = mainQueue 79 | return self 80 | } 81 | 82 | /// Returns the current routing object set to execute its 83 | /// function on the generic concurrent background queue 84 | public var background: __BriskRoutingObj { 85 | self.opQueue = backgroundQueue 86 | return self 87 | } 88 | 89 | /// Returns the current routing object set to execute its 90 | /// function on the specified queue 91 | public func on(_ queue: DispatchQueue) -> __BriskRoutingObj { 92 | self.opQueue = queue 93 | return self 94 | } 95 | 96 | 97 | 98 | // ----------- Execution ------------- 99 | 100 | 101 | /// The sync property returns a function with the same input/output 102 | /// parameters of the original function. It is executed asynchronously 103 | /// on the specified queue. The calling thread is blocked until the 104 | /// called function completes. Not compatible with functions that throw 105 | /// errors. 106 | public var sync: (I) -> O { 107 | guard let opQ = opQueue else { 108 | brisk_raise("You must specify a queue for this function to operate on") 109 | } 110 | 111 | // If we're synchronous on the main thread already, just run the function immediately. 112 | if opQ === mainQueue && Thread.current.isMainThread { 113 | return { i in 114 | return self.wrappedFunction(i) 115 | } 116 | } 117 | 118 | guard !synchronized(lock, block: { let o = self.operated; self.operated = false; return o }) else { 119 | brisk_raise("You may not retain or use this routing object in a way that it can be executed more than once.") 120 | } 121 | 122 | return { i in 123 | self.dispatchGroup.enter() 124 | opQ.async { 125 | self.response = self.wrappedFunction(i) 126 | self.dispatchGroup.leave() 127 | } 128 | _ = self.dispatchGroup.wait(timeout: DispatchTime.distantFuture) 129 | return self.response! // Will be set in the async call above 130 | } 131 | } 132 | 133 | 134 | /// Processes the async handler applied to this routing object. 135 | fileprivate func processAsyncHandler(_ handler: @escaping (O) -> Void) { 136 | guard let hQ = self.handlerQueue else { 137 | brisk_raise("The handler queue was not specified before routing the async response") 138 | } 139 | 140 | backgroundQueue.async { 141 | _ = self.dispatchGroup.wait(timeout: DispatchTime.distantFuture) 142 | hQ.async { 143 | handler(self.response!) // Will be set in the async call before wait completes 144 | } 145 | } 146 | } 147 | } 148 | 149 | 150 | public final class __BriskRoutingObjVoid: __BriskRoutingObj { 151 | 152 | // Instantiate ourselves with a function 153 | override public init(function: @escaping (I) -> Void, defaultOpQueue: DispatchQueue? = nil) { 154 | super.init(function: function, defaultOpQueue: defaultOpQueue) 155 | } 156 | 157 | /// The async property returns a function that takes the parameters 158 | /// from the original function, executes the function with those 159 | /// parameters in the desired queue, then returns Void back to the 160 | /// originating thread. (for functions that originally return Void) 161 | /// 162 | /// When calling the wrapped function, the internal dispatchQueue 163 | /// is not exited until the wrapped function completes. This 164 | /// internal dispatchQueue can be waited on to funnel the response 165 | /// of the wrapped function to yet another async dispatch. 166 | public var async: (I) -> Void { 167 | guard let opQ = opQueue else { 168 | brisk_raise("You must specify a queue for this function to operate on") 169 | } 170 | 171 | guard !synchronized(lock, block: { let o = self.operated; self.operated = false; return o }) else { 172 | brisk_raise("You may not retain or use this routing object in a way that it can be executed more than once.") 173 | } 174 | 175 | return { i in 176 | self.dispatchGroup.enter() 177 | opQ.async { 178 | self.response = self.wrappedFunction(i) 179 | self.dispatchGroup.leave() 180 | } 181 | } 182 | } 183 | } 184 | 185 | public final class __BriskRoutingObjNonVoid: __BriskRoutingObj { 186 | 187 | // Instantiate ourselves with a function 188 | override public init(function: @escaping (I) -> O, defaultOpQueue: DispatchQueue? = nil) { 189 | super.init(function: function, defaultOpQueue: defaultOpQueue) 190 | } 191 | 192 | /// The async property returns a function that takes the parameters 193 | /// from the original function, executes the function with those 194 | /// parameters in the desired queue, then returns the original 195 | /// routing object back to the originating thread. 196 | /// 197 | /// When calling the wrapped function, the internal dispatchQueue 198 | /// is not exited until the wrapped function completes. This 199 | /// internal dispatchQueue can be waited on to funnel the response 200 | /// of the wrapped function to yet another async dispatch. 201 | public var async: (I) -> __BriskRoutingObjNonVoid { 202 | guard let opQ = opQueue else { 203 | brisk_raise("You must specify a queue for this function to operate on") 204 | } 205 | 206 | guard !synchronized(lock, block: { let o = self.operated; self.operated = false; return o }) else { 207 | brisk_raise("You may not retain or use this routing object in a way that it can be executed more than once.") 208 | } 209 | 210 | return { i in 211 | self.dispatchGroup.enter() 212 | opQ.async { 213 | self.response = self.wrappedFunction(i) 214 | self.dispatchGroup.leave() 215 | } 216 | return self 217 | } 218 | } 219 | } 220 | 221 | 222 | 223 | // MARK: - Operators 224 | 225 | postfix operator ->> 226 | postfix operator ~>> 227 | postfix operator +>> 228 | 229 | //postfix operator ?->> 230 | //postfix operator ?~>> 231 | //postfix operator ?+>> 232 | 233 | 234 | /// The ```->>``` postfix operator generates an internal routing object that 235 | /// requires you to specify the operation queue. An example of this 236 | /// would be: 237 | /// 238 | /// ```handler->>.main.async(result: nil)``` 239 | @inline(__always) public postfix func ->>(function: @escaping (I) -> Void) -> __BriskRoutingObjVoid { 240 | return __BriskRoutingObjVoid(function: function) 241 | } 242 | 243 | /// The ```->>``` postfix operator generates an internal routing object that 244 | /// requires you to specify the operation queue. An example of this 245 | /// would be: 246 | /// 247 | /// ```handler->>.main.async(result: nil)``` 248 | @inline(__always) public postfix func ->>(function: @escaping (I) -> O) -> __BriskRoutingObjNonVoid { 249 | return __BriskRoutingObjNonVoid(function: function) 250 | } 251 | 252 | /// The ```->>``` postfix operator generates an internal routing object that 253 | /// requires you to specify the operation queue. An example of this 254 | /// would be: 255 | /// 256 | /// ```handler->>.main.async(result: nil)``` 257 | //@inline(__always) public postfix func ?->>(function: ((I) -> Void)?) -> __BriskRoutingObjVoid? { 258 | // return (function == nil) ? nil : __BriskRoutingObjVoid(function: function!) 259 | //} 260 | 261 | /// The ```->>``` postfix operator generates an internal routing object that 262 | /// requires you to specify the operation queue. An example of this 263 | /// would be: 264 | /// 265 | /// ```handler->>.main.async(result: nil)``` 266 | //@inline(__always) public postfix func ?->>(function: ((I) -> O)?) -> __BriskRoutingObjNonVoid? { 267 | // return (function == nil) ? nil : __BriskRoutingObjNonVoid(function: function!) 268 | //} 269 | 270 | 271 | 272 | 273 | /// The ```~>>``` postfix operator generates an internal routing object that 274 | /// defaults to the concurrent background queue. An example of this 275 | /// would be: 276 | /// 277 | /// ```handler~>>.async(result: nil)``` 278 | public postfix func ~>>(function: @escaping (I) -> Void) -> __BriskRoutingObjVoid { 279 | return __BriskRoutingObjVoid(function: function, defaultOpQueue: backgroundQueue) 280 | } 281 | 282 | /// The ```~>>``` postfix operator generates an internal routing object that 283 | /// defaults to the concurrent background queue. An example of this 284 | /// would be: 285 | /// 286 | /// ```handler~>>.async(result: nil)``` 287 | public postfix func ~>>(function: @escaping (I) -> O) -> __BriskRoutingObjNonVoid { 288 | return __BriskRoutingObjNonVoid(function: function, defaultOpQueue: backgroundQueue) 289 | } 290 | 291 | /// The ```~>>``` postfix operator generates an internal routing object that 292 | /// defaults to the concurrent background queue. An example of this 293 | /// would be: 294 | /// 295 | /// ```handler~>>.async(result: nil)``` 296 | //@inline(__always) public postfix func ?~>>(function: ((I) -> Void)?) -> __BriskRoutingObjVoid? { 297 | // return (function == nil) ? nil : __BriskRoutingObjVoid(function: function!, defaultOpQueue: backgroundQueue) 298 | //} 299 | 300 | /// The ```~>>``` postfix operator generates an internal routing object that 301 | /// defaults to the concurrent background queue. An example of this 302 | /// would be: 303 | /// 304 | /// ```handler~>>.async(result: nil)``` 305 | //@inline(__always) public postfix func ?~>>(function: ((I) -> O)?) -> __BriskRoutingObjNonVoid? { 306 | // return (function == nil) ? nil : __BriskRoutingObjNonVoid(function: function!, defaultOpQueue: backgroundQueue) 307 | //} 308 | 309 | 310 | 311 | 312 | /// The ```+>>``` postfix operator generates an internal routing object that 313 | /// defaults to the main queue. An example of this would be: 314 | /// 315 | /// ```handler+>>.async(result: nil)``` 316 | public postfix func +>>(function: @escaping (I) -> Void) -> __BriskRoutingObjVoid { 317 | return __BriskRoutingObjVoid(function: function, defaultOpQueue: mainQueue) 318 | } 319 | 320 | /// The ```+>>``` postfix operator generates an internal routing object that 321 | /// defaults to the main queue. An example of this would be: 322 | /// 323 | /// ```handler+>>.async(result: nil)``` 324 | public postfix func +>>(function: @escaping (I) -> O) -> __BriskRoutingObjNonVoid { 325 | return __BriskRoutingObjNonVoid(function: function, defaultOpQueue: mainQueue) 326 | } 327 | 328 | /// The ```+>>``` postfix operator generates an internal routing object that 329 | /// defaults to the main queue. An example of this would be: 330 | /// 331 | /// ```handler+>>.async(result: nil)``` 332 | //@inline(__always) public postfix func ?+>>(function: ((I) -> Void)?) -> __BriskRoutingObjVoid? { 333 | // return (function == nil) ? nil : __BriskRoutingObjVoid(function: function!, defaultOpQueue: mainQueue) 334 | //} 335 | 336 | /// The ```+>>``` postfix operator generates an internal routing object that 337 | /// defaults to the main queue. An example of this would be: 338 | /// 339 | /// ```handler+>>.async(result: nil)``` 340 | //@inline(__always) public postfix func ?+>>(function: ((I) -> O)?) -> __BriskRoutingObjNonVoid? { 341 | // return (function == nil) ? nil : __BriskRoutingObjNonVoid(function: function!, defaultOpQueue: mainQueue) 342 | //} 343 | 344 | 345 | /* -- old precendence = 140 -- */ 346 | precedencegroup AsyncRedirectPrecendence { 347 | higherThan: RangeFormationPrecedence 348 | lowerThan: MultiplicationPrecedence 349 | associativity: left 350 | } 351 | 352 | infix operator +>> : AsyncRedirectPrecendence 353 | infix operator ~>> : AsyncRedirectPrecendence 354 | infix operator ?+>> : AsyncRedirectPrecendence 355 | infix operator ?~>> : AsyncRedirectPrecendence 356 | 357 | 358 | /// The ```~>>``` infix operator allows for shorthand creation of a routing object 359 | /// that operates asynchronously on the global concurrent background queue. 360 | /// 361 | /// - e.g.: ```handler~>>(param: nil)``` 362 | public func ~>>(lhs: @escaping (I) -> Void, rhs: I) -> Void { 363 | return __BriskRoutingObjVoid(function: lhs, defaultOpQueue: backgroundQueue).async(rhs) 364 | } 365 | 366 | /// The ```~>>``` infix operator allows for shorthand creation of a routing object 367 | /// that operates asynchronously on the global concurrent background queue. 368 | /// 369 | /// - e.g.: ```handler~>>(param: nil)``` 370 | @discardableResult public func ~>>(lhs: @escaping (I) -> O, rhs: I) -> __BriskRoutingObjNonVoid { 371 | return __BriskRoutingObjNonVoid(function: lhs, defaultOpQueue: backgroundQueue).async(rhs) 372 | } 373 | 374 | /// The ```~>>``` infix operator allows for shorthand execution of the wrapped function 375 | /// on its defined operation queue. 376 | /// 377 | /// - e.g.: ```handler~>>(param: nil)``` 378 | public func ~>>(lhs: __BriskRoutingObjVoid, rhs: I) -> Void { 379 | return lhs.async(rhs) 380 | } 381 | 382 | /// The ```~>>``` infix operator allows for shorthand execution of the wrapped function 383 | /// on its defined operation queue. 384 | /// 385 | /// - e.g.: ```handler~>>(param: nil)``` 386 | @discardableResult public func ~>>(lhs: __BriskRoutingObjNonVoid, rhs: I) -> __BriskRoutingObjNonVoid { 387 | return lhs.async(rhs) 388 | } 389 | 390 | 391 | 392 | /// The ```~>>``` infix operator allows for shorthand creation of a routing object 393 | /// that operates asynchronously on the global concurrent background queue. 394 | /// 395 | /// - e.g.: ```handler~>>(param: nil)``` 396 | public func ?~>>(lhs: ((I) -> Void)?, rhs: I) -> Void { 397 | if let lhs = lhs { __BriskRoutingObjVoid(function: lhs, defaultOpQueue: backgroundQueue).async(rhs) } 398 | } 399 | 400 | /// The ```~>>``` infix operator allows for shorthand creation of a routing object 401 | /// that operates asynchronously on the global concurrent background queue. 402 | /// 403 | /// - e.g.: ```handler~>>(param: nil)``` 404 | @discardableResult public func ?~>>(lhs: ((I) -> O)?, rhs: I) -> __BriskRoutingObjNonVoid? { 405 | return (lhs == nil) ? nil : __BriskRoutingObjNonVoid(function: lhs!, defaultOpQueue: backgroundQueue).async(rhs) 406 | } 407 | 408 | /// The ```~>>``` infix operator allows for shorthand execution of the wrapped function 409 | /// on its defined operation queue. 410 | /// 411 | /// - e.g.: ```handler~>>(param: nil)``` 412 | public func ?~>>(lhs: __BriskRoutingObjVoid?, rhs: I) -> Void { 413 | lhs?.async(rhs) 414 | } 415 | 416 | /// The ```~>>``` infix operator allows for shorthand execution of the wrapped function 417 | /// on its defined operation queue. 418 | /// 419 | /// - e.g.: ```handler~>>(param: nil)``` 420 | @discardableResult public func ?~>>(lhs: __BriskRoutingObjNonVoid?, rhs: I) -> __BriskRoutingObjNonVoid? { 421 | return lhs?.async(rhs) 422 | } 423 | 424 | 425 | 426 | 427 | 428 | /// The ```+>>``` infix operator allows for shorthand creation of a routing object 429 | /// that operates asynchronously on the main queue. 430 | /// 431 | /// - e.g.: ```handler+>>(param: nil)``` 432 | public func +>>(lhs: @escaping (I) -> Void, rhs: I) -> Void { 433 | return __BriskRoutingObjVoid(function: lhs, defaultOpQueue: mainQueue).async(rhs) 434 | } 435 | 436 | /// The ```+>>``` infix operator allows for shorthand creation of a routing object 437 | /// that operates asynchronously on the main queue. 438 | /// 439 | /// - e.g.: ```handler+>>(param: nil)``` 440 | @discardableResult public func +>>(lhs: @escaping (I) -> O, rhs: I) -> __BriskRoutingObjNonVoid { 441 | return __BriskRoutingObjNonVoid(function: lhs, defaultOpQueue: mainQueue).async(rhs) 442 | } 443 | 444 | /// The ```+>>``` infix operator allows for shorthand creation of a routing object 445 | /// that operates asynchronously on the main queue. 446 | /// 447 | /// - e.g.: ```handler+>>(param: nil)``` 448 | public func ?+>>(lhs: ((I) -> Void)?, rhs: I) -> Void { 449 | if let lhs = lhs { __BriskRoutingObjVoid(function: lhs, defaultOpQueue: mainQueue).async(rhs) } 450 | } 451 | 452 | /// The ```+>>``` infix operator allows for shorthand creation of a routing object 453 | /// that operates asynchronously on the main queue. 454 | /// 455 | /// - e.g.: ```handler+>>(param: nil)``` 456 | @discardableResult public func ?+>>(lhs: ((I) -> O)?, rhs: I) -> __BriskRoutingObjNonVoid? { 457 | return (lhs == nil) ? nil : __BriskRoutingObjNonVoid(function: lhs!, defaultOpQueue: mainQueue).async(rhs) 458 | } 459 | 460 | 461 | 462 | 463 | 464 | /// The special ```~>>``` infix operator between a function and a queue creates a 465 | /// routing object that will call its operation on that queue. 466 | /// 467 | /// - e.g.: ```handler +>> (param: nil) ~>> myQueue ~>> { result in ... }``` 468 | /// - e.g.: ```handler ~>> myQueue ~>> (param: nil) ~>> myOtherQueue ~>> { result in ... }``` 469 | public func ~>>(lhs: @escaping (I) -> Void, rhs: DispatchQueue) -> __BriskRoutingObjVoid { 470 | return __BriskRoutingObjVoid(function: lhs, defaultOpQueue: rhs) 471 | } 472 | 473 | /// The special ```~>>``` infix operator between a function and a queue creates a 474 | /// routing object that will call its operation on that queue. 475 | /// 476 | /// - e.g.: ```handler +>> (param: nil) ~>> myQueue ~>> { result in ... }``` 477 | /// - e.g.: ```handler ~>> myQueue ~>> (param: nil) ~>> myOtherQueue ~>> { result in ... }``` 478 | public func ~>>(lhs: @escaping (I) -> O, rhs: DispatchQueue) -> __BriskRoutingObjNonVoid { 479 | return __BriskRoutingObjNonVoid(function: lhs, defaultOpQueue: rhs) 480 | } 481 | 482 | /// The special ```~>>``` infix operator allows you to specify the queues for the 483 | /// routing operations. This sets the initial operation queue if it hasn't already 484 | /// been defined by on(). If the initial operation queue has already been defined, 485 | /// this sets the response handler queue. 486 | /// 487 | /// - e.g.: ```handler +>> (param: nil) ~>> myQueue ~>> { result in ... }``` 488 | /// - e.g.: ```handler ~>> myQueue ~>> (param: nil) ~>> myOtherQueue ~>> { result in ... }``` 489 | public func ~>>(lhs: __BriskRoutingObjNonVoid, rhs: DispatchQueue) -> __BriskRoutingObjNonVoid { 490 | lhs.handlerQueue = rhs 491 | return lhs 492 | } 493 | 494 | /// The special ```~>>``` infix operator between a function and a queue creates a 495 | /// routing object that will call its operation on that queue. 496 | /// 497 | /// - e.g.: ```handler +>> (param: nil) ~>> myQueue ~>> { result in ... }``` 498 | /// - e.g.: ```handler ~>> myQueue ~>> (param: nil) ~>> myOtherQueue ~>> { result in ... }``` 499 | public func ?~>>(lhs: ((I) -> Void)?, rhs: DispatchQueue) -> __BriskRoutingObjVoid? { 500 | return (lhs == nil) ? nil : __BriskRoutingObjVoid(function: lhs!, defaultOpQueue: rhs) 501 | } 502 | 503 | /// The special ```~>>``` infix operator between a function and a queue creates a 504 | /// routing object that will call its operation on that queue. 505 | /// 506 | /// - e.g.: ```handler +>> (param: nil) ~>> myQueue ~>> { result in ... }``` 507 | /// - e.g.: ```handler ~>> myQueue ~>> (param: nil) ~>> myOtherQueue ~>> { result in ... }``` 508 | public func ?~>>(lhs: ((I) -> O)?, rhs: DispatchQueue) -> __BriskRoutingObjNonVoid? { 509 | return (lhs == nil) ? nil : __BriskRoutingObjNonVoid(function: lhs!, defaultOpQueue: rhs) 510 | } 511 | 512 | /// The special ```~>>``` infix operator allows you to specify the queues for the 513 | /// routing operations. This sets the initial operation queue if it hasn't already 514 | /// been defined by on(). If the initial operation queue has already been defined, 515 | /// this sets the response handler queue. 516 | /// 517 | /// - e.g.: ```handler +>> (param: nil) ~>> myQueue ~>> { result in ... }``` 518 | /// - e.g.: ```handler ~>> myQueue ~>> (param: nil) ~>> myOtherQueue ~>> { result in ... }``` 519 | public func ~>>(lhs: __BriskRoutingObjNonVoid?, rhs: DispatchQueue) -> __BriskRoutingObjNonVoid? { 520 | lhs?.handlerQueue = rhs 521 | return lhs 522 | } 523 | 524 | 525 | 526 | 527 | /// The ```~>>``` infix operator routes the result of your asynchronous operation 528 | /// to a completion handler that is executed on the predefined queue, or the global 529 | /// concurrent background queue by default if none was specified. 530 | /// 531 | /// -e.g.: ```handler~>>(param: nil) ~>> { result in ... }``` 532 | public func ~>>(lhs: __BriskRoutingObjNonVoid, rhs: @escaping (O) -> Void) { 533 | if lhs.handlerQueue == nil { lhs.handlerQueue = backgroundQueue } 534 | lhs.processAsyncHandler(rhs) 535 | } 536 | 537 | /// The ```+>>``` infix operator routes the result of your asynchronous operation 538 | /// to a completion handler that is executed on the main queue. 539 | /// 540 | /// -e.g.: ```handler~>>(param: nil) +>> { result in ... }``` 541 | public func +>>(lhs: __BriskRoutingObjNonVoid, rhs: @escaping (O) -> Void) { 542 | lhs.handlerQueue = mainQueue 543 | lhs.processAsyncHandler(rhs) 544 | } 545 | 546 | /// The ```~>>``` infix operator routes the result of your asynchronous operation 547 | /// to a completion handler that is executed on the predefined queue, or the global 548 | /// concurrent background queue by default if none was specified. 549 | /// 550 | /// -e.g.: ```handler~>>(param: nil) ~>> { result in ... }``` 551 | public func ~>>(lhs: __BriskRoutingObjNonVoid?, rhs: @escaping (O) -> Void) { 552 | if let lhs = lhs { 553 | if lhs.handlerQueue == nil { lhs.handlerQueue = backgroundQueue } 554 | lhs.processAsyncHandler(rhs) 555 | } 556 | } 557 | 558 | /// The ```+>>``` infix operator routes the result of your asynchronous operation 559 | /// to a completion handler that is executed on the main queue. 560 | /// 561 | /// -e.g.: ```handler~>>(param: nil) +>> { result in ... }``` 562 | public func +>>(lhs: __BriskRoutingObjNonVoid?, rhs: @escaping (O) -> Void) { 563 | lhs?.handlerQueue = mainQueue 564 | lhs?.processAsyncHandler(rhs) 565 | } 566 | 567 | 568 | 569 | 570 | 571 | 572 | -------------------------------------------------------------------------------- /Brisk/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Brisk/Swift2/BriskGCD_Swift2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BriskGCD.swift 3 | // Brisk 4 | // 5 | // Copyright (c) 2016-Present Jason Fieldman - https://github.com/jmfieldman/Brisk 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | 29 | 30 | 31 | /// The leeway granted for inexact timers. 32 | private let kInexactTimerLeeway = UInt64(0.01 * Double(NSEC_PER_SEC)) 33 | 34 | 35 | // MARK: - Dispatch Helpers 36 | 37 | 38 | /// Execute a block asynchronously on the main dispatch queue 39 | /// 40 | /// - parameter block: The block to execute. 41 | public func dispatch_main_async(_ block: @escaping () -> ()) { 42 | mainQueue.async(execute: block) 43 | } 44 | 45 | 46 | /// Execute a block asynchronously on the generic concurrent background queue. 47 | /// 48 | /// - parameter block: The block to execute. 49 | public func dispatch_bg_async(_ block: @escaping () -> ()) { 50 | backgroundQueue.async(execute: block) 51 | } 52 | 53 | 54 | // Data Structures for dispatch_async 55 | private var queueForId: [String : DispatchQueue] = [:] 56 | private var queueLock: NSLock = NSLock() 57 | 58 | /// Executes a block on an ad-hoc named serial dispatch queue. If a queue was already created 59 | /// with the provided name, it is reused. This allows you to dispatch code on serial queues 60 | /// whose uniqueness is identified by a string, rather than a pre-allocated queue instance. 61 | /// - note: This is a more a convenience feature than a recommended practice. It is generally 62 | /// safer from a coding perspective to use a pre-instantiated queue variable. 63 | public func dispatch_async(_ queueName: String, block: @escaping () -> ()) { 64 | 65 | if let queue: DispatchQueue = synchronized(queueLock, block: { return queueForId[queueName] }) { 66 | queue.async(execute: block) 67 | return 68 | } 69 | 70 | synchronized(queueLock) { 71 | let queue = DispatchQueue(label: queueName, attributes: []) 72 | queueForId[queueName] = queue 73 | queue.async(execute: block) 74 | } 75 | } 76 | 77 | 78 | /// Execute a block synchronously on the main dispatch queue. If called 79 | /// from the main queue, the block is executed immediately. 80 | /// 81 | /// - parameter block: The block to execute. 82 | public func dispatch_main_sync(_ block: () -> ()) { 83 | 84 | if Thread.current.isMainThread { 85 | block() 86 | } else { 87 | mainQueue.sync(execute: block) 88 | } 89 | } 90 | 91 | 92 | /// Dispatch a block asynchronously on a dispatch queue after a certain time interval. 93 | /// The dispatch timer will use the standard leeway (non-exact). 94 | /// 95 | /// - parameter after: The time to wait before running the block asynchronously. 96 | /// - parameter block: The block to execute. 97 | @inline(__always) public func dispatch_after(_ seconds: TimeInterval, 98 | _ onQueue: DispatchQueue, 99 | _ block: @escaping () -> ()) { 100 | 101 | onQueue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(seconds * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: block) 102 | } 103 | 104 | 105 | /// Dispatch a block asynchronously on a dispatch queue after a certain time interval. 106 | /// The dispatch timer will use 0 leeway to make the timing as exact as possible. This is used 107 | /// mainly for animation or other activities that require exact timing. 108 | /// 109 | /// - parameter after: The time to wait before running the block asynchronously. 110 | /// - parameter block: The block to execute. 111 | @inline(__always) public func dispatch_after_exactly(_ seconds: TimeInterval, 112 | _ onQueue: DispatchQueue, 113 | _ block: @escaping (() -> ())) { 114 | 115 | let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: UInt(0)), queue: onQueue) 116 | timer.setEventHandler { 117 | block() 118 | } 119 | 120 | timer.scheduleOneshot(deadline: DispatchTime.now() + seconds, leeway: DispatchTimeInterval.microseconds(0)) 121 | timer.resume() 122 | } 123 | 124 | 125 | /// Dispatch a block every interval on the given queue. You stop the timer by calling 126 | /// dispatch_source_cancel on the returned timer. 127 | /// 128 | /// - parameter interval: The interval to call the block. 129 | /// - parameter onQueue: The queue to call the block on. 130 | /// - parameter block: The block to execute. 131 | /// 132 | /// - returns: The dispatch_source_t that represents the timer. 133 | /// You must eventually cancel this with dispatch_source_cancel() 134 | @discardableResult @inline(__always) public func dispatch_every(_ interval: TimeInterval, 135 | _ onQueue: DispatchQueue, 136 | _ block: @escaping ((DispatchSourceTimer) -> ())) -> DispatchSourceTimer { 137 | 138 | let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: UInt(0)), queue: onQueue) 139 | timer.setEventHandler { 140 | block(timer) 141 | } 142 | 143 | timer.scheduleRepeating(deadline: DispatchTime.now() + interval, interval: interval) 144 | timer.resume() 145 | return timer 146 | } 147 | 148 | 149 | /// Dispatch a block every interval on the given queue. You stop the timer by calling 150 | /// dispatch_source_cancel on the returned timer. 151 | /// 152 | /// This function uses the most exact timing possible the timer. 153 | /// 154 | /// - parameter interval: The interval to call the block. 155 | /// - parameter onQueue: The queue to call the block on. 156 | /// - parameter block: The block to execute. 157 | /// 158 | /// - returns: The dispatch_source_t that represents the timer. 159 | /// You must eventually cancel this with dispatch_source_cancel() 160 | @discardableResult @inline(__always) public func dispatch_every_exact(_ interval: TimeInterval, 161 | _ onQueue: DispatchQueue, 162 | _ block: @escaping ((DispatchSourceTimer) -> ())) -> DispatchSourceTimer { 163 | 164 | let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: UInt(0)), queue: onQueue) 165 | timer.setEventHandler { 166 | block(timer as! DispatchSource) 167 | } 168 | 169 | timer.scheduleRepeating(deadline: DispatchTime.now() + interval, interval: interval, leeway: DispatchTimeInterval.microseconds(0)) 170 | timer.resume() 171 | return timer as! DispatchSource 172 | } 173 | 174 | 175 | /// Dispatch a block asynchronously on the main dispatch queue after a certain time interval. 176 | /// The dispatch timer will use the standard leeway (non-exact). 177 | /// 178 | /// - parameter after: The time to wait before running the block asynchronously. 179 | /// - parameter block: The block to execute. 180 | public func dispatch_main_after(_ seconds: TimeInterval, 181 | _ block: @escaping () -> ()) { 182 | 183 | dispatch_after(seconds, mainQueue, block) 184 | } 185 | 186 | 187 | /// Dispatch a block asynchronously on the main dispatch queue after a certain time interval. 188 | /// The dispatch timer will use 0 leeway to make the timing as exact as possible. This is used 189 | /// mainly for animation or other activities that require exact timing. 190 | /// 191 | /// - parameter after: The time to wait before running the block asynchronously. 192 | /// - parameter block: The block to execute. 193 | public func dispatch_main_after_exactly(_ seconds: TimeInterval, 194 | _ block: @escaping (() -> ())) { 195 | 196 | dispatch_after_exactly(seconds, mainQueue, block) 197 | } 198 | 199 | 200 | /// Dispatch a block every interval on the main queue. You stop the timer by calling 201 | /// dispatch_source_cancel on the returned timer. 202 | /// 203 | /// - parameter interval: The interval to call the block. 204 | /// - parameter block: The block to execute. 205 | /// 206 | /// - returns: The dispatch_source_t that represents the timer. 207 | /// You must eventually cancel this with dispatch_source_cancel() 208 | @discardableResult @inline(__always) public func dispatch_main_every(_ interval: TimeInterval, 209 | _ block: @escaping ((DispatchSourceTimer) -> ())) -> DispatchSourceTimer { 210 | 211 | return dispatch_every(interval, DispatchQueue.main, block) 212 | } 213 | 214 | 215 | /// Dispatch a block every interval on the main queue. You stop the timer by calling 216 | /// dispatch_source_cancel on the returned timer. 217 | /// 218 | /// This function uses the most exact timing possible on the timer. 219 | /// 220 | /// - parameter interval: The interval to call the block. 221 | /// - parameter block: The block to execute. 222 | /// 223 | /// - returns: The dispatch_source_t that represents the timer. 224 | /// You must eventually cancel this with dispatch_source_cancel() 225 | @discardableResult @inline(__always) public func dispatch_main_every_exact(_ interval: TimeInterval, 226 | _ block: @escaping ((DispatchSourceTimer) -> ())) -> DispatchSourceTimer { 227 | 228 | return dispatch_every_exact(interval, DispatchQueue.main, block) 229 | } 230 | 231 | 232 | 233 | // Data Structures for dispatch_once_after 234 | private var operationTimerForId: [String : DispatchSourceTimer] = [:] 235 | private var operationTimerLock: NSLock = NSLock() 236 | 237 | /// Queue up an action to take place on an queue in the future, but make sure it only triggers once. 238 | /// This allows you to queue up the same operation several times and not worry about 239 | /// it being called multiple times later. 240 | public func dispatch_once_after(_ after: TimeInterval, 241 | operationId: String, 242 | onQueue queue: DispatchQueue, 243 | block: @escaping () -> ()) { 244 | 245 | // Check if we already have a timer source for this operation ID 246 | if let existingTimer: DispatchSourceTimer = synchronized(operationTimerLock, block: { return operationTimerForId[operationId] }) { 247 | existingTimer.scheduleOneshot(deadline: DispatchTime.now() + after) 248 | return 249 | } 250 | 251 | // Timer doesn't exist, we have to make one! 252 | let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: UInt(0)), queue: queue) 253 | timer.setEventHandler { 254 | 255 | // one shot timer -- remove from dictionary 256 | synchronized(operationTimerLock) { 257 | operationTimerForId.removeValue(forKey: operationId) 258 | timer.cancel() 259 | } 260 | block() 261 | } 262 | 263 | // Set it! 264 | synchronized(operationTimerLock) { operationTimerForId[operationId] = timer } 265 | timer.scheduleOneshot(deadline: DispatchTime.now() + after) 266 | timer.resume() 267 | } 268 | 269 | 270 | /// Queue up an action to take place on the main queue in the future, but make sure it only triggers once. 271 | /// This allows you to queue up the same operation several times and not worry about 272 | /// it being called multiple times later. 273 | public func dispatch_main_once_after(_ after: TimeInterval, 274 | operationId: String, 275 | block: @escaping () -> ()) { 276 | 277 | dispatch_once_after(after, operationId: operationId, onQueue: mainQueue, block: block) 278 | } 279 | 280 | /// This is a helper to take advantage of multiple cores when performing an activity in parallel on 281 | /// multiple values in an array. 282 | /// 283 | /// - parameter elements: An array of elements to process 284 | /// - parameter queue: The dispatch queue to process on (should be concurrent) 285 | /// - parameter block: The block to process for each element. 286 | public func dispatch_each(_ elements: [T], 287 | queue: DispatchQueue, 288 | block: (T) -> ()) { 289 | 290 | DispatchQueue.concurrentPerform(iterations: elements.count) { i in 291 | block(elements[i]) 292 | } 293 | } 294 | 295 | 296 | -------------------------------------------------------------------------------- /BriskTests/BriskTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BriskTests.swift 3 | // BriskTests 4 | // 5 | // Copyright (c) 2016-Present Jason Fieldman - https://github.com/jmfieldman/Brisk 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import XCTest 27 | @testable import Brisk 28 | 29 | 30 | private let mainQueueKey = DispatchSpecificKey() 31 | private let mainQueueValue: Int = 31337 32 | 33 | private func onMainQueue() -> Bool { 34 | return DispatchQueue.getSpecific(key: mainQueueKey) == mainQueueValue 35 | } 36 | 37 | private func onMainThread() -> Bool { 38 | return Thread.current.isMainThread 39 | } 40 | 41 | private func onMainEverything() -> Bool { 42 | return onMainThread() && onMainQueue() 43 | } 44 | 45 | class BriskTests: XCTestCase { 46 | 47 | 48 | 49 | override func setUp() { 50 | super.setUp() 51 | // Put setup code here. This method is called before the invocation of each test method in the class. 52 | 53 | DispatchQueue.main.setSpecific(key: /*Migrator FIXME: Use a variable of type DispatchSpecificKey*/ mainQueueKey, 54 | value: mainQueueValue) 55 | } 56 | 57 | override func tearDown() { 58 | // Put teardown code here. This method is called after the invocation of each test method in the class. 59 | super.tearDown() 60 | } 61 | 62 | 63 | // MARK: - Swift 2.x dispatch stuff 64 | 65 | func testBasicSwift2xDispatchMain() { 66 | let spin = MainSpin() 67 | var qPassed = false 68 | 69 | spin.start() 70 | dispatch_main_async { 71 | qPassed = onMainEverything() 72 | spin.done() 73 | } 74 | spin.wait() 75 | 76 | XCTAssertTrue(qPassed, "on incorrect queue") 77 | } 78 | 79 | 80 | func testBasicSwift2xDispatchAsync() { 81 | let spin = AsyncSpin() 82 | var qPassed = false 83 | 84 | spin.start() 85 | dispatch_bg_async { 86 | qPassed = !onMainQueue() 87 | spin.done() 88 | } 89 | spin.wait() 90 | 91 | XCTAssertTrue(qPassed, "on incorrect queue") 92 | } 93 | 94 | 95 | func testBasicSwift2xDispatchMainAfter() { 96 | 97 | let spin = MainSpin() 98 | let time1: CFAbsoluteTime = CFAbsoluteTimeGetCurrent() 99 | var time2: CFAbsoluteTime = 0 100 | var qPassed = false 101 | 102 | spin.start() 103 | dispatch_main_after(0.5) { 104 | time2 = CFAbsoluteTimeGetCurrent() 105 | qPassed = onMainEverything() 106 | spin.done() 107 | } 108 | spin.wait() 109 | 110 | XCTAssertGreaterThan(time2 - time1, 0.25, "diff must be greater than 0.25") 111 | XCTAssertTrue(qPassed, "on incorrect queue") 112 | } 113 | 114 | func testBasicSwift2xDispatchAsyncAfter() { 115 | 116 | let spin = AsyncSpin() 117 | let time1: CFAbsoluteTime = CFAbsoluteTimeGetCurrent() 118 | var time2: CFAbsoluteTime = 0 119 | var qPassed = false 120 | 121 | spin.start() 122 | dispatch_after(0.5, backgroundQueue) { 123 | time2 = CFAbsoluteTimeGetCurrent() 124 | qPassed = !onMainQueue() 125 | spin.done() 126 | } 127 | spin.wait() 128 | 129 | XCTAssertGreaterThan(time2 - time1, 0.25, "diff must be greater than 0.5") 130 | XCTAssertTrue(qPassed, "on incorrect queue") 131 | } 132 | 133 | 134 | func testBasicSwift2xDispatchMainOnce() { 135 | 136 | let spin = MainSpin() 137 | let time1: CFAbsoluteTime = CFAbsoluteTimeGetCurrent() 138 | var time2: CFAbsoluteTime = 0 139 | var qPassed = false 140 | var count = 0 141 | 142 | spin.start() 143 | let block = { 144 | time2 = CFAbsoluteTimeGetCurrent() 145 | count += 1 146 | qPassed = onMainEverything() 147 | spin.done() 148 | } 149 | for _ in 0 ..< 10 { 150 | dispatch_main_once_after(0.5, operationId: "testop2") { 151 | block() 152 | } 153 | } 154 | spin.wait() 155 | 156 | XCTAssertEqual(count, 1, "counted more than once") 157 | XCTAssertGreaterThan(time2 - time1, 0.25, "diff must be greater than 0.5") 158 | XCTAssertTrue(qPassed, "on incorrect queue") 159 | 160 | } 161 | 162 | 163 | func testBasicSwift2xDispatchAsyncOnce() { 164 | 165 | let spin = AsyncSpin() 166 | let time1: CFAbsoluteTime = CFAbsoluteTimeGetCurrent() 167 | var time2: CFAbsoluteTime = 0 168 | var qPassed = false 169 | var count = 0 170 | 171 | spin.start() 172 | let block = { 173 | time2 = CFAbsoluteTimeGetCurrent() 174 | count += 1 175 | qPassed = !onMainQueue() 176 | spin.done() 177 | } 178 | for _ in 0 ..< 10 { 179 | dispatch_once_after(0.5, operationId: "testop", onQueue: backgroundQueue) { 180 | block() 181 | } 182 | } 183 | spin.wait() 184 | 185 | XCTAssertEqual(count, 1, "counted more than once") 186 | XCTAssertGreaterThan(time2 - time1, 0.25, "diff must be greater than 0.5") 187 | XCTAssertTrue(qPassed, "on incorrect queue") 188 | 189 | } 190 | 191 | 192 | func testBasicSwift2xDispatchEveryMain() { 193 | 194 | let spin = MainSpin() 195 | var count = 0 196 | var qPassed = true 197 | 198 | spin.start() 199 | dispatch_main_every(0.1) { t in 200 | count += 1 201 | if !onMainEverything() { qPassed = false } 202 | if count == 10 { 203 | spin.done() 204 | t.cancel() 205 | } 206 | } 207 | spin.wait() 208 | 209 | XCTAssertEqual(count, 10, "counted incorrect times") 210 | XCTAssertTrue(qPassed, "on incorrect queue") 211 | } 212 | 213 | func testBasicSwift2xDispatchEveryMainExact() { 214 | 215 | let spin = MainSpin() 216 | var count = 0 217 | var qPassed = true 218 | 219 | spin.start() 220 | dispatch_main_every_exact(0.1) { t in 221 | count += 1 222 | if !onMainEverything() { qPassed = false } 223 | if count == 10 { 224 | spin.done() 225 | t.cancel() 226 | } 227 | } 228 | spin.wait() 229 | 230 | XCTAssertEqual(count, 10, "counted incorrect times") 231 | XCTAssertTrue(qPassed, "on incorrect queue") 232 | } 233 | 234 | func testBasicSwift2xDispatchEveryAsync() { 235 | 236 | let spin = AsyncSpin() 237 | var count = 0 238 | var qPassed = true 239 | 240 | spin.start() 241 | dispatch_every(0.1, backgroundQueue) { t in 242 | count += 1 243 | if onMainQueue() { qPassed = false } 244 | if count == 10 { 245 | spin.done() 246 | t.cancel() 247 | } 248 | } 249 | spin.wait() 250 | 251 | XCTAssertEqual(count, 10, "counted incorrect times") 252 | XCTAssertTrue(qPassed, "on incorrect queue") 253 | } 254 | 255 | 256 | func testBasicSwift2xDispatchEveryAsyncExact() { 257 | 258 | let spin = AsyncSpin() 259 | var count = 0 260 | var qPassed = true 261 | 262 | spin.start() 263 | dispatch_every_exact(0.1, backgroundQueue) { t in 264 | count += 1 265 | if onMainQueue() { qPassed = false } 266 | if count == 10 { 267 | spin.done() 268 | t.cancel() 269 | } 270 | } 271 | spin.wait() 272 | 273 | XCTAssertEqual(count, 10, "counted incorrect times") 274 | XCTAssertTrue(qPassed, "on incorrect queue") 275 | } 276 | 277 | 278 | // MARK: - Swift 3.x dispatch stuff 279 | 280 | 281 | func testBasicSwift3xDispatchMainAfter() { 282 | 283 | let spin = MainSpin() 284 | let time1: CFAbsoluteTime = CFAbsoluteTimeGetCurrent() 285 | var time2: CFAbsoluteTime = 0 286 | var qPassed = false 287 | 288 | spin.start() 289 | DispatchQueue.main.async(after: 0.5) { 290 | time2 = CFAbsoluteTimeGetCurrent() 291 | qPassed = onMainEverything() 292 | spin.done() 293 | } 294 | spin.wait() 295 | 296 | XCTAssertGreaterThan(time2 - time1, 0.25, "diff must be greater than 0.25") 297 | XCTAssertTrue(qPassed, "on incorrect queue") 298 | } 299 | 300 | func testBasicSwift3xDispatchAsyncAfter() { 301 | 302 | let spin = AsyncSpin() 303 | let time1: CFAbsoluteTime = CFAbsoluteTimeGetCurrent() 304 | var time2: CFAbsoluteTime = 0 305 | var qPassed = false 306 | 307 | spin.start() 308 | backgroundQueue.async(after: 0.5) { 309 | time2 = CFAbsoluteTimeGetCurrent() 310 | qPassed = !onMainQueue() 311 | spin.done() 312 | } 313 | spin.wait() 314 | 315 | XCTAssertGreaterThan(time2 - time1, 0.25, "diff must be greater than 0.5") 316 | XCTAssertTrue(qPassed, "on incorrect queue") 317 | } 318 | 319 | 320 | func testBasicSwift3xDispatchMainOnce() { 321 | 322 | let spin = MainSpin() 323 | let time1: CFAbsoluteTime = CFAbsoluteTimeGetCurrent() 324 | var time2: CFAbsoluteTime = 0 325 | var qPassed = false 326 | var count = 0 327 | 328 | spin.start() 329 | let block = { 330 | time2 = CFAbsoluteTimeGetCurrent() 331 | count += 1 332 | qPassed = onMainEverything() 333 | spin.done() 334 | } 335 | for _ in 0 ..< 10 { 336 | DispatchQueue.main.once(operationId: "testop2", after: 0.5) { 337 | block() 338 | } 339 | } 340 | spin.wait() 341 | 342 | XCTAssertEqual(count, 1, "counted more than once") 343 | XCTAssertGreaterThan(time2 - time1, 0.25, "diff must be greater than 0.5") 344 | XCTAssertTrue(qPassed, "on incorrect queue") 345 | 346 | } 347 | 348 | 349 | func testBasicSwift3xDispatchAsyncOnce() { 350 | 351 | let spin = AsyncSpin() 352 | let time1: CFAbsoluteTime = CFAbsoluteTimeGetCurrent() 353 | var time2: CFAbsoluteTime = 0 354 | var qPassed = false 355 | var count = 0 356 | 357 | spin.start() 358 | let block = { 359 | time2 = CFAbsoluteTimeGetCurrent() 360 | count += 1 361 | qPassed = !onMainQueue() 362 | spin.done() 363 | } 364 | for _ in 0 ..< 10 { 365 | backgroundQueue.once(operationId: "testop", after: 0.5) { 366 | block() 367 | } 368 | } 369 | spin.wait() 370 | 371 | XCTAssertEqual(count, 1, "counted more than once") 372 | XCTAssertGreaterThan(time2 - time1, 0.25, "diff must be greater than 0.5") 373 | XCTAssertTrue(qPassed, "on incorrect queue") 374 | 375 | } 376 | 377 | 378 | func testBasicSwift3xDispatchEveryMain() { 379 | 380 | let spin = MainSpin() 381 | var count = 0 382 | var qPassed = true 383 | 384 | spin.start() 385 | DispatchQueue.main.async(every: 0.1) { (t: DispatchSourceTimer) in 386 | count += 1 387 | if !onMainEverything() { qPassed = false } 388 | if count == 10 { 389 | spin.done() 390 | //t.cancel() 391 | } 392 | } 393 | spin.wait() 394 | 395 | XCTAssertEqual(count, 10, "counted incorrect times") 396 | XCTAssertTrue(qPassed, "on incorrect queue") 397 | } 398 | 399 | func testBasicSwift3xDispatchEveryMainExact() { 400 | 401 | let spin = MainSpin() 402 | var count = 0 403 | var qPassed = true 404 | 405 | spin.start() 406 | DispatchQueue.main.async(every: 0.1, leeway: 0) { (t: DispatchSourceTimer) in 407 | count += 1 408 | if !onMainEverything() { qPassed = false } 409 | if count == 10 { 410 | spin.done() 411 | t.cancel() 412 | } 413 | } 414 | spin.wait() 415 | 416 | XCTAssertEqual(count, 10, "counted incorrect times") 417 | XCTAssertTrue(qPassed, "on incorrect queue") 418 | } 419 | 420 | func testBasicSwift3xDispatchEveryAsync() { 421 | 422 | let spin = AsyncSpin() 423 | var count = 0 424 | var qPassed = true 425 | 426 | spin.start() 427 | backgroundQueue.async(every: 0.1) { (t: DispatchSourceTimer) in 428 | count += 1 429 | if onMainQueue() { qPassed = false } 430 | if count == 10 { 431 | spin.done() 432 | t.cancel() 433 | } 434 | } 435 | spin.wait() 436 | 437 | XCTAssertEqual(count, 10, "counted incorrect times") 438 | XCTAssertTrue(qPassed, "on incorrect queue") 439 | } 440 | 441 | 442 | func testBasicSwift3xDispatchEveryAsyncExact() { 443 | 444 | let spin = AsyncSpin() 445 | var count = 0 446 | var qPassed = true 447 | 448 | spin.start() 449 | backgroundQueue.async(every: 0.1, leeway: 0) { (t: DispatchSourceTimer) in 450 | count += 1 451 | if onMainQueue() { qPassed = false } 452 | if count == 10 { 453 | spin.done() 454 | t.cancel() 455 | } 456 | } 457 | spin.wait() 458 | 459 | XCTAssertEqual(count, 10, "counted incorrect times") 460 | XCTAssertTrue(qPassed, "on incorrect queue") 461 | } 462 | 463 | 464 | 465 | // MARK: - Async To Sync 466 | 467 | 468 | func testBasicAsync2SyncConceptBG() { 469 | let spin = MainSpin() 470 | spin.start() 471 | 472 | var res: Int = 0 473 | 474 | backgroundQueue.async { 475 | res = <<~{ self.asyncTest_CallsOnMainReturns4($0) } 476 | spin.done() 477 | } 478 | 479 | spin.wait() 480 | 481 | XCTAssertEqual(res, 4, "incorrect response") 482 | } 483 | 484 | 485 | func testBasicAsync2SyncConceptMain() { 486 | let spin = MainSpin() 487 | spin.start() 488 | 489 | var res: Int = 0 490 | 491 | backgroundQueue.async { 492 | res = <<+{ self.asyncTest_CallsOnMainReturns4($0) } 493 | spin.done() 494 | } 495 | 496 | spin.wait() 497 | 498 | XCTAssertEqual(res, 4, "incorrect response") 499 | } 500 | 501 | func testBasicAsync2SyncConceptBG2() { 502 | let spin = MainSpin() 503 | spin.start() 504 | 505 | var res: Int = 0 506 | 507 | backgroundQueue.async { 508 | res = <<~{ self.asyncTest_CallsOnMainReturnsI(3, handler: $0) } 509 | spin.done() 510 | } 511 | 512 | spin.wait() 513 | 514 | XCTAssertEqual(res, 3, "incorrect response") 515 | } 516 | 517 | func testBasicAsync2SyncConceptBG3() { 518 | let spin = MainSpin() 519 | spin.start() 520 | 521 | var res: Int = 0 522 | var str: String = "" 523 | 524 | backgroundQueue.async { 525 | (res, str) = <<~{ self.asyncTest_CallsOnMainReturnsIforBoth(3, handler: $0) } 526 | spin.done() 527 | } 528 | 529 | spin.wait() 530 | 531 | XCTAssertEqual(res, 3, "incorrect response int") 532 | XCTAssertEqual(str, "3", "incorrect response str") 533 | } 534 | 535 | func testBasicAsync2SyncConceptBG3_ImmediateOp() { 536 | let spin = MainSpin() 537 | spin.start() 538 | 539 | var res: Int = 0 540 | var str: String = "" 541 | 542 | backgroundQueue.async { 543 | (res, str) = <<-{ self.asyncTest_CallsOnMainReturnsIforBoth(3, handler: $0) } 544 | spin.done() 545 | } 546 | 547 | spin.wait() 548 | 549 | XCTAssertEqual(res, 3, "incorrect response int") 550 | XCTAssertEqual(str, "3", "incorrect response str") 551 | } 552 | 553 | 554 | func testBasicAsync2SyncConceptBG3_QueueOp() { 555 | let spin = MainSpin() 556 | spin.start() 557 | 558 | var res: Int = 0 559 | var str: String = "" 560 | 561 | let myQueue = DispatchQueue(label: "test", attributes: []) 562 | 563 | backgroundQueue.async { 564 | (res, str) = <<~myQueue ~~~ { self.asyncTest_CallsOnMainReturnsIforBoth(3, handler: $0) } 565 | spin.done() 566 | } 567 | 568 | spin.wait() 569 | 570 | XCTAssertEqual(res, 3, "incorrect response int") 571 | XCTAssertEqual(str, "3", "incorrect response str") 572 | } 573 | 574 | 575 | func testBasicAsync2SyncConceptONMain() { 576 | let (res, str) = <<-{ asyncTest_CallsOnMainReturnsIforBoth(3, handler: $0) } 577 | 578 | XCTAssertEqual(res, 3, "incorrect response int") 579 | XCTAssertEqual(str, "3", "incorrect response str") 580 | } 581 | 582 | func testBasicAsync2SyncConceptONMain2() { 583 | let (res, str) = <<+{ self.asyncTest_CallsOnMainReturnsIforBoth(3, handler: $0) } 584 | 585 | XCTAssertEqual(res, 3, "incorrect response int") 586 | XCTAssertEqual(str, "3", "incorrect response str") 587 | } 588 | 589 | func testBasicAsync2SyncConceptONMain3() { 590 | let (res, str) = <<~{ self.asyncTest_CallsOnMainReturnsIforBoth(3, handler: $0) } 591 | 592 | XCTAssertEqual(res, 3, "incorrect response int") 593 | XCTAssertEqual(str, "3", "incorrect response str") 594 | } 595 | 596 | 597 | /* 598 | func testOptionalReturn() { 599 | { [weak self] in 600 | // This will fatal error 601 | let (res, str) = <<~{ self?.asyncTest_CallsOnMainReturnsIforBoth(3, handler: $0) } 602 | 603 | XCTAssertEqual(res, 3, "incorrect response int") 604 | XCTAssertEqual(str, "3", "incorrect response str") 605 | }() 606 | }*/ 607 | 608 | 609 | // MARK: - Sync To Async 610 | 611 | func testSync2Async_MainA() { 612 | let spin = MainSpin() 613 | spin.start() 614 | var r: Int = 0 615 | var inMain = false 616 | 617 | syncTest_Return4+>>() +>> { inMain = Thread.current.isMainThread; r = $0; spin.done() } 618 | spin.wait() 619 | 620 | XCTAssertEqual(r, 4, "incorrect response int") 621 | XCTAssertEqual(inMain, true, "incorrect queue") 622 | } 623 | 624 | func testSync2Async_MainB() { 625 | let spin = MainSpin() 626 | spin.start() 627 | var r: Int = 0 628 | var inMain = false 629 | 630 | syncTest_Return4 +>> () +>> { inMain = Thread.current.isMainThread; r = $0; spin.done() } 631 | spin.wait() 632 | 633 | XCTAssertEqual(r, 4, "incorrect response int") 634 | XCTAssertEqual(inMain, true, "incorrect queue") 635 | } 636 | 637 | func testSync2Async_MainC() { 638 | let spin = MainSpin() 639 | spin.start() 640 | var r: Int = 0 641 | var inMain = false 642 | 643 | syncTest_ReturnsI+>>.async(4) +>> { inMain = Thread.current.isMainThread; r = $0; spin.done() } 644 | spin.wait() 645 | 646 | XCTAssertEqual(r, 4, "incorrect response int") 647 | XCTAssertEqual(inMain, true, "incorrect queue") 648 | } 649 | 650 | func testSync2Async_MainD() { 651 | let spin = MainSpin() 652 | spin.start() 653 | var r: Int = 0 654 | var inMain = false 655 | 656 | syncTest_ReturnsI +>> (4) +>> { inMain = Thread.current.isMainThread; r = $0; spin.done() } 657 | spin.wait() 658 | 659 | XCTAssertEqual(r, 4, "incorrect response int") 660 | XCTAssertEqual(inMain, true, "incorrect queue") 661 | } 662 | 663 | func testSync2Async_MainE() { 664 | let spin = MainSpin() 665 | spin.start() 666 | var r: Int = 0 667 | var inMain = false 668 | 669 | syncTest_ReturnsI ~>> (4) +>> { inMain = Thread.current.isMainThread; r = $0; spin.done() } 670 | spin.wait() 671 | 672 | XCTAssertEqual(r, 4, "incorrect response int") 673 | XCTAssertEqual(inMain, true, "incorrect queue") 674 | } 675 | 676 | func testSync2Async_MainBothA() { 677 | let spin = MainSpin() 678 | spin.start() 679 | var r: Int = 0 680 | var s: String = "" 681 | var inMain = false 682 | 683 | syncTest_ReturnsIforBoth ~>> (4) +>> { inMain = onMainQueue(); (r, s) = $0; spin.done() } 684 | spin.wait() 685 | 686 | XCTAssertEqual(r, 4, "incorrect response int") 687 | XCTAssertEqual(s, "4", "incorrect response str") 688 | XCTAssertEqual(inMain, true, "incorrect queue") 689 | } 690 | 691 | func testSync2Async_MainBothB() { 692 | let spin = MainSpin() 693 | spin.start() 694 | var r: Int = 0 695 | var s: String = "" 696 | var inMain = false 697 | 698 | syncTest_ReturnsIforBoth ~>> (4) ~>> DispatchQueue.main ~>> { inMain = onMainQueue(); (r, s) = $0; spin.done() } 699 | spin.wait() 700 | 701 | XCTAssertEqual(r, 4, "incorrect response int") 702 | XCTAssertEqual(s, "4", "incorrect response str") 703 | XCTAssertEqual(inMain, true, "incorrect queue") 704 | } 705 | 706 | func testSync2Async_BGBothB() { 707 | let spin = MainSpin() 708 | spin.start() 709 | var r: Int = 0 710 | var s: String = "" 711 | var inMain = false 712 | 713 | syncTest_ReturnsIforBoth ~>> (4) ~>> DispatchQueue(label: "", attributes: []) ~>> { inMain = onMainQueue(); (r, s) = $0; spin.done() } 714 | spin.wait() 715 | 716 | XCTAssertEqual(r, 4, "incorrect response int") 717 | XCTAssertEqual(s, "4", "incorrect response str") 718 | XCTAssertEqual(inMain, false, "incorrect queue") 719 | } 720 | 721 | func testSync2Async_MainRetA() { 722 | var r: Int = 0 723 | var s: String = "" 724 | 725 | (r, s) = syncTest_ReturnsIforBoth~>>.sync(4) 726 | 727 | XCTAssertEqual(r, 4, "incorrect response int") 728 | XCTAssertEqual(s, "4", "incorrect response str") 729 | } 730 | 731 | func testSync2Async_MainRetB() { 732 | var r: Int = 0 733 | var s: String = "" 734 | 735 | (r, s) = syncTest_ReturnsIforBoth+>>.sync(4) 736 | 737 | XCTAssertEqual(r, 4, "incorrect response int") 738 | XCTAssertEqual(s, "4", "incorrect response str") 739 | } 740 | 741 | func testSync2Async_MainRetC() { 742 | var r: Int = 0 743 | var inMain = false 744 | 745 | r = { (i: Int) in inMain = onMainQueue(); return i }+>>.sync(4) 746 | 747 | XCTAssertEqual(r, 4, "incorrect response int") 748 | XCTAssertEqual(inMain, true, "incorrect queue") 749 | } 750 | 751 | func testSync2Async_MainRetD() { 752 | var r: Int = 0 753 | var inMain = true 754 | 755 | r = { (i: Int) in inMain = onMainQueue(); return i }~>>.sync(4) 756 | 757 | XCTAssertEqual(r, 4, "incorrect response int") 758 | XCTAssertEqual(inMain, false, "incorrect queue") 759 | } 760 | 761 | func testSync2Async_MainRetE() { 762 | var inMain = true; 763 | 764 | { inMain = onMainQueue() }~>>.sync() 765 | 766 | XCTAssertEqual(inMain, false, "incorrect queue") 767 | } 768 | 769 | func testSync2Async_MainRetF() { 770 | var inMain = false; 771 | 772 | { inMain = onMainQueue() }+>>.sync() 773 | 774 | XCTAssertEqual(inMain, true, "incorrect queue") 775 | } 776 | 777 | // Compile checks 778 | 779 | func makeSureThisCompiles() { 780 | 781 | syncTest_Return4+>>() +>> { i in } 782 | syncTest_ReturnsI~>>.async(3) +>> { i in } 783 | syncTest_Return4~>>() 784 | syncTest_Return4~>>() ~>> { i in } 785 | syncTest_ReturnsI+>>(4) 786 | (syncTest_ReturnsI+>>(4)) ~>> { (i: Int) in } 787 | syncTest_ReturnsI+>>(4) ~>> { (i: Int) in } 788 | syncTest_ReturnsI~>>(4) 789 | syncTest_ReturnsI~>>(4) +>> { i in } 790 | 791 | let q = DispatchQueue(label: "dkjfd", attributes: []) 792 | 793 | syncTest_ReturnsI +>> (4) ~>> q ~>> { i in } 794 | syncTest_ReturnsI +>> (4) ~>> q ~>> { i in } 795 | syncTest_ReturnsI +>> (4) ~>> { i in } 796 | syncTest_ReturnsVoid ~>> () 797 | syncTest_ReturnsVoidParam ~>> q ~>> (3) 798 | 799 | var s: Int = 0; 800 | { s = 3 } +>> (); 801 | { s = 3 }+>>.sync() 802 | let _ = s 803 | } 804 | 805 | func makeSureThisCompiles(_ p: Int, completionHandler: ((_ i: Int) -> Int)?) { 806 | completionHandler ?+>> (3) +>> { i in } 807 | completionHandler?+>>.async(3) +>> { i in } 808 | let _: Int? = completionHandler?~>>.sync(3) 809 | } 810 | 811 | 812 | // MARK: - Async Functions to Test With 813 | 814 | func asyncTest_CallsOnMainReturns4(_ handler: @escaping (Int) -> Void) { 815 | dispatch_main_async { handler(4) } 816 | } 817 | 818 | func asyncTest_CallsOnMainReturnsI(_ i: Int, handler: @escaping (Int) -> Void) { 819 | dispatch_main_async { handler(i) } 820 | } 821 | 822 | func asyncTest_CallsOnMainReturnsIforBoth(_ i: Int, handler: @escaping (_ i: Int, _ s: String) -> Void) { 823 | dispatch_main_async { handler(i, "\(i)") } 824 | } 825 | 826 | // MARK: - Sync Functions to Test With 827 | 828 | func syncTest_ReturnsVoid() { 829 | 830 | } 831 | 832 | func syncTest_ReturnsVoidParam(_ i: Int) { 833 | 834 | } 835 | 836 | func syncTest_Return4() -> Int { 837 | return 4 838 | } 839 | 840 | func syncTest_ReturnsI(_ i: Int) -> Int { 841 | return i 842 | } 843 | 844 | func syncTest_ReturnsIforBoth(_ i: Int) -> (i: Int, s: String) { 845 | return (i: i, s: "\(i)") 846 | } 847 | 848 | } 849 | 850 | 851 | 852 | -------------------------------------------------------------------------------- /BriskTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /BriskTests/Spin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Spin.swift 3 | // Brisk 4 | // 5 | // Copyright (c) 2016-Present Jason Fieldman - https://github.com/jmfieldman/Brisk 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | protocol Spin : AnyObject { 29 | func start() 30 | func done() 31 | func wait() 32 | } 33 | 34 | 35 | internal class MainSpin : Spin { 36 | var finished: Bool = false 37 | 38 | func start() { 39 | self.finished = false 40 | } 41 | 42 | func done() { 43 | self.finished = true 44 | } 45 | 46 | func wait() { 47 | while !finished { 48 | RunLoop.current.run(mode: RunLoopMode.defaultRunLoopMode, before: Date(timeIntervalSinceNow: 0.1)) 49 | } 50 | } 51 | } 52 | 53 | internal class AsyncSpin : Spin { 54 | let group = DispatchGroup() 55 | 56 | 57 | func start() { 58 | group.enter() 59 | } 60 | 61 | func done() { 62 | group.leave() 63 | } 64 | 65 | func wait() { 66 | _ = group.wait(timeout: DispatchTime.distantFuture) 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Brisk Changelog 2 | 3 | ## 3.1.1 -- 3/31/17 4 | 5 | * Trying to fix cocoapods internal version tagging issue 6 | 7 | ## 3.1.0 -- 3/30/17 8 | 9 | * Updates for Xcode 8.3/Swift 3.1 10 | 11 | ## 3.0.2 -- 12/24/16 12 | 13 | * Added fatalerror calls when passing an optional function to the await operators (<<+) since those must guarantee to call their return function. 14 | 15 | ## 3.0.1 -- 10/13/16 16 | 17 | * Fixed podspec issue for Swift 3.0/cocoapods 18 | 19 | ## 3.0.0 -- 9/22/16 20 | 21 | * First release for Swift 3.0 22 | * Changed GCD component for LibDispatch updates 23 | 24 | ## 2.3.1 -- 9/11/16 25 | 26 | * First release for Swift 2.3 27 | * Removed OSSpinLock API [info](http://engineering.postmates.com/Spinlocks-Considered-Harmful-On-iOS/) 28 | 29 | ## 2.2.2 -- 8/16/16 30 | 31 | * Added operators for optional functions (```?+>>``` and ```?~>>```) 32 | 33 | ## 2.2.1 -- 8/14/16 34 | 35 | * Initial Release for Swift 2.2 36 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | * [Jason Fieldman](https://github.com/jmfieldman) - Maintainer 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jason Fieldman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Package.swift 3 | // Brisk 4 | // 5 | // Copyright (c) 2016-Present Jason Fieldman - https://github.com/jmfieldman/Brisk 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import PackageDescription 26 | 27 | let package = Package( 28 | name: "Brisk" 29 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Brisk](/Assets/Banner.png) 2 | 3 | > NOTE 4 | > 5 | > Brisk is being mothballed due to general incompatibilities with modern version of Swift. I recommend checking out ReactiveSwift, which solves many of the issues that prompted me to make Brisk in the first place. 6 | 7 | Swift support for blocks and asynchronous code is powerful, but can lead to a maze of indented logic that 8 | quickly becomes unreadable and error-prone. 9 | 10 | Brisk offers two distinct but complimentary functions: 11 | 12 | 1. Provides shorthand operators for swiveling the concurrency of your functions (akin to async/await) 13 | 2. Extends ```DispatchQueue``` with several functions that help make standard usage a bit more concise. 14 | 15 | ```swift 16 | // Example: Making an asynchronous function be synchronous 17 | let (data, response, error) = <<-{ URLSession.shared.dataTask(url, completionHandler: $0).resume() } 18 | ``` 19 | 20 | ## Versioning ## 21 | 22 | To help with Cocoapods versioning syntax, all versions of Brisk compatible with Swift 2.2 will begin with Major/Minor 2.2. All versions comptible with Swift 2.3 will begin with Major/Minor 2.3. All versions compatible with Swift 3.0 will begin with Major/Minor 3.0, etc. 23 | 24 | This means your Cocoapod inclusion can look like: 25 | 26 | ``` 27 | pod 'Brisk', '~> 2.2' # Latest version compatible with Swift 2.2 28 | pod 'Brisk', '~> 2.3' # Latest version compatible with Swift 2.3 29 | pod 'Brisk', '~> 3.0' # Latest version compatible with Swift 3.0 30 | pod 'Brisk', '~> 3.1' # Latest version compatible with Swift 3.1 31 | ``` 32 | 33 | > The Brisk API is different in Swift 2.x. Please refer to ```README_SWIFT2.md``` 34 | 35 | ### Quick Look: Concurrency Swiveling ### 36 | 37 | Consider the following hypothetical asynchronous API: 38 | 39 | ```swift 40 | // API we're given: 41 | func findClosestPokemon(within: Double, 42 | completionHandler: (pokemon: Pokemon?, error: NSError?) -> Void) 43 | 44 | func countPokeballs(completionHandler: (number: Int?, error: NSError?) -> Void) 45 | 46 | func throwPokeballAt(pokemon: Pokemon, 47 | completionHandler: (success: Bool, error: NSError?) -> Void) 48 | ``` 49 | 50 | Let's assume that all of the completion handlers are called on the main thread. We want to 51 | make this utility function: 52 | 53 | ```swift 54 | // Utility we want: 55 | func throwAtClosestPokemon(within: Double, 56 | completionHandler: (success: Bool, pokemon: Pokemon?, error: NSError?) -> Void) 57 | ``` 58 | 59 | This function represents a common occurrence of chaining asynchronous functions into a helper utility for a single use case. 60 | Using only the standard GCD library, your function might look like this: 61 | 62 | ```swift 63 | // The old way... 64 | func throwAtClosestPokemon(within: Double, 65 | completionHandler: (success: Bool, pokemon: Pokemon?, error: NSError?) -> Void) { 66 | // Step 1 67 | findClosestPokemon(within: within) { pokemon, error in 68 | guard let p = pokemon where error == nil else { 69 | DispatchQueue.main.async { 70 | completionHandler(success: false, pokemon: nil, error: error) 71 | } 72 | return 73 | } 74 | 75 | // Step 2 76 | countPokeballs { number, error in 77 | guard let n = number where error == nil else { 78 | DispatchQueue.main.async { 79 | completionHandler(success: false, pokemon: nil, error: error) 80 | } 81 | return 82 | } 83 | 84 | // Step 3 85 | throwPokeballAt(pokemon: p) { success, error in 86 | DispatchQueue.main.async { 87 | completionHandler(success: success, error: error) 88 | } 89 | } 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | Yikes! It can quickly look even worse if your async logic needs to branch. Let's look at how scoping/flow works with Brisk: 96 | 97 | ```swift 98 | // The new way... 99 | func throwAtClosestPokemon(within: Double, 100 | completionHandler: (success: Bool, pokemon: Pokemon?, error: NSError?) -> Void) { 101 | 102 | // Run everything inside a specified async queue, or DispatchQueue.global() 103 | myQueue.async { 104 | 105 | // Step 1 106 | let (pokemon, error) = <<+{ findClosestPokemon(within: within, completionHandler: $0) } 107 | guard let p = pokemon where error == nil else { 108 | return completionHandler +>> (success: false, error: error) 109 | } 110 | 111 | // Step 2 112 | let (number, error2) = <<+{ countPokeballs($0) } 113 | guard let n = number where error2 == nil else { 114 | return completionHandler +>> (success: false, error: error2) 115 | } 116 | 117 | // Step 3 118 | let (success, error3) = <<+{ throwPokeballAt(pokemon: p, completionHandler: $0) } 119 | completionHandler +>> (success: success, error: error3) 120 | } 121 | } 122 | ``` 123 | 124 | With Brisk the asynchronous functions can be coded using a seemingly-synchronous flow. 125 | The asynchronous nature of the methods is hidden behind the custom operators. *Unlike PromiseKit, all return values 126 | remain in scope as well*. 127 | 128 | ## Calling Asynchronous Functions Synchronously ## 129 | 130 | This section refers the idea of taking am asynchronous function and calling 131 | it synchronously, generally for the purpose of chaining multiple asynchronous 132 | operations. This is essentially the same offering of PromiseKit but without 133 | the needless indentation and scope shuffle that comes with it. 134 | 135 | To see a practical use case, refer to the Quick Look example above. 136 | 137 | When we talk about an asynchronous function, it must abide by these characteristics: 138 | 139 | * Returns ```Void``` 140 | * Takes any number of input parameters 141 | * Has a single "completion" parameter that takes a function of the form ```(...) -> Void``` 142 | 143 | These are all examples of suitable asynchronous functions: 144 | 145 | ```swift 146 | func getAlbum(named: String, handler: (album: PhotoAlbum?, error: NSError?) -> Void) 147 | func saveUser(completionHandler: (success: Bool) -> Void) 148 | func verifyUser(name: String, password: String, completion: (valid: Bool) -> Void) 149 | 150 | // Typical use of a function would look like: 151 | getAlbum("pics") { photo, error in 152 | // ... 153 | } 154 | ``` 155 | 156 | With Brisk, you can use the ```<<+```, ```<<~``` or ```<<-``` operators to call your function in 157 | a way that blocks the calling thread until your function has called its completion 158 | handler. 159 | 160 | ```swift 161 | // <<+ will execute getAlbum on the main queue 162 | let (album, error) = <<+{ getAlbum("pics", handler: $0) } 163 | 164 | // <<~ will execute saveUser on the global concurrent background queue 165 | let success = <<~{ saveUser($0) } 166 | 167 | // <<- will execute verifyUser immediately in the current queue (note that the 168 | // current thread will wait for the completion handler to be called before 169 | // returning the final value.) 170 | let valid = <<-{ verifyUser("myname", password: "mypass", completion: $0) } 171 | 172 | // You can also specify *any* queue you want. Here saveUser is called on myQueue. 173 | let myQueue = dispatch_queue_create("myQueue", nil) 174 | let valid = <<~myQueue ~~~ { saveUser($0) } 175 | ``` 176 | 177 | > Tip: Use ```<<+``` for functions that 178 | > need to be called on the main thread (like UI updates). Use ```<<-``` for others. 179 | 180 | In all of the above examples, execution of the outer thread is paused until the completion 181 | handler ```$0``` is called. Once ```$0``` is called, the values passed into it are routed back 182 | to the original assignment operation. 183 | 184 | Note that the ```$0``` handler can accommodate any number of parameters (e.g. ```getAlbum``` 185 | above can take ```album``` and ```error```), *but it must be assigned to a variable that 186 | of the same tuple*. Also note that it is not possible to extract ```NSError``` parameters 187 | to transform them into do/try/catch methodology -- you will have to check the ```NSError``` 188 | as part of the returned tuple. 189 | 190 | **Also note that the outer thread *WILL WAIT* until ```$0``` is called.** This means that 191 | Brisk can only be used for functions that guarantee their completion handlers will 192 | be called at some deterministic point in the future. It is not suitable for open-ended 193 | asynchronous functions like ```NSNotification``` handlers. 194 | 195 | 196 | ## Calling Synchronous Functions Asynchronously ## 197 | 198 | There are many reasons to call synchronous functions asynchronously. It happens any 199 | time you see this pattern: 200 | 201 | ```swift 202 | dispatch_async(someQueue) { 203 | completionHandler(..) 204 | } 205 | ``` 206 | 207 | You're burning three lines and an indentation scope just to route a single function call to 208 | another queue. 209 | 210 | An example of this is in the Quick Look example from the beginning of the documentation. This 211 | routing must take place each time the completion handler is called on the main queue. It 212 | has a negative impact on the readability of the overall function, since the actual function 213 | name gets buried in the scope of the dispatch. Wouldn't it be nice if that could be 214 | accomplished in one line, with the function name first? 215 | 216 | The ```~>>``` and ```+>>``` operators introduced in Brisk can be thought of as the 217 | synchronous->asynchronous translators. The main difference between the two is that 218 | the ```+>>``` operator dispatches to the main queue, while the ```~>>``` operator 219 | allows you to specify the queue (or use the concurrent background queue by default). 220 | 221 | For the examples below, consider the following normal synchronous functions: 222 | 223 | ```swift 224 | func syncReturnsVoid() { } 225 | func syncReturnsParam(p: Int) -> Int { return p+1 } 226 | func syncReturnsParamTuple(p: Int) -> (Int, String) { return (p+1, "\(p+1)") } 227 | ``` 228 | 229 | Use the infix operator between a function and its parameters to quickly dispatch a synchronous function on 230 | another queue. 231 | 232 | ```swift 233 | dispatch_async(someQueue) { 234 | 235 | // syncReturnsVoid() is called on the main thread 236 | syncReturnsVoid +>> () 237 | 238 | // syncReturnsParam(p: 3) is called on the main thread 239 | // Note in this case the return value is ignored! 240 | syncReturnsParam +>> (p: 3) 241 | 242 | // syncReturnsVoid() is called on the global concurrent background queue 243 | syncReturnsVoid ~>> () 244 | 245 | // syncReturnsParam(p: 3) is called on the global concurrent background queue 246 | // Note in this case the return value is ignored! 247 | syncReturnsParam ~>> (p: 3) 248 | 249 | let otherQueue = dispatch_queue_create("otherQueue", nil) 250 | 251 | // syncReturnsVoid() is called on otherQueue 252 | syncReturnsVoid ~>> otherQueue ~>> () 253 | 254 | // syncReturnsParam(p: 3) is called on otherQueue 255 | // Note in this case the return value is ignored! 256 | syncReturnsParam ~>> otherQueue ~>> (p: 3) 257 | } 258 | ``` 259 | 260 | You can also use the operators in a postfix fashion for a more functional syntax: 261 | 262 | ```swift 263 | dispatch_async(someQueue) { 264 | let otherQueue = dispatch_queue_create("otherQueue", nil) 265 | 266 | // The following three lines are equivalent 267 | syncReturnsParam~>>.on(otherQueue).async(p: 3) 268 | syncReturnsParam~>>otherQueue~>>(p: 3) 269 | syncReturnsParam ~>> otherQueue ~>> (p: 3) 270 | } 271 | ``` 272 | 273 | In all of the above examples, the return values were ignored. This is generally fine 274 | for the synchronous functions that return ```Void``` (like most completion handlers). 275 | Because the functions are called asynchronously, you have to process the return 276 | values asynchronously as well: 277 | 278 | ```swift 279 | dispatch_async(someQueue) { 280 | 281 | // syncReturnsParam(p: 3) is called on the main thread 282 | // Its response is also handled on the main thread 283 | syncReturnsParam +>> (p: 3) +>> { i in print(i) } // prints 4 284 | 285 | // syncReturnsParam(p: 3) is called on the main thread 286 | // Its response is handled on the global concurrent background queue 287 | // Note the positions and difference between +>> and ~>> 288 | syncReturnsParam +>> (p: 3) ~>> { i in print(i) } // prints 4 289 | 290 | // syncReturnsParamTuple(p: 3) is called on the global concurrent background queue 291 | // Its response is handled on an instantiated queue 292 | syncReturnsParamTuple ~>> (p: 3) ~>> otherQueue ~>> { iInt, iStr in print(pInt) } 293 | 294 | // Using the more functional style 295 | syncReturnsParam~>>.on(otherQueue).async(p: 3) +>> { i in print(i) } 296 | } 297 | ``` 298 | 299 | ### Optionals ### 300 | 301 | When the function you are routing is an optional, you must use the ```?~>>``` and ```?+>>``` 302 | operators when referencing the function: 303 | 304 | ```swift 305 | func myTest(param: Int, completionHandler: (Int -> Int)? = nil) { 306 | 307 | // These will cause a compiler error because the handler is optional: 308 | completionHandler +>> (param) 309 | completionHandler+>>.async(param) +>> { i in print(i) } 310 | 311 | // Instead use these: 312 | completionHandler ?+>> (param) 313 | completionHandler?~>>.async(param) 314 | 315 | // For anything past the initial function, use normal operators: 316 | // (+>> instead of ?+>>) --v 317 | completionHandler ?+>> (param) +>> { i in print(i) } 318 | 319 | } 320 | ``` 321 | 322 | > Note that if you were using the syntax for optionals 323 | > ```completionHandler?~>>.main.async(param)``` 324 | > -- Swift 3.1 does not allow ? characters at the beginning of postfix 325 | > operators so you can no longer explicitly choose a queue name 326 | > like ".main". You can still use queues implicitly with the operator, like 327 | > ```completionHandler?+>>.async(param)``` 328 | 329 | 330 | ## Swift 3.x LibDispatch Additions ## 331 | 332 | Brisk extensions ```DispatchQueue``` with functions that make the ```async``` function 333 | more concise: 334 | 335 | ```swift 336 | /// LibDispatch: 337 | func asyncAfter(deadline: DispatchTime, 338 | qos: DispatchQoS = default, 339 | flags: DispatchWorkItemFlags = default, 340 | execute: () -> Void) 341 | 342 | // Brisk allows you to specify time/intervals as a Double instead of DispatchTime. 343 | // It also allows you to capture the timer used to dispatch the block, in case 344 | // you want to cancel it. 345 | func async(after seconds: Double, 346 | leeway: QuickDispatchTimeInterval? = nil, 347 | qos: DispatchQoS = .default, 348 | flags: DispatchWorkItemFlags = [], 349 | execute block: @escaping () -> Void) -> DispatchSourceTimer 350 | ``` 351 | 352 | Also consider scheduling a block to run repeatedly at an interval: 353 | 354 | ```swift 355 | // LibDispatch Requires: 356 | let timer = DispatchSource.makeTimerSource(flags: ..., queue: ...) 357 | timer.setEventHandler(qos: ..., flags: ..., handler: ...) 358 | timer.scheduleRepeating(deadline: ..., interval: ..., leeway: ...) 359 | timer.resume() 360 | 361 | // Brisk allows you to schedule timers in one function, and passes the timer 362 | // into the block so it can be canceled based on logic inside or outside the handler. 363 | func async(every interval: Double, 364 | startingIn: Double? = nil, 365 | startingAt: NSDate? = nil, 366 | leeway: QuickDispatchTimeInterval? = nil, 367 | qos: DispatchQoS = .default, 368 | flags: DispatchWorkItemFlags = [], 369 | execute block: @escaping (_ tmr: DispatchSourceTimer) -> Void) -> DispatchSourceTimer 370 | 371 | ``` 372 | 373 | Another new function allows you to coalesce multiple async calls into a single execution, 374 | based on an ```operationId```. This is useful when several simultaneous asynchronous 375 | actions want to trigger a block to occur (but you only want that block to occur once). 376 | 377 | ```swift 378 | func once(operationId: String, 379 | after interval: Double? = nil, 380 | at date: NSDate? = nil, 381 | leeway: QuickDispatchTimeInterval? = nil, 382 | qos: DispatchQoS = .default, 383 | flags: DispatchWorkItemFlags = [], 384 | execute block: @escaping () -> Void) -> DispatchSourceTimer 385 | ``` 386 | 387 | There are several variations of the above functions. See ```BriskDispatch.swift``` for more details. 388 | 389 | ## Deprecated Swift 2.x GCD Additions ## 390 | 391 | The following code examples show the GCD additions provided by Brisk for the Swift 2.x syntax. These are 392 | fairly self-documenting. More information about each method can be found in its comment section. They are 393 | included in the Swift 3.x release for backwards compatibility. 394 | 395 | ```swift 396 | dispatch_main_async { 397 | // Block runs on the main queue 398 | } 399 | 400 | dispatch_main_sync { 401 | // Block runs on the main queue; this function does not return until 402 | // the block completes. 403 | } 404 | 405 | dispatch_bg_async { 406 | // Block runs on the global concurrent background queue 407 | } 408 | 409 | dispatch_async("myNewQueue") { 410 | // Block runs on a brisk-created serial queue with the specified string ID. 411 | // Calling this function multiple times with the same string will reuse the 412 | // named queue. Useful for dynamic throw-away serial queues. 413 | } 414 | 415 | dispatch_main_after(2.0) { 416 | dispatch_after(2.0, myQueue) { 417 | // Block is called on specified queue after specified number of seconds using 418 | // a loose leeway (+/- 0.1 seconds). 419 | } 420 | 421 | dispatch_main_after_exactly(2.0) { 422 | dispatch_after_exactly(2.0, myQueue) { 423 | // Block is called on specified queue after specified number of seconds using 424 | // as tight a timer leeway as possible. Useful for animation timing but 425 | // uses more battery power. 426 | } 427 | 428 | dispatch_main_every(2.0) { timer in 429 | dispatch_every(2.0, myQueue) { timer in 430 | dispatch_main_every_exact(2.0) { timer in 431 | dispatch_every_exact(2.0, myQueue) { timer in 432 | // Block is run on specified thread every N seconds. 433 | // Stop the timer with: 434 | dispatch_source_cancel(timer) 435 | } 436 | 437 | dispatch_main_once_after(2.0, "myOperationId") { 438 | dispatch_once_after(2.0, myQueue, "myOperationId") { 439 | // Block runs after specified time on specified queue. The block is 440 | // only executed ONCE -- repeat calls to this function with the same 441 | // operation ID will reset its internal timer instead of calling the 442 | // block again. Useful for calling a completion block after several 443 | // disparate asynchronous methods (e.g. saving the database to disk 444 | // after downloading multiple records on separate threads.) 445 | } 446 | 447 | dispatch_each(myArray, myQueue) { element in 448 | // Each element in the array has this block called with it as a parameter. 449 | // Should be used on a concurrent queue. 450 | } 451 | ``` 452 | -------------------------------------------------------------------------------- /README_SWIFT2.md: -------------------------------------------------------------------------------- 1 | ![Brisk](/Assets/Banner.png) 2 | 3 | > This is the README for the Swift 2.x Brisk Library 4 | 5 | Swift support for blocks and asynchronous code is powerful, but can lead to a maze of indented logic that 6 | quickly becomes unreadable and error-prone. 7 | 8 | Brisk offers two distinct but complimentary functions: 9 | 10 | 1. Extends the standard GCD library with several functions that help make standard usage a bit more concise. 11 | 2. Provides shorthand operators for swiveling the concurrency of your functions. 12 | 13 | These might be best explained by code example. 14 | 15 | ### Quick Look: Extending the GCD Library for Simplicity ### 16 | 17 | Consider the existing methods required from the standard GCD library: 18 | 19 | ```swift 20 | // Dispatch a block to the main queue 21 | dispatch_async(dispatch_get_main_queue()) { 22 | // ... 23 | } 24 | 25 | // Dispatch a block to the main queue after 2.0 seconds 26 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Double(NSEC_PER_SEC))), 27 | dispatch_get_main_queue()) { 28 | // ... 29 | } 30 | ``` 31 | 32 | Compared to their Brisk equivalents: 33 | 34 | ```swift 35 | // Dispatch a block to the main queue 36 | dispatch_main_async { 37 | // ... 38 | } 39 | 40 | // Dispatch a block to the main queue after 2.0 seconds 41 | dispatch_main_after(2.0) { 42 | // ... 43 | } 44 | ``` 45 | 46 | Brisk offers this type of simplification for many standard GCD use cases. 47 | 48 | ### Quick Look: Concurrency Swiveling ### 49 | 50 | Consider the following hypothetical asynchronous API (using Swift 2.2 function syntax): 51 | 52 | ```swift 53 | // API we're given: 54 | func findClosestPokemonWithin(within: Double, 55 | completionHandler: (pokemon: Pokemon?, error: NSError?) -> Void) 56 | 57 | func countPokeballs(completionHandler: (number: Int?, error: NSError?) -> Void) 58 | 59 | func throwPokeballAtPokemon(pokemon: Pokemon, 60 | completionHandler: (success: Bool, error: NSError?) -> Void) 61 | ``` 62 | 63 | Let's assume that all of the completion handlers are called on the main thread. We want to 64 | make this utility function: 65 | 66 | ```swift 67 | // Utility we want: 68 | func throwAtClosestPokemonWithin(within: Double, 69 | completionHandler: (success: Bool, pokemon: Pokemon?, error: NSError?) -> Void) 70 | ``` 71 | 72 | This function represents a common occurrence of chaining asynchronous functions into a helper utility for a single use case. 73 | Using only the standard GCD library, your function might look like this: 74 | 75 | ```swift 76 | // The old way... 77 | func throwAtClosestPokemonWithin(within: Double, 78 | completionHandler: (success: Bool, pokemon: Pokemon?, error: NSError?) -> Void) { 79 | // Step 1 80 | findClosestPokemonWithin(within) { pokemon, error in 81 | guard let p = pokemon where error == nil else { 82 | dispatch_main_async { 83 | completionHandler(success: false, pokemon: nil, error: error) 84 | } 85 | return 86 | } 87 | 88 | // Step 2 89 | countPokeballs { number, error in 90 | guard let n = number where error == nil else { 91 | dispatch_main_async { 92 | completionHandler(success: false, pokemon: nil, error: error) 93 | } 94 | return 95 | } 96 | 97 | // Step 3 98 | throwPokeballAtPokemon(p) { success, error in 99 | dispatch_main_async { 100 | completionHandler(success: success, error: error) 101 | } 102 | } 103 | } 104 | } 105 | } 106 | ``` 107 | 108 | With Brisk: 109 | 110 | ```swift 111 | // The new way... 112 | func throwAtClosestPokemonWithin(within: Double, 113 | completionHandler: (success: Bool, pokemon: Pokemon?, error: NSError?) -> Void) { 114 | dispatch_bg_async { 115 | 116 | // Step 1 117 | let (pokemon, error) = <<+{ findClosestPokemonWithin(within, completionHandler: $0) } 118 | guard let p = pokemon where error == nil else { 119 | return completionHandler +>> (success: false, error: error) 120 | } 121 | 122 | // Step 2 123 | let (number, error2) = <<+{ countPokeballs($0) } 124 | guard let n = number where error2 == nil else { 125 | return completionHandler +>> (success: false, error: error2) 126 | } 127 | 128 | // Step 3 129 | let (success, error3) = <<+{ throwPokeballAtPokemon(p, completionHandler: $0) } 130 | completionHandler +>> (success: success, error: error3) 131 | } 132 | } 133 | ``` 134 | 135 | The main advantage with Brisk is that the asynchronous functions can be coded using a seemingly-synchronous flow. 136 | The asynchronous nature of the methods is hidden behind the custom operators. *Unlike PromiseKit, all return values 137 | remain in scope as well*. 138 | 139 | ## Detailed GCD Additions ## 140 | 141 | The following code examples show the GCD additions provided by Brisk. These are 142 | fairly self-documenting. More information about each method can be found in its comment section. 143 | 144 | ```swift 145 | dispatch_main_async { 146 | // Block runs on the main queue 147 | } 148 | 149 | dispatch_main_sync { 150 | // Block runs on the main queue; this function does not return until 151 | // the block completes. 152 | } 153 | 154 | dispatch_bg_async { 155 | // Block runs on the global concurrent background queue 156 | } 157 | 158 | dispatch_async("myNewQueue") { 159 | // Block runs on a brisk-created serial queue with the specified string ID. 160 | // Calling this function multiple times with the same string will reuse the 161 | // named queue. Useful for dynamic throw-away serial queues. 162 | } 163 | 164 | dispatch_main_after(2.0) { 165 | dispatch_after(2.0, myQueue) { 166 | // Block is called on specified queue after specified number of seconds using 167 | // a loose leeway (+/- 0.1 seconds). 168 | } 169 | 170 | dispatch_main_after_exactly(2.0) { 171 | dispatch_after_exactly(2.0, myQueue) { 172 | // Block is called on specified queue after specified number of seconds using 173 | // as tight a timer leeway as possible. Useful for animation timing but 174 | // uses more battery power. 175 | } 176 | 177 | dispatch_main_every(2.0) { timer in 178 | dispatch_every(2.0, myQueue) { timer in 179 | dispatch_main_every_exact(2.0) { timer in 180 | dispatch_every_exact(2.0, myQueue) { timer in 181 | // Block is run on specified thread every N seconds. 182 | // Stop the timer with: 183 | dispatch_source_cancel(timer) 184 | } 185 | 186 | dispatch_main_once_after(2.0, "myOperationId") { 187 | dispatch_once_after(2.0, myQueue, "myOperationId") { 188 | // Block runs after specified time on specified queue. The block is 189 | // only executed ONCE -- repeat calls to this function with the same 190 | // operation ID will reset its internal timer instead of calling the 191 | // block again. Useful for calling a completion block after several 192 | // disparate asynchronous methods (e.g. saving the database to disk 193 | // after downloading multiple records on separate threads.) 194 | } 195 | 196 | dispatch_each(myArray, myQueue) { element in 197 | // Each element in the array has this block called with it as a parameter. 198 | // Should be used on a concurrent queue. 199 | } 200 | ``` 201 | 202 | ## Calling Asynchronous Functions Synchronously ## 203 | 204 | This section refers the idea of taking a naturally asynchronous function and calling 205 | it synchronously, generally for the purpose of chaining multiple asynchronous 206 | operations. This is essentially the same offering of PromiseKit but without 207 | the needless indentation and scope shuffle that comes with it. 208 | 209 | To see a practical use case, refer to the Quick Look example at the beginning of 210 | this document. 211 | 212 | When we talk about an asynchronous function, it must abide by these characteristics: 213 | 214 | * Returns ```Void``` 215 | * Takes any number of input parameters 216 | * Has a single "completion" parameter that takes a function of the form ```(...) -> Void``` 217 | 218 | These are all examples of suitable asynchronous functions: 219 | 220 | ```swift 221 | func getAlbum(named: String, handler: (album: PhotoAlbum?, error: NSError?) -> Void) 222 | func saveUser(completionHandler: (success: Bool) -> Void) 223 | func verifyUser(name: String, password: String, completion: (valid: Bool) -> Void) 224 | 225 | // Typical use of a function would look like: 226 | getAlbum("pics") { photo, error in 227 | // ... 228 | } 229 | ``` 230 | 231 | With Brisk, you can use the ```<<+```, ```<<~``` or ```<<-``` operators to call your function in 232 | a way that blocks the calling thread until your function has called its completion 233 | handler. 234 | 235 | ```swift 236 | // <<+ will execute getAlbum on the main queue 237 | let (album, error) = <<+{ getAlbum("pics", handler: $0) } 238 | 239 | // <<~ will execute saveUser on the global concurrent background queue 240 | let success = <<~{ saveUser($0) } 241 | 242 | // <<- will execute verifyUser immediately in the current queue (note that the 243 | // current thread will wait for the completion handler to be called before 244 | // returning the final value.) 245 | let valid = <<-{ verifyUser("myname", password: "mypass", completion: $0) } 246 | 247 | // You can also specify *any* queue you want. Here saveUser is called on myQueue. 248 | let myQueue = dispatch_queue_create("myQueue", nil) 249 | let valid = <<~myQueue ~~~ { saveUser($0) } 250 | ``` 251 | 252 | In all of the above examples, execution of the outer thread is paused until the completion 253 | handler ```$0``` is called. Once ```$0``` is called, the values passed into it are routed back 254 | to the original assignment operation. 255 | 256 | Note that the ```$0``` handler can accommodate any number of parameters (e.g. ```getAlbum``` 257 | above can take ```album``` and ```error```), but it must be assigned to variables that 258 | create the same tuple. Also note that it is not possible to extract ```NSError``` parameters 259 | to transform them into do/try/catch methodology -- you will have to check the ```NSError``` 260 | as part of the returned tuple. 261 | 262 | Also note that the outer thread WILL PAUSE until ```$0``` is called. This means that 263 | Brisk can only be used for functions that guarantee their completion handlers will 264 | be called at some deterministic point in the future. It is not suitable for open-ended 265 | asynchronous functions like ```NSNotification``` handlers. 266 | 267 | Another really important note: because of the requirement that ```$0``` is called, you should 268 | never use this pattern with optional functions unless you can guarantee they are not nil! 269 | 270 | ```swift 271 | func testFunction(handler: (Int -> Void)? = nil) { 272 | // This call will block forever if handler is nil! 273 | let z: Int = <<~{ handler?($0) } 274 | } 275 | ``` 276 | 277 | ## Calling Synchronous Functions Asynchronously ## 278 | 279 | There are many reasons to call synchronous functions asynchronously. It's happening any 280 | time you see this pattern: 281 | 282 | ```swift 283 | dispatch_async(someQueue) { 284 | // Do something 285 | } 286 | ``` 287 | 288 | The stylistic problem with the pattern above is when ```"// Do something"``` is a single function. 289 | You're burning three lines and an indentation scope just to route a single function call to 290 | another queue. 291 | 292 | An example of this is in the Quick Look example from the beginning of the documentation. This 293 | routing must take place each time the completion handler is called on the main queue. It 294 | has a negative impact on the readability of the overall function, since the actual function 295 | name gets buried in the scope of the dispatch. Wouldn't it be nice if that could be 296 | accomplished in one line, with the function name first? 297 | 298 | The ```~>>``` and ```+>>``` operators introduced in Brisk can be thought of as the 299 | synchronous->asynchronous translators. The main difference between the two is that 300 | the ```+>>``` operator dispatches to the main queue, while the ```~>>``` operator 301 | allows you to specify the queue (or use the concurrent background queue by default). 302 | 303 | For the examples below, consider the following normal synchronous functions: 304 | 305 | ```swift 306 | func syncReturnsVoid() { } 307 | func syncReturnsParam(p: Int) -> Int { return p+1 } 308 | func syncReturnsParamTuple(p: Int) -> (Int, String) { return (p+1, "\(p+1)") } 309 | ``` 310 | 311 | Use the infix operator between a function and its parameters to quickly dispatch a synchronous function on 312 | another queue. 313 | 314 | ```swift 315 | dispatch_async(someQueue) { 316 | 317 | // syncReturnsVoid() is called on the main thread 318 | syncReturnsVoid +>> () 319 | 320 | // syncReturnsParam(p: 3) is called on the main thread 321 | // Note in this case the return value is ignored! 322 | syncReturnsParam +>> (p: 3) 323 | 324 | // syncReturnsVoid() is called on the global concurrent background queue 325 | syncReturnsVoid ~>> () 326 | 327 | // syncReturnsParam(p: 3) is called on the global concurrent background queue 328 | // Note in this case the return value is ignored! 329 | syncReturnsParam ~>> (p: 3) 330 | 331 | let otherQueue = dispatch_queue_create("otherQueue", nil) 332 | 333 | // syncReturnsVoid() is called on otherQueue 334 | syncReturnsVoid ~>> otherQueue ~>> () 335 | 336 | // syncReturnsParam(p: 3) is called on otherQueue 337 | // Note in this case the return value is ignored! 338 | syncReturnsParam ~>> otherQueue ~>> (p: 3) 339 | } 340 | ``` 341 | 342 | You can also use the operators in a postfix fashion for a more functional syntax: 343 | 344 | ```swift 345 | dispatch_async(someQueue) { 346 | let otherQueue = dispatch_queue_create("otherQueue", nil) 347 | 348 | // The following three lines are equivalent 349 | syncReturnsParam~>>.on(otherQueue).async(p: 3) 350 | syncReturnsParam~>>otherQueue~>>(p: 3) 351 | syncReturnsParam ~>> otherQueue ~>> (p: 3) 352 | } 353 | ``` 354 | 355 | In all of the above examples, the return values were ignored. This is generally fine 356 | for the synchronous functions that return ```Void``` (like most completion handlers). 357 | Because the functions are called asynchronously, you have to process the return 358 | values asynchronously as well: 359 | 360 | ```swift 361 | dispatch_async(someQueue) { 362 | 363 | // syncReturnsParam(p: 3) is called on the main thread 364 | // Its response is also handled on the main thread 365 | syncReturnsParam +>> (p: 3) +>> { i in print(i) } // prints 4 366 | 367 | // syncReturnsParam(p: 3) is called on the main thread 368 | // Its response is handled on the global concurrent background queue 369 | // Note the positions and difference between +>> and ~>> 370 | syncReturnsParam +>> (p: 3) ~>> { i in print(i) } // prints 4 371 | 372 | // syncReturnsParamTuple(p: 3) is called on the global concurrent background queue 373 | // Its response is handled on an instantiated queue 374 | syncReturnsParamTuple ~>> (p: 3) ~>> otherQueue ~>> { iInt, iStr in print(pInt) } 375 | 376 | // Using the more functional style 377 | syncReturnsParam~>>.on(otherQueue).async(p: 3) +>> { i in print(i) } 378 | } 379 | ``` 380 | 381 | ### Optionals ### 382 | 383 | When the function you are routing is an optional, you must use the ```?~>>``` and ```?+>>``` 384 | operators when referencing the function: 385 | 386 | ```swift 387 | func myTest(param: Int, completionHandler: (Int -> Int)? = nil) { 388 | 389 | // These will cause a compiler error because the handler is optional: 390 | completionHandler +>> (param) 391 | completionHandler+>>.async(param) +>> { i in print(i) } 392 | 393 | // Instead use these: 394 | completionHandler ?+>> (param) 395 | completionHandler?~>>.async(param) 396 | 397 | // For anything past the initial function, use normal operators: 398 | // (+>> instead of ?+>>) --v 399 | completionHandler ?+>> (param) +>> { i in print(i) } 400 | 401 | } 402 | ``` 403 | 404 | ### Routing a Block Synchronously to Another Queue ### 405 | 406 | Using the functional syntax, you can route a function (or any block) to another 407 | thread while the calling thread waits. This can be very useful for cases where 408 | we want to update UI, or some other main-thread-dependent resource from a background 409 | thread. 410 | 411 | ```swift 412 | dispatch_async(someQueue) { 413 | // In the middle of some background code we want to change the UI 414 | 415 | // This does it asynchronously (both are the same): 416 | { self.label.hidden = true }+>>(); 417 | { self.label.hidden = true }+>>.async(); 418 | 419 | // This does it synchronously (call waits until change is made) 420 | { self.label.hidden = true }+>>.sync(); 421 | 422 | // The above statement is equivalent to 423 | dispatch_main_sync { 424 | self.label.hidden = true 425 | } 426 | } 427 | ``` 428 | 429 | *Note that because of the Swift compiler, you may need to include a semicolon on the line 430 | before you create a statement with a block as the left-most expression.* 431 | 432 | ## Versioning ## 433 | 434 | To help with Cocoapods versioning syntax, all versions of Brisk compatible with Swift 2.2 will begin with Major/Minor 2.2. All versions comptible with Swift 2.3 will begin with Major/Minor 2.3. All versions compatible with Swift 3.0 will begin with Major/Minor 3.0, etc. 435 | 436 | This means your Cocoapod inclusion can look like: 437 | 438 | ``` 439 | pod 'Brisk', '~> 2.2' # Latest version compatible with Swift 2.2 440 | pod 'Brisk', '~> 2.3' # Latest version compatible with Swift 2.3 441 | pod 'Brisk', '~> 3.0' # Latest version compatible with Swift 3.0 442 | ``` 443 | --------------------------------------------------------------------------------