IOS 5 पर फास्ट और कुशल कोर डेटा आयात को लागू करना


101

प्रश्न : माता-पिता के संदर्भ में बने परिवर्तनों को देखने के लिए मुझे अपना बच्चा संदर्भ कैसे मिलेगा ताकि वे UI को अपडेट करने के लिए मेरे NSFetchedResultsController को ट्रिगर करें?

यहाँ सेटअप है:

आपको एक ऐप मिला है जो बहुत सारे XML डेटा (लगभग 2 मिलियन रिकॉर्ड, प्रत्येक पाठ के सामान्य पैराग्राफ के आकार) को डाउनलोड और जोड़ता है। .sqlite फ़ाइल लगभग 500 एमबी आकार की हो जाती है। इस सामग्री को कोर डेटा में जोड़ने में समय लगता है, लेकिन आप चाहते हैं कि उपयोगकर्ता ऐप का उपयोग करने में सक्षम हो, जबकि डेटा स्टोर में डेटा को सामान्य रूप से लोड करता है। यह अदृश्य और उपयोगकर्ता के लिए अगोचर माना जाता है कि बड़ी मात्रा में डेटा चारों ओर ले जाया जा रहा है, इसलिए कोई लटका नहीं, कोई झटके नहीं: मक्खन की तरह स्क्रॉल। फिर भी, ऐप अधिक उपयोगी है, इसमें अधिक डेटा जोड़ा जाता है, इसलिए हम कोर डेटा स्टोर में डेटा को जोड़ने के लिए हमेशा इंतजार नहीं कर सकते। कोड में इसका मतलब है कि मैं वास्तव में आयात कोड में इस तरह कोड से बचना चाहूंगा:

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];

आईओएस 5 ऐप केवल इतना धीमा डिवाइस है जिसे यह समर्थन करने की आवश्यकता है कि यह एक iPhone 3GS है।

अपने वर्तमान समाधान को विकसित करने के लिए मैंने अब तक जिन संसाधनों का उपयोग किया है:

Apple के कोर डेटा प्रोग्रामिंग गाइड: कुशलता से डेटा आयात करना

  • मेमोरी को डाउन रखने के लिए ऑटोरेलिज पूल का इस्तेमाल करें
  • रिश्तों की कीमत। फ्लैट आयात करें, फिर अंत में रिश्तों को पैच अप करें
  • यदि आप इसे मदद कर सकते हैं तो क्वेरी न करें, यह चीजों को ओ (एन ^ 2) तरीके से धीमा करता है
  • बैचों में आयात करें: सहेजें, रीसेट करें, नाली करें और दोहराएं
  • आयात पर पूर्ववत् प्रबंधक बंद करें

iDeveloper TV - कोर डेटा प्रदर्शन

  • 3 संदर्भों का उपयोग करें: मास्टर, मुख्य और कारावास संदर्भ प्रकार

iDeveloper TV - Mac, iPhone और iPad अपडेट के लिए कोर डेटा

  • PerformBlock के साथ अन्य कतारों पर रनिंग सेव करने से चीज़ें तेज़ होती हैं।
  • एन्क्रिप्शन चीजों को धीमा कर देता है, यदि आप कर सकते हैं तो इसे बंद कर दें।

मार्कस ज़रा द्वारा कोर डेटा में बड़े डेटा सेट का आयात और प्रदर्शन

  • आप वर्तमान रन लूप को समय देकर आयात को धीमा कर सकते हैं, इसलिए चीजें उपयोगकर्ता को सहज महसूस होती हैं।
  • सैंपल कोड साबित करता है कि बड़े आयात करना संभव है और यूआई को उत्तरदायी बनाए रखना है, लेकिन 3 संदर्भों और डिस्क पर एसिंक्स की बचत के साथ तेज़ नहीं है।

मेरा वर्तमान समाधान

मुझे NSManagedObjectContext के 3 उदाहरण मिले हैं:

MasterManagedObjectContext - यह वह संदर्भ है जिसमें NSPersistentStoreCoordinator है और डिस्क पर सहेजने के लिए जिम्मेदार है। मैं ऐसा करता हूं इसलिए मेरी बचत अतुल्यकालिक हो सकती है और इसलिए बहुत तेज है। मैं इसे इस तरह लॉन्च करता हूं:

masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];

mainManagedObjectContext - यह वह संदर्भ है जिसका यूआई हर जगह उपयोग करता है। यह MasterManagedObjectContext का बच्चा है। मैं इसे इस तरह से बनाता हूं:

mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];

backgroundContext - यह संदर्भ मेरे NSOperation उपवर्ग में बनाया गया है जो XML डेटा को कोर डेटा में आयात करने के लिए जिम्मेदार है। मैं इसे ऑपरेशन की मुख्य विधि में बनाता हूं और इसे मास्टर संदर्भ से जोड़ता हूं।

backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];

यह वास्तव में बहुत तेजी से काम करता है। बस इस 3 संदर्भ सेटअप को करके मैं 10x से अधिक अपनी आयात गति में सुधार करने में सक्षम था! ईमानदारी से, यह विश्वास करना मुश्किल है। (यह मूल डिजाइन मानक कोर डेटा टेम्पलेट का हिस्सा होना चाहिए ...)

आयात प्रक्रिया के दौरान मैं 2 अलग-अलग तरीकों से बचत करता हूं। प्रत्येक 1000 आइटम जो मैं पृष्ठभूमि के संदर्भ में सहेजता हूं:

BOOL saveSuccess = [backgroundContext save:&error];

फिर आयात प्रक्रिया के अंत में, मैं मास्टर / माता-पिता के संदर्भ पर बचत करता हूं, जो कि, मूल रूप से, मुख्य संदर्भ सहित अन्य बाल संदर्भों में संशोधनों को धक्का देता है:

[masterManagedObjectContext performBlock:^{
   NSError *parentContextError = nil;
   BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];

समस्या : समस्या यह है कि जब तक मैं दृश्य को लोड नहीं करता तब तक मेरा UI अपडेट नहीं होगा।

मेरे पास UITableView के साथ एक साधारण UIViewController है जिसे NSFetchedResultsController का उपयोग करके डेटा खिलाया जा रहा है। जब आयात प्रक्रिया पूरी हो जाती है, तो NSFetchedResultsController माता-पिता / मास्टर संदर्भ से कोई परिवर्तन नहीं देखता है और इसलिए UI स्वचालित रूप से अपडेट नहीं होता है जैसे मैं देखने के लिए उपयोग किया जाता हूं। यदि मैं UIViewController को स्टैक से पॉप करता हूं और इसे फिर से लोड करता हूं तो सभी डेटा है।

प्रश्न : माता-पिता के संदर्भ में बने परिवर्तनों को देखने के लिए मुझे अपना बच्चा संदर्भ कैसे मिलेगा ताकि वे UI को अपडेट करने के लिए मेरे NSFetchedResultsController को ट्रिगर करें?

मैंने निम्नलिखित कोशिश की है जो सिर्फ ऐप को लटकाता है:

- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];    
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    NSError *error = nil;
    BOOL saveSuccess = [masterManagedObjectContext save:&error];

    [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}

