Replace threadsafe var with new concurrency model (Actor?)

Added, removed, moved, updated, yeah. That's quite error prone to do right in general case when all these things can happen simultaneously. This is easier with SwiftUI, and if you are limited to UIKit - you may try its iOS 13+ diffable data source API that performs this heavy lifting for you. Interestingly its main apply(snapshot) call is not limited to the main thread (contrary to the classic reloadRows / reloadData):

Yes, you can do that work on a background queue and update UI on the main thread (or even background thread as discussed above). However, you don't have to protect that data if you only read / modify it on a single background queue... Example:

        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1
        ...
        NotificationCenter.default.addObserver(forName: Notification.Name(""), object: nil, queue: queue) { n in
            dispatchPrecondition(condition: .notOnQueue(.main))
            // we are in background
            urls = ... // change urls here. single queue access → can be unprotected
            // you may apply diffable snapshot here (see above)
            
            let urlsCopy = urls // grab a copy. single queue access → can be unprotected

            DispatchQueue.main.async {
                ... urlsCopy // use urlsCopy here
                // you may apply diffable snapshot here (see above)
                // or you may apply insert / delete / move / modify updates here manually
            }
        }