{"id":930,"url":"https://github.com/jmfieldman/Brisk","last_synced_at":"2025-07-30T19:33:04.000Z","repository":{"id":56904034,"uuid":"65643993","full_name":"jmfieldman/Brisk","owner":"jmfieldman","description":"A Swift DSL that allows concise and effective concurrency manipulation","archived":false,"fork":false,"pushed_at":"2019-05-24T15:26:11.000Z","size":135,"stargazers_count":25,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-15T23:48:07.216Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jmfieldman.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-08-14T01:14:12.000Z","updated_at":"2019-05-24T15:26:13.000Z","dependencies_parsed_at":"2022-08-21T01:50:53.256Z","dependency_job_id":null,"html_url":"https://github.com/jmfieldman/Brisk","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmfieldman%2FBrisk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmfieldman%2FBrisk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmfieldman%2FBrisk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmfieldman%2FBrisk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jmfieldman","download_url":"https://codeload.github.com/jmfieldman/Brisk/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228179030,"owners_count":17881126,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-01-05T20:15:34.884Z","updated_at":"2024-12-04T19:32:25.662Z","avatar_url":"https://github.com/jmfieldman.png","language":"Swift","funding_links":[],"categories":["Concurrency"],"sub_categories":["Linter","Other free courses"],"readme":"![Brisk](/Assets/Banner.png)\n\n\u003e NOTE\n\u003e \n\u003e 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.\n\nSwift support for blocks and asynchronous code is powerful, but can lead to a maze of indented logic that\nquickly becomes unreadable and error-prone.\n\nBrisk offers two distinct but complimentary functions:\n\n1. Provides shorthand operators for swiveling the concurrency of your functions (akin to async/await)\n2. Extends ```DispatchQueue``` with several functions that help make standard usage a bit more concise.\n\n```swift\n// Example: Making an asynchronous function be synchronous \nlet (data, response, error) = \u003c\u003c-{ URLSession.shared.dataTask(url, completionHandler: $0).resume() }\n```\n\n## Versioning ##\n\nTo 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.\n\nThis means your Cocoapod inclusion can look like:\n\n```\npod 'Brisk', '~\u003e 2.2' # Latest version compatible with Swift 2.2\npod 'Brisk', '~\u003e 2.3' # Latest version compatible with Swift 2.3\npod 'Brisk', '~\u003e 3.0' # Latest version compatible with Swift 3.0\npod 'Brisk', '~\u003e 3.1' # Latest version compatible with Swift 3.1\n```\n\n\u003e The Brisk API is different in Swift 2.x.  Please refer to ```README_SWIFT2.md```\n\n### Quick Look: Concurrency Swiveling ###\n\nConsider the following hypothetical asynchronous API:\n\n```swift\n// API we're given:\nfunc findClosestPokemon(within: Double,\n             completionHandler: (pokemon: Pokemon?, error: NSError?) -\u003e Void)\n\nfunc countPokeballs(completionHandler: (number: Int?, error: NSError?) -\u003e Void)\n\nfunc throwPokeballAt(pokemon: Pokemon,\n           completionHandler: (success: Bool, error: NSError?) -\u003e Void)\n```\n\nLet's assume that all of the completion handlers are called on the main thread.  We want to\nmake this utility function:\n\n```swift\n// Utility we want:\nfunc throwAtClosestPokemon(within: Double,\n                completionHandler: (success: Bool, pokemon: Pokemon?, error: NSError?) -\u003e Void)\n```\n\nThis function represents a common occurrence of chaining asynchronous functions into a helper utility for a single use case.\nUsing only the standard GCD library, your function might look like this:\n\n```swift\n// The old way...\nfunc throwAtClosestPokemon(within: Double,\n                completionHandler: (success: Bool, pokemon: Pokemon?, error: NSError?) -\u003e Void) {\n    // Step 1\n    findClosestPokemon(within: within) { pokemon, error in\n        guard let p = pokemon where error == nil else {\n            DispatchQueue.main.async {\n                completionHandler(success: false, pokemon: nil, error: error)\n            }\n            return\n        }\n\n        // Step 2\n        countPokeballs { number, error in\n            guard let n = number where error == nil else {\n                DispatchQueue.main.async {\n                    completionHandler(success: false, pokemon: nil, error: error)\n                }\n                return\n            }\n\n            // Step 3\n            throwPokeballAt(pokemon: p) { success, error in\n                DispatchQueue.main.async {\n                    completionHandler(success: success, error: error)\n                }\n            }\n        }\n    }\n}\n```\n\nYikes!  It can quickly look even worse if your async logic needs to branch.  Let's look at how scoping/flow works with Brisk:\n\n```swift\n// The new way...\nfunc throwAtClosestPokemon(within: Double,\n                completionHandler: (success: Bool, pokemon: Pokemon?, error: NSError?) -\u003e Void) {\n\n    // Run everything inside a specified async queue, or DispatchQueue.global()\n    myQueue.async {\n\n        // Step 1\n        let (pokemon, error) = \u003c\u003c+{ findClosestPokemon(within: within, completionHandler: $0) }\n        guard let p = pokemon where error == nil else {\n            return completionHandler +\u003e\u003e (success: false, error: error)\n        }\n\n        // Step 2\n        let (number, error2) = \u003c\u003c+{ countPokeballs($0) }\n        guard let n = number where error2 == nil else {\n            return completionHandler +\u003e\u003e (success: false, error: error2)\n        }\n\n        // Step 3\n        let (success, error3) = \u003c\u003c+{ throwPokeballAt(pokemon: p, completionHandler: $0) }\n        completionHandler +\u003e\u003e (success: success, error: error3)\n    }\n}\n```\n\nWith Brisk the asynchronous functions can be coded using a seemingly-synchronous flow.\nThe asynchronous nature of the methods is hidden behind the custom operators.  *Unlike PromiseKit, all return values\nremain in scope as well*.\n\n## Calling Asynchronous Functions Synchronously ##\n\nThis section refers the idea of taking am asynchronous function and calling\nit synchronously, generally for the purpose of chaining multiple asynchronous\noperations.  This is essentially the same offering of PromiseKit but without\nthe needless indentation and scope shuffle that comes with it.\n\nTo see a practical use case, refer to the Quick Look example above.\n\nWhen we talk about an asynchronous function, it must abide by these characteristics:\n\n* Returns ```Void```\n* Takes any number of input parameters\n* Has a single \"completion\" parameter that takes a function of the form ```(...) -\u003e Void```\n\nThese are all examples of suitable asynchronous functions:\n\n```swift\nfunc getAlbum(named: String, handler: (album: PhotoAlbum?, error: NSError?) -\u003e Void)\nfunc saveUser(completionHandler: (success: Bool) -\u003e Void)\nfunc verifyUser(name: String, password: String, completion: (valid: Bool) -\u003e Void)\n\n// Typical use of a function would look like:\ngetAlbum(\"pics\") { photo, error in\n    // ...\n}\n```\n\nWith Brisk, you can use the ```\u003c\u003c+```, ```\u003c\u003c~``` or ```\u003c\u003c-``` operators to call your function in\na way that blocks the calling thread until your function has called its completion\nhandler.\n\n```swift\n// \u003c\u003c+ will execute getAlbum on the main queue\nlet (album, error) = \u003c\u003c+{ getAlbum(\"pics\", handler: $0) }\n\n// \u003c\u003c~ will execute saveUser on the global concurrent background queue\nlet success        = \u003c\u003c~{ saveUser($0) }\n\n// \u003c\u003c- will execute verifyUser immediately in the current queue (note that the\n//     current thread will wait for the completion handler to be called before\n//     returning the final value.)\nlet valid          = \u003c\u003c-{ verifyUser(\"myname\", password: \"mypass\", completion: $0) }\n\n// You can also specify *any* queue you want.  Here saveUser is called on myQueue.\nlet myQueue        = dispatch_queue_create(\"myQueue\", nil)\nlet valid          = \u003c\u003c~myQueue ~~~ { saveUser($0) }\n```\n\n\u003e Tip:  Use ```\u003c\u003c+``` for functions that\n\u003e need to be called on the main thread (like UI updates).  Use ```\u003c\u003c-``` for others.\n\nIn all of the above examples, execution of the outer thread is paused until the completion\nhandler ```$0``` is called.  Once ```$0``` is called, the values passed into it are routed back\nto the original assignment operation.\n\nNote that the ```$0``` handler can accommodate any number of parameters (e.g. ```getAlbum```\nabove can take ```album``` and ```error```), *but it must be assigned to a variable that\nof the same tuple*.  Also note that it is not possible to extract ```NSError``` parameters\nto transform them into do/try/catch methodology -- you will have to check the ```NSError```\nas part of the returned tuple.\n\n**Also note that the outer thread *WILL WAIT* until ```$0``` is called.**  This means that\nBrisk can only be used for functions that guarantee their completion handlers will\nbe called at some deterministic point in the future.  It is not suitable for open-ended\nasynchronous functions like ```NSNotification``` handlers.\n\n\n## Calling Synchronous Functions Asynchronously ##\n\nThere are many reasons to call synchronous functions asynchronously.  It happens any\ntime you see this pattern:\n\n```swift\ndispatch_async(someQueue) {\n    completionHandler(..)\n}\n```\n\nYou're burning three lines and an indentation scope just to route a single function call to\nanother queue.\n\nAn example of this is in the Quick Look example from the beginning of the documentation.  This\nrouting must take place each time the completion handler is called on the main queue.  It\nhas a negative impact on the readability of the overall function, since the actual function\nname gets buried in the scope of the dispatch.  Wouldn't it be nice if that could be\naccomplished in one line, with the function name first?\n\nThe ```~\u003e\u003e``` and ```+\u003e\u003e``` operators introduced in Brisk can be thought of as the\nsynchronous-\u003easynchronous translators.  The main difference between the two is that\nthe ```+\u003e\u003e``` operator dispatches to the main queue, while the ```~\u003e\u003e``` operator\nallows you to specify the queue (or use the concurrent background queue by default).\n\nFor the examples below, consider the following normal synchronous functions:\n\n```swift\nfunc syncReturnsVoid() { }\nfunc syncReturnsParam(p: Int) -\u003e Int { return p+1 }\nfunc syncReturnsParamTuple(p: Int) -\u003e (Int, String) { return (p+1, \"\\(p+1)\") }\n```\n\nUse the infix operator between a function and its parameters to quickly dispatch a synchronous function on\nanother queue.\n\n```swift\ndispatch_async(someQueue) {\n\n    // syncReturnsVoid() is called on the main thread\n    syncReturnsVoid +\u003e\u003e ()\n\n    // syncReturnsParam(p: 3) is called on the main thread\n    // Note in this case the return value is ignored!\n    syncReturnsParam +\u003e\u003e (p: 3)\n\n    // syncReturnsVoid() is called on the global concurrent background queue\n    syncReturnsVoid ~\u003e\u003e ()\n\n    // syncReturnsParam(p: 3) is called on the global concurrent background queue\n    // Note in this case the return value is ignored!\n    syncReturnsParam ~\u003e\u003e (p: 3)\n\n    let otherQueue = dispatch_queue_create(\"otherQueue\", nil)\n\n    // syncReturnsVoid() is called on otherQueue\n    syncReturnsVoid ~\u003e\u003e otherQueue ~\u003e\u003e ()\n\n    // syncReturnsParam(p: 3) is called on otherQueue\n    // Note in this case the return value is ignored!\n    syncReturnsParam ~\u003e\u003e otherQueue ~\u003e\u003e (p: 3)\n}\n```\n\nYou can also use the operators in a postfix fashion for a more functional syntax:\n\n```swift\ndispatch_async(someQueue) {    \n    let otherQueue = dispatch_queue_create(\"otherQueue\", nil)\n\n    // The following three lines are equivalent\n    syncReturnsParam~\u003e\u003e.on(otherQueue).async(p: 3)\n    syncReturnsParam~\u003e\u003eotherQueue~\u003e\u003e(p: 3)\n    syncReturnsParam ~\u003e\u003e otherQueue ~\u003e\u003e (p: 3)\n}\n```\n\nIn all of the above examples, the return values were ignored.  This is generally fine\nfor the synchronous functions that return ```Void``` (like most completion handlers).\nBecause the functions are called asynchronously, you have to process the return\nvalues asynchronously as well:\n\n```swift\ndispatch_async(someQueue) {\n\n    // syncReturnsParam(p: 3) is called on the main thread\n    // Its response is also handled on the main thread\n    syncReturnsParam +\u003e\u003e (p: 3) +\u003e\u003e { i in print(i) } // prints 4\n\n    // syncReturnsParam(p: 3) is called on the main thread\n    // Its response is handled on the global concurrent background queue\n    // Note the positions and difference between +\u003e\u003e and ~\u003e\u003e\n    syncReturnsParam +\u003e\u003e (p: 3) ~\u003e\u003e { i in print(i) } // prints 4\n\n    // syncReturnsParamTuple(p: 3) is called on the global concurrent background queue\n    // Its response is handled on an instantiated queue\n    syncReturnsParamTuple ~\u003e\u003e (p: 3) ~\u003e\u003e otherQueue ~\u003e\u003e { iInt, iStr in print(pInt) }\n\n    // Using the more functional style\n    syncReturnsParam~\u003e\u003e.on(otherQueue).async(p: 3) +\u003e\u003e { i in print(i) }    \n}\n```\n\n### Optionals ###\n\nWhen the function you are routing is an optional, you must use the ```?~\u003e\u003e``` and ```?+\u003e\u003e```\noperators when referencing the function:\n\n```swift\nfunc myTest(param: Int, completionHandler: (Int -\u003e Int)? = nil) {\n\n    // These will cause a compiler error because the handler is optional:\n    completionHandler +\u003e\u003e (param)\n    completionHandler+\u003e\u003e.async(param) +\u003e\u003e { i in print(i) }\n\n    // Instead use these:\n    completionHandler ?+\u003e\u003e (param)\n    completionHandler?~\u003e\u003e.async(param)\n\n    // For anything past the initial function, use normal operators:\n    //     (+\u003e\u003e instead of ?+\u003e\u003e) --v\n    completionHandler ?+\u003e\u003e (param) +\u003e\u003e { i in print(i) }\n\n}\n```\n\n\u003e Note that if you were using the syntax for optionals\n\u003e ```completionHandler?~\u003e\u003e.main.async(param)```\n\u003e -- Swift 3.1 does not allow ? characters at the beginning of postfix\n\u003e operators so you can no longer explicitly choose a queue name\n\u003e like \".main\".  You can still use queues implicitly with the operator, like\n\u003e ```completionHandler?+\u003e\u003e.async(param)```\n\n\n## Swift 3.x LibDispatch Additions ##\n\nBrisk extensions ```DispatchQueue``` with functions that make the ```async``` function\nmore concise:\n\n```swift\n/// LibDispatch:\nfunc asyncAfter(deadline: DispatchTime,\n                     qos: DispatchQoS = default,\n                   flags: DispatchWorkItemFlags = default,\n                 execute: () -\u003e Void)\n\n// Brisk allows you to specify time/intervals as a Double instead of DispatchTime.\n// It also allows you to capture the timer used to dispatch the block, in case\n// you want to cancel it.\nfunc async(after seconds: Double,\n                  leeway: QuickDispatchTimeInterval? = nil,\n                     qos: DispatchQoS = .default,\n                   flags: DispatchWorkItemFlags = [],\n           execute block: @escaping () -\u003e Void) -\u003e DispatchSourceTimer\n```\n\nAlso consider scheduling a block to run repeatedly at an interval:\n\n```swift\n// LibDispatch Requires:\nlet timer = DispatchSource.makeTimerSource(flags: ..., queue: ...)\ntimer.setEventHandler(qos: ..., flags: ..., handler: ...)\ntimer.scheduleRepeating(deadline: ..., interval: ..., leeway: ...)\ntimer.resume()\n\n// Brisk allows you to schedule timers in one function, and passes the timer\n// into the block so it can be canceled based on logic inside or outside the handler.\nfunc async(every interval: Double,\n               startingIn: Double? = nil,\n               startingAt: NSDate? = nil,\n                   leeway: QuickDispatchTimeInterval? = nil,\n                      qos: DispatchQoS = .default,\n                    flags: DispatchWorkItemFlags = [],\n            execute block: @escaping (_ tmr: DispatchSourceTimer) -\u003e Void) -\u003e DispatchSourceTimer\n\n```\n\nAnother new function allows you to coalesce multiple async calls into a single execution,\nbased on an ```operationId```.  This is useful when several simultaneous asynchronous\nactions want to trigger a block to occur (but you only want that block to occur once).\n\n```swift\nfunc once(operationId: String,\n       after interval: Double? = nil,\n              at date: NSDate? = nil,\n               leeway: QuickDispatchTimeInterval? = nil,\n                  qos: DispatchQoS = .default,\n                flags: DispatchWorkItemFlags = [],\n        execute block: @escaping () -\u003e Void) -\u003e DispatchSourceTimer\n```\n\nThere are several variations of the above functions.  See ```BriskDispatch.swift``` for more details.\n\n## Deprecated Swift 2.x GCD Additions ##\n\nThe following code examples show the GCD additions provided by Brisk for the Swift 2.x syntax.  These are\nfairly self-documenting.  More information about each method can be found in its comment section.  They are\nincluded in the Swift 3.x release for backwards compatibility.\n\n```swift\ndispatch_main_async {\n    // Block runs on the main queue\n}\n\ndispatch_main_sync {\n    // Block runs on the main queue; this function does not return until\n    // the block completes.\n}\n\ndispatch_bg_async {\n    // Block runs on the global concurrent background queue\n}\n\ndispatch_async(\"myNewQueue\") {\n    // Block runs on a brisk-created serial queue with the specified string ID.\n    // Calling this function multiple times with the same string will reuse the\n    // named queue.  Useful for dynamic throw-away serial queues.\n}\n\ndispatch_main_after(2.0) {\ndispatch_after(2.0, myQueue) {\n    // Block is called on specified queue after specified number of seconds using\n    // a loose leeway (+/- 0.1 seconds).\n}\n\ndispatch_main_after_exactly(2.0) {\ndispatch_after_exactly(2.0, myQueue) {\n    // Block is called on specified queue after specified number of seconds using\n    // as tight a timer leeway as possible.  Useful for animation timing but\n    // uses more battery power.\n}\n\ndispatch_main_every(2.0) { timer in\ndispatch_every(2.0, myQueue) { timer in\ndispatch_main_every_exact(2.0) { timer in\ndispatch_every_exact(2.0, myQueue) { timer in\n    // Block is run on specified thread every N seconds.\n    // Stop the timer with:\n    dispatch_source_cancel(timer)\n}\n\ndispatch_main_once_after(2.0, \"myOperationId\") {\ndispatch_once_after(2.0, myQueue, \"myOperationId\") {\n    // Block runs after specified time on specified queue.  The block is\n    // only executed ONCE -- repeat calls to this function with the same\n    // operation ID will reset its internal timer instead of calling the\n    // block again.  Useful for calling a completion block after several\n    // disparate asynchronous methods (e.g. saving the database to disk\n    // after downloading multiple records on separate threads.)\n}\n\ndispatch_each(myArray, myQueue) { element in\n    // Each element in the array has this block called with it as a parameter.\n    // Should be used on a concurrent queue.\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmfieldman%2FBrisk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjmfieldman%2FBrisk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmfieldman%2FBrisk/lists"}