I'm getting a warning with this code and I don't understand how to fix it. I'm curious to know how to pass a function into the task modifier.
private func placeholderRHSView(for client: AppClient) -> some View {
Color.clear
.frame(width: 290, height: 290)
.task(load(client)) // Warning: Converting non-sendable function value to '#Sendable () async -> Void' may introduce data races
}
private func load(_ client: AppClient) -> () -> Void {
return {
Task {
do {
let apps = try await client.recent()
} catch {}
}
}
}
How should I re-structure this code to make it thread safe?
First issue is having that floating Task {/*code*/} that isn't thread safe and should not be used without a very good reason and precautions that mimic .task lifecycle behavior.
Now to get a function that can be used directly in .task we have to mimic the signature
func task(priority: TaskPriority = .userInitiated, _ action: #escaping () async -> Void) -> some View
The action is () async -> Void so the function should return that exact type. You are missing the async part.
You also have to add #Sendable which we only know about from warnings
private func load(_ client: AppClient) -> (#Sendable () async -> Void) {
() means that this will be a closure/function so the body will be enclosed with return {/*async code here*/}
private func load(_ client: AppClient) -> (#Sendable () async -> Void) {
return {
do {
let apps = try await client.recent()
print(apps)
} catch {
print(error)
}
}
}
Now your View will work as expected.
private func placeholderRHSView(for client: AppClient) -> some View {
Color.clear
.frame(width: 290, height: 290)
.task(load(client))
}
I created a little mock AppClient to make this complie.
struct AppClient{
func recent() async throws -> String{
return "recent"
}
}
Related
I am stuck with this situation where I have a custom JSONDecoder struct which contains a private function to decode data, and another function which is exposed, and should return a specific, Decodable type. I would like these functions to throw successively so I only have to write my do/catch block inside the calling component, but I'm stuck with this error on the exposedFunc() function:
Invalid conversion from throwing function of type '(Completion) throws -> ()' (aka '(Result<Data, any Error>) throws -> ()') to non-throwing function type '(Completion) -> ()' (aka '(Result<Data, any Error>) -> ()')
Here is the code:
import Foundation
import UIKit
typealias Completion = Result<Data, Error>
let apiProvider = ApiProvider()
struct DecodableTest: Decodable {
}
struct CustomJSONDecoder {
private static func decodingFunc<T: Decodable>(
_ response: Completion,
_ completion: #escaping (T) -> Void
) throws {
switch response {
case .success(let success):
try completion(
JSONDecoder().decode(
T.self,
from: success
)
)
case .failure(let error):
throw error
}
}
static func exposedFunc(
value: String,
_ completion: #escaping (DecodableTest) -> Void
) throws {
apiProvider.request {
try decodingFunc($0, completion)
}
}
}
class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
do {
try CustomJSONDecoder.exposedFunc(value: "test_value") { result in
// Do something with result
}
} catch {
print(error)
}
}
}
class ApiProvider: NSObject {
func request(_ completion: #escaping (Completion) -> ()) {
}
}
Thank you for your help
This defines method that takes a non-throwing function:
class ApiProvider: NSObject {
func request(_ completion: #escaping (Completion) -> ()) {
}
}
So in all cases, this function must take a Completion and return Void without throwing. However, you pass the following:
apiProvider.request {
try decodingFunc($0, completion)
}
This method does throw (note the uncaught try), so that's not allowed. You need to do something if this fails:
apiProvider.request {
do {
try decodingFunc($0, completion)
} catch {
// Here you must deal with the error without throwing.
}
}
}
Not using concurrency features is making your code hard to understand. Switch!
final class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
let result = DecodableTest()
// Do something with result
}
}
}
extension DecodableTest {
init() async throws {
self = try JSONDecoder().decode(Self.self, from: await APIProvider.data)
}
}
enum APIProvider {
static var data: Data {
get async throws { .init() }
}
}
If you have a sync function, how would you convert it to an async function?
func syncFunc() -> Int {
//Do something
}
Would this work?
func asyncFunc(_ syncFunc:()->Int, _ completion:(Int)->()) -> Int {
DispatchQueue.background.async{
completion( syncFunc() )
}
}
No, functions containing an asynchronous task cannot return any value from the closure body and both closures must be marked as #escaping
func asyncFunc(_ syncFunc: #escaping ()->Int, completion: #escaping (Int)->()) {
DispatchQueue.global().async {
completion( syncFunc() )
}
}
I am chaining some functions together and I can't figure out how to call a completion handler with a return value once all the functions are done running.
class AirQualityProvider {
var aBlock: ((Int?) -> Void)?
func getAirQuality(completion: #escaping (Int?) -> Void) {
aBlock = completion
callAPI()
}
private func callAPI() {
let data = Data()
parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
for d in data {
dosomeMath(d)
}
}
private func dosomeMath(data: Int) {
// HERE IS WHERE I WANT IT TO SUM UP ALL THE NUMBERS
THEN ONLY RETURN ONE VALUE using a completion handler.
Currently, it returns the average as it is being generated.
}
Almost got it working with help to Alexander. The code Alexander supplied works perfectly, it is amazing. The issue is, when I run taskrunner inside alamofire it returns empty. Outside alamofire it works as usual. I need to run this inside alamofire.
func A(json : JSON){
for (key,subJson) in json{
if subJson["free"].doubleValue > 0.0 {
func B(asset: subJson["asset"].stringValue, json: subJson)
}
}
print(taskRunner.getResults())
}
func B(asset : String, json : JSON){
//OUTSIDE ALAMOFIRE WORKS
self.taskRunner.execute{
return 100
}
Alamofire.request(url).responseJSON { response in
//INSIDE ALAMOFIRE DOESN'T WORK. Returns []
self.taskRunner.execute{
return 100
}
}
}
I would use a dispatch queue to synchronize the aggregation of results (by synchronizing Array.append(_:) calls, and the subsequent reading of the array). Here's a simple example:
import Dispatch
import Foundation
class ParallelTaskRunner<Result> {
private var results = [Result]()
private let group = DispatchGroup()
private let resultAggregatorQueue = DispatchQueue(label: "Result Aggregator")
func execute(_ closure: (#escaping (Result) -> Void) -> Void) {
group.enter() // Register that a new task is in-flight
closure { result in
self.resultAggregatorQueue.sync { // Synchronize access to the array
self.results.append(result) // Record the result
}
self.group.leave() // This task is done
}
}
func getResults() -> [Result] {
group.wait() // Make sure all in-flight tasks are done
return resultAggregatorQueue.sync { return results }
}
}
let taskQueue = DispatchQueue(label: "Task Queue", attributes: .concurrent)
let taskRunner = ParallelTaskRunner<Int>()
for i in 0...100 {
taskRunner.execute { completionHandler in
taskQueue.async { // Simulated async computation
let randomTime = 3.0
print("Sleeping for \(randomTime)")
Thread.sleep(forTimeInterval: randomTime) // Simulates intesnive computation
let result = i // Simulate a result
completionHandler(result)
}
}
}
print(taskRunner.getResults()) // Oh look, all the results are here! :D
I am chaining some functions together and I can't figure out how to call a completion handler with a return value once all the functions are done running.
func getAirQuality(completion: (aqi: Int?) -> Void) {
callAPI()
}
private func callAPI() {
// ... get data
self.parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
let aqi = aqi
// Send aqi up to completion handler in getAirQuality
}
So that when everything is said and done I can just do something like this:
getAirQuality(completion: { aqi -> Void in {
// Do something with aqi
})
My first assumption is that your first 3 functions are part of a class. If so, one approach is to save the completion handler as an instance variable.
class AirQualityProvider {
var aBlock: ((Int?) -> Void)?
func getAirQuality(completion: #escaping (Int?) -> Void) {
aBlock = completion
callAPI()
}
private func callAPI() {
let data = Data()
parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
let aqi = 1
if let completion = aBlock {
completion(aqi)
}
}
}
Here's an example of a caller as written in a playground.
let aqp = AirQualityProvider()
aqp.getAirQuality { (value) in
if let value = value {
print("Value = \(value)")
}
}
I want to download data from my server to be displayed on a map. Therefore I use async methods to get the data. The goal is to have an array of annotation objects to be displayed.
Therefore I first download Information A and then Information B. As both are async methods, I guess I need to wait for the completionHandler to return true so I know the data is loaded. This is easy for one method. But how do I handle to wait for both methods before the completionHandler of getInformationFromServer returns true and triggers therefore the addition of annotations?
override func viewWillAppear(animated: Bool) {
self.customizeInterface()
self.getInformationFromServer { (completed) -> Void in
if(completed) {
self.mapView.addAnnotations(self.annotationArray)
}
}
}
func getInformationFromServer(completionHandler: (completed: Bool) -> Void) {
getInformationFromServerA { (downloadCompleted) -> Void in
completionHandler(completed: downloadCompleted)
}
// HOW DO I MANAGE TO ONLY RETURN THE COMPLETION HANDLER TRUE WHEN
// BOTH FUNCTIONS RETURNED TRUE?
}
func getInformationFromServerA(completionHandler: (downloadCompleted: Bool) -> Void) {
Server().getJsonInformationFromServer(url: "aeds", completionHandler: { (response) -> Void in
self.parseAEDInformationToAnnotation(response["data"])
completionHandler(downloadCompleted: true)
})
}
func getInformationFromServerB(completionHandler: (downloadCompleted: Bool) -> Void) {
Server().getJsonInformationFromServer(url: "aeds", completionHandler: { (response) -> Void in
self.parseAEDInformationToAnnotation(response["data"])
completionHandler(downloadCompleted: true)
})
}
You may use a dispatch group to wait until both downloads finish.
func getInformationFromServer(completionHandler: (completed: Bool) -> Void) {
let dispatchGroup = dispatch_group_create()
var downloadCompletedA: Bool = false
dispatch_group_enter(dispatchGroup)
getInformationFromServerA { (downloadCompleted) -> Void in
downloadCompletedA = downloadCompleted
dispatch_group_leave(dispatchGroup)
}
var downloadCompletedB: Bool = false
dispatch_group_enter(dispatchGroup)
getInformationFromServerB { (downloadCompleted) -> Void in
downloadCompletedB = downloadCompleted
dispatch_group_leave(dispatchGroup)
}
// wait until both downloads are finished
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER)
completionHandler(downloadCompletedA && downloadCompletedB)
}
See Apple's Concurrency Programming Guide:
Dispatch groups are a way to block a thread until one or more tasks
finish executing. You can use this behavior in places where you cannot
make progress until all of the specified tasks are complete.
Another solution which I can recommend for you and it's not so "complex" as solution with dispatch_group_wait:
func getInformationFromServer(completionHandler: (completed: Bool) -> Void) {
getInformationFromServerA { [weak self] (downloadCompleted: Bool) -> Void in
if downloadCompleted {
self?.getInformationFromServerB({ (downloadCompleted: Bool) -> Void in
completionHandler(completed: downloadCompleted)
})
}
else {
completionHandler(completed: downloadCompleted)
}
}
}