Loading and Displaying a Large Data Feed

Hi everybody,

I am moving the database of my app on my server and I am downloading the data and, through core data, I am entering the data locally in the database, so that the user can have them even in the absence of the internet.

Currently, due to development, the app downloads all the data every time. the problem is that the json download takes a few milliseconds. While data entry takes more than two minutes for only 21,000 entries. This duration is excessive and I am trying to speed it up. Also because there are now 21,000 entriese, but I’m going to add them. What if I get to 100,000? what would happen?

In my research I found this explanation that comes from the official Apple website:Apple tutorial. I understand it theoretically. But I can’t apply it in my specific case.
Below are the two portions of code that I use in my app to download and insert data.

can you help me please?

regards

This function download the json from url
        private func downloadHeadwords(){
            AF.request("http://jaapp.it/API/lemma/read.php").responseJSON { response in
                switch response.result {
                    case .success(let json):
                        if let headwordData = json as? [[String: Any]] {
                            for headword in headwordData {
                                DatabaseManager.insertNewLemma(headword) //<- this insert the data
                            }
                            print("numero di lemma:\(DatabaseManager.getLemma().count)")
                        }
                    case .failure(let error):
                        if let error = error.errorDescription {
                            print("radicali Errore:\(error)")
                            //TODO: gestire l'errore
                        }
                }
            }
        }

//this is the function for insertion of data
class func insertNewLemma(_ datiLemma:Dictionary<String,Any>) {
        let context = getContext()
        do {
            guard let listScritture = datiLemma["Scrittura"] as? [[String:String]] else { return }
            var lemmaObject:Lemma? = nil
            for scritturaData in listScritture {
                guard let scritturaKanji = scritturaData["scritturaKanji"] else { return }
                guard let scritturaKana = scritturaData["scritturaKana"] else { return }
                let requestLemma:NSFetchRequest<Lemma> = Lemma.fetchRequest()
                requestLemma.predicate = NSPredicate(format: "ANY scrittura.lemmaKanji == '%@' && ANY scrittura.lemmaKana == '%@'", scritturaKanji, scritturaKana)
                let listaLemma = try context.fetch(requestLemma)
                if let lemmaItem = listaLemma.first {
                    lemmaObject = lemmaItem
                }
            }
            
            if let lemma = lemmaObject {
                self.populateLemma(lemma, datiLemma: datiLemma)
            }else{
                let theLemma = NSEntityDescription.insertNewObject(forEntityName: "Lemma", into: context) as! Lemma
                self.populateLemma(theLemma, datiLemma: datiLemma)
            }
            
        } catch {
            print("C'è stato un errore nel database")
        }
    }

These functions make insertion to relatives tables
class func populateLemma(_ lemmaItem:Lemma, datiLemma:Dictionary<String,Any>) {
        let context = getContext()
        if let ambitoUso = datiLemma["ambito_uso"] {
            lemmaItem.ambitoUso = ambitoUso as? String
        }
        if let catGramm = datiLemma["catGramm"] {
            lemmaItem.catGramm = catGramm as? String
        }
        if let confrontaCon = datiLemma["confrontaCon"] {
            lemmaItem.confrontaCon = confrontaCon as? String
        }
        if let contrario = datiLemma["contrario"] {
            lemmaItem.contrario = contrario as? String
        }
        if let frequenza = datiLemma["frequenza"] as? NSString {
            lemmaItem.frequenza = frequenza.intValue
        }
        if let kanjiRef = datiLemma["kanjiRef"] as? String {
            lemmaItem.kanjiRef = kanjiRef
            let kanjiFind = getKanji().filter{((($0.kanji?.contains(kanjiRef))!))}
            if let kanji = kanjiFind.first {
                lemmaItem.kanji = kanji
            }
        }
        if let noKanjiLemmaID = datiLemma["noKanjiLemmaID"] {
            lemmaItem.noKanjiLemmaID = noKanjiLemmaID as? String
        }
        if let subCatGramm = datiLemma["subCatGramm"] {
            lemmaItem.subCatGramm = subCatGramm as? String
        }
        if let xRef = datiLemma["xRef"] {
            lemmaItem.xRef = xRef as? String
        }
        if let listaScrittureSet = lemmaItem.scrittura {
            if let listaScritture = listaScrittureSet.allObjects as? [LemmaScrittura] {
                for scrittura in listaScritture {
                    context.delete(scrittura)
                }
            }
        }
        
