Swift Realm: เธรดการอ่านและเขียนดัชนีพร้อมกันอย่างปลอดภัยอยู่นอกขอบเขต

ฉันกำลังพยายามอ่านและเขียนพร้อมกันอย่างปลอดภัยบนฐานข้อมูล Realm นี่คือสิ่งที่ฉันพยายามที่จะบรรลุ

ฉันกำลังดึงภาพจาก Flickr และเมื่อดาวน์โหลด imageData แล้ว ออบเจ็กต์ Photo จะถูกเขียนลงในฐานข้อมูล Realm ฉันได้รวม notification เพื่อฟัง insertions ไว้ด้วย เมื่อวัตถุภาพถ่ายถูกเขียนไปยัง Realm แล้ว ให้อัปเดตคุณสมบัติ transport ของรายการเดียวกันนั้น อย่างไรก็ตาม การใช้งานของฉันขัดข้องเป็นครั้งคราว กล่าวคือ ขัดข้องทุกๆ 3-5 ครั้งของการใช้งาน

รหัสดังกล่าว:

override func viewDidLoad() {
    super.viewDidLoad()

    subscribeToRealmNotifications()
}

fileprivate func subscribeToRealmNotifications() {
    do {
        let realm = try Realm()
        let results = realm.objects(Photo.self)

        token = results.observe({ (changes) in
            switch changes {
            case .initial:
                self.setupInitialData()
                self.collectionView.reloadData()

            case .update(_, _, let insertions, _):
                if !insertions.isEmpty {
                    self.handleInsertionsWhenNotified(insertions: insertions)
                }

            case .error(let error):
                self.handleError(error as NSError)
            }
        })

    } catch let error {
        NSLog("Error subscribing to Realm Notifications: %@", error.localizedDescription)
    }
}

fileprivate func handleInsertionsWhenNotified(insertions: [Int]) {
    let lock = NSLock()
    let queue = DispatchQueue(label: "queue", qos: .userInitiated) //Serial queue

    queue.async(flags: .barrier) {
        do {
            let realm = try Realm()
            let objects = realm.objects(Photo.self)

            lock.lock()
            for insertion in insertions {
                print(insertion, objects.count, objects[insertion].id ?? "")
                let photo = objects[insertion] //Crash here
                self.update(photo: photo)
            }

            lock.unlock()

        } catch let error {
            NSLog("Error updating photos in Realm Notifications", error.localizedDescription)
        }
    }
}

func update(photo: Photo) {
    do {
        let realm = try Realm()
        let updatedPhoto = createCopy(photo: photo)

        let transport = Transport()
        transport.name = searchText
        updatedPhoto.transport = transport

        try realm.write {
            realm.add(updatedPhoto, update: true)
        }
    } catch let error {
        NSLog("Error updating photo name on realm: %@", error.localizedDescription)
    }
}

func createCopy(photo: Photo) -> Photo {
    let copiedPhoto = Photo()
    copiedPhoto.id = photo.id
    copiedPhoto.farm = photo.farm
    copiedPhoto.server = photo.server
    copiedPhoto.secret = photo.secret
    copiedPhoto.imageData = photo.imageData
    copiedPhoto.name = photo.name
    return copiedPhoto
}

//On push of a button, call fetchPhotos to download images.
fileprivate func fetchPhotos() {
    FlickrClient.shared.getPhotoListWithText(searchText, completion: { [weak self] (photos, error) in
        self?.handleError(error)

        guard let photos = photos else {return}

        let queue = DispatchQueue(label: "queue1", qos: .userInitiated , attributes: .concurrent)

        queue.async { 
            for (index, _) in photos.enumerated() {
                FlickrClient.shared.downloadImageData(photos[index], { (data, error) in
                    self?.handleError(error)

                    if let data = data {
                        let photo = photos[index]
                        photo.imageData = data
                        self?.savePhotoToRealm(photo: photo)

                        DispatchQueue.main.async {
                            self?.photosArray.append(photo)

                            if let count = self?.photosArray.count {
                                let indexPath = IndexPath(item: count - 1, section: 0)
                                self?.collectionView.insertItems(at: [indexPath])
                            }
                        }
                    }
                })
            }
        }
    })
}

fileprivate func savePhotoToRealm(photo: Photo) {
    do {
        let realm = try Realm()
        let realmPhoto = createCopy(photo: photo)

        try realm.write {
            realm.add(realmPhoto)
            print("Successfully saved photo:", photo.id ?? "")
        }
    } catch let error {
        print("Error writing to photo realm: ", error.localizedDescription)
    }
}

โปรดทราบว่าโค้ดด้านบนขัดข้องทุกๆ 3-5 ครั้ง ดังนั้นฉันสงสัยว่าการอ่านและเขียนไม่เสร็จอย่างปลอดภัย บันทึกการพิมพ์และบันทึกข้อผิดพลาดจะแสดงเมื่อเกิดข้อขัดข้อง

Successfully saved photo: 45999333945 
4 6 31972639607 
6 7 45999333945 
Successfully saved photo: 45999333605 
Successfully saved photo: 45999333675 
7 8 45999333605 
8 9 45999333675 
Successfully saved photo: 45999333285 
Successfully saved photo: 33038412228 
2019-01-29 14:46:09.901088+0800 GCDTutorial[24139:841805] *** Terminating app due to uncaught exception 'RLMException', reason: 'Index 9 is out of bounds (must be less than 9).'

ใครก็ได้ช่วยบอกทีว่าฉันผิดตรงไหน?

หมายเหตุ: ฉันได้ลองเรียกใช้ queue.sync ที่ handleInsertionsWhenNotified แล้ว การทำเช่นนี้จะช่วยลดข้อขัดข้องโดยสิ้นเชิง แต่จะค้าง UI ขณะที่ทำงานบนเธรดหลัก นี่ไม่เหมาะในกรณีของฉัน


person Koh    schedule 29.01.2019    source แหล่งที่มา


คำตอบ (2)


หลังจากศึกษาบันทึกอย่างละเอียดมากขึ้นอีกเล็กน้อย ฉันสังเกตว่าการนับอ็อบเจ็กต์ไม่นับรวมทุกครั้งที่แอปขัดข้อง กล่าวอีกนัยหนึ่ง จำนวนอ็อบเจ็กต์ทั้งหมดที่พิมพ์เมื่อ Realm แจ้งการแทรกคือ 9 (แม้ว่าการตรวจสอบฐานข้อมูล realm ทางเบราว์เซอร์จะแสดงมากกว่า 9) แต่ดัชนีการแทรกคือ 9

ซึ่งหมายความว่าเมื่อมีการสืบค้น จำนวนออบเจ็กต์อาจยังไม่อัปเดต (ไม่แน่ใจเหมือนกันว่าเพราะเหตุใด) หลังจากอ่านบทความเพิ่มเติมเกี่ยวกับ realm docs และที่นี่ ฉันใช้งาน realm.refresh() ก่อนที่จะสอบถามออบเจ็กต์ วิธีนี้ช่วยแก้ปัญหาได้

//Updated code for handleInsertionsWhenNotified
fileprivate func handleInsertionsWhenNotified(insertions: [Int]) {
    let lock = NSLock()
    let queue = DispatchQueue(label: "queue", qos: .userInitiated) //Serial queue

    queue.async(flags: .barrier) {
        do {
            let realm = try Realm()
            realm.refresh() // Call refresh here
            let objects = realm.objects(Photo.self)

            lock.lock()
            for insertion in insertions {
                print(insertion, objects.count, objects[insertion].id ?? "")
                let photo = objects[insertion] //Crash here
                self.update(photo: photo)
            }

            lock.unlock()

        } catch let error {
            NSLog("Error updating photos in Realm Notifications", error.localizedDescription)
        }
    }
}

หวังว่ามันจะช่วยใครก็ได้ที่นั่น

person Koh    schedule 30.01.2019

CollectionView แทรกแถวที่เรียกก่อนเรียกว่า numberOfIteminSection ฉันหวังว่ารหัสนี้จะใช้งานได้

let indexPath = IndexPath(item: count - 1, section: 0)
self?.collectionView.numberOfItems(inSection: 0)
self?.collectionView.insertItems(at: [indexPath])
person Pradip Patel    schedule 29.01.2019
comment
ความผิดพลาดไม่เกิดขึ้นใน collectionView.insertItems มันอยู่ใน handleInsertionsWhenNotified - person Koh; 29.01.2019
comment
ไม่สนับสนุนคำตอบแบบโค้ดเท่านั้น โปรดคลิกแก้ไขและเพิ่มคำเพื่อสรุปว่าโค้ดของคุณตอบคำถามอย่างไร - person Prakash Thete; 29.01.2019