- (void)contextChanged:(NSNotification*)notification
{
    if ([notification object] == mainManagedObjectContext) return;

    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
        return;
    }

    [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

26
सबसे अच्छा, कभी भी तैयार किए गए प्रश्न के लिए +1000000। मेरे पास एक उत्तर भी है ... हालांकि इसे टाइप करने में कुछ मिनट लगेंगे ...
जॉडी हैगिन्स

1
जब आप कहते हैं कि ऐप लटका हुआ है, तो यह कहां है? यह क्या कर रहा है?
जोड़ी हगींस

लंबे समय के बाद इसे लाने के लिए क्षमा करें। क्या आप स्पष्ट कर सकते हैं कि "आयात फ्लैट, फिर अंत में रिश्तों को पैच अप" का क्या मतलब है? क्या आपके पास अभी भी संबंध स्थापित करने के लिए स्मृति में वह वस्तुएं नहीं हैं? मैं तुम्हारी तरह एक समाधान को लागू करने की कोशिश कर रहा हूं और मैं वास्तव में स्मृति पदचिह्न को कम करने के लिए कुछ मदद का उपयोग कर सकता हूं।
एंड्रिया स्प्रेगा

इस लेख के पहले लिंक से जुड़े Apple डॉक्स देखें। यह बताते हैं। सौभाग्य!
डेविड वीस

1
वास्तव में अच्छा सवाल है और मैंने आपके सेटअप द्वारा दिए गए विवरण से कुछ साफ-सुथरी चालें
उठाईं

जवाबों:


47

आपको संभवतः मास्टर MOC को स्ट्राइड में भी सहेजना चाहिए। कोई मतलब नहीं है कि MOC को बचाने के लिए अंत तक प्रतीक्षा करें। इसका अपना धागा है, और यह स्मृति को नीचे रखने में भी मदद करेगा।

आप ने लिखा:

फिर आयात प्रक्रिया के अंत में, मैं मास्टर / माता-पिता के संदर्भ पर बचत करता हूं, जो कि, मूल रूप से, मुख्य संदर्भ सहित अन्य बाल संदर्भों में संशोधनों को धक्का देता है:

आपके कॉन्फ़िगरेशन में, आपके पास दो बच्चे (मुख्य एमओसी और पृष्ठभूमि एमओसी) हैं, दोनों "मास्टर।"

जब आप एक बच्चे को बचाते हैं, तो यह माता-पिता में होने वाले बदलावों को बढ़ाता है। उस एमओसी के अन्य बच्चे अगली बार जब वे एक भ्रूण प्रदर्शन करते हैं तो डेटा देखेंगे ... उन्हें स्पष्ट रूप से सूचित नहीं किया जाता है।

इसलिए, जब बीजी बचता है, तो इसका डेटा मास्टर को धकेल दिया जाता है। हालाँकि, ध्यान दें कि इस डेटा में से कोई भी तब तक डिस्क पर नहीं है जब तक कि मास्टर बचाता नहीं है। इसके अलावा, किसी भी नए आइटम को स्थायी आईडी तब तक नहीं मिलेगी, जब तक कि डिस्क को सहेजने के लिए मास्टर न हो।

अपने परिदृश्य में, आप डेटा को एमएडएससी नोटिफिकेशन के दौरान मास्टर से विलय करके एमएआईएन एमओसी में खींच रहे हैं।

यह काम करना चाहिए, इसलिए मैं उत्सुक हूं कि यह "त्रिशंकु" कहां है। मैं ध्यान दूंगा, कि आप मुख्य MOC धागे पर विहित तरीके से नहीं चल रहे हैं (कम से कम iOS 5 के लिए नहीं)।

इसके अलावा, आप शायद केवल मास्टर MOC से परिवर्तनों को मर्ज करने में रुचि रखते हैं (हालांकि आपका पंजीकरण ऐसा लगता है कि यह केवल वैसे भी है)। अगर मुझे अपडेट-ऑन-सेव-नोटिफिकेशन का उपयोग करना था, तो मैं यह करूँगा ...

- (void)contextChanged:(NSNotification*)notification {
    // Only interested in merging from master into main.
    if ([notification object] != masterManagedObjectContext) return;

    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];

        // NOTE: our MOC should not be updated, but we need to reload the data as well
    }];
}

अब, हैंग के संबंध में आपका वास्तविक मुद्दा क्या हो सकता है ... आप मास्टर को बचाने के लिए दो अलग-अलग कॉल दिखाते हैं। पहली अच्छी तरह से अपने स्वयं के प्रदर्शन में संरक्षित है, लेकिन दूसरा नहीं है (हालांकि आप एक प्रदर्शन में saveMasterContext कह सकते हैं ...

हालाँकि, मैं इस कोड को भी बदलूंगा ...

- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];    
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    // Make sure the master runs in it's own thread...
    [masterManagedObjectContext performBlock:^{
        NSError *error = nil;
        BOOL saveSuccess = [masterManagedObjectContext save:&error];
        // Handle error...
        [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
    }];
}

हालांकि, ध्यान दें कि मुख्य मास्टर का एक बच्चा है। इसलिए, इसमें बदलाव को मर्ज नहीं करना चाहिए। इसके बजाय, बस मास्टर पर डिडसेव के लिए देखें, और बस फिर से पढ़ें! डेटा आपके माता-पिता में पहले से ही बैठा है, बस आपके लिए इसके लिए इंतजार कर रहा है। यह पहली जगह में माता-पिता में डेटा होने के लाभों में से एक है।

विचार करने के लिए एक और विकल्प (और मुझे आपके परिणामों के बारे में सुनने में दिलचस्पी होगी - यह बहुत सारा डेटा है ...)

बैकग्राउंड MOC को MASTER का बच्चा बनाने के बजाय, इसे MAIN का बच्चा बनाएं।

ये लो। जब भी बीजी बचाता है, यह स्वचालित रूप से मुख्य में धकेल दिया जाता है। अब, MAIN को सेव कॉल करना है, और फिर मास्टर को सेव कॉल करना है, लेकिन जो भी कर रहे हैं वह पॉइंटर्स को मूव कर रहा है ... जब तक कि मास्टर डिस्क पर सेव नहीं करता।

उस पद्धति की सुंदरता यह है कि डेटा पृष्ठभूमि एमओसी से सीधे आपके अनुप्रयोगों में जाता है एमओसी (फिर सहेजने के लिए गुजरता है)।

नहीं है कुछ पास के माध्यम से के लिए दंड, लेकिन सभी बड़े कार्य मास्टर में किया जाता है, जब यह डिस्क पूरी करता है। और अगर आप परफैक्टब्लॉक के साथ मास्टर पर सेव करने वालों को किक मारते हैं, तो मुख्य धागा बस अनुरोध को भेजता है, और तुरंत वापस आ जाता है।

कृपया मुझे बतायेँ कि यह कैसा चलेगा!


बहुत बढ़िया जवाब। मैं आज इन विचारों की कोशिश करूँगा और देखूँगा कि मुझे क्या पता है। धन्यवाद!
डेविड वीस

बहुत बढ़िया! यह पूरी तरह से काम किया! फिर भी, मैं मास्टर -> मुख्य -> ​​बीजी के आपके सुझाव की कोशिश करने जा रहा हूं और देखें कि यह प्रदर्शन कैसे काम करता है, यह एक बहुत ही दिलचस्प विचार है। महान विचारों के लिए धन्यवाद!
डेविड वीस

4
PerformBlockAndWait को PerformBlock में बदलने के लिए अद्यतन किया गया। यह निश्चित नहीं है कि यह मेरी कतार में फिर से क्यों उत्पन्न हुआ, लेकिन जब मैंने इसे इस बार पढ़ा, तो यह स्पष्ट था ... निश्चित नहीं कि मैंने इसे पहले क्यों जाने दिया। हां, PerformBlockAndWait फिर से प्रवेश है। हालाँकि, इस तरह के एक नेस्टेड वातावरण में, आप एक मूल संदर्भ के भीतर से एक बच्चे के संदर्भ में तुल्यकालिक संस्करण नहीं कह सकते। अधिसूचना (इस मामले में) मूल संदर्भ से भेजी जा सकती है, जो गतिरोध का कारण बन सकती है। मुझे उम्मीद है कि जो भी साथ आएंगे और बाद में इसे पढ़ेंगे, उनके लिए यह स्पष्ट है। धन्यवाद, डेविड।
जोड़ी हगींस

1
@DavidWeiss आपने मास्टर -> मुख्य -> ​​बीजी की कोशिश की है? मुझे इस डिज़ाइन पैटर्न में दिलचस्पी है और यह जानने की उम्मीद है कि क्या यह आपके लिए अच्छा काम करता है। धन्यवाद।
गैर-

2
MASTER -> MAIN -> BG पैटर्न के साथ समस्या यह है कि जब आप BG संदर्भ से प्राप्त करते हैं, तो यह MAIN से भी आएगा और यह UI को ब्लॉक करेगा और आपको एप्लिकेशन को उत्तरदायी नहीं
बनाएगा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.