Skip to content

Instantly share code, notes, and snippets.

@helje5
Last active January 12, 2022 18:44
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save helje5/0f8f41ac73c2ea0bf161db81defaa08e to your computer and use it in GitHub Desktop.
Save helje5/0f8f41ac73c2ea0bf161db81defaa08e to your computer and use it in GitHub Desktop.
Using async/await concurrency on iOS 14 and before
// Created by Helge Heß 2021-06-17
import Foundation
// They use obfuscated names to hide it from us!
import JavaScriptCore
/// Setup our async/await runtime.
let runtime = JSContext()!
/// Ask our async/await runtime to tell us about errors.
runtime.exceptionHandler = { _, error in
print("Caught exception:", error as Any)
}
/// Add a global `print` function to our async/await runtime
runtime.setObject(
{()->@convention(block) (JSValue)->Void in { print($0) }}(),
forKeyedSubscript: "print" as NSString
)
/// We need this protoocol to tell our async/await runtime about URLSession.
@objc protocol AsyncURLSession: JSExport {
/// Give access to `URLSession.shared`
@objc(shared) // overlap w/ `sharedSession` on the ObjC side
static var sharedSwift : URLSession { get }
/// Our async/await enabled URL fetcher,
/// returns an async error or a [ data, response ] tuple.
func data(_ url: String) -> JSValue
}
/// Here we implement our async aware function.
@objc extension URLSession: AsyncURLSession {
dynamic class var sharedSwift : URLSession { shared }
func data(_ url: String) -> JSValue {
guard let ctx = JSContext.current() else { fatalError("No runtime") }
/// Create our continuation
return JSValue(newPromiseIn: ctx) { resolve, reject in
guard let url = URL(string: url) else {
reject?.call(withArguments: [ "invalidURL" ]); return
}
self.dataTask(with: URLRequest(url: url)) { data, response, error in
RunLoop.main.perform {
if let error = error {
reject?.call(withArguments: [ error.localizedDescription ])
}
else if let data = data, let response = response {
resolve?.call(withArguments: [ [ data, response ] ])
}
else {
reject?.call(withArguments: [ "missingResponse" ])
}
}
}
.resume()
}
}
}
runtime.setObject(URLSession.self, forKeyedSubscript: "URLSession" as NSString)
runtime.evaluateScript("URLSession.shared = URLSession.shared();")
/// Finally, execute our fetch using async/await.
runtime.evaluateScript(#"""
async function mainActor() {
let [ data, res ] =
await URLSession.shared.data("https://zeezide.com/#dontTrackMe")
print(data)
print(res)
}
mainActor()
"""#)
/// Keep it alive
RunLoop.main.run()
@helje5
Copy link
Author

helje5 commented Jun 17, 2021

More information is available in the Async/Await for iOS 14 and before blog post.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment