ตัวอย่างหรือคำอธิบายของ Core Data Migration with multiple pass?


85

แอพ iPhone ของฉันจำเป็นต้องย้ายที่เก็บข้อมูลหลักและฐานข้อมูลบางส่วนมีขนาดค่อนข้างใหญ่ เอกสารของ Apple แนะนำให้ใช้ "บัตรผ่านหลายใบ" ในการย้ายข้อมูลเพื่อลดการใช้หน่วยความจำ อย่างไรก็ตามเอกสารประกอบมี จำกัด มากและอธิบายได้ไม่ดีว่าจะทำอย่างไร ใครบางคนสามารถชี้ให้ฉันเห็นตัวอย่างที่ดีหรืออธิบายรายละเอียดเกี่ยวกับกระบวนการดึงสิ่งนี้ออกมาได้จริงหรือ?


คุณประสบปัญหาหน่วยความจำจริงหรือ? การโยกย้ายของคุณมีน้ำหนักเบาหรือต้องการใช้ NSMigrationManager หรือไม่?
Nick Weaver

ใช่คอนโซล GDB แสดงให้เห็นว่ามีคำเตือนเกี่ยวกับหน่วยความจำจากนั้นแอปจะหยุดทำงานเนื่องจากหน่วยความจำ จำกัด ฉันได้ลองใช้ทั้งการโอนย้ายที่มีน้ำหนักเบาและ NSMigrationManager แต่ตอนนี้ฉันกำลังพยายามใช้ NSMigrationManager
Jason

โอเคช่วยลงรายละเอียดอีกนิดว่ามีอะไรเปลี่ยนแปลงไหม
Nick Weaver

ในที่สุดฉันก็ค้นพบอ่านคำตอบของฉัน
Nick Weaver

สวัสดีเจสันคุณช่วยแก้ไขปัญหาที่คล้ายกันได้ไหม
Yuchen Zhong

คำตอบ:


175

ฉันพบสิ่งที่ Apple บอกเป็นนัยในเอกสารของพวกเขาแล้ว จริงๆแล้วมันง่ายมาก แต่ต้องไปอีกไกลก่อนที่มันจะชัดเจน ฉันจะอธิบายคำอธิบายพร้อมตัวอย่าง สถานการณ์เริ่มต้นคือ:

แบบจำลองข้อมูลเวอร์ชัน 1

ใส่คำอธิบายภาพที่นี่ ใส่คำอธิบายภาพที่นี่

เป็นแบบจำลองที่คุณจะได้รับเมื่อสร้างโครงการด้วยเทมเพลต "แอปที่ใช้การนำทางพร้อมที่เก็บข้อมูลหลัก" ฉันรวบรวมมันและทำการกดปุ่มอย่างหนักด้วยความช่วยเหลือของ for loop เพื่อสร้างรายการประมาณ 2k ทั้งหมดด้วยค่าที่แตกต่างกัน เราไปที่นั่น 2.000 เหตุการณ์ด้วยค่า NSDate

ตอนนี้เราเพิ่มรุ่นที่สองของโมเดลข้อมูลซึ่งมีลักษณะดังนี้:

ใส่คำอธิบายภาพที่นี่

แบบจำลองข้อมูลเวอร์ชัน 2

ความแตกต่างคือเอนทิตีเหตุการณ์หายไปและเรามีสองรายการใหม่ หนึ่งที่เก็บประทับเวลาเป็นและเป็นหนึ่งที่สองที่ควรจัดเก็บวันที่เป็นdoubleNSString

เป้าหมายคือการถ่ายโอนเหตุการณ์เวอร์ชัน 1ทั้งหมดไปยังเอนทิตีใหม่สองรายการและแปลงค่าตามการย้ายข้อมูล ส่งผลให้ค่าเป็นสองเท่าของค่าแต่ละประเภทในเอนทิตีแยกต่างหาก

ในการย้ายข้อมูลเราเลือกการย้ายด้วยมือและสิ่งนี้ใช้กับโมเดลการทำแผนที่ นี่เป็นส่วนแรกของคำตอบสำหรับคำถามของคุณ เราจะทำการย้ายข้อมูลในสองขั้นตอนเนื่องจากการย้ายข้อมูล 2k รายการใช้เวลานานและเราต้องการให้หน่วยความจำเหลือน้อย

คุณยังสามารถดำเนินการต่อและแยกโมเดลการทำแผนที่เหล่านี้เพิ่มเติมเพื่อย้ายเฉพาะช่วงของเอนทิตี สมมติว่าเรามีบันทึกหนึ่งล้านรายการอาจทำให้กระบวนการทั้งหมดผิดพลาด เป็นไปได้ที่จะ จำกัด เอนทิตีที่ดึงมาให้แคบลงด้วยฟิลเตอร์เพรดิเคต

กลับไปที่โมเดลการทำแผนที่สองแบบของเรา

เราสร้างรูปแบบการทำแผนที่แรกดังนี้:

1. ไฟล์ใหม่ -> ทรัพยากร -> โมเดลการแมป ใส่คำอธิบายภาพที่นี่

2. เลือกชื่อฉันเลือก StepOne

3. กำหนดรูปแบบข้อมูลต้นทางและปลายทาง

ใส่คำอธิบายภาพที่นี่

โมเดลการทำแผนที่ขั้นตอนที่หนึ่ง

ใส่คำอธิบายภาพที่นี่

ใส่คำอธิบายภาพที่นี่

ใส่คำอธิบายภาพที่นี่

การย้ายข้อมูลหลายรายการไม่จำเป็นต้องมีนโยบายการย้ายเอนทิตีที่กำหนดเองอย่างไรก็ตามเราจะดำเนินการเพื่อให้ได้รายละเอียดเพิ่มเติมเล็กน้อยสำหรับตัวอย่างนี้ ดังนั้นเราจึงเพิ่มนโยบายที่กำหนดเองให้กับเอนทิตี นี่เป็นคลาสย่อยของNSEntityMigrationPolicy.

ใส่คำอธิบายภาพที่นี่

ระดับนโยบายนี้ใช้วิธีการบางอย่างเพื่อให้การย้ายข้อมูลเกิดขึ้น createDestinationInstancesForSourceInstance:entityMapping:manager:error:แต่มันง่ายในกรณีนี้ดังนั้นเราจะต้องใช้เพียงหนึ่งวิธีการ:

การใช้งานจะมีลักษณะดังนี้:

StepOneEntityMigrationPolicy.m

#import "StepOneEntityMigrationPolicy.h"


@implementation StepOneEntityMigrationPolicy

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance 
                                      entityMapping:(NSEntityMapping *)mapping 
                                            manager:(NSMigrationManager *)manager 
                                              error:(NSError **)error
{
    // Create a new object for the model context
    NSManagedObject *newObject = 
        [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName] 
                                      inManagedObjectContext:[manager destinationContext]];

    // do our transfer of nsdate to nsstring
    NSDate *date = [sInstance valueForKey:@"timeStamp"];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];    

    // set the value for our new object
    [newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"];
    [dateFormatter release];

    // do the coupling of old and new
    [manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];

    return YES;
}

ขั้นตอนสุดท้าย: การโยกย้ายเอง

ฉันจะข้ามส่วนสำหรับการตั้งค่ารูปแบบการทำแผนที่ที่สองซึ่งเกือบจะเหมือนกันเพียงแค่ timeIntervalSince1970 ที่ใช้ในการแปลง NSDate เป็นสองเท่า

ในที่สุดเราก็ต้องเริ่มการโยกย้าย ฉันจะข้ามรหัสสำเร็จรูปไปก่อน ถ้าคุณต้องการฉันจะโพสต์ที่นี่ สามารถดูได้ที่การปรับแต่งกระบวนการย้ายข้อมูลเป็นเพียงการรวมสองตัวอย่างโค้ดแรกเข้าด้วยกัน ส่วนที่สามและสุดท้ายจะได้รับการแก้ไขดังนี้: แทนที่จะใช้เมธอดคลาสของNSMappingModelคลาสmappingModelFromBundles:forSourceModel:destinationModel:เราจะใช้initWithContentsOfURL:เนื่องจากเมธอดคลาสจะส่งคืนเพียงอันเดียวอาจเป็นโมเดลแรกที่พบในบันเดิล

ตอนนี้เรามีโมเดลการทำแผนที่สองแบบซึ่งสามารถใช้ได้ในทุกรอบของการวนซ้ำและส่งวิธีการย้ายข้อมูลไปยังตัวจัดการการย้ายข้อมูล แค่นั้นแหละ.

NSArray *mappingModelNames = [NSArray arrayWithObjects:@"StepOne", @"StepTwo", nil];
NSDictionary *sourceStoreOptions = nil;

NSURL *destinationStoreURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataMigrationNew.sqlite"];

NSString *destinationStoreType = NSSQLiteStoreType;

NSDictionary *destinationStoreOptions = nil;

for (NSString *mappingModelName in mappingModelNames) {
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:mappingModelName withExtension:@"cdm"];

    NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:fileURL];

    BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL
                                               type:sourceStoreType
                                            options:sourceStoreOptions
                                   withMappingModel:mappingModel
                                   toDestinationURL:destinationStoreURL
                                    destinationType:destinationStoreType
                                 destinationOptions:destinationStoreOptions
                                              error:&error2];
    [mappingModel release];
} 

หมายเหตุ

  • โมเดลการแมปจะสิ้นสุดในcdmบันเดิล

  • ต้องมีการจัดเก็บปลายทางและไม่ควรเป็นร้านค้าต้นทาง หลังจากย้ายข้อมูลสำเร็จแล้วให้ลบไฟล์เก่าและเปลี่ยนชื่อใหม่

  • ฉันได้ทำการเปลี่ยนแปลงบางอย่างกับโมเดลข้อมูลหลังจากสร้างโมเดลการแม็ปซึ่งส่งผลให้เกิดข้อผิดพลาดเกี่ยวกับความเข้ากันได้ซึ่งฉันสามารถแก้ไขได้ด้วยการสร้างโมเดลการแมปใหม่เท่านั้น


59
นรกนองเลือดที่ซับซ้อน Apple กำลังคิดอะไรอยู่?
aroth

7
ฉันไม่รู้ แต่เมื่อใดก็ตามที่ฉันคิดว่าข้อมูลหลักเป็นความคิดที่ดีฉันจะพยายามอย่างเต็มที่เพื่อค้นหาโซลูชันที่ง่ายและดูแลรักษาได้มากขึ้น
Nick Weaver

5
ขอบคุณ! นี่คือคำตอบที่ยอดเยี่ยม ดูเหมือนซับซ้อน แต่ก็ไม่ได้แย่ขนาดนั้นเมื่อคุณเรียนรู้ขั้นตอนต่างๆ ปัญหาใหญ่ที่สุดคือเอกสารไม่ได้สะกดให้คุณแบบนี้
bentford

2
นี่คือลิงค์ที่อัปเดตสำหรับการปรับแต่งกระบวนการย้ายข้อมูล มันย้ายไปตั้งแต่โพสต์นี้ถูกเขียนขึ้น developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
user1021430

@NickWeaver คุณกำหนด destinationStoreURL ได้อย่างไร? คุณกำลังสร้างหรือสร้างโดยระบบข้อมูลหลักในระหว่างขั้นตอนการย้าย
dev gr

3

คำถามเหล่านี้เกี่ยวข้อง:

ปัญหาเกี่ยวกับหน่วยความจำในการย้ายที่เก็บข้อมูล CoreData ขนาดใหญ่บน iPhone

การโอนย้ายข้อมูลหลายแกนผ่านในกลุ่มด้วย iOS

