【问题标题】:How to access iOS Contacts in batches?如何批量访问iOS通讯录?
【发布时间】:2019-01-26 15:45:31
【问题描述】:

我正在尝试在 Xamarin.iOS 中实现一个联系人阅读器,它会尝试遍历所有 CNContactStore 容器中的 iOS 联系人。我需要逐批迭代联系人结果集(分页联系人),而不是将所有联系人加载到内存中。然而,我在 SO 中看到的所有示例都首先将几乎所有联系人加载到内存中。

This question 有大量类似的示例,可以一次读取所有联系人。尽管这些示例具有逐个迭代的逻辑,但对我来说,如何跳过 N 并获取下 N 个联系人而不在下一次调用时从头开始迭代并不明显(至少对我来说这看起来不是最佳的)。

Apple's own documentation 读取

When fetching all contacts and caching the results, first fetch all contacts identifiers, then fetch batches of detailed contacts by identifiers as required

我能够使用其 SDK 中提供的基于光标的方法轻松地为 Android 执行此操作。这对 iOS 来说可能吗?如果不是,我们如何处理大量联系人(例如超过 2000 的联系人等)。我不介意快速的例子。我应该能够将它们转换为 Xamarin。

提前致谢。

【问题讨论】:

  • 我没有找到任何关于 CNContactStore 的 Apple 文档页面上明确说明的示例,即首先获取标识符,然后分批获取其他密钥。
  • @Augie 完全正确。我自己也在想这个。如果没有实际的方法,他们为什么会在文档中提到这一点。
  • 正确。今天早上我确实更新了我的联系人缓存机制。主要变化是预先获取所有标识符,然后在应用需要时懒惰地获取联系人。它更快,但没有进行实际性能测试。我使用电话号码作为键,值是包含 cncontact 以及我的 ContactsCache 类使用的其他属性的自定义类,主要是为了知道是否已经执行了提取和多久以前。感觉比应该做的更难,必须观察 CNContactStoreDidChange,在初始播种发生时排队等待查找请求,竞争案例
  • @Augie 你能在这里添加一个代码示例吗?

标签: ios swift xamarin xamarin.ios ios-contacts


【解决方案1】:

这是我采取的方法,因为我的要求不允许持续联系,只能保持活动记忆。并不是说这是正确的方法,而是首先获取所有标识符,然后根据需要延迟获取特定联系人的所有密钥,确实提高了性能。它还避免在联系人不存在时执行查找。 我也尝试使用 NSCache 而不是字典,但是当我需要遍历缓存时遇到了问题。

我截断了与主题不相关但仍能传达方法的函数。

import Contacts

extension CNContactStore {

    // Used to seed a Contact Cache with all identifiers
    func getAllIdentifiers() -> [String: CNContact]{

        // keys to fetch from store
        let minimumKeys: [CNKeyDescriptor] = [
            CNContactPhoneNumbersKey as CNKeyDescriptor,
            CNContactIdentifierKey as CNKeyDescriptor
        ]

        // contact request
        let request = CNContactFetchRequest(keysToFetch: minimumKeys)

        // dictionary to hold results, phone number as key
        var results: [String: CNContact] = [:]

        do {
            try enumerateContacts(with: request) { contact, stop in

                for phone in contact.phoneNumbers {
                    let phoneNumberString = phone.value.stringValue
                    results[phoneNumberString] = contact
                }
            }
        } catch let enumerateError {
            print(enumerateError.localizedDescription)
        }

        return results
    }

    // retreive a contact using an identifier
    // fetch keys lists any CNContact Keys you need
    func get(withIdentifier identifier: String, keysToFetch: [CNKeyDescriptor]) -> CNContact? {

        var result: CNContact?
        do {
            result = try unifiedContact(withIdentifier: identifier, keysToFetch: keysToFetch)
        } catch {
            print(error)
        }

        return result
    }
}

final class ContactsCache {

    static let shared = ContactsCache()

    private var cache : [String : ContactCacheItem] = [:]

    init() {

        self.initializeCache()  // calls CNContactStore().getAllIdentifiers() and loads into cache

        NotificationCenter.default.addObserver(self, selector: #selector(contactsAppUpdated), name: .CNContactStoreDidChange, object: nil)
    }

    private func initializeCache() {

        DispatchQueue.global(qos: .background).async {

            let seed = CNContactStore.getAllIdentifiers()

            for (number, contact) in seed{

                let item = ContactCacheItem.init(contact: contact, phoneNumber: number )
                self.cache[number] = item
            }
        }
    }

    // if the contact is in cache, return immediately, else fetch and execute completion when finished. This is bit wonky to both return value and execute completion, but goal was to reduce visible cell async update as much as possible
    public func contact(for phoneNumber: String, completion: @escaping (CNContact?) -> Void) -> CNContact?{

        if !initialized {   // the cache has not finished seeding, queue request

            queueRequest(phoneNumber: phoneNumber, completion: completion)  // save request to be executed as soon as seeding completes
            return nil
        }

        // item is in cache
        if let existingItem = getCachedContact(for: phoneNumber) {

            // is it being looked up
            if existingItem.lookupInProgress(){
                existingItem.addCompletion(completion: completion)
            }
            // is it stale or has it never been looked up
            else if existingItem.shouldPerformLookup(){

                existingItem.addCompletion(completion: completion)
                refreshCacheItem( existingItem )
            }
            // its current, return it
            return existingItem.contact
        }

        // item is not in cache
        completion(nil)
        return nil
    }

    private func getCachedContact(for number: String) -> ContactCacheItem?  {
        return self.cache.first(where: { (key, _) in key.contains( number) })?.value
    }


    // during the async initialize/seeding of the cache, requests may come in from app, so they are temporarily 'queued'
    private func queueRequest(phoneNumber: String, completion: @escaping (CNContact?) -> Void){..}
    // upon async initialize/seeding completion, queued requests can be executed
    private func executeQueuedRequests() {..}
    // if app receives notification of update to user contacts, refresh cache
    @objc func contactsAppUpdated(_ notification: Notification) {..}
    // if a contact has gone stale or never been fetched, perform the fetch
    private func refreshCacheItem(_ item: ContactCacheItem){..}
    // if app receives memory warning, dump data
    func clearCaches() {..}
}

class ContactCacheItem : NSObject {

    var contact: CNContact? = nil
    var lookupAttempted : Date?  // used to determine when last lookup started
    var lookupCompleted : Date?  // used to determien when last successful looup completed
    var phoneNumber: String     //the number used to look this item up
    private var callBacks = ContactLookupCompletion()  //used to keep completion blocks for lookups in progress, in case multilpe callers want the same contact info

    init(contact: CNContact?, phoneNumber: String){..}
    func updateContact(contact: CNContact?){..}  // when a contact is fetched from store, update it here
    func lookupInProgress() -> Bool {..}
    func shouldPerformLookup() -> Bool {..}
    func hasCallBacks() -> Bool {..}
    func addCompletion(completion: @escaping (CNContact?) -> Void){..}
}

【讨论】:

    猜你喜欢
    • 2011-10-16
    • 2019-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多