NSFileCoordinator เป็นคลาส Foundation ใน iOS และ macOS ที่รับประกันการเข้าถึงไฟล์อย่างปลอดภัยเมื่อเธรด โพรเซส หรือส่วนขยายหลายตัวทำงานพร้อมกัน ตาม เอกสารประกอบนักพัฒนา Apple, 2024 NSFileCoordinator ป้องกันสภาวะการแข่งขันเมื่ออ่านและเขียนไฟล์ โดยรับประว่าไม่มีโพรเซสใดอ่านข้อมูลในขณะที่อีกโพรเซสหนึ่งกำลังแก้ไข ผู้ประสานงานใช้ใน iCloud Drive, File Provider Extension และการดำเนินการไฟล์แบบหลายเธรดใด ๆ
ประเด็นสำคัญ
NSFileCoordinator เป็นกลไกการซิงค์การเข้าถึงไฟล์ในระดับระบบปฏิบัติการที่ Apple เปิดตัวใน iOS 5 และ macOS 10.7 Lion แตกต่างจากการล็อกแบบดั้งเดิม (NSLock, pthread_mutex) ผู้ประสานงานทำงานในระดับระบบไฟล์และสามารถประสานงานการเข้าถึงระหว่างโพรเซสต่าง ๆ ไม่ใช่แค่ระหว่างเธรดของแอปพลิเคชันเดียวกัน
ความจำเป็นของ NSFileCoordinator เกิดจากสถาปัตยกรรม Sandbox ใน iOS: แต่ละโพรเซส (แอปพลิเคชัน ส่วนขยาย บริการระบบ) ทำงานในสภาพแวดล้อมที่แยกออกจากกันด้วยการเข้าถึงไฟล์ของตนเอง เมื่อหลายโพรเซสพยายามอ่านและเขียนไฟล์เดียวกันพร้อมกัน (เช่น ระหว่างการซิงค์ iCloud Drive) หากไม่มีผู้ประสานงานจะเกิดสภาวะการแข่งขัน: โพรเซส A อ่านไฟล์ในขณะที่โพรเซส B เขียนทับบางส่วนแล้ว
ตาม WWDC 2023 Apple แนะนำอย่างยิ่งให้ใช้ NSFileCoordinator สำหรับการดำเนินการไฟล์ทั้งหมดในคอนเทนเนอร์ Ubiquity (iCloud Drive) และเมื่อทำงานกับ File Provider Extension การละเลยการประสานงานเป็นหนึ่งในสาเหตุทั่วไปของข้อมูลเสียหายและบักที่ไม่สามารถทำซ้ำได้ในแอปพลิเคชัน iOS
ความตั้งใจในการประสานงาน (NSFileCoordinator.ReadingIntent / WritingIntent) เป็นออบเจกต์ที่ประกาศประเภทของการดำเนินการที่เธรดหรือโพรเซสวางแผนจะดำเนินการ ผู้ประสานงานใช้ความตั้งใจเหล่านี้เพื่อกำหนดลำดับการเข้าถึงและแก้ไขข้อขัดแย้ง
| ประเภทความตั้งใจ | คำอธิบาย | เมื่อใดควรใช้ |
|---|---|---|
| ReadingIntent | อ่านไฟล์โดยไม่มีการแก้ไข | เปิดเอกสาร โหลดข้อมูล |
| WritingIntent | เขียนด้วยการแก้ไขเนื้อหาที่เป็นไปได้ | บันทึกเอกสาร แก้ไข |
| ReadingIntent(URL, options: .withoutChanges) | อ่านโดยไม่ติดตามการเปลี่ยนแปลง | ดูตัวอย่างเนื้อหาอย่างรวดเร็ว |
| WritingIntent(URL, options: .contentIndependentMetadataOnly) | แก้ไขเฉพาะเมทาดาตา | อัปเดตวันที่หรือแอตทริบิวต์ |
| WritingIntent(URL, options: .forDeleting) | ลบไฟล์ | การลบเอกสารโดยผู้ใช้ |
กฎการประสานงาน: อนุญาตให้อ่านพร้อมกันหลายครั้ง (หากไม่มีการเขียนที่กำลังดำเนินการ) การเขียนเป็นแบบเอกสิทธิ์ — ไม่อนุญาตให้อ่านหรือเขียนใด ๆ ระหว่างการดำเนินการเขียน ซึ่งเป็นไปตามโมเดลล็อกผู้อ่าน-ผู้เขียน แต่มีการสนับสนุนเพิ่มเติมสำหรับการประสานงานระหว่างโพรเซสผ่าน launchd และ XPC
ความละเอียดอ่อนที่สำคัญ: NSFileCoordinator ไม่ป้องกันการเข้าถึงไฟล์ผ่าน NSData หรือ FileManager ทั่วไป — มันประสานงานเฉพาะการดำเนินการที่ถูกห่อหุ้มอย่างชัดเจนในบล็อกการประสานงาน หากเธรดอื่นเข้าถึงไฟล์โดยตรงโดยไม่มีผู้ประสานงาน จะเกิดสภาวะการแข่งขันแบบเดียวกับที่ผู้ประสานงานถูกออกแบบมาเพื่อป้องกัน
รูปแบบพื้นฐาน ของการใช้ NSFileCoordinator ประกอบด้วยสามขั้นตอน: สร้างอินสแตนซ์ของผู้ประสานงาน ประกาศความตั้งใจ (อ่านหรือเขียน) และดำเนินการภายในบล็อกการประสานงาน ผู้ประสานงานรับประกันว่าไม่มีผู้ประสานงานอื่นทำงานพร้อมกันกับไฟล์เดียวกัน
import Foundation
let coordinator = NSFileCoordinator()
let fileURL = getDocumentURL()
// Safe reading
let readIntent = NSFileCoordinator
.ReadingIntent(url: fileURL)
var content: Data?
var readError: NSError?
coordinator.coordinate(with: readIntent) { error in
if let error = error {
readError = error
return
}
content = try? Data(contentsOf: fileURL)
}
// Safe writing
let writeIntent = NSFileCoordinator
.WritingIntent(url: fileURL)
coordinator.coordinate(with: writeIntent) { error in
guard error == nil else { return }
do {
try newData.write(to: fileURL)
} catch {
Logger.storage.error(
"Write failed: \(error)"
)
}
}
การดำเนินการแบบชุด — ผู้ประสานงานสามารถจัดการหลายไฟล์ในการดำเนินการเดียวโดยใช้อาร์เรย์ของความตั้งใจ สะดวกสำหรับการย้าย คัดลอก หรือลบชุดไฟล์เป็นธุรกรรมเดียว หากไม่สามารถดำเนินการตามความตั้งใจใดได้ การดำเนินการทั้งหมดจะถูกยกเลิกพร้อมข้อผิดพลาด
let coordinator = NSFileCoordinator()
let readIntent = NSFileCoordinator
.ReadingIntent(url: sourceURL)
let writeIntent = NSFileCoordinator
.WritingIntent(url: destURL)
coordinator.coordinate(
with: [readIntent, writeIntent]
) { error in
try? FileManager.default
.copyItem(at: sourceURL, to: destURL)
}
การประสานงานแบบอะซิงก์ — เริ่มจาก iOS 15 NSFileCoordinator รองรับเมธอดแบบอะซิงก์ด้วยตัวจัดการการเสร็จสิ้น ทำให้สามารถประสานงานได้โดยไม่บล็อกเธรดที่เรียก ซึ่งสำคัญสำหรับเธรด UI ที่การรอแบบซิงก์เพื่อการประสานงานอาจทำให้อินเทอร์เฟซค้างเป็นวินาที
NSFilePresenter เป็นโพรโทคอลที่ออบเจกต์นำไปใช้เพื่อรับการแจ้งเตือนเกี่ยวกับการเปลี่ยนแปลงไฟล์ที่ประสานงานโดย NSFileCoordinator หากแอปพลิเคชันของคุณแสดงเนื้อหาของไฟล์ที่อาจถูกเปลี่ยนแปลงโดยโพรเซสอื่น (เช่น iCloud Drive ซิงค์เวอร์ชันใหม่) การนำ NSFilePresenter ไปใช้ช่วยให้อัปเดตอินเทอร์เฟซได้ทันเวลา
class DocumentPresenter: NSFilePresenter {
let presentedItemURL: URL?
let presentedItemOperationQueue: OperationQueue
init(url: URL) {
presentedItemURL = url
presentedItemOperationQueue = OperationQueue()
}
func presentedItemDidChange() {
DispatchQueue.main.async {
NotificationCenter.default
.post(name: .documentDidChange,
object: self)
}
}
func presentedItemDidMove(to newURL: URL) {
Logger.storage.info(
"File moved to: \(newURL.lastPathComponent)"
)
}
func accommodatePresentedItemDeletion(
completionHandler: @escaping (Error?) -> Void
) {
Logger.storage.warn("File deleted externally")
completionHandler(nil)
}
}
เมธอดของโพรโทคอล: presentedItemDidChange ถูกเรียกเมื่อเนื้อหาไฟล์เปลี่ยนแปลง presentedItemDidMove(to:) — หลังการย้ายไฟล์ accommodatePresentedItemDeletion — ก่อนการลบไฟล์โดยโพรเซสอื่น (ช่วยให้แอปพลิเคชันปิดไฟล์อย่างเหมาะสม) นอกจากนี้ โพรโทคอลรองรับการควบคุมเวอร์ชันผ่าน presentedItemDidGainVersion: และ presentedItemDidLoseVersion:
สำคัญ: NSFilePresenter ต้องลงทะเบียนในระบบผ่าน NSFileCoordinator.addFilePresenter: หากไม่ลงทะเบียน การแจ้งเตือนจะไม่ถูกส่ง การลงทะเบียนทำครั้งเดียวเมื่อเริ่มต้นแอปพลิเคชันและไม่ต้องลงทะเบียนใหม่เมื่อผู้เสนอถูกสร้างใหม่
ใช้ผู้ประสานงานเสมอ สำหรับไฟล์ในคอนเทนเนอร์ Ubiquity (iCloud Drive) และไดเรกทอรีที่ส่วนขยายเข้าถึงได้ แม้ว่าแอปพลิเคชันปัจจุบันเป็นเธรดเดียว การอัปเดตในอนาคตหรือการเปลี่ยนแปลงระบบอาจเพิ่มการเข้าถึงแบบขนาน และการขาดการประสานงานจะนำไปสู่บักที่ค้นหายาก
ลดเวลาในบล็อกการประสานงาน ในขณะที่บล็อกกำลังดำเนินการ โพรเซสอื่นไม่สามารถเข้าถึงไฟล์ได้ การดำเนินการยาวภายในบล็อก (การประมวลผลข้อมูลซับซ้อน คำขอเครือข่าย) จะบล็อกทั้งระบบการเข้าถึงไฟล์ ดำเนินการเฉพาะการอ่านหรือเขียนข้อมูลภายในบล็อก และประมวลผลภายนอกบล็อก
หลีกเลี่ยงดีดล็อก: อย่าเรียกผู้ประสานงานจากภายในบล็อกของผู้ประสานงานอื่นสำหรับไฟล์เดียวกัน — จะทำให้เกิดดีดล็อกซึ่งกันและกัน ใช้การดำเนินการแบบชุด (อาร์เรย์ของความตั้งใจ) แทนการเรียกซ้อนกัน หากจำเป็นต้องซ้อน ให้ใช้คิวต่างกันหรือ URL ต่างกัน
ตาม objc.io (2024) ข้อผิดพลาดทั่วไปเมื่อทำงานกับ NSFileCoordinator ได้แก่: การขาดการจัดการข้อผิดพลาดในตัวจัดการการเสร็จสิ้น (นำไปสู่การดำเนินการที่ไม่สมบูรณ์); การประสานงานเฉพาะการเขียนแต่ไม่ใช่การอ่าน; การใช้ API แบบซิงก์ที่ล้าสมัยบนเธรด UI; การละเลยโพรโทคอล NSFilePresenter เมื่อทำงานกับ iCloud Drive ข้อผิดพลาดสุดท้ายร้ายกาจที่สุด: แอปพลิเคชันแสดงข้อมูลที่ล้าสมัยโดยไม่รู้ว่าไฟล์ถูกแก้ไขแล้ว
คำถามที่พบบ่อย
NSFileCoordinator เป็นคลาส Foundation สำหรับการเข้าถึงไฟล์อย่างปลอดภัยจากหลายเธรดหรือโพรเซส ป้องกันสภาวะการแข่งขันโดยประสานงานการดำเนินการอ่านและเขียนในระดับระบบไฟล์
NSLock ทำงานเฉพาะภายในโพรเซสเดียว (ระหว่างเธรด) NSFileCoordinator ประสานงานการเข้าถึงระหว่างโพรเซสและส่วนขยายต่าง ๆ รวมถึงการซิงค์ iCloud Drive และ File Provider Extension
ใช่ Apple แนะนำอย่างยิ่งให้ใช้ NSFileCoordinator สำหรับการดำเนินการไฟล์ทั้งหมดในคอนเทนเนอร์ Ubiquity หากไม่มีผู้ประสานงาน อาจเกิดข้อมูลเสียหายระหว่างการซิงค์ระหว่างอุปกรณ์และข้อขัดแย้งกับ File Provider Extension
NSFilePresenter เป็นโพรโทคอลสำหรับรับการแจ้งเตือนเกี่ยวกับการเปลี่ยนแปลงไฟล์ ช่วยให้แอปพลิเคชันตอบสนองต่อการเปลี่ยนแปลงโดยโพรเซสอื่น: อัปเดต UI เมื่อแก้ไข จัดการการย้าย หรือเตรียมพร้อมสำหรับการลบไฟล์
ห้าประเภท: ReadingIntent (อ่าน), WritingIntent (เขียน), ReadingIntent กับ .withoutChanges (อ่านไม่ติดตาม), WritingIntent กับ .contentIndependentMetadataOnly (เฉพาะเมทาดาตา) และ WritingIntent กับ .forDeleting (ลบ) แต่ละประเภทกำหนดระดับการเข้าถึงไฟล์
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม