Как приложения для обмена сообщениями iOS, такие как Viber, Telegram, WhatsApp, получают контакты так быстро и эффективно

Я не знаю, подходит ли этот вопрос для того, чтобы быть здесь, или нет, но даже после стольких исследований я не смог найти подходящего руководства по этому вопросу. Я надеюсь, что я получу ответ здесь.

Я вижу, что все приложения для обмена сообщениями, такие как Viber, WhatsApp, Telegram, извлекают контакты пользователей и анализируют их так быстро и эффективно, что задержка практически равна нулю. Я пытался повторить это, но никогда не был успешным. Парсинг 3000 контактов всегда занимает 40-60 секунд, выполняя всю операцию в фоновом потоке. Даже это вызывает зависание пользовательского интерфейса на медленных устройствах, таких как 5 и 5S. После получения контактов я должен отправить их на сервер, чтобы определить, какой пользователь зарегистрирован на платформе, что также увеличивает общее время. Вышеупомянутые приложения делают это в кратчайшие сроки!

Я был бы рад, если кто-то может предложить способ синтаксического анализа контактов наиболее эффективным и быстрым способом, не блокируя основной поток.

Вот код, который я использую на данный момент.

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
    }
}


}`

И контакты выбираются здесь при запуске приложения

private func ApplicationLaunched() {
    DispatchQueue.global(qos: .background).async {
        let contactsService:ContactsService = self.serviceHolder.get()
        contactsService.fetchContacts()
    }

2 ответа

Решение

Я предполагаю, что количество контактов, которые вы отправляете на сервер, огромно. 3000 контактов слишком много, и я думаю, что происходит одно из следующих действий:

  1. Либо запрос слишком велик, и для его доставки требуется время.
  2. Это слишком тяжело для бэкэнда и требует времени для обработки и возврата клиенту, и это то, что вызывает задержку для вас.

Наименее вероятная проблема:

  1. Ваш метод разбора очень сильно загружает процессор. Но это очень маловероятно.

Вы измерили продолжительность между началом и окончанием разбора?

Я думаю, что вы должны измерить продолжительность между всеми действиями, которые вы делаете, например:

  1. Измерьте, сколько времени требуется, чтобы получить контакты с устройства.
  2. Измерьте, сколько времени занимает разбор контактов.
  3. Измерьте, сколько времени требуется, чтобы получить ответ от бэкэнда.

Это поможет вам точно определить, что занимает слишком много времени.

Я надеюсь, что это поможет в решении вашей проблемы.

Другое решение - использовать правильный метод в PhoneNumberKit:-)

У меня была та же проблема, что и у вас, а затем я понял, что у PhoneNumberKit есть два метода и что я использовал неправильный:

  • Первый, который используется для анализа отдельного телефонного номера (который вы используете в своем коде выше). Он принимает один объект в качестве ввода.
  • Еще один, который позволяет разобрать массив телефонных номеров сразу. Он принимает массив номера телефона в качестве ввода.

Наименование этих двух методов сбивает с толку, поскольку они идентичны, за исключением их ввода, но разница в производительности ошеломляет:

  • Использование отдельного метода анализа телефонных номеров (с циклом for, как вы) заняло ~60 секунд
  • С помощью метода разбора массива проанализировал ~500 телефонных номеров за < 2 сек.

Поэтому, если кто-то захочет использовать нативную библиотеку Swift, я бы посоветовал вам использовать Phone Number Kit, так как он отлично работает и имеет много удобных методов (например, автоформатирование TextFields).

Другие вопросы по тегам