如Viber,Telegram,WhatsApp等iOS消息应用程序如何快速有效地获取联系人

时间:2017-10-25 09:11:11

标签: ios swift ios11 cncontact cncontactstore

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

我看到所有的消息应用程序,如Viber,WhatsApp,Telegram都会获取用户联系人并快速有效地解析它们,几乎没有延迟。我试图复制它但从未成功过。通过在后台线程上运行整个操作来解析3000个联系人总是需要40-60秒的时间。即使这样也会导致UI在5和5S等较慢的设备上冻结。在获取联系人之后,我必须将它们发送到后端以识别在平台上注册了哪个用户,这也累计了总时间。上面提到的应用程序很快就会这样做!

如果有人可以建议一种方法以最有效和更快的方式解析联系人而不阻塞主线程,我会很高兴。

这是我目前使用的代码。

jsmith1,23/10/2017 20:58 
jsmith2,23/10/2017 21:00 
jsmith3,23/10/2017 20:59 
jsmith4,23/10/2017 21:15 
jsmith5,23/10/2017 21:26 
jsmith6,23/10/2017 21:05 
jsmith7,23/10/2017 21:47 

启动应用时,会在此处提取联系人

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


}`

2 个答案:

答案 0 :(得分:2)

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

  1. 请求太大,需要时间才能为后端提供服务。
  2. 后端太重了,处理并返回客户端需要时间,这就是造成延误的原因。
  3. 最不可能的问题是:

    1. 您的解析方法在CPU上非常繁重。但这不太可能。
    2. 您是否测量了解析开始和结束之间的持续时间?

      我认为你应该衡量你所做的所有行动之间的持续时间,例如:

      1. 衡量从设备获取联系人所需的时间。
      2. 衡量解析联系人所需的时间。
      3. 衡量从后端获得响应所需的时间。
      4. 这将帮助您精确确定需要花费的时间。

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

答案 1 :(得分:1)

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

我和你有同样的问题,然后意识到 PhoneNumberKit有两种方法,我使用的是错误的方法:

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

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

  • 使用个人电话号码解析方法(使用for循环 像你一样)花了大约60秒
  • 使用数组解析方法解析了<<< 2秒。

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