Can't retrieve CKRecords to resolve conflicts on CKErrorCodeServerRecordChanged in CKModifyRecordsOperation - cloudkit

I ran into some problem when trying to handle errors when doing batch operations on records in CloudKit.
I am successfully extracting the dictionary containing the partial errors, which I can iterate over. However, I am not able to get the records needed to resolve the conflict for CKErrorCodeServerRecordChanged. According to the docs I should be able to get 3 records out of the dictionary:
CKRecordChangedErrorServerRecordKey
CKRecordChangedErrorAncestorRecordKey
CKRecordChangedErrorClientRecordKey
Thank you for any hints on what I am doing wrong here.
func pushRecordChangesForZoneID(recordZoneID: CKRecordZoneID) {
// ...
modifyRecordsOperation.modifyRecordsCompletionBlock = { (savedRecords, deletedRecordIDs, error) -> Void in
if (error != nil) {
if error.code == CKErrorCode.PartialFailure.rawValue {
if let errorDict = error.userInfo?[CKPartialErrorsByItemIDKey] as? [CKRecordID : NSError] {
for (recordID, partialError) in errorDict {
if partialError.code == CKErrorCode.ServerRecordChanged.rawValue {
if let userInfo = partialError.userInfo {
let serverRecord = userInfo[CKRecordChangedErrorServerRecordKey] as? CKRecord
// serverRecord will always be nil
}
}
}
}
}
}
}
}
Additional information: When I print the description of the userInfo dict of the partial error (partialError.userInfo) it doesn’t look like it contains the other CKRecords:
[NSDebugDescription: CKInternalErrorDomain: 2037, NSLocalizedDescription: Error saving record <CKRecordID: 0x7fb41bf7e640; DA39FE08-AB0B-4F07-A42E-F5732B114706:(userData:__defaultOwner__)> to server: Protection data didn't match, NSUnderlyingError: <CKError 0x7fd89a92d370: "Unknown Error" (2037)>]
The description of the source error's dictionary (errorDict) looks like this (and I can successfully get the dictionary containing the recordIDs and partial errors out via CKPartialErrorsByItemIDKey):
[<CKRecordID: 0x7fb5bb88afa0; C1575083-F992-448A-8D77-D62C4A42D696:(userData:__defaultOwner__)>: <CKError 0x7fb5b961c6a0: "Batch Request Failed" (22/2024); server message = "Atomic failure"; uuid = 1E4C0FD5-EC10-4071-B277-102A9F1B0E5E; container ID = "iCloud.net.neverthesamecolor.atsumeru">, <CKRecordID: 0x7fb5bb848ad0; DA39FE08-AB0B-4F07-A42E-F5732B114706:(userData:__defaultOwner__)>: <CKError 0x7fb5b9653060: "Server Record Changed" (14/2037); "Error saving record <CKRecordID: 0x7fb41bd9ca50; DA39FE08-AB0B-4F07-A42E-F5732B114706:(userData:__defaultOwner__)> to server: Protection data didn't match">]

The docs say it's a custom zone feature only.
Edit 21st Jan 2016: It's working for me right now even in the default zone and public database. This is a big change from before.

Related

Ambiguous reference to member 'save(_:completionHandler:)' with CloudKit save attempt

I'm trying to save back to CloudKit after updating a reference list and getting the error on the first line of this code block.
Error: Ambiguous reference to member 'save(_:completionHandler:)'
CKContainer.default().publicCloudDatabase.save(establishment) { [unowned self] record, error in
DispatchQueue.main.async {
if let error = error {
print("error handling to come")
} else {
print("success")
}
}
}
This sits within a function where the user going to follow a given location (Establishment). We're taking the existing establishment, and its record of followers, checking to see if the selected user is in it, and appending them to the list if not (or creating it if the list of followers is null).
Edit, in case helpful
//Both of these are passed in from the prior view controller
var establishment: Establishment?
var loggedInUserID: String?
#objc func addTapped() {
// in here, we want to take the logged in user's ID and append it to the list of people that want to follow this establishment
// which is a CK Record Reference
let userID = CKRecord.ID(recordName: loggedInUserID!)
var establishmentTemp: Establishment? = establishment
var followers: [CKRecord.Reference]? = establishmentTemp?.followers
let reference = CKRecord.Reference(recordID: userID, action: CKRecord_Reference_Action.none)
if followers != nil {
if !followers!.contains(reference) {
establishmentTemp?.followers?.append(reference)
}
} else {
followers = [reference]
establishmentTemp?.followers = followers
establishment = establishmentTemp
}
[this is where the CKContainer.default.....save block pasted at the top of the question comes in]
I've looked through the various posts on 'ambiguous reference' but haven't been able to figure out the source of my issue. tried to explicitly set the types for establisthmentTemp and followers in case that was the issue (based on the solutions to other related posts) but no luck.
Afraid I'm out of ideas as a relatively inexperienced newbie!
Help appreciated.
Documenting the solution that I figured out:
Combination of two issues:
I was trying to save an updated version of a CK Record instead of updating
I was not passing a CK Record to the save() call - but a custom object
(I believe point two was the cause of the 'ambiguous reference to member'
error)
I solved it by replacing the save attempt (first block of code in the question) with:
//first get the record ID for the current establishment that is to be updated
let establishmentRecordID = establishment?.id
//then fetch the item from CK
CKContainer.default().publicCloudDatabase.fetch(withRecordID: establishmentRecordID!) { updatedRecord, error in
if let error = error {
print("error handling to come")
} else {
//then update the 'people' array with the revised one
updatedRecord!.setObject(followers as __CKRecordObjCValue?, forKey: "people")
//then save it
CKContainer.default().publicCloudDatabase.save(updatedRecord!) { savedRecord, error in
}
}
}

CloudKit was Working but Getting a Permission Error All of a Sudden

I had CloudKit working for the last couple weeks and this morning when I was trying to save a record, something I had done a number of times before, I started getting a Permission Error
Error saving to CloudKit: <CKError 0x600001f48ed0: "Permission Failure"
(10/2007); server message = "Operation not permitted"; uuid = 78FA3DD1-
EA44-4701-9A7E-8291F076DD8F; container ID = "[CloudKit Container Name]"> - Error fetching auth tokens from server:
Operation not permitted'
So I have triple checked that it's requesting to the right container, which it is (I only have two containers setup on my CloudKit so it's an easy check.
I have turned off and turned CloudKit on again in XCode as an attempt to force reset it (not sure if that did anything helpful but figured I would give it a shot)
In case this is needed, this is how I am saving to CloudKit (had no problem with this bit of code in the past.
func createRecord(title: String, type: String, comment: String) {
let audioRecord = CKRecord(recordType: "Audio")
audioRecord["title"] = title as CKRecordValue
audioRecord["type"] = type as CKRecordValue
audioRecord["comment"] = comment as CKRecordValue
let audioURL = audioRecorder.getAudioURL()
let audioAsset = CKAsset(fileURL: audioURL)
audioRecord["audio"] = audioAsset
DispatchQueue.main.async {
CKContainer.default().publicCloudDatabase.save(audioRecord) { [self] record, error in
//print(CKContainer.default())
if let error = error {
print("Error saving to CloudKit: \(error.self) - \(error.localizedDescription)")
} else {
print("Record has been successfully saved to CloudKit")
}
}
}
}

Kentico Cloud Swift SDK not returning items

I'm testing out the Kentico Cloud Swift SDK to return some 'article' content types (I have created two of them and they are published).
I am using the Boilerplate code as described here:
The result I get is : [Kentico Cloud] Getting items action has succeeded. Received nil items.
My code:
let client = DeliveryClient.init(projectId: <project id>, previewApiKey: <preview key>, secureApiKey: <secure key>, enableDebugLogging: true)
func getArticles(){
// Note: Using "items" as custom query returns all content items,
// but to map them to a single model, a filter is needed.
let customQuery = "items?system.type=article"
// More about strongly-typed models https://github.com/Kentico/cloud-sdk-swift#using-strongly-typed-models
client.getItems(modelType: Article.self, customQuery: customQuery) { (isSuccess, itemsResponse, error) in
if isSuccess {
// We get here and itemsResponse != nil but items == nil
if let articles = itemsResponse?.items {
for article in articles {
}
}
} else {
if let error = error {
print(error)
}
}
}
}
I believe this error message would appear before ObjectMapper is triggered to convert the JSON into Article objects. I could be wrong though.
Anyone have any ideas?
UPDATE
Interestingly, if I request a single article object like so ...
client.getItem(modelType: Article.self, itemName: <codename>) { (isSuccess, itemResponse, error) in
if isSuccess {
if let article = itemResponse?.item {
// Use your item here
}
} else {
if let error = error {
print(error)
}
}
}
... then it works. I get the Article object. It's just asking for all of the articles that fails.
I'm going to investigate the issue later today, however, from your description, it might be caused by the Delivery API item readiness delay - the project was not fully synced with the delivery API yet. After the publishing/unpublishing item or creating/generating the project, there might be a small delay in processing messages by Delivery API which could cause unavailability of item. This delay might be variable - from my experience, it may vary from a couple of seconds to 2-3 minutes. Nevertheless, I'm going to check it just to be sure. I'll keep you updated.
Edit: I'm pretty sure the project was not synced and processed on the Delivery API at the time you were requested the items. The API returned 200, which caused isSuccess in the callback to be true, however, there might have been none or just a subset of items available - I've reproduced this behavior (screenshot below), although it's by design (the content/messages in Event Hub must be processed asynchronously).
I've also suggested the improvement for Kentico Cloud's documentation to mention/explain the possible delay caused by processing events queue messages from Event Hubs.
Just to be sure - could you try it again with your getArticles custom query?
Edit2: Back to your question about the ObjectMapper. This is not an error just a debug message, however, there shouldn't be probably nil but 0 (zero) in the debug message. This message came from:
private func sendGetItemsRequest<T>(url: String, completionHandler: #escaping (Bool, ItemsResponse<T>?, Error?) -> ()) where T: Mappable {
sessionManager.request(url, headers: self.headers).responseObject { (response: DataResponse<ItemsResponse<T>>) in
switch response.result {
case .success:
if let value = response.result.value {
let deliveryItems = value
if self.isDebugLoggingEnabled {
print("[Kentico Cloud] Getting items action has succeeded. Received \(String(describing: deliveryItems.items?.count)) items.")
}
completionHandler(true, deliveryItems, nil)
}
case .failure(let error):
if self.isDebugLoggingEnabled {
print("[Kentico Cloud] Getting items action has failed. Check requested URL: \(url)")
}
completionHandler(false, nil, error)
}
}
}
Ok. This is very weird. After checking the API by requesting an individual item (see the update in the post above), and getting a result (woot). It now seems the original code (unchanged) now works.
I'm wondering if it takes a while for the data to propagate and be available in the API?
Who knows. Weird.

Workaround for EKParcipant URL accessing crash?

Some of my users have been sent me logs identifying a EXC_BREAKPOINT (SIGTRAP) Error on this line of code. I've been been trying to make it safe but all of the properties of EKParticipant are non optional so comparing to nil just gives me a warning saying it will always be true. If something is nil here how should I handle it?
Error Line
let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "")
Apple Error Description
Trace Trap [EXC_BREAKPOINT // SIGTRAP]
Similar to an Abnormal Exit, this exception is intended to give an
attached debugger the chance to interrupt the process at a specific
point in its execution. You can trigger this exception from your own
code using the __builtin_trap() function. If no debugger is attached,
the process is terminated and a crash report is generated. Lower-level
libraries (e.g, libdispatch) will trap the process upon encountering a
fatal error. Additional information about the error can be found in
the Additional Diagnostic Information section of the crash report, or
in the device's console. Swift code will terminate with this exception
type if an unexpected condition is encountered at runtime such as:
a non-optional type with a nil value
a failed forced type conversion Look at the Backtraces to determine where the unexpected condition was encountered. Additional
information may have also been logged to the device's console. You
should modify the code at the crashing location to gracefully handle
the runtime failure. For example, use Optional Binding instead of
force unwrapping an optional."
Full Method
/**
Parses participants for a given event.
Goes through the EKEvents attendees array to build Attendee objects used to model a participant.
- parameter event: The calendar event we'll be finding the participants for.
- returns: An array of Attendee objects with the participants name, email, required/optional status and whether they've accepted their invitation to the event.
*/
private static func parseParticipantsIn(event: EKEvent) -> [Attendee] {
var participants = [Attendee]()
if let attendees = event.attendees, event.attendees?.isEmpty == false {
for participant in attendees {
let participantName : String? = parse(EKParticipantName: participant)
let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "")
let isRequiredParticipant : Bool = participant.participantRole == EKParticipantRole.required
let hasAccepted : Bool = participant.participantStatus == EKParticipantStatus.accepted
guard (participantName != nil && participantEmail != nil)
else
{
log.error("Participant could not be parsed")
continue
}
let attendee = Attendee(name: participantName!, email: participantEmail!, required: isRequiredParticipant, hasAccepted: hasAccepted)
participants.append(attendee)
}
}
return participants
}
This appears to be a problem with the EKParticipant.url property. Any attempted access of EKParticipant.url causes a crash if you have a participant with the following email field within it.
"Bill Gates" <billgates#google.com>
I'd guess the quotation marks end the String prematurely. It is fine when accessed from EKParticipant.description so I intend to parse it from there.
This is a ridiculous issue, and Deco pinpointed it exactly. I used a different approach to get around it though: Since I'm already working in a mixed code base (obj-c and Swift), I created a class method on one of my obj-c classes that takes an EKParticipant and returns its URL as a string. Then, in Swift, I call that class method to get the URL instead of directly accessing the property (and crashing). It's hacky, but better than crashing and saved me from parsing the description.
This is rather old question but yet I hit this issue myself. My solution is to fallback to ObjC in order to workaround it.
Just add this ObjC functions to swift bridging header file and you are good to use them in swift.
static inline BOOL
participantHasNonNilURL (EKParticipant* _Nonnull participant) {
return participant.URL != nil;
}
static inline NSURL* _Nullable
participantURL(EKParticipant* _Nonnull participant) {
if (participant.URL != nil) {
return participant.URL;
}else {
return nil;
}
}
Example of usage:
extension EKParticipant {
var optionalURL: URL? {
return participantURL(self)
}
var hasURL: Bool {
return participantHasNonNilURL(self)
}
}
This is still an issue on macOS 11.2... I have reported it to Apple. I encourage anyone else hitting this issue to do the same.
The only Swift-only workaround that worked for me is:
extension EKParticipant {
public var safeURL: URL? {
perform(#selector(getter: EKParticipant.url))?.takeUnretainedValue() as? NSURL? as? URL
}
}
Validations are added incorrectly, please check the below response about how the guard could be used.
if let attendees = event.attendees, event.attendees?.isEmpty == false {
for participant in attendees {
guard let participantName : String? = parse(EKParticipantName: participant) else{
log.error("error in participant name")
return
}
guard let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "") else{
log.error("error in participant email")
return
}
let isRequiredParticipant : Bool = participant.participantRole == EKParticipantRole.required
let hasAccepted : Bool = participant.participantStatus == EKParticipantStatus.accepted
/* guard validation is not required here */
if (participantName != nil && participantEmail != nil){
let attendee = Attendee(name: participantName!, email: participantEmail!, required: isRequiredParticipant, hasAccepted: hasAccepted)
participants.append(attendee)
}
}
}
return participants

query if the data is found - Parse & Swift 3

I'm trying to develop an app that provide User sign up and within the UI I want to query if the email is exists or not.
The problem is since I change from swift 2 to swift 3 I got these errors
var query = PFObject(className:"User")
query.whereKey("email", equalTo: email)
query.findObjectsInBackground {
(objects, error) -> Void in
if error == nil {
// The find succeeded.
print("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects {
for object in objects {
print(object.objectId)
}
}
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
/Users/**************/SignUpSenderViewController.swift:49:9:
Value of type 'PFObject' has no member 'whereKey'
/Users/i*************/SignUpSenderViewController.swift:50:9:
Value of type 'PFObject' has no member 'findObjectsInBackground'
any suggestion to solve this challenge ?
I dont know what documentation you checked but the query has to be done this way...
let query = PFQuery(className:"_User")