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)
Related
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.
I have a function that creates a new record in CloudKit. Once that record was successfully created it calls a function called refresh() which will call some other function that queries for the record I just created. However, it says I created the record, but it never goes inside of the next query which is telling me that the new record may not be reaching CloudKit in time before it is being queried for. Incidentally, if I were to look in the database, the record WAS created correctly.
Creating new record function:
func saveCreatedGroup2(){
let Group = CKRecord(recordType: "Group")
print(groupName)
Group.setObject(groupName as CKRecordValue?, forKey: "groupName")
Group.setObject(groupDesc as CKRecordValue?, forKey: "groupDesc")
Group.setObject(publicOrPrivate as CKRecordValue?, forKey: "PublicOrPrivate")
Group.setObject(members as CKRecordValue?, forKey: "memberUsernames")
Group.setObject(leaders as CKRecordValue?, forKey: "leaders")
database.save(Group) { (savedRecord, error) in
if error != nil{
print(error.debugDescription)
}else{
DispatchQueue.main.async{
print("SAVED RECORD, CREATED NEW GROUP!!!!")
// IT DOES SAY THIS^
thisTeamName = selectedCellName
createdNewGroup = false
self.refresh()
}
}
}
}
refresh() function:
func refresh(){
tempThisGroupTuple = []
sortedTupleArray = []
otherTeam = []
otherSortedArray = []
tempOtherTeamTuple = []
canSwitchViews = false
if (canRefresh == false){
print ("ABANDONED")
refreshControl.endRefreshing()
}else{
loadGroupPage()
DispatchQueue.main.async{
self.requestsTableView.reloadData()
self.groupFeedTableView.reloadData()
}
}
}
loadGroupPage() (querying for just created record) function - prints line that is before query, but does not print line that is inside query!:
func loadGroupPage() {
print("loadingf group pagew...\(thisTeamName)") // PRINTS THIS
let pred = NSPredicate(format: "groupName = %#", thisTeamName)
let query = CKQuery(recordType: "Group", predicate: pred)
let operation = CKQueryOperation(query: query)
operation.qualityOfService = .userInteractive
var canJoin = false
var requestVisible = false
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil{
self.OurGroup = record
groupMembs = (record.object(forKey: "memberUsernames") as! Array) // loads group members
print("original groupMembs: \(groupMembs)") // DOES NOT PRINT THIS LINE, SO IT IS TELLING ME IT DID NOT FIND THE RECORD
...(There's more to this function but it's unnecessary)
I also confirmed that when the query begins, the predicate/"thisTeamName" is supposed to be what it is, which is the groupName of the record just created. I am confused on why it is not able to retrieve/query for the record.
Your refresh function calls loadGroupPage() then immediately fires off an async call to reload the tables without waiting for the loadGroupPage function to complete so even if you do get a record I don't think it's going to work properly.
You don't show in the loadGroupPage function where you actually add your request operation to a database. I'm assuming that's in there?
The recordFetchedBlock will fire once for each record returned, you don't need to force unwrap the record (record: CKRecord!). You also don't need to check record != nil since it's guaranteed to be there.
The CKQuery recordFetchedBlock docs say:
Warning
Query indexes are updated asynchronously so they are not guaranteed to be current. If you query for records that you recently changed and not allow enough time for those changes to be processed, the query results may be incorrect. The results may not contain the correct records and the records may be out of order.
I'm not an expert by any means (and haven't used the convenience API) but have built syncing into my app and just from my experience I'd be surprised if the problem is the index hasn't updated.
I would recommend adding in a queryCompletionBlock and check for errors there. You should always be checking for errors when using CloudKit because even when the code is right there will still be times when you get errors (network unavailable, iCloud down, etc.)
I want to append a User object into an array. However, I only want to do so after its data has been downloaded.
var items = [User]()
for id in user_ids {
let user = User(id: id)
user.downloadData({ [weak self] in
items.append(user) //will "user" be THIS instance?
})
}
Ordering is important. I also don't want any complications with duplicates, etc.
Inserting an instance into an array inside the callback of the same instance is making me wonder if I'm doing it correctly.
Am I doing it correctly?
Yes, it's the same user. However, there's no guarantee that each user will finish in the same order with which you started it. Make 2 changes: (1) use a dictionary to store the downloaded data and (2) use Grand Central Dispatch to wait till all of them are downloaded:
// Assume user's ID is an Int
let userData = [Int: User]()
let groupID = dispatch_group_create()
for id in user_ids {
let user = User(id: id)
dispatch_group_enter(groupID)
user.downloadData{ [weak self] in
userData[id] = user
dispatch_group_leave(groupID)
}
}
// Wait until data for all users have been downloaded
dispatch_group_wait(groupID, DISPATCH_TIME_FOREVER)
// Now all users' data have been downloaded. If you want to put them
// into an array according to some order, do this:
let items = user_ids.map { userData[$0]! }
I am having an issue with core data's predicate method, in that it seems to be somewhat inconsistent in the results it is returning. I have a "Users" entity that is populated from a web API, rather than deleting all the entries then downloading and adding the users, I prefer to set a flag for all users "isModified" to false, then download the users over the API and update or add users depending on whether they are in the store or not, setting the "isModified" flag when they are added/updated. Finally the code deletes any users that did not have their "isModified" flag set. I do it this way because in the event that the web API is not available I don't lose all my users and the app can continue to work.
The problem I am having is that the method that deletes users that haven't had their "isModified" flag set is deleting users that HAVE been updated!
Here's the method:
func deleteUsersNotUpdated() -> Bool {
// default result
var result = false
// setup the fetch request
let fetchRequest = NSFetchRequest(entityName: "Users")
// set a predicate that filters only records with updated set to false
fetchRequest.predicate = NSPredicate(format: "isModified == %#", false)
do {
let fetchResults = try self.managedObjectContext.executeFetchRequest(fetchRequest) as! [NSManagedObject]
for user in fetchResults {
print("Deleting \(user)")
self.managedObjectContext.deleteObject(user)
try self.managedObjectContext.save()
}
result = true
} catch let error as NSError {
print("\(error)")
}
return result
}
The method mostly works, but intermittently will delete a user for no good reason, i.e. even though it has the "isModified" flag set, e.g. here is the output of the line: print("Deleting (user)")
Deleting <NSManagedObject: 0x7b6d6ec0> (entity: Users; id: 0x7b64abd0 <x-coredata://1A7606EB-3539-4E85-BE1C-15C722AD7125/Users/p14> ; data: {
created = "2016-01-17 16:54:21 +0000";
familyName = Doe;
givenName = John;
isModified = 1;
pin = 3932;
type = STAFF;
})
As you can see, the "isModified" flag is very definitely set, yet the predicate is supposed to select only records that have the flag reset (false).
The method above is part of a class I have created, it's basically a CRUD class for the Users entity.
Scratching my head here!
I can supply the rest of the code if required.
I think your code looks perfectly fine (although you should save outside the loop). Swift booleans are automatically converted to NSNumber and back in most situations. There are many valid ways to write this predicate.
The only possible explanation that comes to mind is that another class is using the same managed object context to modify the fetched objects. If you have a single context app this a conceivable scenario.
The standard way to avoid this is to make the changes on a background context.
Use an boolean literal rather than a object placeholder %# for a boolean value
NSPredicate(format: "isModified == FALSE")
and don't call context.save() in each iteration of the repeat loop, call it once after the repeat loop.
I'm building an app which allows users to upload / download info from a common store. I thought cloudKit storage would be ideal for this.
I'd like for users to be able to search records in the store by a KeyWord field, then download the entire record when they select one.
In SQL terms this would be like:
SELECT id,KeyWords from myDB WHERE KeyWords LIKE %searchstring%
Followed by:
SELECT * FROM myDB WHERE id = selectedID
I have been using this code pattern to retrieve records from cloudKit storage:
var publicDatabase: CKDatabase?
let container = CKContainer.defaultContainer()
override func viewDidLoad() {
super.viewDidLoad()
publicDatabase = container.publicCloudDatabase
}
func performQuery(){
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Library", predicate: predicate)
publicDatabase?.performQuery(query, inZoneWithID: nil,
completionHandler: ({results, error in
...
[code to handle results / error here]
...
})
}
but this returns all the content of each record which is a lot of unnecessary info.
I'd like to only fetch a single field from the cloudkit records.
Is there an easy way to do this and does anyone have a code snippet?
CKFetchRecordsOperation allows downloading of restricted columns but requires that you know the ids of the records to download.
For this you can use the desiredKeys property on a CKQueryOperation. For more information see: desiredkeys documentation
OK Got something working. This may well be hacky but I put it up for info for anyone else in the same situation...
let predicate = NSPredicate(value: true) // returns all - replace with whatever condition you want
let query = CKQuery(recordType: "Library", predicate: predicate) // create a query using the predicate
var operation = CKQueryOperation(query: query) // create an operation using the query
operation.desiredKeys = ["KeyWords"] // Array of whatever 'columns' you want to return
// operation.resultsLimit = 15 // optional limit on records
// Define a closure for what to do for each returned record
operation.recordFetchedBlock = { [weak self] (record:CKRecord!) in
// Do whatever you want with each returned record
println(record.objectForKey("KeyWords"))
}
// Define a closure for when the operation is complete
operation.queryCompletionBlock = { [weak self] (cursor:CKQueryCursor!, error:NSError!) in
if cursor != nil {
// returns the point where the operation left off if you want t retrieve the rest of the records
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Do what you want when process complete
self!.tableView.reloadData()
if error != nil {
println("there was an error")
}
})
}
self.publicDatabase!.addOperation(operation) // Perform the operation on given database