I am new to Realm DataBase and I need a way to read data from realmCloud, but from two different app projects. The way I have tried to implement this is by using query-synced realm. At the moment I'm using a singe realm user to write the data in one app, and the same realm user to read data from another app. The problem is that making a query from the second app(the one used for reading) doesn't return any realm objects ( I have also noticed that user identifier is different from the first one, and also the user permissions are nil.
I have tried setting permissions directly from RealmStudio since documentation is not precise on how to set them from code
func openRealm() {
do {
realm = try Realm(configuration: SyncUser.current!.configuration())
let queryResults = realm.objects(*className*.self)
let syncSubscription = queryResults.subscribe()
let notificationToken = queryResults.observe() { [weak self] (changes) in
switch (changes) {
case .initial: print(queryResults)
case .error(let error): print(error)
default: print("default")
}
}
for token in queryResults {
print(token.tokenString)
}
syncSubscription.unsubscribe()
notificationToken.invalidate()
} catch {
print(error)
}
}
This function prints the data in one app project, but used in another app project with the same user logged in, and the same classFile referenced in the project, it does not. (note that SyncUser.current.identifier is different also
There are a couple of issues.
Some of these calls are asynchronous and the code in your question is going out of scope before the data is sync'd (retreived). The bottom line is code is faster than the internet and you need to design the flow of the app around async calls; don't try to work with the data until it's available.
For example
let notificationToken = queryResults.observe() { [weak self] (changes) in
//here is where results are fully populated
}
// this code may run before results are populated //
for token in queryResults {
print(token.tokenString)
}
Also, let notificationToken is a local var and goes out of scope before the results are populated as well.
These issues are super easy to fix. First is to keep the notification token alive while waiting for results to be populated and the second is to work with the results inside the closure, as that's when they are valid.
var notificationToken: NotificationToken? = nil //a class var
func openRealm() {
do {
let config = SyncUser.current?.configuration()
let realm = try Realm(configuration: config!)
let queryResults = realm.objects(Project.self)
let syncSubscription = queryResults.subscribe(named: "my-projects")
self.notificationToken = queryResults.observe() { changes in
switch changes {
case .initial:
print("notification: initial results are populated")
queryResults.forEach { print($0) }
case .update(_, let deletions, let insertions, let modifications):
print("notification: results, inserted, deleteed or modified")
insertions.forEach { print($0) } //or mods or dels
case .error(let error):
fatalError("\(error)")
}
}
} catch {
print(error)
}
}
deinit {
self.notificationToken?.invalidate()
}
The other advantage of keeping that token (and its corresponding code) alive is when there are further changes, your app will be notified. So if another project is added for example, the code in the 'changes' section will run and display that change.
Related
I'm trying to create a feed that should include posts made by people a user follows, and I'm using following function to get posts for user feed:
func observePosts(for userID: String, completion: #escaping ([UserPost], String) -> Void) {
let databaseKey = "Posts/\(userID)/"
databaseRef.child(databaseKey).queryOrderedByKey().queryLimited(toLast: 12).observe(.value) { snapshot in
var retrievedPosts = [UserPost]()
var lastSeenPostKey = ""
let dispatchGroup = DispatchGroup()
for snapshotChild in snapshot.children.reversed() {
guard let postSnapshot = snapshotChild as? DataSnapshot,
let postDictionary = postSnapshot.value as? [String: Any]
else {
print("Couldn't cast snapshot as DataSnapshot")
return
}
dispatchGroup.enter()
lastSeenPostKey = postSnapshot.key
do {
let jsonData = try JSONSerialization.data(withJSONObject: postDictionary as Any)
let decodedPost = try JSONDecoder().decode(UserPost.self, from: jsonData)
UserService.shared.observeUser(for: decodedPost.userID) { postAuthor in
decodedPost.author = postAuthor
retrievedPosts.append(decodedPost)
dispatchGroup.leave()
}
} catch {
print("Couldn't create UserPost from postDictionary")
}
}
dispatchGroup.notify(queue: .main) {
completion(retrievedPosts, lastSeenPostKey)
}
}
}
First step I'm getting user feed posts with .observe function and after that I'm iterating through every received post calling a UserService.shared.observeUser method that observes user data for every post to set post author's profile picture and username.
The problem with my method is that I'm using DispatchGroup's .enter and .leave methods to notify once every observe function got user data so that I could call completion block in which I'm reloading tableView.
Sure it works fine first time but since it's observe function it's called every time there's a change to user so once user updates his data (profile pic, usernama etc.) it crashes on dispatchGroup.leave() line since it's not balanced with dispatchGroup.enter()
What would be a correct approach to solve this issue?
I've been trying to solve this problem for 3 days searching all the internet, and only advice I found is to fetch it one time using observeSingleValue() instead of observing, but I really need to observe user profile data to keep profile pics and other necessary data up to date.
I'm trying to port a little RSS feed reader app from UIKit over to SwiftUI, this app uses Realm for persistence.
In order to make Realm bindable in SwiftUI, I added the following code to my project:
import Foundation
import SwiftUI
import RealmSwift
final class FeedData: ObservableObject {
#Published var feeds: [Feed] {
didSet {
cleanRealm()
}
}
private var feedsToken: NotificationToken?
private func activateFeedsToken() {
let realm = try! Realm()
let feeds = realm.objects(Feed.self)
feedsToken = feeds.observe { _ in
self.feeds = Array(feeds)
}
}
func cleanRealm() {
let realm = try! Realm()
let tempFeeds = realm.objects(Feed.self)
let diff = feeds.difference(from: tempFeeds)
for change in diff {
switch change {
case .remove(_, let element, _):
do {
try realm.write {
print("Removing \(element.name)")
if element.isInvalidated {
print("Error: element invalidated.")
} else {
realm.delete(element)
print("Removed \(element.name)")
}
}
} catch {
fatalError(error.localizedDescription)
}
default:
break
}
}
}
init() {
let realm = try! Realm()
feeds = Array(realm.objects(Feed.self))
activateFeedsToken()
}
deinit {
feedsToken?.invalidate()
}
}
So, we have a feeds array with a DidSet observer that invokes the cleanRealm() function when triggered, which then uses collection diffing to remove Feed objects which are no longer in the array but still stored in Realm - I'm aware this is super clunky, but I figured it would at least keep the array in sync with the Realm database.
In my SwiftUI view, I then use FeedData as EnvironmentObject and use a List showing all Feed objects using ForEach.
When a user deletes a feed from the List, it is then removed from the array like so:
.onDelete(perform: deleteItems)
Which then calls this function:
func deleteItems(at offsets: IndexSet) {
print("Removing feeds at requested offsets.")
feedData.feeds.remove(atOffsets: offsets)
print("Removed feeds at requested offsets.")
}
Problem: when I run my app and then delete an entry from the list, the following exception is thrown:
* Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
* First throw call stack:
(0x1a4a97ab0 0x1a47b1028 0x10098f6a8 0x100996324 0x1009962e8 0x10000dfa4 0x1b215d830 0x1b215d3f0 0x1b215d170 0x1b215f470 0x1db363c4c 0x1db36a0c8 0x1db36a240 0x1db36a6c0 0x1b22d647c 0x1db3ca2c4 0x1db3c9f04 0x1b21a924c 0x1b21a943c 0x1b21a9b50 0x1b22d647c 0x1daefcd50 0x1db3f0ac4 0x1db3eb7b8 0x1db3ead20 0x1db14dca4 0x1db14c5c0 0x1db4d5d0c 0x1db1bdc1c 0x1db1b836c 0x1db1bef70 0x1cf72c9c0 0x1cf713e9c 0x1cf714164 0x1cf719130 0x1db07f9f0 0x1db084e10 0x1db3ac770 0x1db07f96c 0x1cf719284 0x1db081ca0 0x1db081a48 0x1db0816c8 0x1db081834 0x1db3ac770 0x1db0817fc 0x1db09f848 0x1daf00a10 0x1daf00970 0x1daf00a8c 0x1a4a12668 0x1a4a0d308 0x1a4a0d8b8 0x1a4a0d084 0x1aec56534 0x1a8b7b8b8 0x10004e520 0x1a488ce18)
libc++abi.dylib: terminating with uncaught exception of type NSException
My research so far suggests that when a Realm object is deleted, it is mutated before being removed entirely.
So, I think what may be going on here is that when the object is mutated prior to being removed from Realm, SwiftUI detects this change, redraws the view and then tries accessing the now invalidated Realm object resulting in the exception being raised.
Realm objects do have a isInvalidated property that I should probably check before adding it to the List, but AFAIK (and please do feel free to correct me on this) there is no way in a ForEach block to check for such a condition and "continue" to the next array item if desired.
Any help with this is greatly appreciated, I've been messing with this issue all day and just cannot find a good solution, it's probably obvious but I'm also still learning about all the wonderful stuff SwiftUI can do while working on this sample project.
Thanks!
Update:
Following Jay's advice, I managed to modify my FeedData class so that it exposes a Realm Results property for my app to work with, instead of copying to a separate array.
That also means I no longer need to do any collection diffing when deleting feeds, but since SwiftUI's .onDelete(perform:) modifier expects a function that accepts an IndexSet, my delete function is still a bit of a hack.
The updated FeedData class now looks as follows:
import Foundation
import SwiftUI
import RealmSwift
final class FeedData: ObservableObject {
#Published var feeds: Results<Feed>
private var feedsToken: NotificationToken?
private func activateFeedsToken() {
let realm = try! Realm()
let feeds = realm.objects(Feed.self)
feedsToken = feeds.observe { _ in
self.feeds = feeds
}
}
func deleteItems(at offsets: IndexSet) {
print("deleteItems called.")
let realm = try! Realm()
do {
try realm.write {
offsets.forEach { index in
print("Attempting to access index \(index).")
if index < feeds.count {
print("Index is valid.")
let item = feeds[index]
print("Removing \(item.name)")
realm.delete(item)
print("Removed item.")
}
}
}
} catch {
fatalError(error.localizedDescription)
}
}
init() {
let realm = try! Realm()
feeds = realm.objects(Feed.self)
activateFeedsToken()
}
deinit {
feedsToken?.invalidate()
}
}
While that technically works, my issue now is that my view's ForEach() method will be called as soon as an object is deleted from the Realm (tested by using breakpoints), this happens even before the Realm notification is dispatched.
This then results in an attempt to access the index of the deleted object, in which case the app will crash with an index out of bounds error from Realm.
That should probably be a separate question though, as my original issue is resolved.
#Jay, can you please mark your first comment as an answer to my question so that I can approve it?
Thanks for the great help!
I ran into this problem, too. Not sure what's going on, some complicated multiple call behavior caused by SwiftUI, I think. When I expanded my notificationToken observation block and switch on the changes to return if a deletion is called, it works fine. In your case:
feedsToken = feeds.observe { [weak self] (changes: RealmCollectionChange) in
switch changes {
case .initial:
print("Initial call")
case .update(_, let deletions, _, _):
print("Updates made!")
if !deletions.isEmpty { return }
self.feeds = feeds
case .error(let error):
print("Error observing changes")
}
}
I am new to core data. I have an app that uses core data as local store. Writing to and reading from core data is done by background threads. While this works generally, in rare cases are the fetched data wrong, i.e. properties of a fetched entity are nil.
To check the situation, I wrote a unit test that starts 2 async threads: One fetches continuously from core data, and the other one overwrites continuously these data by first deleting all data, and then storing new data.
This test pretty quickly provokes the error, but I have no idea why. Of course I guess this is a multi-threading problem, but I don’t see why, because fetches and deletion+writes are done in separate managed contexts of a single persistentContainer.
I am sorry that the code below is pretty long, although shortened, but I think without it one cannot identify the problem.
Any help is highly welcome!
Here is my function to fetch data:
func fetchShoppingItems(completion: #escaping (Set<ShoppingItem>?, Error?) -> Void) {
persistentContainer.performBackgroundTask { (managedContext) in
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
do {
let cdShoppingItems: [CDShoppingItem] = try managedContext.fetch(fetchRequest)
for nextCdShoppingItem in cdShoppingItems {
nextCdShoppingItem.managedObjectContext!.performAndWait {
let nextname = nextCdShoppingItem.name! // Here, sometimes name is nil
} // performAndWait
} // for all cdShoppingItems
completion(nil, nil)
return
} catch let error as NSError {
// error handling
completion(nil, error)
return
} // fetch error
} // performBackgroundTask
} // fetchShoppingItems
I have commented the line that sometimes crashes the test, since name is nil.
Here are my functions to store data:
func overwriteCD(shoppingItems: Set<ShoppingItem>,completion: #escaping () -> Void) {
persistentContainer.performBackgroundTask { (managedContext) in
self.deleteAllCDRecords(managedContext: managedContext, in: "CDShoppingItem")
let cdShoppingItemEntity = NSEntityDescription.entity(forEntityName: "CDShoppingItem",in: managedContext)!
for nextShoppingItem in shoppingItems {
let nextCdShoppingItem = CDShoppingItem(entity: cdShoppingItemEntity,insertInto: managedContext)
nextCdShoppingItem.name = nextShoppingItem.name
} // for all shopping items
self.saveManagedContext(managedContext: managedContext)
completion()
} // performBackgroundTask
} // overwriteCD
func deleteAllCDRecords(managedContext: NSManagedObjectContext, in entity: String) {
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
deleteRequest.resultType = .resultTypeObjectIDs
do {
let result = try managedContext.execute(deleteRequest) as? NSBatchDeleteResult
let objectIDArray = result?.result as? [NSManagedObjectID]
let changes = [NSDeletedObjectsKey: objectIDArray]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes as [AnyHashable: Any], into: [managedContext])
} catch let error as NSError {
// error handling
}
} // deleteAllCDRecords
func saveManagedContext(managedContext: NSManagedObjectContext) {
if !managedContext.hasChanges { return }
do {
try managedContext.save()
} catch let error as NSError {
// error handling
}
} // saveManagedContext
Are you sure that name isn't nil for all requested entities? Just use guard-let to avoid ! for optional variables. Also ! it isn't safe way to unwrap optional variable especially if you can't be sure for source of data.
The problem with my code was apparently a race condition:
While the „fetch“ thread fetched the core data records, and tried to assign the attributes to the properties, the „store“ thread deleted the records.
This apparently released the attribute objects, so that nil was stored as property.
I thought that the persistentContainer would automatically prevent this, but it does not.
The solution is to execute both background threads of the persistentContainer in a concurrent serial queue, the „fetch“ thread synchronously, and the „store“ thread asynchronously with a barrier.
So, concurrent fetches can be executed, while a store waits until all current fetches are finished.
The concurrent serial queue is defined as
let localStoreQueue = DispatchQueue(label: "com.xxx.yyy.LocalStore.localStoreQueue",
attributes: .concurrent)
EDIT:
In the following fetch and store functions, I moved the core data function persistentContainer.performBackgroundTask inside the localStoreQueue. If it were outside as in my original answer, the store code in localStoreQueue.async(flags: .barrier) would setup a new thread and thus use managedContext in another thread that it was created in, which is a core data multi-threading error.
The „fetch“ thread is modified as
localStoreQueue.sync {
self.persistentContainer.performBackgroundTask { (managedContext) in
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
//…
} // performBackgroundTask
} // localStoreQueue.sync
and the „store“ thread as
localStoreQueue.async(flags: .barrier) {
self.persistentContainer.performBackgroundTask { (managedContext) in
self.deleteAllCDRecords(managedContext: managedContext, in: "CDShoppingItem")
//…
} // performBackgroundTask
} // localStoreQueue.async
I have a CloudKit database with some data. By pressing a button my app should check for existence of some data in the Database. The problem is that all processes end before my app get the results of its search. I found this useful Answer, where it is said to use Closures.
I tried to follow the same structure but Swift asks me for parameters and I get lost very quick here.
Does someone can please help me? Thanks for any help
func reloadTable() {
self.timePickerView.reloadAllComponents()
}
func getDataFromCloud(completionHandler: #escaping (_ records: [CKRecord]) -> Void) {
print("I begin asking process")
var listOfDates: [CKRecord] = []
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Riservazioni", predicate: predicate)
let queryOperation = CKQueryOperation(query: query)
queryOperation.resultsLimit = 20
queryOperation.recordFetchedBlock = { record in
listOfDates.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
if error != nil {
print("error")
print(error!.localizedDescription)
} else {
print("NO error")
self.Array = listOfDates
completionHandler(listOfDates)
}
}
}
var Array = [CKRecord]()
func generateHourArray() {
print("generate array")
for hour in disponibleHours {
let instance = CKRecord(recordType: orderNumber+hour)
if Array.contains(instance) {
disponibleHours.remove(at: disponibleHours.index(of: hour)!)
}
}
}
func loadData() {
timePickerView.reloadAllComponents()
timePickerView.isHidden = false
}
#IBAction func checkDisponibility(_ sender: Any) {
if self.timePickerView.isHidden == true {
getDataFromCloud{ (records) in
print("gotData")
self.generateHourArray()
self.loadData()
}
print(Array)
}
}
Im struggling to understand your code and where the CloudKit elements fit in to it, so Im going to try and give a generic answer which will hopefully still help you.
Lets start with the function we are going to call to get our CloudKit data, lets say we are fetching a list of people.
func getPeople() {
}
This is simple enough so far, so now lets add the CloudKit code.
func getPeople() {
var listOfPeople: [CKRecord] = [] // A place to store the items as we get them
let query = CKQuery(recordType: "Person", predicate: NSPredicate(value: true))
let queryOperation = CKQueryOperation(query: query)
queryOperation.resultsLimit = 20
// As we get each record, lets store them in the array
queryOperation.recordFetchedBlock = { record in
listOfPeople.append(record)
}
// Have another closure for when the download is complete
queryOperation.queryCompletionBlock = { cursor, error in
if error != nil {
print(error!.localizedDescription)
} else {
// We are done, we will come back to this
}
}
}
Now we have our list of people, but we want to return this once CloudKit is done. As you rightly said, we want to use a closure for this. Lets add one to the function definition.
func getPeople(completionHandler: #escaping (_ records: [CKRecord]) -> Void) {
...
}
This above adds a completion hander closure. The parameters that we are going to pass to the caller are the records, so we add that into the definition. We dont expect anyone to respond to our completion handler, so we expect a return value of Void. You may want a boolean value here as a success message, but this is entirely project dependent.
Now lets tie the whole thing together. On the line I said we would come back to, you can now replace the comment with:
completionHandler(listOfPeople)
This will then send the list of people to the caller as soon as CloudKit is finished. Ive shown an example below of someone calling this function.
getPeople { (records) in
// This code wont run until cloudkit is finished fetching the data!
}
Something to bare in mind, is which thread the CloudKit API runs on. If it runs on a background thread, then the callback will also be on the background thread - so make sure you don't do any UI changes in the completion handler (or move it to the main thread).
There are lots of improvements you could make to this code, and adapt it to your own project, but it should give you a start. Right off the bat, Id image you will want to change the completion handler parameters to a Bool to show whether the data is present or not.
Let me know if you notice any mistakes, or need a little more help.
I have a few realm managed objects in my app that need to keep a track of the timestamp it was last modified.
I could implement the repository programming pattern for this, or I could just remember to set the property every time I change something on those objects (which is a fallback idea, as horrible as it sounds).
What I've done is create a singleton object that monitors collections of objects that I want to keep a "last modified" timestamp for. This is booted up on app start and keep's an eye on the collections for any modifications.
The general "flow" is:
Wait for change notification
On change, loop the modified indexes
Check the index exists before trying to access the object (just in case)
realm.write a new last modified Date to the object
As you can probably guess already (and as I suspected), that realm.write to update my last modified timestamp, then creates a new change notification, which in turn makes the timestamp update again. So it ends up just looping forever after the first change.
Looking for options on how best to solve this.
Here's what I have implemented:
class RealmCollectionLastModifiedMonitor {
static let shared = RealmCollectionLastModifiedMonitor()
private var formObjectNotificationToken: NotificationToken?
private init() {
startMonitoring()
}
deinit {
stopMonitoring()
}
func startMonitoring() {
let realm = try! Realm()
let caseforms = realm.objects(CaseForm.self)
formObjectNotificationToken = caseforms.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
switch changes {
case .initial:
// Do nothing for now - as i've not worked out what this is :P
break
case .update(_, let deletions, let insertions, let modifications):
// Some debug output
print("Form collection change: Deletions = \(deletions.count), Insertions = \(insertions.count), Modifications = \(modifications.count)")
// Loop the modificated indexes, get a reference to the form, then update the last modified timestamp
for i in modifications {
if caseforms.indices.contains(i) {
do {
try realm.write {
caseforms[i].lastModified = Date()
}
} catch {
print("Failed to update modified date")
}
}
}
break
case .error(let error):
// Do nothing for now - If a write fails, we could rollback, but it's not uber-important
break
}
}
}
func stopMonitoring() {
formObjectNotificationToken = nil
}
}
Unfortunately my suggestion is a refactoring of your current approach.
You're intent is not to monitor general changes to the collection, but rather changes to specific properties on specific objects.
With this is mine I feel that you will be far better served by monitoring each individual object, and this will allow you introspection into the properties that have changed on said object.
For example,
func monitor(_ caseForm: CaseForm) -> NotificationToken {
return caseForm.addNotificationBlock { change in
switch change {
case .change(let properties):
guard properties.count > 0 !properties.first(where: { $0.name == "lastModified" }) else { return }
// modify last modified date
case .deleted:
break
case .error(let error):
// handle error
}
}
}
}