Невозможно вызвать изменяющую асинхронную функцию create для изолированного от актера свойства dataService

В моем примере приложения я считываю данные в класс DomainService ObservableObject. Я добавляю зависимости в представление, используя Environment. Чтение данных работает нормально, но когда я пытаюсь создать или обновить свои данные, я не могу вызвать мутирующую асинхронную функцию.

Вторая проблема заключается в том, что я не могу показать пользователю .alert при возникновении ошибки. Предупреждение не отображается, поскольку «Публикация изменений из обновлений представления не разрешена».

// this sample app uses a DataAdapter to read and create items.
// the DataAdapter conforms to IDataService protocol and the DataService makes the data available for the app.
// the DomainService consumes data conforming to IDataService protocol and is the ObervableObject to the screens/ views.
// the main screen/ view injects the dependencies into an EnvironmentObject.

import SwiftUI

//Data
struct Item: Hashable {
    var name: String
}

struct MockDb {
    private var items: [Item] = []
    
    func readItems() async throws -> [Item] {
        throw AdapterError.dataError //testing the error handling.
        
        [Item(name: "Peter"), Item(name: "Joan")]
    }
    mutating func createItem(_ item: Item) async throws -> Bool {
        items.append(item)
        return true
    }
}

enum AdapterError: Error {
    case dataError
}

struct ItemDataAdapter: IDataService {
    var mock: MockDb
    
    func read() async -> Result<[Item], AdapterError> {
        do {
            let result = try await mock.readItems()
            return .success(result)
        } catch {
            return .failure(AdapterError.dataError)
        }
    }
    mutating func create(_ item: Item) async -> Result<Bool, AdapterError> {
        do {
            let result = try await mock.createItem(item)
            return .success(result)
        } catch {
            return .failure(AdapterError.dataError)
        }
    }
}

struct ItemDataService: IDataService {
    var dataAdapter: IDataService
    
    func read() async -> Result<[Item], AdapterError> {
        await dataAdapter.read()
    }
    mutating func create(_ item: Item) async -> Result<Bool, AdapterError> {
        await dataAdapter.create(item)
    }
}

protocol IDataService {
    func read() async -> Result<[Item], AdapterError>
    mutating func create(_ item: Item) async -> Result<Bool, AdapterError>
}

//Domain
@MainActor
class ItemDomainStore: ObservableObject {
    @Published var items: [Item] = []
    
    var dataService: ItemDataService
    
    init(dataService: IDataService) {
        self.dataService = ItemDataService(dataAdapter: dataService)
        
        Task {
            await read()
        }
    }
    
    func read() async {
        let result = await dataService.read()
        
        switch result {
        case .success(let items):
            self.items = items
        case .failure(let error):
            items = []
            
            //thrown error from mockDb is shown here.
            //how can I show the error as an alert to the user?
            print(error)
        }
    }
    func create(_ item: Item) async {
        //debugger message: Cannot call mutating async function 'create' on actor-isolated property 'dataService'
        //how can I call create?
        //let result = await dataService.create(item)
    }
}

//Presentation
struct ItemListScreen: View {
    @EnvironmentObject private var domainStore: ItemDomainStore
    
    var body: some View {
        VStack {
            List(domainStore.items, id: \.self) { item in
                Text(item.name)
            }
            Button("create item") {
                Task {
                    await domainStore.create(Item(name: "Marry"))
                }
            }
        }
    }
}

//main screen.
struct MVConcurrentDataFlow: View {
    let mock = MockDb()
    
    var body: some View {
        ItemListScreen()
            .environmentObject(ItemDomainStore(dataService: ItemDataService(dataAdapter: ItemDataAdapter(mock: mock))))
    }
}

Будет ли несколько ItemDataService, ItemDataAdapter или MockDb?

Radioactive 09.07.2024 09:07

Да, существует несколько источников данных/адаптеров/служб данных, все они соответствуют IDataService.

MatFetsch 09.07.2024 09:32

Обычно асинхронная функция внутри службы принимает объект модели в качестве параметра, чтобы можно было установить в него данные. Служба обычно представляет собой структуру только с функциями, без внутреннего состояния.

