最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - Prevent function from blocking the UI - Stack Overflow

programmeradmin1浏览0评论
.onScrollVisibilityChange { isVisible in
    if isVisible {
        Task {
            saveIdsWatchedTime(postTime: meme.time)
        }
    }
}

The onScrollVisibilityChange is attached to a LazyVStack item and every time saveIdsWatchedTime is called, the UI get's blocked and the scrolling is lagging.

Why is that when I'm using Task, which is basically async?

The function saveIdsWatchedTime isn't even a big deal and is finished in couple milliseconds.

This is the function:

func saveIdsWatchedTime(postTime: Int) {
    
    if(watchedTimeChecked){
        return
    }
    watchedTimeChecked = true
    
    if((gal == "latest" || gal == "top") && !endOfMemesReached && postTime < lastTimeChecked){
        
        var endString = ""
        var endArray = [String]()
        
        let newestMemeViewed = Int(memesWatchedTimeRanges[0]ponents(separatedBy: ",")[0])
        var oldestMemeViewed = Int(memesWatchedTimeRanges[0]ponents(separatedBy: ",")[1])

        if(oldestMemeViewed! > postTime){
            memesWatchedTimeRanges[0] = String(newestMemeViewed!) + "," + String(postTime)
            oldestMemeViewed = postTime
        }

        var firstPassed = false
        for bla in memesWatchedTimeRanges{
            
            let newestTimePastScroll = Int(blaponents(separatedBy: ",")[0])
            let oldestTimePastScroll = Int(blaponents(separatedBy: ",")[1])
            
            if(!firstPassed){
                
                firstPassed = true
                
                endArray.append(bla)
                endString += String(newestTimePastScroll!) + "," + String(oldestTimePastScroll!) + "|"

            }else{
                if(newestMemeViewed! > newestTimePastScroll! && oldestMemeViewed! < oldestTimePastScroll!){

                }else{
                    endArray.append(bla)
                    endString += String(newestTimePastScroll!) + "," + String(oldestTimePastScroll!) + "|"
                }
            }
        }
        
        if(endString.onlyLastCharacters(1) == "|"){
            endString = String(endString.dropLast(1))
        }
        
        memesWatchedTimeRanges = endArray

        if(gal == "latest"){
            memesWatchedTimeRangesString = endString // AppStorage, between 25 and 100 characters long
        }else{
            memesWatchedTimeRangesTopString = endString // AppStorage, between 25 and 100 characters long
        }
    }
}
.onScrollVisibilityChange { isVisible in
    if isVisible {
        Task {
            saveIdsWatchedTime(postTime: meme.time)
        }
    }
}

The onScrollVisibilityChange is attached to a LazyVStack item and every time saveIdsWatchedTime is called, the UI get's blocked and the scrolling is lagging.

Why is that when I'm using Task, which is basically async?

The function saveIdsWatchedTime isn't even a big deal and is finished in couple milliseconds.

This is the function:

func saveIdsWatchedTime(postTime: Int) {
    
    if(watchedTimeChecked){
        return
    }
    watchedTimeChecked = true
    
    if((gal == "latest" || gal == "top") && !endOfMemesReached && postTime < lastTimeChecked){
        
        var endString = ""
        var endArray = [String]()
        
        let newestMemeViewed = Int(memesWatchedTimeRanges[0].components(separatedBy: ",")[0])
        var oldestMemeViewed = Int(memesWatchedTimeRanges[0].components(separatedBy: ",")[1])

        if(oldestMemeViewed! > postTime){
            memesWatchedTimeRanges[0] = String(newestMemeViewed!) + "," + String(postTime)
            oldestMemeViewed = postTime
        }

        var firstPassed = false
        for bla in memesWatchedTimeRanges{
            
            let newestTimePastScroll = Int(bla.components(separatedBy: ",")[0])
            let oldestTimePastScroll = Int(bla.components(separatedBy: ",")[1])
            
            if(!firstPassed){
                
                firstPassed = true
                
                endArray.append(bla)
                endString += String(newestTimePastScroll!) + "," + String(oldestTimePastScroll!) + "|"

            }else{
                if(newestMemeViewed! > newestTimePastScroll! && oldestMemeViewed! < oldestTimePastScroll!){

                }else{
                    endArray.append(bla)
                    endString += String(newestTimePastScroll!) + "," + String(oldestTimePastScroll!) + "|"
                }
            }
        }
        
        if(endString.onlyLastCharacters(1) == "|"){
            endString = String(endString.dropLast(1))
        }
        
        memesWatchedTimeRanges = endArray

        if(gal == "latest"){
            memesWatchedTimeRangesString = endString // AppStorage, between 25 and 100 characters long
        }else{
            memesWatchedTimeRangesTopString = endString // AppStorage, between 25 and 100 characters long
        }
    }
}
Share Improve this question edited Feb 8 at 12:23 TheGreatCornholio asked Feb 7 at 23:59 TheGreatCornholioTheGreatCornholio 1,4951 gold badge22 silver badges48 bronze badges 14
  • The function is async, but you call it await, which says "block here until the async function finishes," thereby making it sync. – Tim Roberts Commented Feb 8 at 0:19
  • Yeah I thought async and await together doesn't make sense, but when I remove await then I get the error: "Expression is 'async' but is not marked with 'await'" why is swift/swiftui such disgusting and unintuitive pos??? – TheGreatCornholio Commented Feb 8 at 0:22
  • Task replaces the old function of async. You don't use both. stackoverflow.com/questions/70979038/… – Tim Roberts Commented Feb 8 at 0:25
  • 3 What does saveIdsWatchedTime do? Is it CPU-bound? – Sweeper Commented Feb 8 at 8:07
  • 1 assuming that, you tried commenting out the whole implementation within onScrollVisibilityChange and there it doesn’t block the UI, out of curiosity, try Task.detached(priority:.utility) {…} – Luca Iaco Commented Feb 8 at 10:26
 |  Show 9 more comments

3 Answers 3

Reset to default 0

Ensure that your func saveIdsWatchedTime(postTime: Int) is thread-safe. Marking a function as async actually runs it on a subthread.

.onScrollVisibilityChange { isVisible in
    if isVisible {
        Task {
            await saveIdsWatchedTime(postTime: meme.time)
        }
    }
}

func saveIdsWatchedTime(postTime: Int) async {
    ...
}

I assume that saveIdsWatchedTime is a method of View so by default it will be bounds to MainActor because View is bounded to it.

So using Task or even Task.detach does not work because saveIdsWatchedTime still run in main thread.

To not run saveIdsWatchedTime on main thread which cause block UI, you could consider create a global actor.

struct ContentView: View {
    ...
    @Global func saveIdsWatchedTime() { // <- bound to global actor
        // your code goes here
    }
}

@globalActor actor Global {
    static var shared: Global = .init()
}

You can also implement custom executor to specify which thread you want to run your code too.

View functions by default now run on @MainActor. That means that by default your function looks like this @MainActor func saveIdsWatchedTime(). It doesn't matter if you call it from a Task since the function is has a @MainActor signature it will block the UI. You can move the Task inside the function instead of calling it inside onScrollVisibilityChange. Any assignment you make to a @State variable will still be made in the main actor therefore blocking the UI. Ideally you want to leave any assignments until the end of the function. Progress flags are usually an exception. The other solution is to declare saveIdsWatchedTime inside an @Observable object and mark the function as nonisolated and async. Note that in this case you'd have to move any @State variables to the @Observable object and make sure any UI exposed variables are called on the main actor since the function is now nonisolated.

发布评论

评论列表(0)

  1. 暂无评论