I don't know if this question qualifies to be here or not, but even after so much of research, I could not find a suitable guide for this question. I hope I get an answer here.
I see that all the messaging apps like Viber, WhatsApp, Telegram fetch the user contacts and parse them so fast and efficiently that there is almost zero delay. I was trying to replicate that but was never successful. It always takes good 40-60 seconds time for parsing 3000 contacts by running the whole operation on the background thread. Even that is causing the UI freezing on slower devices like 5 and 5S. After fetching the contacts I have to send them to the backend to identify which user are registered on the platform which also adds up to the total time. The above mentioned apps does this in no time!
I would be glad if someone can suggest a way to parse the contacts in the most efficient and faster way without blocking the main thread.
Here is the code, I use at the moment.
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 are fetched here when the app is launched
private func ApplicationLaunched() {
DispatchQueue.global(qos: .background).async {
let contactsService:ContactsService = self.serviceHolder.get()
contactsService.fetchContacts()
}
allowedContactKeys
? Maybe theCNContactThumbnailImageDataKey
is too heavy for 3000 contacts? I never tried it for that many contacts, but I'm fetching 200 contacts almost instantly in my app, but I'm not requesting the thumbnail image. – GrittyCNContactThumbnailImageDataKey
andCNContactOrganizationNameKey as CNKeyDescriptor
. The fetching is faster, but still not the level expected. previously it was taking 65 seconds, but it takes 60-62 secs. – Chondro.userInitiated
or other mode, or create your own dispatch queue. – Ellene