        if let listaSignificatiSet = lemmaItem.significato {
            if let listaSignificati = listaSignificatiSet.allObjects as? [LemmaSignificato] {
                for significato in listaSignificati {
                    context.delete(significato)
                }
            }
        }
        
        if let coniugazioneVerbo = lemmaItem.coniugVerbo {
            context.delete(coniugazioneVerbo)
        }
        
        if let coniugazioneAggettivo = lemmaItem.coniugAggettivo {
            context.delete(coniugazioneAggettivo)
        }
        
        if let scrittureArray = datiLemma["Scrittura"] as? [[String:String]] {
            for scrittura in scrittureArray {
                let theScrittura = NSEntityDescription.insertNewObject(forEntityName: "LemmaScrittura", into: context) as! LemmaScrittura
                self.populateScrittura(theScrittura, datiLemmaScrittura: scrittura)
                lemmaItem.addToScrittura(theScrittura)
            }
        }
        
        if let significatoArray = datiLemma["Significato"] as? [[String:String]] {
            for significato in significatoArray {
                let theSignificato = NSEntityDescription.insertNewObject(forEntityName: "LemmaSignificato", into: context) as! LemmaSignificato
                self.populateSignificato(theSignificato, datiLemmaSignificato: significato)
                lemmaItem.addToSignificato(theSignificato)
            }
        }
        
        if let coniugazioneVerbo = datiLemma["coniugazioneVerbo"] as? [String:String] {
            let theConiugazioneVerbo = NSEntityDescription.insertNewObject(forEntityName: "ConiugVerbo", into: context) as! ConiugVerbo
            self.populateConiugazioneVerbo(theConiugazioneVerbo, datiConiugazione: coniugazioneVerbo)
            lemmaItem.coniugVerbo = theConiugazioneVerbo
        }
        