หากต้องการอ้างอิงลิงค์แรก:

สิ่งนี้จะกล่าวถึงในเอกสารอย่างเป็นทางการในส่วน "Multiple Passes" แต่ดูเหมือนว่าแนวทางที่แนะนำคือการแบ่งการย้ายข้อมูลของคุณตามประเภทเอนทิตีเช่นสร้างโมเดลการทำแผนที่หลายแบบโดยแต่ละแบบจะย้ายข้อมูลย่อยของประเภทเอนทิตีจาก แบบจำลองข้อมูลที่สมบูรณ์


1
ขอบคุณสำหรับลิงค์ ปัญหาคือไม่มีใครอธิบายรายละเอียดวิธีการตั้งค่าในการผ่านหลายครั้ง ฉันจะตั้งค่าโมเดลการทำแผนที่หลายแบบเพื่อให้ทำงานได้อย่างมีประสิทธิภาพได้อย่างไร
Jason

-5

สมมติว่าสคีมาฐานข้อมูลของคุณมี 5 เอนทิตีเช่นบุคคลนักเรียนหลักสูตรชั้นเรียนและการลงทะเบียนเพื่อใช้ตัวอย่างประเภทมาตรฐานโดยที่คลาสย่อยของนักเรียนจะเป็นบุคคลชั้นเรียนใช้หลักสูตรและการลงทะเบียนเข้าร่วมชั้นเรียนและนักเรียน หากคุณได้ทำการเปลี่ยนแปลงคำจำกัดความของตารางเหล่านี้ทั้งหมดคุณต้องเริ่มต้นที่คลาสพื้นฐานและหาทางแก้ไข ดังนั้นคุณไม่สามารถเริ่มต้นด้วยการแปลงการลงทะเบียนได้เนื่องจากบันทึกการลงทะเบียนแต่ละรายการขึ้นอยู่กับการมีชั้นเรียนและนักเรียนที่นั่น ดังนั้นคุณจะเริ่มต้นด้วยการย้ายเฉพาะตาราง Person คัดลอกแถวที่มีอยู่ไปยังตารางใหม่และกรอกข้อมูลในฟิลด์ใหม่ที่มี (ถ้าเป็นไปได้) และทิ้งคอลัมน์ที่ถูกลบ ทำการโอนย้ายแต่ละครั้งภายในกลุ่มการปล่อยอัตโนมัติดังนั้นเมื่อเสร็จแล้วหน่วยความจำของคุณจะกลับมาเริ่มทำงาน

เมื่อสร้างตารางบุคคลเสร็จแล้วคุณสามารถแปลงตารางนักเรียนได้ จากนั้นข้ามไปที่หลักสูตรและชั้นเรียนและในที่สุดตารางการลงทะเบียน

การพิจารณาอื่น ๆ คือจำนวนของเร็กคอร์ดถ้าเช่น Person มีหนึ่งพันแถวคุณจะต้องดำเนินการ NSManagedObject เทียบเท่ากับรีลีสซึ่งจะบอกบริบทอ็อบเจ็กต์ที่มีการจัดการ [moc refreshObject: ob mergeChanges: ไม่]; ตั้งค่าตัวจับเวลาข้อมูลเก่าของคุณให้ต่ำเพื่อให้หน่วยความจำถูกล้างบ่อยครั้ง


ดังนั้นโดยพื้นฐานแล้วคุณแนะนำให้มีสคีมาข้อมูลหลักใหม่ซึ่งไม่ได้เป็นส่วนหนึ่งของสคีมาเก่าและคัดลอกข้อมูลไปยังสคีมาใหม่ด้วยตนเองหรือไม่?
Jason

-1 การทำแผนที่ฐานข้อมูลของคุณด้วยตนเองไม่จำเป็น คุณสามารถโอนย้ายฐานข้อมูลที่ปรับใช้โดยใช้การโอนย้ายที่มีน้ำหนักเบาหรือด้วย Explicit MappingModels
bentford
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.