Search code examples
swiftasynchronousswiftuicombine

Executing a task asynchronously using Combine with Swift


I recently started to study the Combine and ran into a certain problem. First, I will describe what I am doing. I trying to use Clean Architecture Here you can see my Repository

protocol Repository {
    func test()
}

class MockRepository: Repository {
    func test() {
        sleep(3)
    }
}

Then I created UseCase

class UseCaseBase<TInput, TOutput> {
    var task: TOutput? { return nil }

    var repository: Repository

    init(_ repository: Repository) {
        self.repository = repository
    }

    func execute(with payload: TInput) -> AnyPublisher<TOutput, Never> {
        return AnyPublisher(Future<TOutput, Never> { promise in
            promise(.success(self.task!))
        })
            .eraseToAnyPublisher()
    }
}

class MockUseCase: UseCaseBase<String, Int> {
    override var task: Int? {
        repository.test()
        return 1
    }
}

And then in a init block ContentView I did something like that

init() {
        let useCase = MockUseCase(MockRepository())
        var cancellables = Set<AnyCancellable>()
        
        useCase.execute(with: "String")
            .sink(receiveValue: { value in
                print(value)
            })
            .store(in: &cancellables)
        
        print("Started")
        
    }

At first, I want to get "Started" and then after sleep(3) value "1"

Now I get "1" and then "Started"


Solution

  • Your sleep(3) call runs on the main thread, which means that it blocks any other operations, including the code that prints the "Started" text.

    I won't be rambling about how bad it is to block the main thread, this is well known information, but this is the reason you see the behaviour you asked about.

    I don't see any thread switching code in your question, so if you wish to achieve some kind of asynchronicity, then you can either go with Rob's solution of using dispatch(after:), or do the locomotion (the sleep) on another thread:

    func execute(with payload: TInput) -> AnyPublisher<TOutput, Never> {
        return AnyPublisher(Future<TOutput, Never> { promise in
            DispatchQueue.global().async {
                promise(.success(self.task!))
            }
        })
            .eraseToAnyPublisher()
    }