簡體   English   中英

如何批量訪問iOS聯系人?

[英]How to access iOS Contacts in batches?

我正在嘗試在Xamarin.iOS中實現聯系人讀取器,該讀取器試圖遍歷所有CNContactStore容器中的iOS聯系人。 除了將所有聯系人加載到內存中之外,我還需要逐批迭代聯系人結果集(分頁聯系人)。 但是,我在SO中看到的所有示例都首先將幾乎所有聯系人加載到內存中。

即, 這個問題有大量類似的例子,可以一次讀取所有聯系人。 盡管這些示例具有逐個迭代的邏輯,但對我來說,如何跳過N個並接下N個聯系人,而又不從下一個呼叫的開頭迭代(至少對我來說看起來不太理想),這對我來說並不明顯。

蘋果自己的文檔閱讀

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。

提前致謝。

這是我采用的方法,但要滿足我的要求,即不允許持久聯系,而只能保留活動內存。 不是說這是正確的方法,而是先獲取所有標識符,然后根據需要懶惰地獲取特定聯系人的所有鍵,確實提高了性能。 當聯系人不存在時,它還避免執行查找。 我也嘗試使用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){..}
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM