Client received error 1298: This operation has been rate limited error from CloudKit when downloading records with CKQueryOperation, only once, during Apple review. How can I fix this issue?
Here is to code, nothing special:
let query = CKQuery(recordType: "Movie", predicate: NSPredicate(format: "creationDate > %#", d!))
let qo = CKQueryOperation(query: query)
let fb: (CKRecord!) -> () = {record in
temporaryContext.performBlockAndWait({
let fr = NSFetchRequest(entityName: "Movie")
fr.predicate = NSPredicate(format: "recordName = %#", record.recordID.recordName)
let a = temporaryContext.executeFetchRequest(fr, error: nil) as! [Movie]
if a.count == 0 {
let m = NSEntityDescription.insertNewObjectForEntityForName("Movie", inManagedObjectContext: temporaryContext) as! Movie
m.title = record.valueForKey("title") as! String
m.image = (record.valueForKey("image") as! CKAsset).fileURL.description
m.imageSize = Int32(record.valueForKey("imageSize") as! Int)
m.recordName = record.recordID.recordName
}
})
}
let c: ()->() = {
temporaryContext.performBlockAndWait({
let success = temporaryContext.save(nil)
})
Utility.managedObjectContext().performBlockAndWait({
let success = Utility.managedObjectContext().save(nil)
})
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "moviesDownloaded")
NSUserDefaults.standardUserDefaults().synchronize()
dispatch_semaphore_signal(self.sema)
}
let cb: (CKQueryCursor!, NSError!) -> () = {cursor, error in
if error == nil {
if cursor != nil {
let qo2 = Utility.qo(cursor, recordFetchedBlock: fb, completion: c)
publicDatabase.addOperation(qo2)
} else {
c()
}
} else {
Utility.log("error 1298: \(error.localizedDescription)")
dispatch_sync(dispatch_get_main_queue(), {
self.status.backgroundColor = UIColor.orangeColor()
})
NSThread.sleepForTimeInterval(0.5)
dispatch_semaphore_signal(self.sema)
}
}
qo.recordFetchedBlock = fb
qo.queryCompletionBlock = cb
publicDatabase.addOperation(qo)
dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER)
I try to put this whole code into a loop like:
for i in 1 ... 2 {
var rateLimited = false
...
if error == nil {
} else {
NSThread.sleepForTimeInterval(3)
rateLimited = true
}
...
if !rateLimited {
break
}
}
Do you think it will work?
If you get CKErrorRequestRateLimited the error will have a CKErrorRetryAfterKey key in the error's userInfo dictionary. You should wait at least that amount of time before retrying your operation.
Waiting with a sleep is a bad idea because it can cause unexpected hangs in your application, especially if that code runs on your main thread. Use dispatch_after or a NSTimer to re-send your operation.
You will also get this error if you are not logged in to your iCloud account.
Related
This is a method I call to fetch public records:
private func fetchPublicRecordZonesChanges(completion: ErrorHandler?) {
let zone = CKRecordZone.default()
let options = CKFetchRecordZoneChangesOptions()
options.previousServerChangeToken = nil
var records = [CKRecord]()
var recordIDsToDelete = [CKRecordID]()
let operation = CKFetchRecordZoneChangesOperation(recordZoneIDs: [zone.zoneID], optionsByRecordZoneID: [zone.zoneID: options])
operation.database = CloudAssistant.shared.publicDatabase
operation.recordChangedBlock = { record in
records.append(record)
}
operation.recordWithIDWasDeletedBlock = { recordID, string in
recordIDsToDelete.append(recordID)
}
operation.recordZoneChangeTokensUpdatedBlock = { _, token, _ in
if let token = token {
Token.temporaryPublicZoneServerChangeToken = token
}
}
operation.recordZoneFetchCompletionBlock = { [weak self] _, token, _, _, error in
if let error = error, error.isTokenExpiredError {
UserDefaults.remove(forKey: PublicZoneServerChangeTokenKey)
self?.fetchPublicRecordZonesChanges(completion: completion)
return
}
if let token = token {
Token.temporaryPublicZoneServerChangeToken = token
}
}
operation.fetchRecordZoneChangesCompletionBlock = { [weak self] error in
self?.save(records: records, recordIDsToDelete: recordIDsToDelete) { error in
completion?(error)
}
}
operationQueue.addOperation(operation)
}
But nothing is fetched although that method is called, additionally not even once recordChangedBlock closure is called. Why?
I am pretty sure I have a records there:
and environment is also very fine, because private development records are fetched correctly (using of course different method). What am I doing wrong?
To be notified of changes to your Public database, create a CKQuerySubscription for the record type(s) you care about. Here's an example:
let subscription = CKQuerySubscription(
recordType: "Question",
predicate: NSPredicate(value: true),
subscriptionID: "subscriptionQuestion",
options: [
.firesOnRecordCreation,
.firesOnRecordUpdate,
.firesOnRecordDeletion
])
let info = CKNotificationInfo()
info.shouldSendContentAvailable = true
subscription.notificationInfo = info
let operation = CKModifySubscriptionsOperation(subscriptionsToSave: [subscription], subscriptionIDsToDelete: nil)
operation.modifySubscriptionsCompletionBlock = { saved, deleted, error in
if let error = error{
print("Add subscription error: \(error)")
}else{
print("Successfully added Question subscription.")
}
}
//:::
let container = CKContainer(identifier: "...")
container.publicCloudDatabase.add(operation)
i'm trying to download user data from AWS DynamoDB like this:
func downloadUserData(id: String) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
let qe = AWSDynamoDBQueryExpression()
qe.hashKeyAttribute = "id"
qe.hashKeyValues = id
dynamoDBObjectMapper!.query(DDBUser.self, expression: qe).continueWithBlock { (task: AWSTask) -> AnyObject? in
if task.error != nil {
NSLog("🦁🦁🦁\(self)")
NSLog("🦁Fehler beim Laden der Userdaten: \(task.error)")
} else {
let paginatedOutput = task.result as! AWSDynamoDBPaginatedOutput
print("output \(paginatedOutput.items)")
for user in paginatedOutput.items as! [DDBUser] {
SessionController.sharedInstance.user = user
for (index, tacklId) in SessionController.sharedInstance.user.tacklIds.enumerate() {
if tacklId == "empty" {
SessionController.sharedInstance.user.tacklIds.removeAtIndex(index)
}
}
if !(SessionController.sharedInstance.user.endpoints.contains(SessionController.sharedInstance.currEndpoint!)) {
SessionController.sharedInstance.user.endpoints += [SessionController.sharedInstance.currEndpoint!]
}
}
NSLog("User Download Complete")
dispatch_async(dispatch_get_main_queue()) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
self.userdataDelegate?.userdataDownloadComplete!()
}
}
return nil
}
}
Not alway, but most of the time, the Execution stops at this line:
for user in paginatedOutput.items as! [DDBUser] {
With this Error code:
exc_breakpoint (code=1, subcode=0x10015d440)
It even happens with Breakpoints disabled and i can't continue with the Execution.
The line before the break:
print("output \(paginatedOutput.items)")
works well. Could the problem be on another thread and it falsely shows me the error here? How to best debug this? Thank you
You are trying to use
let qe = AWSDynamoDBQueryExpression()
qe.hashKeyAttribute = "id"
qe.hashKeyValues = id
which is deprecated. I would suggest you use 'keyConditionExpression' and 'expressionAttributeValues' instead.
Thanks,
Rohan
i am facing this kind of problem working with CloudKit. Trying to fetch all data from "Data" record. Result is limited by 100. How to get all data? Please, thank for any advice.
func getAllDataFromCloudKit(){
let predicate = NSPredicate(value: true)
let container = CKContainer.defaultContainer()
let privateDatabase = container.privateCloudDatabase
let query = CKQuery(recordType: "Data", predicate: predicate)
privateDatabase.performQuery(query, inZoneWithID: nil) { results, error in
if error != nil {
print(error)
}
else {
for result in results! {
// return only 100 first
}
}
}
}
P.S. i found one similar question, still not clear or answer is too old and does not work with the new Swift version
EDIT: See my final solution how to get all data from private database below:
Ok, i found a solution. See below:
func loadDataFromCloudKit() {
var results: [AnyObject] = []
let cloudContainer = CKContainer.defaultContainer()
let privateDatabase = cloudContainer.privateCloudDatabase
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Data", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
let queryOperation = CKQueryOperation(query: query)
queryOperation.desiredKeys = ["id","name"]
queryOperation.queuePriority = .VeryHigh
// Max limit is still 100
queryOperation.resultsLimit = 100
queryOperation.recordFetchedBlock = { (record:CKRecord!) -> Void in
results.append(record)
}
queryOperation.queryCompletionBlock = { (cursor, error) in
dispatch_async(dispatch_get_main_queue()) {
if (error != nil) {
print("Failed to get data from iCloud - \(error!.localizedDescription)")
} else {
print("Successfully retrieve the data form iCloud")
}
}
// see cursor here, if it is nil than you have no more records
// if it has a value than you have more records to get
if cursor != nil {
print("there is more data to fetch")
let newOperation = CKQueryOperation(cursor: cursor!)
newOperation.recordFetchedBlock = { (record:CKRecord!) -> Void in
results.append(record)
}
newOperation.queryCompletionBlock = queryOperation.queryCompletionBlock
privateDatabase.addOperation(newOperation)
} else {
// gets more then 100
print(results.count)
}
}
privateDatabase.addOperation(queryOperation)
}
Kevin,
You use the cursor returned by CKQueryOperation; it is very much a standard approach in the world of databases; I know some dropbox operations use the same approach for example. Here is the basic code for a CKQueryOperation.
func query4Cloud(theLink: String, theCount: Int) {
var starCount:Int = 0
let container = CKContainer(identifier: "iCloud.ch")
let publicDB = container.publicCloudDatabase
let singleLink2LinkthemALL = CKRecordID(recordName: theLink)
let recordToMatch = CKReference(recordID: singleLink2LinkthemALL, action: .DeleteSelf)
let predicate = NSPredicate(format:"theLink == %#", recordToMatch)
let query = CKQuery(recordType: "Files", predicate: predicate)
// You run the query operation replacing query with your cursor
readerOperation = CKQueryOperation(query: query)
readerOperation.desiredKeys = ["record.recordID.recordName"];
readerOperation.recordFetchedBlock = { (record) in
starCount += 1
}
// see cursor here, if it is nil than you have no more records
// if it has a value than you have more records to get
readerOperation.queryCompletionBlock = {(cursor, error) in
print("fcuk query4Cloud \(theLink) \(theCount) \(starCount)" )
if error != nil {
self.showAlert(message: error!.localizedDescription)
print("ting, busted")
} else {
// it's done
}
}
print("publicDB.addOperation(operation)")
readerOperation.qualityOfService = .Background
publicDB.addOperation(readerOperation)
}
This is my NSBatchUpdateRequest:
let batchUpdateRequest = NSBatchUpdateRequest(entityName: "WLItem")
batchUpdateRequest.predicate = NSPredicate(format: "source != rt")
batchUpdateRequest.propertiesToUpdate = ["position": 2]
batchUpdateRequest.resultType = NSBatchUpdateRequestResultType.UpdatedObjectsCountResultType
do {
let batchUpdateResult = try NSManagedObjectContext.MR_defaultContext().executeRequest(batchUpdateRequest) as! NSBatchUpdateResult
} catch {
print("error: \(error)")
}
output on console is:
error: NilError
What is wrong? Is it a problem with XCode7? I am working with NSBatchUpdateRequest first time, so I do not know whether was it like that before or not. For me that code should just works.
You have to refresh your managedContextObject to update the database. I have created a function in which am using the NSBatchUpdateRequest to update the database. Below is the function:-
let managedContext = SharedInstance.appDelegate?.persistentContainer.viewContext
func updateDataFromTable(tableName:String, idOfPaticularTable ID:String, updatedDict:Dictionary<String, String>, success:#escaping (_ response:Bool)-> Void, failure:#escaping (_ error: Error?) -> Void) {
let batchRequest = NSBatchUpdateRequest(entityName: tableName)
let predicate = NSPredicate(format: "id == %#", ID)
batchRequest.predicate = predicate
batchRequest.propertiesToUpdate = updatedDict
batchRequest.resultType = .updatedObjectIDsResultType
do {
// Execute Batch Request
let batchUpdateResult = try managedContext?.execute(batchRequest) as! NSBatchUpdateResult
// Extract Object IDs
let objectIDs = batchUpdateResult.result as! [NSManagedObjectID]
for objectID in objectIDs {
// Turn Managed Objects into Faults
let managedObject = managedContext?.object(with: objectID)
managedContext?.refresh(managedObject!, mergeChanges: false)
}
success(true)
} catch let error as NSError {
print(error)
failure(error)
}
}
struct SharedInstance {
static let appDelegate = UIApplication.shared.delegate as? AppDelegate
static let employeetable = "EmployeeModel"
}
Same problem that I faced in my project. Below solution working for me. Try this
let batchRequest = NSBatchUpdateRequest(entityName: "Card")
let predicate = NSPredicate(format: "set == %#", set)
batchRequest.predicate = predicate
batchRequest.propertiesToUpdate = [ "count" : 54 ]
batchRequest.resultType = .UpdatedObjectsCountResultType
do {
try self.managedObjectContext.executeRequest(batchRequest) as! NSBatchUpdateResult
} catch _ {
}
I am using the code below to update a record. Once the record has been updated I would like to run my refresh function. At the moment the refresh function is sometimes called before the record has been updated so the refresh results are the same as before the record was updated.
Thanks
var tempDocumentsArray:NSArray!
let recordID = CKRecordID(recordName: "layerAbove")
var predicate = NSPredicate(format: "recordID = %#", recordID)
let query = CKQuery(recordType: "Layers", predicate: predicate)
self.publicDB.performQuery(query, inZoneWithID: nil) { (results, error) -> Void in
tempDocumentsArray = results
print("Results are: \(tempDocumentsArray)")
let record = tempDocumentsArray[0] as! CKRecord
var layerAbovePrevPos = record.objectForKey("layerNumber") as! Int
layerAbovePrevPos = layerAbovePrevPos - 1
let nlnChanged = record.setObject(layerAbovePrevPos, forKey: "layerNumber")
self.publicDB.saveRecord(record, completionHandler: { (returnRecord, error) -> Void in
if let err = error {
print("Error: \(err.localizedDescription)")
} else {
dispatch_async(dispatch_get_main_queue()) {
print("Success")
//TODO:This is sometimes called before the save is complete!
self.resetAndGet()
}
}
})
}