0

I'm currently working on a new iOS project. To be up to date with the new concurrency from Swift I have switched the Strict concurrency checking setting too "complete". This has of course sprung up lot's of warning and error that I was mostly able to fix.

Never the less there are a few cases that I can't figure out. My project structure is a very commun MVVM pattern in Swift/SwiftUI. Where my viewModels implement the ObservableObject and Sendable protocols.

They contain @Published variables that are update internally to refresh my associated SwiftUI views.

My problem is that I have concurrency warnings on these @Published variables and I can't figure out how to get rid of them except settings my viewmodels as @unchecked Sendable, which is not really recommended.

Following is an example of a viewModel and it's associated warning:

final class HomeViewModel: ObservableObject, Sendable {

    // The warning concerns this variable
    @Published private(set) var pageState: PageState<HomePageModel> = .loading

    private var reloadTask: Task<Void, Never>?

    init() {
        setUp()
    }

    deinit {
        reloadTask?.cancel()
    }

    func fetchHomeContent() async {
        if Task.isCancelled { return }
        do {
            XXXXX async execution happening here

            try Task.checkCancellation()
            let sortedSections = sections.sorted { $0.order < $1.order }
            await updatePageStage(with: .loaded(HomePageModel(sections: sortedSections)))
        } catch {
            await updatePageStage(with: .error(error.localizedDescription))
        }
    }

    func reloadData() {
        reloadTask?.cancel()
        reloadTask = Task { [weak self] in
            await self?.fetchHomeContent()
        }
    }
}

private extension HomeViewModel {
    @MainActor
    func updatePageStage(with state: PageState<HomePageModel>) {
        pageState = state
    }
}

Warning

Stored property '_pageState' of 'Sendable'-conforming class 'HomeViewModel' is mutable

As you can see the warning is quite clear but I cannot seem to find a way to make it disappear.

nonisolated is not supported on properties with property wrappers so that doesn't work.

Having a private(set) also.

As you can see the function updatePageStage is @MainActor bounded and my HomeViewModel is a @StateObject making it also @MainActor bounded through Swift implementation.

My question is simple: is there a way to remove this warning without using tricks like @unchecked Sendable or setting explicitly the entire class as @MainActor

Thank you in advance for any tips.

Martin
  • 843
  • 8
  • 17
  • Put the MainActor on the class not the function – lorem ipsum Dec 14 '22 at 12:00
  • As said at the bottom of my post I was trying to avoid that, but maybe I'm wrong to do so. – Martin Dec 14 '22 at 12:03
  • because this is a ViewModel and the variables directly affect the View that requires the Main for updates it should be on the class, that is what needs to be guaranteed. You can put it in functions other places that don't have variables whose purpose is to affect views. – lorem ipsum Dec 14 '22 at 12:39

1 Answers1

0

When you use async/await you no longer need an object, it's simply:

struct HomeView {

    @State var results = []
    @State var error = "" 

    var body: some View {
        
        .task {
            do {
                results = try await fetchHomeContent()
            } catch {
                error = 
            }
        }
    }

The task is started when the underlying UIView appears and cancelled when it disappears. This simplifies async lifecycle management greatly. Also you can use task(id: pageNumber) to cancel and restart the task when the id param changes.

malhal
  • 26,330
  • 7
  • 115
  • 133