简体   繁体   English

如何批量访问iOS联系人?

[英]How to access iOS Contacts in batches?

I'm trying to implement a contacts reader in Xamarin.iOS which tries to iterate over the iOS contacts in all CNContactStore containers. 我正在尝试在Xamarin.iOS中实现联系人读取器,该读取器试图遍历所有CNContactStore容器中的iOS联系人。 Instead of loading all contacts into memory, I need to iterate over a contacts resultset batch by batch (paging contacts). 除了将所有联系人加载到内存中之外,我还需要逐批迭代联系人结果集(分页联系人)。 However all the examples that I saw in SO load almost all contacts into memory first. 但是,我在SO中看到的所有示例都首先将几乎所有联系人加载到内存中。

ie This question has loads of similar examples that read all contacts at once. 即, 这个问题有大量类似的例子,可以一次读取所有联系人。 Although these examples have logic which iterates one by one, it is not evident to me how to skip N and take the next N number of contacts without iterating from the beginning on the next call (which looks sub optimal at least to me). 尽管这些示例具有逐个迭代的逻辑,但对我来说,如何跳过N个并接下N个联系人,而又不从下一个呼叫的开头迭代(至少对我来说看起来不太理想),这对我来说并不明显。

Apple's own documentation reads 苹果自己的文档阅读

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

I was able to do this easily for Android using the cursor based approach available in its SDK. 我能够使用其SDK中提供的基于游标的方法轻松地针对Android执行此操作。 Is this at all possible for iOS? iOS完全可以吗? If not how can we handle a large number of contacts (eg something above 2000, etc.). 如果不是,我们如何处理大量的联系(例如2000年以上的联系,等等)。 I don't mind examples in swift. 我不介意举个例子。 I should be able to convert them to Xamarin. 我应该能够将它们转换为Xamarin。

Thanks in advance. 提前致谢。

Here's the approach I took, granted my requirements did not allow persisting contacts, only holding in active memory. 这是我采用的方法,但要满足我的要求,即不允许持久联系,而只能保留活动内存。 Not saying it's the right approach, but fetching all identifiers first, then lazily fetching all keys for a specific contact as needed, did improve performance. 不是说这是正确的方法,而是先获取所有标识符,然后根据需要懒惰地获取特定联系人的所有键,确实提高了性能。 It also avoids performing a lookup when the contact doesn't exist. 当联系人不存在时,它还避免执行查找。 I also tried using NSCache instead of dictionary, but ran into issue when I needed to iterate over the cache. 我也尝试使用NSCache而不是字典,但是当我需要遍历缓存时遇到了问题。

I truncated functions that aren't relevant to the topic but hopefully still convey the approach. 我截断了与该主题无关的功能,但希望仍能传达出这种方法。

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){..}
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM