คำถาม : ฉันจะทำให้บริบทลูกของฉันเห็นการเปลี่ยนแปลงที่ยังคงอยู่ในบริบทหลักได้อย่างไรเพื่อที่จะเรียก NSFetchedResultsController ของฉันเพื่ออัปเดต UI
นี่คือการตั้งค่า:
คุณมีแอปที่ดาวน์โหลดและเพิ่มข้อมูล XML จำนวนมาก (ประมาณ 2 ล้านระเบียนแต่ละรายการมีขนาดเท่ากับย่อหน้าปกติของข้อความ) ไฟล์. sqlite จะมีขนาดประมาณ 500 MB การเพิ่มเนื้อหานี้ลงในข้อมูลหลักต้องใช้เวลา แต่คุณต้องการให้ผู้ใช้สามารถใช้แอปได้ในขณะที่ข้อมูลโหลดลงในที่เก็บข้อมูลทีละน้อย ผู้ใช้จะต้องมองไม่เห็นและมองไม่เห็นว่ามีการเคลื่อนย้ายข้อมูลจำนวนมากไปรอบ ๆ จึงไม่แฮงค์ไม่กระวนกระวายใจ: เลื่อนเหมือนเนย ถึงกระนั้นแอปก็มีประโยชน์มากขึ้นยิ่งมีการเพิ่มข้อมูลมากขึ้นดังนั้นเราจึงไม่สามารถรอได้ตลอดไปเพื่อเพิ่มข้อมูลไปยังที่เก็บข้อมูลหลัก ในรหัสหมายความว่าฉันต้องการหลีกเลี่ยงรหัสเช่นนี้ในรหัสนำเข้า:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
แอปนี้เป็น iOS 5 เท่านั้นดังนั้นอุปกรณ์ที่ช้าที่สุดที่ต้องรองรับคือ iPhone 3GS
นี่คือแหล่งข้อมูลที่ฉันใช้ในการพัฒนาโซลูชันปัจจุบันของฉัน:
คู่มือการเขียนโปรแกรมข้อมูลหลักของ Apple: การนำเข้าข้อมูลอย่างมีประสิทธิภาพ
- ใช้ Autorelease Pools เพื่อให้หน่วยความจำไม่ทำงาน
- ต้นทุนความสัมพันธ์ นำเข้าแบบแบนจากนั้นแก้ไขความสัมพันธ์ในตอนท้าย
- อย่าถามว่าคุณสามารถช่วยได้หรือไม่มันจะทำให้สิ่งต่างๆช้าลงในลักษณะ O (n ^ 2)
- นำเข้าเป็นชุด: บันทึกรีเซ็ตระบายและทำซ้ำ
- ปิดตัวจัดการการเลิกทำเมื่อนำเข้า
iDeveloper TV - ประสิทธิภาพข้อมูลหลัก
- ใช้บริบท 3 ประเภท ได้แก่ ประเภทบริบทหลักหลักและประเภทการกักขัง
iDeveloper TV - ข้อมูลหลักสำหรับการอัปเดต Mac, iPhone และ iPad
- การรันจะช่วยประหยัดคิวอื่น ๆ ด้วย performBlock ทำให้สิ่งต่างๆรวดเร็ว
- การเข้ารหัสจะทำให้สิ่งต่างๆช้าลงให้ปิดหากทำได้
การนำเข้าและการแสดงชุดข้อมูลขนาดใหญ่ในข้อมูลหลักโดย Marcus Zarra
- คุณสามารถชะลอการนำเข้าได้โดยให้เวลากับการวนรอบการทำงานปัจจุบันเพื่อให้ผู้ใช้รู้สึกราบรื่น
- โค้ดตัวอย่างพิสูจน์ได้ว่าเป็นไปได้ที่จะทำการอิมพอร์ตจำนวนมากและรักษาการตอบสนองของ UI ไว้ แต่ไม่เร็วเท่ากับ 3 บริบทและการบันทึกแบบไม่ซิงค์ลงในดิสก์
โซลูชันปัจจุบันของฉัน
ฉันมี NSManagedObjectContext 3 อินสแตนซ์:
masterManagedObjectContext - นี่คือบริบทที่มี NSPersistentStoreCoordinator และรับผิดชอบในการบันทึกลงในดิสก์ ฉันทำเช่นนี้เพื่อให้การบันทึกของฉันเป็นแบบอะซิงโครนัสและเร็วมาก ฉันสร้างมันเมื่อเปิดตัวเช่นนี้:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext - นี่คือบริบทที่ UI ใช้ทุกที่ เป็นลูกของ masterManagedObjectContext ฉันสร้างมันแบบนี้:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext - บริบทนี้ถูกสร้างขึ้นในคลาสย่อย NSOperation ของฉันที่รับผิดชอบการนำเข้าข้อมูล XML ไปยัง Core Data ฉันสร้างมันในวิธีการหลักของการดำเนินการและเชื่อมโยงกับบริบทหลักที่นั่น
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
สิ่งนี้ใช้งานได้จริงรวดเร็วมาก เพียงแค่ตั้งค่าบริบท 3 รายการนี้ฉันก็สามารถปรับปรุงความเร็วในการนำเข้าได้มากกว่า 10 เท่า! สุจริตนี้ยากที่จะเชื่อ (การออกแบบพื้นฐานนี้ควรเป็นส่วนหนึ่งของเทมเพลตข้อมูลหลักมาตรฐาน ... )
ในระหว่างกระบวนการนำเข้าฉันบันทึก 2 วิธีที่แตกต่างกัน ทุก 1,000 รายการที่ฉันบันทึกในบริบทพื้นหลัง:
BOOL saveSuccess = [backgroundContext save:&error];
จากนั้นในตอนท้ายของกระบวนการอิมพอร์ตฉันบันทึกบริบทหลัก / พาเรนต์ซึ่งเห็นได้ชัดว่าจะผลักดันการปรับเปลี่ยนไปยังบริบทย่อยอื่น ๆ รวมถึงบริบทหลัก:
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
ปัญหา : ปัญหาคือ UI ของฉันจะไม่อัปเดตจนกว่าฉันจะโหลดมุมมองใหม่
ฉันมี UIViewController ที่เรียบง่ายพร้อม UITableView ที่ถูกป้อนข้อมูลโดยใช้ NSFetchedResultsController เมื่อกระบวนการนำเข้าเสร็จสมบูรณ์ NSFetchedResultsController จะเห็นว่าไม่มีการเปลี่ยนแปลงใด ๆ จากบริบทหลัก / หลักดังนั้น UI จึงไม่อัปเดตโดยอัตโนมัติอย่างที่ฉันเคยเห็น ถ้าฉันเปิด UIViewController ออกจากสแต็กและโหลดอีกครั้งข้อมูลทั้งหมดจะอยู่ที่นั่น
คำถาม : ฉันจะทำให้บริบทลูกของฉันเห็นการเปลี่ยนแปลงที่ยังคงอยู่ในบริบทหลักได้อย่างไรเพื่อที่จะเรียก NSFetchedResultsController ของฉันเพื่ออัปเดต UI
ฉันได้ลองสิ่งต่อไปนี้ซึ่งทำให้แอปแฮงค์:
- (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];
}