Search code examples
iosswiftasynchronousswift-concurrencyswift-task

Swift async/await implement refresh using a task


I have a scenario where the user can try to refresh the app from multiple places, but I just want to refresh one time per request.

Just wanted to know if something like this could work, every time there is a second refresh or third request the app will wait for the current request that is running.

class Test {

  private var currentTask: Task<Int, Never>?

  @MainActor
  func refresh() async -> Result<Int, Never> {
    if let currentTask {
      return await currentTask.result
    }

    let task = Task {
      // Long operation

      return 1
    }

    currentTask = task

    let result = await task.result

    currentTask = nil

    return result
  }

}

Solution

  • Yes this approach would work. I am using something very similar in my app.

    actor Service {
        private var task: Task<Void, Error>?
    
        func fetchData() async throws {
            if let task {
                return try await task.value
            }
    
            let task = Task {
                try await actualFetchData()
            }
    
            self.task = task
            defer {
                self.task = nil
            }
            return try await task.value
        }
    
        private func actualFetchData() async throws {
            // Fetching data from the server/database
        }
    }
    

    What you are basically doing is exposing a wrapper method that makes sure to perform the fetch request only if another one is not in progress. Your implementation should definitely work.

    This functionality could be extended to support fetching discrete data for particular object, to which you would refer with its unique identifier.

    actor Service {
        private var tasks = [UUID: Task<Void, Error>]()
    
        func fetchData(for uuid: UUID) async throws {
            if let task = tasks[uuid] {
                return try await task.value
            }
    
            let task = Task {
                try await actualFetchData(for: uuid)
            }
    
            tasks[uuid] = task
            defer {
                tasks[uuid] = nil
            }
            return try await task.value
        }
    
        private func actualFetchData(for uuid: UUID) async throws {
            // Fetching data from the server/database
        }
    }