        if let coniugazioneAggettivo = datiLemma["coniugazioneAggettivo"] as? [String:String] {
            let theConiugazioneAggettivo = NSEntityDescription.insertNewObject(forEntityName: "ConiugAggettivo", into: context) as! ConiugAggettivo
            self.populateConiugazioneAggettivo(theConiugazioneAggettivo, datiConiugazione: coniugazioneAggettivo)
            lemmaItem.coniugAggettivo = theConiugazioneAggettivo
        }
    }
    
    class func populateScrittura(_ scritturaItem:LemmaScrittura, datiLemmaScrittura:Dictionary<String,String>) {
        if let scritturaKanji = datiLemmaScrittura["scritturaKanji"] {
            scritturaItem.lemmaKanji = scritturaKanji
        }
        if let scritturaKana = datiLemmaScrittura["scritturaKana"] {
            scritturaItem.lemmaKana = scritturaKana
        }
        if let scritturaRomaji = datiLemmaScrittura["scritturaRomaji"] {
            scritturaItem.lemmaRomaji = scritturaRomaji
        }
        if let scritturaRomaji1 = datiLemmaScrittura["scritturaRomaji1"] {
            scritturaItem.lemmaRomaji1 = scritturaRomaji1
        }
        if let scritturaVideo = datiLemmaScrittura["scritturaVideo"] {
            scritturaItem.lemmaVideo = scritturaVideo
        }
    }
    
    class func populateSignificato(_ significatoItem:LemmaSignificato, datiLemmaSignificato:Dictionary<String,String>) {
        if let significatoITA = datiLemmaSignificato["significatoITA"] {
            significatoItem.significatoITA = significatoITA
        }
        if let significatoEN = datiLemmaSignificato["significatoEN"] {
            significatoItem.significatoEN = significatoEN
        }
        if let spiegazioneITA = datiLemmaSignificato["spiegazioneITA"] {
            significatoItem.spiegazioneITA = spiegazioneITA
        }
        if let spiegazioneEN = datiLemmaSignificato["spiegazioneEN"] {
            significatoItem.spiegazioneEN = spiegazioneEN
        }
    }
    
    class func populateConiugazioneVerbo(_ coniugazioneItem:ConiugVerbo, datiConiugazione:Dictionary<String,String>) {
        if let base1 = datiConiugazione["base1"] {
            coniugazioneItem.base1 = base1
        }
        if let base2 = datiConiugazione["base2"] {
            coniugazioneItem.base2 = base2
        }
        if let base3 = datiConiugazione["base3"] {
            coniugazioneItem.base3 = base3
        }
        if let base4 = datiConiugazione["base4"] {
            coniugazioneItem.base4 = base4
        }
        if let base5 = datiConiugazione["base5"] {
            coniugazioneItem.base5 = base5
        }
        if let baseTE = datiConiugazione["baseTE"] {
            coniugazioneItem.baseTe = baseTE
        }
        if let baseTA = datiConiugazione["baseTA"] {
            coniugazioneItem.baseTa = baseTA
        }
    }
    
    class func populateConiugazioneAggettivo(_ coniugazioneItem:ConiugAggettivo, datiConiugazione:Dictionary<String,String>) {
        if let presAffForm = datiConiugazione["pres_Aff_Form"] {
            coniugazioneItem.presAffForm = presAffForm
        }
        if let presAffInf = datiConiugazione["pres_Aff_Inf"] {
            coniugazioneItem.presAffInf = presAffInf
        }
        if let presNegForm = datiConiugazione["pres_Neg_Form"] {
            coniugazioneItem.presNegForm = presNegForm
        }
        if let presNegInf = datiConiugazione["pres_Neg_Inf"] {
            coniugazioneItem.presNegInf = presNegInf
        }
        if let passAffForm = datiConiugazione["pass_Aff_Form"] {
            coniugazioneItem.passAffForm = passAffForm
        }
        if let passAffInf = datiConiugazione["pass_Aff_Inf"] {
            coniugazioneItem.passAffInf = passAffInf
        }
    }
    
    class func populateFraseEsempio(_ fraseEsempio:FraseEsempio, datiEsempio:Dictionary<String,String>){
        if let fraseKanji = datiEsempio["esempioKanji"]{
            fraseEsempio.esempioKanji = fraseKanji
        }
        if let fraseRomaji = datiEsempio["esempioRomaji"] {
            fraseEsempio.esempioRomaji = fraseRomaji
        }
        if let fraseEN = datiEsempio["esempioEN"] {
            fraseEsempio.esempioEN = fraseEN
        }
        if let fraseITA = datiEsempio["esempioITA"] {
            fraseEsempio.esempioITA = fraseITA
        }
        if let appartenenza = datiEsempio["appartenenza"] {
            fraseEsempio.appartenenza = appartenenza
        }
    }
    
    class func populateFraseFrasario(_ fraseFrasario:Frasario, datiFrase:Dictionary<String,String>){
        if let id = datiFrase["id"] as NSString? {
            fraseFrasario.id = Int16(id.intValue)
        }
        
        if let categoria = datiFrase["categoria"] {
            fraseFrasario.categoria = categoria
        }
        
        if let fraseKanji = datiFrase["fraseKanji"] {
            fraseFrasario.fraseKanji = fraseKanji
        }
        
        if let fraseRomaji = datiFrase["fraseRomaji"] {
            fraseFrasario.fraseRomaji = fraseRomaji
        }
        
        if let fraseIta = datiFrase["fraseITA"] {
            fraseFrasario.fraseITA = fraseIta
        }
        
        if let fraseEN = datiFrase["fraseEN"] {
            fraseFrasario.fraseEN = fraseEN
        }
    }

Hi Rufi,
A couple of questions, does your data change? Your approach of using Core Data as a local cache is a good idea, you need to also look at caching the JSON data from the URL, so that it will download only when the cache expires or new data is found depending on how you set this up.

In regards to parsing a large data, you can also try to get our server to use pagination, so instead of returning a whole large chunk of 100,000 or more records/entries, you can get them in batches from 1 to 100, 101 to 200, 201 to … you get the idea, that way you can download the ones that you want in smaller batches, which should be more efficient and faster.

cheers,

This topic was automatically closed after 166 days. New replies are no longer allowed.