วิธีการซิงค์ CoreData และบริการเว็บ REST แบบอะซิงโครนัสและในเวลาเดียวกันอย่างเหมาะสมเผยแพร่ข้อผิดพลาด REST ลงใน UI


85

สวัสดีฉันกำลังทำงานกับเลเยอร์โมเดลสำหรับแอปของเราที่นี่

ข้อกำหนดบางประการมีดังนี้:

  1. ควรใช้กับ iPhone OS 3.0+
  2. แหล่งที่มาของข้อมูลของเราคือแอปพลิเคชัน RESTful Rails
  3. เราควรแคชข้อมูลในเครื่องโดยใช้ Core Data
  4. รหัสไคลเอ็นต์ (ตัวควบคุม UI ของเรา) ควรมีความรู้เกี่ยวกับสิ่งต่างๆในเครือข่ายให้มากที่สุดเท่าที่จะเป็นไปได้และควรสอบถาม / อัปเดตโมเดลด้วย Core Data API

ฉันได้ตรวจสอบWWDC10 เซสชัน 117เกี่ยวกับการสร้างประสบการณ์ผู้ใช้ที่ขับเคลื่อนด้วยเซิร์ฟเวอร์ใช้เวลาพอสมควรในการตรวจสอบObjective Resource , Core ResourceและRestfulCoreData frameworks

เฟรมเวิร์ก Objective Resource ไม่ได้พูดคุยกับ Core Data ด้วยตัวมันเองและเป็นเพียงการนำไคลเอนต์ REST มาใช้เท่านั้น ทรัพยากรหลักและ RestfulCoreData ทั้งหมดถือว่าคุณคุยกับ Core Data ในโค้ดของคุณและพวกเขาแก้ปัญหาถั่วและสลักเกลียวทั้งหมดในพื้นหลังของเลเยอร์โมเดล

ตอนนี้ทุกอย่างดูดีและในตอนแรกฉันแม้ว่า Core Resource หรือ RestfulCoreData จะครอบคลุมข้อกำหนดข้างต้นทั้งหมด แต่ ... มีสองสิ่งที่ดูเหมือนจะไม่เกิดขึ้นเพื่อแก้ไขอย่างถูกต้อง:

  1. ไม่ควรบล็อกเธรดหลักขณะบันทึกการอัปเดตภายในเครื่องไปยังเซิร์ฟเวอร์
  2. หากการดำเนินการบันทึกล้มเหลวข้อผิดพลาดควรเผยแพร่ไปยัง UI และไม่ควรบันทึกการเปลี่ยนแปลงลงในที่เก็บข้อมูลหลักในเครื่อง

ทรัพยากรหลักเกิดขึ้นเพื่อส่งคำขอทั้งหมดไปยังเซิร์ฟเวอร์เมื่อคุณเรียก- (BOOL)save:(NSError **)errorใช้ Managed Object Context ของคุณดังนั้นจึงสามารถจัดเตรียมอินสแตนซ์ NSError ที่ถูกต้องของคำขอพื้นฐานไปยังเซิร์ฟเวอร์ล้มเหลวได้ แต่จะบล็อกเธรดการโทรจนกว่าการดำเนินการบันทึกจะเสร็จสิ้น ล้มเหลว.

RestfulCoreData ช่วยให้การ-save:โทรของคุณเหมือนเดิมและไม่แนะนำเวลารอเพิ่มเติมสำหรับเธรดไคลเอ็นต์ เป็นเพียงการเฝ้าดูNSManagedObjectContextDidSaveNotificationและออกคำขอที่เกี่ยวข้องไปยังเซิร์ฟเวอร์ในตัวจัดการการแจ้งเตือนเท่านั้น แต่ด้วยวิธีนี้การ-save:โทรจะเสร็จสมบูรณ์เสมอ (โดยที่ Core Data นั้นโอเคกับการเปลี่ยนแปลงที่บันทึกไว้) และรหัสไคลเอนต์ที่เรียกว่าจริงไม่มีทางรู้ได้ว่าการบันทึกอาจล้มเหลวในการเผยแพร่ไปยังเซิร์ฟเวอร์เนื่องจากบางส่วน404หรือ421หรืออะไรก็ตาม เกิดข้อผิดพลาดฝั่งเซิร์ฟเวอร์ และยิ่งไปกว่านั้นที่จัดเก็บข้อมูลในเครื่องจะมีการอัปเดตข้อมูล แต่เซิร์ฟเวอร์ไม่เคยรู้เกี่ยวกับการเปลี่ยนแปลง ล้มเหลว.

