【问题标题】:How does iOS messaging apps like Viber, Telegram, WhatsApp fetch contacts so fast and efficientlyViber、Telegram、WhatsApp 等 iOS 消息应用程序如何如此快速高效地获取联系人
【发布时间】:2017-10-25 09:11:11
【问题描述】:

我不知道这个问题是否适合在这里,但即使经过这么多的研究,我也找不到适合这个问题的指南。我希望我能在这里得到答案。

我看到 Viber、WhatsApp、Telegram 等所有消息传递应用程序都如此快速高效地获取用户联系人并对其进行解析,以至于延迟几乎为零。我试图复制它,但从未成功。通过在后台线程上运行整个操作,解析 3000 个联系人总是需要 40-60 秒的时间。即便如此,也会导致 UI 在 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()
    }

【问题讨论】:

  • 只是一个问题,你试过玩allowedContactKeys吗?也许CNContactThumbnailImageDataKey 对于 3000 个联系人来说太重了?我从来没有尝试过这么多联系人,但我几乎可以立即在我的应用中获取 200 个联系人,但我没有请求缩略图。
  • 你试过批量获取吗?
  • 不确定,但我认为 WhatsApp 会在首次打开应用程序后立即开始同步联系人。阅读此quora.com/How-does-the-contacts-sync-work-in-WhatsApp/answer/…
  • 除了在主线程和拇指图像上执行所有这些之外几乎相同。而且它运行得非常快,就像在 Telegram 或其他任何东西(2000 多个联系人)中一样。
  • @TawaNicolas 我尝试删除CNContactThumbnailImageDataKeyCNContactOrganizationNameKey as CNKeyDescriptor。获取速度更快,但仍然没有达到预期的水平。以前需要 65 秒,但现在需要 60-62 秒。

标签: ios swift ios11 cncontact cncontactstore


【解决方案1】:

另一种解决方案是在 PhoneNumberKit 中实际使用正确的方法:-)

我遇到了和你一样的问题,然后意识到 PhoneNumberKit 有两种方法并且我使用了错误的方法:

  • 第一个,用于解析单个电话号码(您在上面的代码中使用的那个)。它需要一个对象作为输入。
  • 另一个允许一次解析电话号码数组的方法。它需要一个电话号码数组作为输入。

这两种方法的命名令人困惑,因为它们除了输入之外都是相同的,但性能上的差异却是惊人的:

  • 使用个人电话号码解析方式(带for循环 像你一样)花了大约 60 秒
  • 使用 array 解析方法在

所以如果有人想使用 Swift 原生库,我会鼓励你使用电话号码工具包,因为它工作得很好并且有很多方便的方法(比如自动格式化 TextFields)。

【讨论】:

    【解决方案2】:

    我的猜测是您发送到后端的联系人数量是巨大的。 3000 个联系人太多了,我认为正在发生以下情况之一:

    1. 要么请求太大,后端交付需要时间。
    2. 后端太重,处理和返回客户端需要时间,这就是造成延迟的原因。

    最不可能的问题是:

    1. 您的解析方法对 CPU 的负担很大。但这不太可能。

    您是否测量了解析开始和结束之间的持续时间?

    我认为您应该测量您正在执行的所有操作之间的持续时间,例如:

    1. 测量从设备获取联系人所需的时间。
    2. 测量解析联系人需要多长时间。
    3. 衡量从后端获得响应所需的时间。

    这将帮助您准确找出耗时过长的原因。

    我希望这有助于解决您的问题。

    【讨论】:

    • 谢谢@TawaNicolas。您的解决方案确实帮助我找到了问题所在。是您提到的 3 导致了问题。我们正在使用 phoneNumberKit 来解析数字并添加国家代码,这非常慢并且一直在花费时间。我搬到了运行良好的 libPhoneNumber-iOS。获取和解析 2900 个联系人的时间从 65 秒缩短到 3 秒,包括网络调用
    • 另外,UI 冻结是由于过滤数组引起的。一旦我们从服务器获得响应,我将所有电话号码存储在一个数组中,并将其与所有联系人的数组进行比较,并与不在我们平台上的所有联系人组成一个新数组。由于它发生在主线程上,因此 UI 被阻塞,将其转移到全局实用程序线程防止了 UI 阻塞。此外,联系人获取已转移到实用程序线程以使其获取更快。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-25
    • 1970-01-01
    • 2015-10-23
    • 1970-01-01
    • 2017-09-29
    • 2019-01-18
    相关资源
    最近更新 更多