I do not know if this question can be here or not, but even after so many studies, I could not find a suitable guide for this question. I hope I get an answer here.
I see that all messaging applications, such as Viber, WhatsApp, Telegram, receive user contacts and analyze them so quickly and efficiently that there is almost zero delay. I tried to reproduce this, but was never successful. It takes only 40-60 seconds to parse 3000 contacts, starting the entire operation on the background thread. Even this leads to freezing the user interface on slower devices such as 5 and 5S. After extracting the contacts, I have to send them to the server to determine which user is registered on the platform, which also adds up to the total time. The above applications do this in no time!
I would be glad if someone could suggest a parsing method for contacts in the most efficient and fastest way without blocking the main thread.
Here is the code I'm currently using.
final class CNContactsService: ContactsService { private let phoneNumberKit = PhoneNumberKit() private var allContacts:[Contact] = [] private let contactsStore: CNContactStore init(network:Network) { contactsStore = CNContactStore() self.network = network } func fetchContacts() { fetchLocalContacts { (error) in if let uError = error { } else { let contactsArray = self.allContacts self.checkContacts(contacts: contactsArray, checkCompletion: { (Users) in let nonUsers = contactsArray.filter { contact in return !Users.contains(contact) } self.Users.value = Users self.nonUsers.value = nonUsers }) } } } func fetchLocalContacts(_ completion: @escaping (NSError?) -> Void) { switch CNContactStore.authorizationStatus(for: CNEntityType.contacts) { case CNAuthorizationStatus.denied, CNAuthorizationStatus.restricted: //User has denied the current app to access the contacts. self.displayNoAccessMsg() case CNAuthorizationStatus.notDetermined: //This case means the user is prompted for the first time for allowing contacts contactsStore.requestAccess(for: CNEntityType.contacts, completionHandler: { (granted, error) -> Void in //At this point an alert is provided to the user to provide access to contacts. This will get invoked if a user responds to the alert if (!granted ){ DispatchQueue.main.async(execute: { () -> Void in completion(error as! NSError) }) } else{ self.fetchLocalContacts(completion) } }) case CNAuthorizationStatus.authorized: //Authorization granted by user for this app. var contactsArray = [EPContact]() let contactFetchRequest = CNContactFetchRequest(keysToFetch: allowedContactKeys) do { // let phoneNumberKit = PhoneNumberKit() try self.contactsStore.enumerateContacts(with: contactFetchRequest, usingBlock: { (contact, stop) -> Void in //Ordering contacts based on alphabets in firstname if let contactItem = self.contactFrom(contact: contact) { contactsArray.append(contactItem) } }) self.allContacts = contactsArray completion(nil) } catch let error as NSError { print(error.localizedDescription) completion(error) } } } private var allowedContactKeys: [CNKeyDescriptor]{ //We have to provide only the keys which we have to access. We should avoid unnecessary keys when fetching the contact. Reducing the keys means faster the access. return [ CNContactGivenNameKey as CNKeyDescriptor, CNContactFamilyNameKey as CNKeyDescriptor, CNContactOrganizationNameKey as CNKeyDescriptor, CNContactThumbnailImageDataKey as CNKeyDescriptor, CNContactPhoneNumbersKey as CNKeyDescriptor, ] } private func checkUsers(contacts:[Contact],checkCompletion:@escaping ([Contact])->Void) { let phoneNumbers = contacts.flatMap{$0.phoneNumbers} if phoneNumbers.isEmpty { checkCompletion([]) return } network.request(.registeredContacts(numbers: phoneNumbersList), completion: { (result) in switch result { case .success(let response): do { let profiles = try response.map([Profile].self) let contacts = profiles.map{ CNContactsService.contactFrom(profile: $0) } checkCompletion(contacts) } catch { checkCompletion([]) } case .failure: checkCompletion([]) } }) } static func contactFrom(profile:Profile) -> Contact { let firstName = "" let lastName = "" let company = "" var displayName = "" if let fullName = profile.fullName { displayName = fullName } else { displayName = profile.nickName ?? "" } let numbers = [profile.phone!] if displayName.isEmpty { displayName = profile.phone! } let contactId = String(profile.id) return Contact(firstName: firstName, lastName: lastName, company: company, displayName: displayName, thumbnailProfileImage: nil, contactId: contactId, phoneNumbers: numbers, profile: profile) } private func parsePhoneNumber(_ number: String) -> String? { do { let phoneNumber = try phoneNumberKit.parse(number) return phoneNumberKit.format(phoneNumber, toType: .e164) } catch { return nil } } }`
And the contacts appear here when the application is running
private func ApplicationLaunched() { DispatchQueue.global(qos: .background).async { let contactsService:ContactsService = self.serviceHolder.get() contactsService.fetchContacts() }