malhal 09.07.2024 11:37
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Result в основном используется для обработчиков завершения. Поскольку у вас нет обработчиков завершения, лучше использовать функции бросания. И я исправил Cannot call mutating async function 'create' on actor-isolated property 'dataService', сделав их классом. Потому что занятия для этого более подходят. И вы можете делать инъекции зависимостей (у вас есть для этого почти идеальная настройка). И оповещение работает.

import SwiftUI

struct Item: Hashable, Sendable {
    var name: String
}

class MockDb {
    private var items: [Item] = []
    
    func readItems() async throws -> [Item] {
        try? await Task.sleep(nanoseconds: 1_000_000_000)
        throw AdapterError.dataError
    }
    func createItem(_ item: Item) async throws -> Bool {
        items.append(item)
        return true
    }
}

enum AdapterError: Error {
    case dataError
}
extension AdapterError: LocalizedError {
    var errorDescription: String? {
        switch self {
        case .dataError: return "Data error accrued"
        }
    }
}

class ItemDataAdapter: IDataService {
    var mock: MockDb
    
    init(mock: MockDb) {
        self.mock = mock
    }
    
    func read() async throws -> [Item] {
        try await mock.readItems()
    }
    func create(_ item: Item) async throws -> Bool {
        try await mock.createItem(item)
    }
}

class ItemDataService: IDataService {
    var dataAdapter: IDataService
    
    init(dataAdapter: IDataService) {
        self.dataAdapter = dataAdapter
    }
    
    func read() async throws -> [Item] {
        try await dataAdapter.read()
    }
    func create(_ item: Item) async throws -> Bool {
        try await dataAdapter.create(item)
    }
}

protocol IDataService {
    func read() async throws -> [Item]
    mutating func create(_ item: Item) async throws -> Bool
}

//Domain
@MainActor
class ItemDomainStore: ObservableObject {
    @Published var isAlertShowing = false
    @Published var items: [Item] = []
    @Published var error: AdapterError?
    
    var dataService: ItemDataService
    
    init(dataService: IDataService) {
        self.dataService = ItemDataService(dataAdapter: dataService)
    }
    
    @Sendable func read() async {
        do {
            let result = try await dataService.read()
            
            await MainActor.run {
                items = result
            }
        } catch let error {
            await MainActor.run {
                self.error = (error as! AdapterError)
                self.isAlertShowing = true
            }
        }
    }
    
    @Sendable func create(_ item: Item) async {
        do {
            let result = try await dataService.create(item)
            
            await MainActor.run {
                // Do some things
            }
        } catch {
            // Do some things
        }
    }
}

//Presentation
struct ItemListScreen: View {
    @EnvironmentObject private var domainStore: ItemDomainStore
    
    var body: some View {
        VStack {
            List(domainStore.items, id: \.self) { item in
                Text(item.name)
            }
            Button("create item") {
                Task {
                    await domainStore.create(Item(name: "Marry"))
                }
            }
            .alert(isPresented: $domainStore.isAlertShowing, error: domainStore.error) { _ in
                Button("Ok") {}
            } message: { error in
                Text("Please try again later.")
            }

        }
        .task {
            await domainStore.read()
        }
    }
}

//main screen.
struct ContentView: View {
    let mock = MockDb()
    
    var body: some View {
        ItemListScreen()
            .environmentObject(ItemDomainStore(dataService: ItemDataService(dataAdapter: ItemDataAdapter(mock: mock))))
    }
}

Спасибо! И еще один вопрос, если вы не возражаете. Я думал, что этот пример кода уже реализует внедрение зависимостей. Я ошибаюсь? Или я просто неправильно понял ваш комментарий по этому поводу?

MatFetsch 09.07.2024 18:28

Структуры не могут использоваться для внедрения зависимостей. Итак, в вашем коде не было внедрения зависимостей. Для внедрения зависимостей требуется ссылочный тип, но структуры являются типом значения.

Radioactive 09.07.2024 18:30

Вы должны убедиться, что ваши сервисы Sendable, если вы хотите иметь возможность их изменять.

struct автоматически согласовывается, поэтому вы можете просто добавить наследование.

protocol IDataService: Sendable {
    func read() async -> Result<[Item], AdapterError>
    mutating func create(_ item: Item) async -> Result<Bool, AdapterError>
}

Другие вопросы по теме