2017-10-25 64 views
5

Nie wiem, czy to pytanie kwalifikuje się do tego, czy nie, ale nawet po tak wielu badaniach nie mogłem znaleźć odpowiedniego przewodnika na to pytanie. Mam nadzieję, że otrzymam odpowiedź tutaj.W jaki sposób aplikacje do wysyłania wiadomości iOS, takie jak Viber, Telegram, WhatsApp pobierają kontakty tak szybko i skutecznie

Widzę, że wszystkie aplikacje do przesyłania wiadomości, takie jak Viber, WhatsApp, Telegram, pobierają kontakty użytkownika i analizują je tak szybko i sprawnie, że opóźnienie jest prawie zerowe. Próbowałem to powtórzyć, ale nigdy się nie udało. Zawsze zajmuje dobre 40-60 sekund na przeanalizowanie 3000 kontaktów, wykonując całą operację na wątku tła. Nawet to powoduje zamrożenie interfejsu użytkownika na wolniejszych urządzeniach, takich jak 5 i 5S. Po pobraniu kontaktów muszę wysłać je do zaplecza, aby zidentyfikować, który użytkownik jest zarejestrowany na platformie, co również daje łączny czas. Powyższe aplikacje robią to w mgnieniu oka!

Byłbym szczęśliwy, gdyby ktoś mógł zaproponować sposób na przeanalizowanie kontaktów w najbardziej wydajny i szybszy sposób bez blokowania głównego wątku.

Oto kod, którego używam w tej chwili.

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


}` 

i styki są pobierane tu, gdy aplikacja jest uruchomiona

private func ApplicationLaunched() { 
    DispatchQueue.global(qos: .background).async { 
     let contactsService:ContactsService = self.serviceHolder.get() 
     contactsService.fetchContacts() 
    } 
+0

Po prostu pytanie, czy próbowałeś grać z 'allowedContactKeys'? Może "CNContactThumbnailImageDataKey" jest zbyt ciężki dla 3000 kontaktów? Nigdy nie próbowałem tego w przypadku wielu kontaktów, ale niemal natychmiast otrzymuję 200 kontaktów w mojej aplikacji, ale nie żądam miniatury. – TawaNicolas

+0

Czy próbowałeś pobrać je wsadowo? – swift2geek

+0

Nie jestem pewien, ale myślę, że WhatsApp rozpoczyna synchronizowanie kontaktów zaraz po otwarciu aplikacji. Przeczytaj ten https://www.quora.com/How-does-the-contacts-sync-work-in-WhatsApp/answer/Jinesh-Soni?srid=RhqE –

Odpowiedz

1

Domyślam się, że liczba kontaktów wysyłasz do backend jest ogromna. 3000 kontaktów to za dużo i myślę, że dzieje się jedno z poniższych:

  1. Albo prośba jest zbyt duża, a dostarczenie do zaplecza wymaga czasu.
  2. Jest zbyt ciężki dla zaplecza, a przetworzenie i zwrócenie się do klienta wymaga czasu, a to powoduje opóźnienie.

najrzadziej problemem jest:

  1. Twoja metoda parsowanie jest bardzo ciężki na CPU. Jest to jednak mało prawdopodobne.

Czy zmierzyłeś czas pomiędzy rozpoczęciem i zakończeniem parsowania?

myślę, że należy zmierzyć czas trwania między wszystkimi działaniami robisz, na przykład:

  1. Measure jak długo to trwa do pobierania kontaktów z urządzenia.
  2. Zmierz czas analizowania kontaktów.
  3. Zmierz czas potrzebny na odpowiedź z zaplecza.

Pomoże to dokładnie określić, co zajmuje zbyt dużo czasu.

Mam nadzieję, że pomoże to w rozwiązaniu problemu.

+0

Dzięki @TawaNicolas. Twoje rozwiązanie naprawdę pomogło mi znaleźć problem. To 3, o których wspomniałeś, jest przyczyną problemu. Używamy phoneNumberKit do analizowania liczb i dodawania kodu kraju, co jest bardzo powolne i zajmuje cały czas. Przeniosłem się do libPhoneNumber-iOS, który działa doskonale. Aby pobrać i przeanalizować 2900 kontaktów, czas sprowadza się do 3 sekund z 65 sekund, w tym do połączenia sieciowego –

+0

. Z powodu filtrowania macierzy powstaje również blokowanie interfejsu użytkownika. Gdy otrzymamy odpowiedź od serwera, przechowuję wszystkie numery telefonów w tablicy i porównuję je z tablicą wszystkich kontaktów i tworzę nową tablicę ze wszystkimi kontaktami, które nie są na naszej platformie. Ponieważ dzieje się to w głównym wątku, interfejs użytkownika jest blokowany, przenosząc go do globalnego wątku użytecznego, który zapobiegał blokowaniu interfejsu użytkownika. Ponadto pobieranie kontaktów zostało przeniesione do wątku użytecznego, aby przyspieszyć pobieranie. –