I am using RxSwift and RxRealm and want to be able to observe changes of resources based on a predicate. The function would be like this
func observe(type: String) -> Observable<[MyRealmObject]> {
let realm = configureRealm() // 1
let predicate = NSPredicate(format: "type = %#", type)
let resources = realm
.objects(MyRealmObject.self)
.filter(predicate)
return Observable.array(from: resources) // 2
}
However, Realm requires // 2 to be run on a thread with a run loop AND // 1 and // 2 have to be called on the same thread.
I can't guarantee that the observe(type: String) function is already being called on a thread with run loop, so I probably have to switch to one within the function - but that way i wont be able to have a synchronous return value anymore.
Any ideas on how to make sure realm observations are always guaranteed to be on a specific run loop?
Update: Using custom Thread with run-loop - but I'm still not satisfied
I created a custom thread that has a run loop but getting the synchronous return value is just ugly - as you can see i used a DispatchGroup to synchronize and i'd really like to avoid that.
func observe(type: String) -> Observable<[MyRealmObject]> {
let observableCalculation = {
let realm = configureRealm() // 1
let predicate = NSPredicate(format: "type = %#", type)
let resources = realm
.objects(MyRealmObject.self)
.filter(predicate)
return Observable.array(from: resources) // 2
}
if Thread.current == self.realmObservationThread {
return observableCalculation()
}
var o: Observable<[MyRealmObject]>!
let g = DispatchGroup()
g.enter()
self.realmObservationThread.runloop?.perform {
o = observableCalculation()
g.leave()
}
g.wait()
return o
}
Any suggestions would be appreciated!
Do you really have to observe each and every array individually by creating the observable. I think it is better to observe Results which is the result of query instead of Observing each items like so.
See sample from here.
You could then wrap this inside your own observable and based on the changes in the Result, you could then do a callback for specific object.
Related
Currently, I perform multiple update operations via the following code.
func updateOrders(_ updates : [(objectID: NSManagedObjectID, order: Int64)]) {
if updates.isEmpty {
return
}
let coreDataStack = CoreDataStack.INSTANCE
let backgroundContext = coreDataStack.backgroundContext
backgroundContext.perform {
for update in updates {
let objectID = update.objectID
let order = update.order
let nsPlainNote = try! backgroundContext.existingObject(with: objectID) as! NSPlainNote
nsPlainNote.order = order
}
RepositoryUtils.saveContextIfPossible(backgroundContext)
}
}
Since I would like to
Make the update operations run faster
Avoid delegate of NSFetchedResultController from being notified
I would like to utilise NSBatchUpdateRequest for performing such update operation.
However, I don't find a way, how I can apply array of NSManagedObjectID and array of Int64 value, to NSBatchUpdateRequest.
Given an array of NSManagedObjectID and Int64, is it possible to use NSBatchUpdateRequest to perform updated on CoreData?
You must use NSPredicate to set object id
func updateOrders(_ updates : [(objectID: NSManagedObjectID, order: Int64)]) {
updates.forEach {
let request = NSBatchUpdateRequest(entityName: "NSPlainNote")
request.propertiesToUpdate = ["order": $0.order]
request.predicate = NSPredicate(format: "objectID == %#", $0.objectID)
let result = try? context.execute(request)
}
}
NSBatchUpdateRequest is not suitable for your task since using it makes sense for large amount of records with a common attribute's value so that you can filter all by your criteria and update all fields with your values at once.
The fact is that the NSBatchDeleteRequest is an NSPersistentStoreRequest which operates at the SQL level in the persistent store itself and it doesn't update your in-memory objects after execution thats why it works so fast and Core Data translates your native requests to a SQL ones where you can not use dynamically code to get and insert needed data from dictionary etc. but you can update the current value of a filed e.g.:
let batchRequest = NSBatchUpdateRequest(entityName: "Note")
batchRequest.predicate = predicate
// Increase `order` value
batchRequest.propertiesToUpdate = ["order" : NSExpression(format: "order + 1")]
do {
try context.execute(batchRequest)
}
catch {
fatalError(error.localizedDescription)
}
I have built a generic function to get any kind of data from realm, that seems to work just fine.
func getData<T: Object>(withFilter: String) -> Results<T>? {
if !checkRealm(){return nil}
return realm!.objects(T.self).filter(withFilter)
}
I can't figure out though how to use this function to delete data.
My delete function is the following:
func removeData(withFilter: String) {
let dataToDelete: Results<Object>? = getData(withFilter: withFilter)
// *** The above line crashes the app ***
if let dataToDeleteUnwrapped = dataToDelete{
try? realm!.write {
realm!.delete(dataToDeleteUnwrapped)
}
}
}
This results to an error, attached bellow. Though Results<Object>? crashes the app, Results<MyCustomObject>? works fine, but then my remove data function is not generic anymore.
Terminating app due to uncaught exception 'RLMException', reason: 'Object type 'RealmSwiftObject' is not managed by the Realm. If using a custom `objectClasses` / `objectTypes` array in your configuration, add `RealmSwiftObject` to the list of `objectClasses` / `objectTypes`.'
I am sure there is a nice short way to solve this one, but I can't figgure it out, so any help is appreciated.
Results cannot hold instances of several classes, all of its elements must be from the same type, so Results<Object> is not a valid type for the collection.
Your current implementation of getData is wrong. You don't use the generic type T anywhere in your function. Generic types should be given to input arguments for generic functions. You need to call both getData and removeData on a specific Realm type inheriting from Object, since Result can only hold a single type and all your types have unique properties, so you can only use a predicate on a specific type.
This is the correct generic implementation of getData to query Realm for a specific class with the given predicate.
func getData<T:Object>(ofType: T.Type, withFilter: String) -> Results<T>? {
if !checkRealm(){return nil}
return realm!.objects(T.self).filter(withFilter)
}
You call it like this (the following example was tested and is working with the playground in the examples project from the Realm website):
let people = getData(ofType: Person.self, withFilter: "cars.#count > 1 && spouse != nil")
people?.first?.name //prints Jennifer when called after adding the objects to realm, but before deleting them in the example project
Seeing how a generic function can be used on Realm with type constraints, you should be able to update your deletion function as well.
use this extension to convert results to real object
extension Results {
func toArray<T>(ofType: T.Type) -> [T] {
var array = [T]()
for i in 0 ..< count {
if let result = self[i] as? T {
array.append(result)
}
}
return array
}
}
how to use in code
let orjObjs = realm.objects(UrObj.self).toArray(ofType: UrObj.self) as [UrObj]
I am trying to get all the subscriptions associated with the current user. According to the documentation, the function to call is:
CKFetchSubscriptionsOperation.fetchAllSubscriptionsOperation()
I call the function this way:
let op = CKFetchSubscriptionsOperation.fetchAllSubscriptionsOperation()
op.fetchSubscriptionCompletionBlock = { (subs, error) in
print("*** fetched subs: \(subs)")
}
let q = OperationQueue()
q.addOperation(op)
However, the subs parameter returns an empty dictionary (Optional([:])). The error param is nil. I'm new to using NSOperation, so I'm wondering if I am doing something wrong.
(As confirmation that subscriptions exist to be retrieved, I separately call the container's publicDatabase.fetchAllSubscriptions() function. This one returns the current user's subscriptions as expected. Unfortunately, it also returns the subscriptions associated with every other user in the schema.)
Your sample code does not set the container and the database on the CKFetchSubscriptionsOperation. And since you are adding the operation to your own OperationQueue, you need to set these.
Option #1:
Set the container and the database on the operation directly.
let op = CKFetchSubscriptionsOperation.fetchAllSubscriptionsOperation()
op.fetchSubscriptionCompletionBlock = { (subs, error) in
print("*** fetched subs: \(subs)")
}
op.container = CKContainer.default()
op.database = CKContainer.default().publicCloudDatabase
let q = OperationQueue()
q.addOperation(op)
Option #2:
Add the operation to the desired database's queue (which sets these for you).
let op = CKFetchSubscriptionsOperation.fetchAllSubscriptionsOperation()
op.fetchSubscriptionCompletionBlock = { (subs, error) in
print("*** fetched subs: \(subs)")
}
CKContainer.default().publicCloudDatabase.add(op)
I currently have 2 arrays, one called rewardsArray and one called expiredRewardsArray. The rewardsArray I'm getting from an api thats fetched every time I connect. The expiredRewardsArray I save locally. What I'm trying to do is in the viewDidLoad, after I retrieve the rewardsArray data I want to compare it to the expiredRewardsArray's data. If there is a match I want to remove the item from the rewardsArray. This is what I have so far but it never goes inside the "if let" brackets so it's not removing the item from the rewardsArray:
func rewardsMinusExpired () {
expiredRewardsArray = rewardManager.getExpiredRewards()
for expiredReward in expiredRewardsArray {
if let ex = rewardsArray.indexOf(expiredReward){
print("Expired Reward to be removed: \(ex)")
rewardsArray.removeAtIndex(ex)
rewardsTableView.reloadData()
}
}
}
Each item in the array has an id, I use that to see if the item is in the expiredRewardsArray:
for expiredReward in expiredRewardsArray {
print("This is an expired reward: \(expiredReward.id)")
}
Here's a solution that uses Set arithmetic. As a consequence, it's really fast, but it will not preserve the ordering of elements, and it will clobber any duplicates. It runs in linear time (O(n)), whereas a naive approach would be O(n^2).
let unexpiredRewards = Set(rewardsArray).subtract(Set(ExpiredRewards))
for item in expiredRewardsArray {
if let index = rewardsArray.index(of: item) {
//found the item
rewardsArray.remove(at: index)
}
}
This will find the item and delete it from the rewardsArray
UPDATE:
You say that each item has an id. If with the code above the if let block is never called than you can be sure that the items aren't actually the same or you don't have any equal items.
for itemOne in expiredRewardsArray {
for itemTwo in rewardsArray {
if itemOne.id == itemTwo.id {
rewardsArray.remove(at: index)
}
}
}
Not very performant but it does its job and keeps the order
You should really use swift method filter in cases like this. This can be easily solved as this,
func rewardMinusExpired() {
let notExpiredRewards = rewardsArray.filter { a in
return !expiredRewardArray.contains(a)
}
}
If expiredRewardsArray and rewardsArray are arrays of objects, then you'll need to compare the elements using their id property to compare them, rather than indexOf(_:). The reason for this is that objects are reference types, so you can have two different objects with identical properties, but if they're not the same object, indexOf(_:) will treat them as separate entities.
Try this code instead:
func rewardsMinusExpired() {
expiredRewardsArray = rewardManager.getExpiredRewards()
rewardsArray = rewardsArray.filter { reward in
let isExpired = expiredRewardsArray.contains { expiredReward in
return expiredReward.id == reward.id
}
return !isExpired
}
}
I am presently making my first steps using Core Data, with Swift.
Even though I have quite a bit of experience with Core Data, I have close to none with Swift.
Here is what I want to do: make a function which is going to compute the number of records in an entity, from the name of the entity.
Something like this:
func countEntity (name:String) -> Int {
var theNumberOfRecords:Int
let fetchRequest = NSFetchRequest(entityName:name)
// What I have tried at this point only gives error messages …..
return theNumberOfRecords
}
It should not be rocket science. But I have not been able to make something work after various trial and errors.
How should I write the necessary code? I presume it should be no more that a few lines. The one I have in Objective C is 6 lines.
You get the number of objects a fetch request would have returned
with countForFetchRequest():
func countEntity (name:String, context : NSManagedObjectContext) -> Int {
let fetchRequest = NSFetchRequest(entityName:name)
var error : NSError?
let theNumberOfRecords = context.countForFetchRequest(fetchRequest, error: &error)
if theNumberOfRecords == NSNotFound {
println("Error: \(error?.localizedDescription)")
}
return theNumberOfRecords
}
In the case of an error, countForFetchRequest() returns
NSNotFound and you have to decide how to handle that situation.