Search code examples
swiftxcodemultithreadingxcode14dispatch-queue

How to move code block out of main thread at SwiftUI


I need to fetch contacts at application launch and store them with Core Data.

For fetching I have following method:

static var fetchAllContacts: PersistenceController = {
        let result = PersistenceController(inMemory: true)

        let viewContext = result.container.viewContext

        // Run this in the background async

        // Get access to the Contacts store
        let store = CNContactStore()

        // Specify which data keys we want to fetch
        let keys = [CNContactGivenNameKey, CNContactPhoneNumbersKey] as [CNKeyDescriptor]
        DispatchQueue.global(qos: .background).async {
            let fetchRequest = CNContactFetchRequest(keysToFetch: keys)

            DispatchQueue.main.async {
                // Call method to fetch all contacts
                do {


                    try store.enumerateContacts( //This method should not be called on the main thread as it may lead to UI unresponsiveness.



                        with: fetchRequest,
                        usingBlock: { contact, _ in
                            // Do something with the contact

                            let newItem = Item(context: viewContext)
                            newItem.id = UUID()
                            newItem.timestamp = Date()
                            newItem.givenName = contact.givenName

                            do {
                                try viewContext.save()
                            } catch {
                                /*
                                 Replace this implementation with code to handle the error appropriately.
                                 fatalError() causes the application to generate a crash log and terminate.
                                 You should not use this function in a shipping application,
                                 although it may be useful during development.
                                */
                                let nsError = error as NSError
                                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
                            }

                        }
                    )
                } catch {
                    // If there was an error, handle it here
                    print("Error")
                }
            }
        }

        return result
    }()

However with Xcode 14 I have alert that asks to separate threads.

             try store.enumerateContacts( //This method should not be called on the main thread as it may lead to UI unresponsiveness.

How may I move store.enumerateContacts out of the main thread so that it will fix an alert.


Solution

  • DispatchQueue has multiple QoSClass priority settings. DispatchQueue.main has the highest priority, and is typically reserved for UI updates. All other QoSClass settings can be viewed here: https://developer.apple.com/documentation/dispatch/dispatchqos/qosclass

    You mention that the CoreData logic should be performed at app launch. I'd first suggest changing the outermost DispatchQueue block to .userInteractive or .userInitiated. This gives the operation higher priority, therefore more compute power if available.

    Secondly, use DispatchQueue.main only for UI updates. I assume that Item is a data object that gets used in a View immediately? Only run the Item setup on .main.

    For example:

    static var fetchAllContacts: PersistenceController = {
        // setup code
        DispatchQueue.global(qos: .userInitiated).async {
            let fetchRequest = CNContactFetchRequest(keysToFetch: keys)
            do {
                try store.enumerateContacts(with: fetchRequest) { contact, _ in
                    DispatchQueue.main.async  {
                        // item creation
                    }
                }
            } catch {
                // error handling
            }
        }
    }