Swift Realm: потокобезопасный параллельный индекс чтения и записи за пределами - PullRequest
0 голосов
/ 29 января 2019

Я пытаюсь одновременно выполнять чтение и запись в базу данных Realm.Это то, чего я пытаюсь достичь.

Я извлекаю изображения из Flickr, и после загрузки imageData объект Photo записывается в базу данных Realm.Я также включил notification для прослушивания insertions.Как только объект Photo был записан в 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.Это полностью исключает сбой, но пользовательский интерфейс замораживается, поскольку он выполняется в основном потоке.Это не идеально в моем случае.

Ответы [ 2 ]

0 голосов
/ 30 января 2019

После более тщательного изучения журналов я заметил, что количество объектов не учитывается при сбое приложения.Другими словами, общее количество объектов, напечатанное, когда Realm уведомляет о вставке, равно 9 (хотя физическая проверка базы данных области через браузер показала более 9), но индекс вставки равен 9.

Это означает, что когдазапрос выполнен, число объектов, вероятно, еще не обновлено (не совсем понятно, почему).После прочтения большего количества статей о документах области и здесь я реализовал 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)
        }
    }
}

Надеюсь, это кому-нибудь поможет.

0 голосов
/ 29 января 2019

Строка вставки CollectionView вызывает сначала вызываемый numberOfIteminSection.Я надеюсь, что этот код - Работа.

let indexPath = IndexPath(item: count - 1, section: 0)
self?.collectionView.numberOfItems(inSection: 0)
self?.collectionView.insertItems(at: [indexPath])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...