Swift Realm: Indeks Baca dan Tulis Bersamaan Aman di luar batas

Saya mencoba melakukan pembacaan dan penulisan secara bersamaan dengan aman di database Realm. Inilah yang ingin saya capai.

Saya mengambil gambar dari Flickr, dan setelah imageData diunduh, objek Photo ditulis ke dalam database Realm. Saya juga menyertakan notification untuk mendengarkan insertions. Setelah objek Foto ditulis ke Realm, perbarui properti transport item yang sama. Namun implementasi saya terkadang crash, yaitu crash setiap 3-5 kali implementasi.

Kode seperti itu:

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)
    }
}

Perhatikan bahwa kode di atas mogok setiap 3-5 kali sekali, jadi saya curiga pembacaan dan penulisan tidak dilakukan dengan aman. Log cetak dan log kesalahan seperti yang ditunjukkan ketika terjadi kerusakan

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).'

Adakah yang bisa membantu memberi tahu di mana kesalahan saya?

CATATAN: Saya telah mencoba menjalankan queue.sync di handleInsertionsWhenNotified. Melakukan hal ini akan menghilangkan kerusakan total, namun membekukan UI saat berjalan di thread utama. Ini tidak ideal dalam kasus saya.


person Koh    schedule 29.01.2019    source sumber


Jawaban (2)


Setelah mempelajari log sedikit lebih hati-hati, saya mengamati bahwa jumlah objek tidak dihitung setiap kali aplikasi mogok. Dengan kata lain, jumlah total objek yang dicetak ketika Realm memberitahukan penyisipan adalah 9 (walaupun pemeriksaan fisik database realm melalui browser menunjukkan lebih dari 9), tetapi indeks penyisipan adalah 9.

Ini berarti ketika kueri dibuat, jumlah objek mungkin belum diperbarui (tidak terlalu yakin mengapa). Setelah membaca lebih banyak artikel di realm docs dan di sini, saya menerapkan realm.refresh() sebelum menanyakan objek. Ini menyelesaikan masalahnya.

//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)
        }
    }
}

Semoga ini bisa membantu siapa pun di luar sana.

person Koh    schedule 30.01.2019

Panggilan baris penyisipan CollectionView pertama kali disebut numberOfIteminSection. Saya harap kode ini Berfungsi.

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
Kecelakaan tidak terjadi pada collectionView.insertItems. Itu ada di dalam handleInsertionsWhenNotified. - person Koh; 29.01.2019
comment
Jawaban yang hanya berupa kode tidak dianjurkan. Silakan klik edit dan tambahkan beberapa kata yang merangkum bagaimana kode Anda menjawab pertanyaan. - person Prakash Thete; 29.01.2019