Обновить текст с прогрессом от работника

Я новичок в Swift и пытаюсь обновить поле Text, основанное на ходе выполнения асинхронной рабочей функции.

Я часами искал и играл над этим, но не там. Этот пост кажется правильной идеей, но я не могу заставить его работать в моем примере. Какие-либо предложения?

public class ExportEngine : ObservableObject
{
    @Published var curFileIdx : Int
    @Published var isRunning : Bool
    
    init?()
    { 
        self.curFileIdx = 0
        self.isRunning = false
    }
    
    @MainActor
    public func exportFiles() async
    {
        isRunning = true;
        
        for i in (0...10)
        {
            curFileIdx = i
            print("i = \(i)")
            sleep(5)
        }
        isRunning = false
    }
}

struct ContentView: View
{
    @ObservedObject var reloadViewHelper = ReloadViewHelper()
    @ObservedObject var exportEngine : ExportEngine

    @State private var pctComplete = 0.0
        
    var body: some View
    {
        VStack
        {
            Button("Run")
            {
                print("run")
                // do what here?
            }
            
            Text("File \(exportEngine.curFileIdx)") // doesn't like this
            .refreshable
            {
                Task
                {
                    await exportEngine.exportFiles()
                }
            }
        }
    }
}
stackoverflow.com/questions/77080477/… также не используйте «сон», используйте «Task.sleep»
lorem ipsum 06.08.2024 13:28
Стоит ли изучать 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
1
60
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Как сказал @lorem ipsum, вам следует переключиться на Task.sleep, поскольку sleep приостановит выполнение потока, который является основным потоком.

print("i = \(i)")
try? await Task.sleep(for: .seconds(5) //<- here

Кроме того, вы привязываете Text к curFileIdx, чтобы при каждом изменении curFileIdx обновлялся и Text. Вы сделали это в exportFiles().

curFileIdx = i

Я предполагаю, что это действие должно выполняться при нажатии кнопки Run.

VStack {
    Button("Run") {
        Task {
            await exportEngine.exportFiles() //<- here
        }
    }
    if exportEngine.isRunning { //<- I added a progress view here to represent running state
        ProgressView()
    }
    Text("File \(exportEngine.curFileIdx)")
}

Это результат:


Однако здесь есть проблема. @MainActor добавлен к вашему exportFiles(). В результате весь процесс всегда выполняется в основном потоке. С другой стороны, все, что нужно, чтобы появиться в тексте, — это curFileIdx. Поэтому лучше выполнять другие действия в разных потоках и обращаться к основному потоку только при необходимости.

@MainActor //<- here
public class ExportEngine: ObservableObject {
     ...

     nonisolated //<- here
     public func exportFiles() async {
         for i in (0...5) {
            await MainActor.run {
                print("get \(i) on thread: \(Thread.current)")
                curFileIdx = i
            }
            print("running \(i) on thread: \(Thread.current)")
            try? await Task.sleep(for: .seconds(1))
        }
     }
}
get 0 on thread: <_NSMainThread: 0x6000017040c0>{number = 1, name = main}
running 0 on thread: <NSThread: 0x6000017021c0>{number = 3, name = (null)}
get 1 on thread: <_NSMainThread: 0x6000017040c0>{number = 1, name = main}
running 1 on thread: <NSThread: 0x6000017021c0>{number = 3, name = (null)}
get 2 on thread: <_NSMainThread: 0x6000017040c0>{number = 1, name = main}
running 2 on thread: <NSThread: 0x60000174c980>{number = 7, name = (null)}
get 3 on thread: <_NSMainThread: 0x6000017040c0>{number = 1, name = main}
running 3 on thread: <NSThread: 0x600001754480>{number = 4, name = (null)}
get 4 on thread: <_NSMainThread: 0x6000017040c0>{number = 1, name = main}
running 4 on thread: <NSThread: 0x600001754480>{number = 4, name = (null)}
get 5 on thread: <_NSMainThread: 0x6000017040c0>{number = 1, name = main}
running 5 on thread: <NSThread: 0x600001754480>{number = 4, name = (null)}

Какое совпадение, я добавил ответ одновременно с вами, а также предоставил ProgressView для демонстрации состояния «загрузки». В любом случае, хорошая мысль с @MainActor! Я бы только добавил (как я уже упоминал в своем ответе), что важно учитывать разные сценарии и соответствующим образом отменять незавершенную задачу.

Hollycene 06.08.2024 15:03

Хаха боже мой, я этого не заметил xD.

sonle 06.08.2024 16:54
Ответ принят как подходящий

Это простое решение для обновления текстового представления с помощью асинхронной функции, запускаемой кнопкой, как в вашем примере.

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

Все зависит от вашего варианта использования и логики вашего приложения.

import SwiftUI

public class ExportEngine : ObservableObject {
    
    @Published var curFileIdx : Int = 0
    @Published var isRunning : Bool = false

    @MainActor
    public func exportFiles() async
    {
        isRunning = true
        
        for i in (0...10) {
            curFileIdx = i
            print("i = \(i)")
            
            // it waits 1 second before the next iteration
            try? await Task.sleep(nanoseconds: 1_000_000_000)
        }
        
        isRunning = false
    }
}

struct ContentView: View {
    
    // Initialize the exportEngine here as the @StateObject or pass the reference from the parent view as the @ObservedObject
    @StateObject private var exportEngine = ExportEngine()
        
    var body: some View {
        VStack {
            Button("Run") {
                // This task handles the execution of your async function.
                // However, you need to handle the cancelation of this task in certain situations
                // (for example cancel the task when the use leaves the screen, etc.)
                Task {
                    await exportEngine.exportFiles()
                }
            }
            
            // Progress view is visible only when the engine is running
            if exportEngine.isRunning {
                ProgressView()
            }
            
            Text("File \(exportEngine.curFileIdx)")
        }
    }
}


#Preview {
    ContentView()
}

Если вы используете .task вместо Task, вам не нужен StateObject.

malhal 06.08.2024 16:24

Да, хорошая мысль, малхал! Я использую @StateObject в своем ответе, потому что в ответе указан класс ExportEngine, поэтому я хотел сохранить ссылку на исходный ответ.

Hollycene 06.08.2024 21:32

@malhal, можешь ли ты объяснить, что ты имеешь в виду, или привести пример? Я парень на C++, который программирует на Swift всего 10 дней. Крутая кривая обучения.

Danny 08.08.2024 01:55

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