ดังนั้นฉันกำลังมองหาทางออกที่เป็นไปได้ / แนวทางปฏิบัติทั่วไปในการจัดการกับปัญหาเหล่านี้:

  1. ฉันไม่ต้องการให้เธรดการโทรถูกบล็อกในการ-save:โทรแต่ละครั้งขณะที่มีการร้องขอเครือข่ายเกิดขึ้น
  2. ฉันต้องการรับการแจ้งเตือนใน UI ว่าการซิงค์บางอย่างผิดพลาด
  3. ฉันต้องการให้การบันทึกข้อมูลหลักจริงล้มเหลวเช่นกันหากการร้องขอเซิร์ฟเวอร์ล้มเหลว

ความคิดใด ๆ ?


1
ว้าวคุณไม่รู้หรอกว่าคุณช่วยฉันได้แค่ไหนด้วยการถามคำถามนี้ ขณะนี้ฉันติดตั้งแอปของฉันเพื่อให้ผู้ใช้รอข้อมูลทุกครั้งที่โทรออก (แม้ว่าจะใช้บริการเว็บ. NET) ฉันคิดหาวิธีทำให้มันไม่ตรงกัน แต่คิดไม่ออกว่าจะทำอย่างไร ขอบคุณสำหรับแหล่งข้อมูลทั้งหมดที่คุณให้มา!
Tejaswi Yerukalapudi

คำถามที่ยอดเยี่ยมขอบคุณ
จัสติน

ลิงก์ไปยัง Core Resource เสียไม่มีใครรู้ว่าตอนนี้โฮสต์อยู่ที่ไหน

ทรัพยากรหลักยังคงโฮสต์บน GitHub ที่นี่: github.com/mikelaurence/CoreResource
eploko

และเว็บไซต์ดั้งเดิมสามารถพบได้ใน gitHub เช่นกัน: github.com/mikelaurence/coreresource.org
eploko

คำตอบ:


26

คุณควรดู RestKit ( http://restkit.org ) สำหรับกรณีการใช้งานนี้ ได้รับการออกแบบมาเพื่อแก้ปัญหาในการสร้างแบบจำลองและการซิงค์ทรัพยากร JSON ระยะไกลกับแคชที่สำรองข้อมูล Core Data ในเครื่อง รองรับโหมดออฟไลน์สำหรับการทำงานทั้งหมดจากแคชเมื่อไม่มีเครือข่าย การซิงค์ทั้งหมดเกิดขึ้นบนเธรดพื้นหลัง (การเข้าถึงเครือข่ายการแยกวิเคราะห์เพย์โหลดและการรวมบริบทอ็อบเจ็กต์ที่มีการจัดการ) และมีชุดวิธีการมอบหมายมากมายเพื่อให้คุณสามารถบอกได้ว่าเกิดอะไรขึ้น


18

มีส่วนประกอบพื้นฐานสามประการ:

  1. การดำเนินการ UI และคงการเปลี่ยนแปลงเป็น CoreData
  2. ยังคงมีการเปลี่ยนแปลงขึ้นอยู่กับเซิร์ฟเวอร์
  3. การรีเฟรช UI ด้วยการตอบสนองของเซิร์ฟเวอร์

NSOperation + NSOperationQueue จะช่วยให้การร้องขอของเครือข่ายเป็นระเบียบเรียบร้อย โปรโตคอลผู้รับมอบสิทธิ์จะช่วยให้คลาส UI ของคุณเข้าใจว่าคำขอเครือข่ายอยู่ในสถานะใดเช่น:

@protocol NetworkOperationDelegate
  - (void)operation:(NSOperation *)op willSendRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
  - (void)operation:(NSOperation *)op didSuccessfullySendRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
  - (void)operation:(NSOperation *)op encounteredAnError:(NSError *)error afterSendingRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
@end

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

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

- (void)managedObjectContextDidSave:(NSNotification *)saveNotification {
  NSArray *inserted = [[saveNotification userInfo] valueForKey:NSInsertedObjects];
  for (NSManagedObject *obj in inserted) {
    //create a new NSOperation for this entity which will invoke the appropraite rest api
    //add to operation queue
  }

  //do the same thing for deleted and updated objects
}

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

หาก REST API ของคุณรองรับการทำแบทช์คุณสามารถส่งอาร์เรย์ทั้งหมดพร้อมกันแล้วแจ้งให้คุณทราบว่ามีการซิงโครไนซ์เอนทิตีหลายรายการ

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


2

ปัญหานี้กลายเป็นปัญหาการซิงค์และไม่ใช่เรื่องง่ายที่จะแก้ไข นี่คือสิ่งที่ฉันต้องทำ: ใน UI ของ iPhone ของคุณใช้บริบทหนึ่งจากนั้นใช้บริบทอื่น (และอีกเธรด) ดาวน์โหลดข้อมูลจากบริการเว็บของคุณ เมื่อทุกอย่างผ่านขั้นตอนการซิงค์ / นำเข้าที่แนะนำด้านล่างแล้วรีเฟรช UI ของคุณหลังจากนำเข้าอย่างถูกต้อง หากเกิดปัญหาขึ้นในขณะที่เข้าถึงเครือข่ายให้ย้อนกลับการเปลี่ยนแปลงในบริบทที่ไม่ใช่ UI มันเป็นงานมากมาย แต่ฉันคิดว่ามันเป็นวิธีที่ดีที่สุดในการเข้าหามัน

ข้อมูลหลัก: การนำเข้าข้อมูลอย่างมีประสิทธิภาพ

ข้อมูลหลัก: การจัดการการเปลี่ยนแปลง

Core Data: Multi-Threading พร้อม Core Data


0

คุณต้องมีฟังก์ชันเรียกกลับที่จะทำงานบนเธรดอื่น (ฟังก์ชันที่เกิดการโต้ตอบกับเซิร์ฟเวอร์จริง) จากนั้นใส่รหัสผลลัพธ์ / ข้อมูลข้อผิดพลาดเป็นข้อมูลกึ่งส่วนกลางซึ่งเธรด UI จะตรวจสอบเป็นระยะ ตรวจสอบให้แน่ใจว่าการปัดเศษของตัวเลขที่ทำหน้าที่เป็นแฟล็กนั้นเป็นอะตอมมิฉะนั้นคุณจะมีเงื่อนไขการแข่งขัน - บอกว่าการตอบสนองข้อผิดพลาดของคุณคือ 32 ไบต์หรือไม่คุณต้องมี int (ซึ่งควรมี atomic acces) จากนั้นคุณก็เก็บ int นั้นไว้ ในสถานะปิด / เท็จ / ไม่พร้อมจนกว่าบล็อกข้อมูลขนาดใหญ่ของคุณจะถูกเขียนจากนั้นจึงเขียน "จริง" เพื่อพลิกสวิตช์เพื่อพูด

สำหรับการบันทึกที่สัมพันธ์กันในฝั่งไคลเอ็นต์คุณต้องเก็บข้อมูลนั้นไว้และไม่บันทึกจนกว่าคุณจะได้รับการยอมรับจากเซิร์ฟเวอร์เพื่อให้แน่ใจว่าคุณมีตัวเลือกการย้อนกลับจำนวนมาก - กล่าวว่าวิธีการลบคือเซิร์ฟเวอร์ล้มเหลว

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

ตามหลักการแล้วคุณจะดำเนินการเวอร์ชันบล็อกทั้งหมดในเธรดแยกต่างหาก แต่คุณต้องใช้ 4.0 สำหรับสิ่งนั้น

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