perform(_:inZoneWith:completionHandler:) deprecated? or not? iOS 15 - cloudkit

In Xcode 13 beta for iOS 15, I am receiving a message that perform(_:inZoneWith:completionHandler:) (CloudKit) is deprecated in iOS 15 and renamed to fetchRecords(matching:inZoneWith:desiredKeys:resultsLimit:completionHandler:) However...
The Apple Docs website does not declare this method as deprecated: https://developer.apple.com/documentation/cloudkit/ckdatabase/1449127-perform
Apple is showing other deprecations for iOS 15 (another method): https://developer.apple.com/documentation/cloudkit/ckdatabase/3794331-records/
fetchRecords(matching:inZoneWith:desiredKeys:resultsLimit:completionHandler:) does not seem to exist.. yet.. (Value of type 'CKDatabase' has no member 'fetchRecords')
So, is this just an incorrect message because its beta? Should I worry about rewriting a function that uses perform(_:inZoneWith:completionHandler:)?
Here is my function, I've tried to adapt it to fetchRecords, but it does not exist. I tried adapting it to fetch(withQuery:completionHandler: but I'm kind of lost getting it to work..
(This function just deletes records from the iCloud private database):
let container = CKContainer(identifier: "MyContainerNameHere")
let recordType = "DBName"
//delete all saved icloud records
let query = CKQuery(recordType: recordType, predicate: NSPredicate(value: true))
container.privateCloudDatabase.perform(query, inZoneWith: nil) { (rec, err) in
if let err = err {
print(err.localizedDescription)
completion(.failure(err))
return
}
guard let rec = rec else {
completion(.failure(CloudKitHelperError.castFailure))
return
}
for record in rec {
container.privateCloudDatabase.delete(withRecordID: record.recordID) { (recordId, err) in
if let err = err {
print(err.localizedDescription)
completion(.failure(err))
return
}
guard recordId != nil else {
completion(.failure(CloudKitHelperError.recordIDFailure))
return
}
}
}
}
Any insight appreciated..
Thx
Update
I will say, that yes, this seems to be an error or at least a premature message, however, after rewriting the code for async/await, it is much cleaner and easier to read. For those struggling to figure this out, here is an example of the code above converted to Async/Await:
#MainActor func newDeleteCloudKit() async throws {
let container = CKContainer(identifier: "MyContainerNameHere")
let recordType = "DBName"
let query = CKQuery(recordType: recordType, predicate: NSPredicate(value: true))
let result = try await container.privateCloudDatabase.records(matching: query)
for record in result.0 {
try await container.privateCloudDatabase.deleteRecord(withID: record.0)
}
}

I'm in beta 5 and I still get this warning, but the method hasn't been implemented, so it looks like they are not deprecating the old one and just forgot to remove the warning. We should have the final version of Xcode in a few days.
UPDATE: It looks like the made a mistake. The new method is not called fetchedRecords(), it is called records() https://developer.apple.com/documentation/cloudkit/ckdatabase/3856524-records

Related

Why is CloudKit on Apple Watch super slow?

I am using CloudKit to store very simple workout data. The quantity is negligible.
I am using the same code to interact with CloudKit for the iOS app as well as the watchOS app. This is the code I use for loading data:
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: recordType.rawValue, predicate: predicate)
let queryOperation = CKQueryOperation(query: query)
var results: [CKRecord] = []
queryOperation.recordFetchedBlock = { (record: CKRecord ) in
results.append(record)
}
queryOperation.queryCompletionBlock = { (cursor: CKQueryOperation.Cursor?, error: Error?) in
if let error = error {
print("Getting all " + recordType.rawValue + " records with CloudKit was unsuccessful", error)
response(false, nil)
return
}
if cursor == nil {
response(true, results)
return
}
let nextOperation = CKQueryOperation(cursor: cursor!)
nextOperation.recordFetchedBlock = queryOperation.recordFetchedBlock
nextOperation.queryCompletionBlock = queryOperation.queryCompletionBlock
privateDatabase.add(nextOperation)
}
privateDatabase.add(queryOperation)
On iOS the loading is almost instant, on watchOS this can take minutes which is basically unusable. Sporadically the loading speed on watchOS can be decent.
What could be the cause?
Concept
qualityOfService is set to default when you don't assign a configuration.
Assume the watch is low on battery then system decides whether to process the operation immediately or later.
So setting it explicitly might help the system determine how you would like the operation to be handled.
Code
Can you try setting the configuration as follows:
let configuration = CKOperation.Configuration()
configuration.qualityOfService = .userInitiated
queryOperation.configuration = configuration
queryOperation.queuePriority = .veryHigh //Use this wisely, marking everything as very high priority doesn't help

Can't assign variable to Firebase Data within getDocuments querysnapshot (Swift) [duplicate]

This question already has answers here:
Storing asynchronous Cloud Firestore query results in Swift
(1 answer)
How do I save data from cloud firestore to a variable in swift?
(1 answer)
Closed 1 year ago.
I'm trying to set a variable to a boolean based on this code here:
Firestore.firestore().collection("users").whereField("username", isEqualTo: usernameTextField.text!).getDocuments
{ (querySnapshot, error) in
if let error = error { print(error.localizedDescription) /*ALERT*/ }
else
{
if querySnapshot!.isEmpty
{
print("QuerySnapshot is empty, this is unique")
retVal = true
}
else
{
print("There's a bloody username in here, get a new one")
retVal = false
}
}
}
return retVal
The issue is however, retVal is not changed. I understand what's going on here, it's an asynchronous block of code, however I don't understand how to fix it or work around it to fit my needs. How do I get the value of retVal, (or honestly just return true or false within this block of code) despite it being asynchronous. Is there anyway I can like wait for it to finish before further execution of code?
Since your call is asynchronous, you should use a callback or completion handler in your function, this way the retVal will be returned when the Firestore query finishes.
Something like this:
func userIsUnique(completionHandler: (Bool) -> Void) {
Firestore.firestore().collection("users").whereField("username", isEqualTo: usernameTextField.text!).getDocuments
{ (querySnapshot, error) in
if let error = error { print(error.localizedDescription) /*ALERT*/ }
else
{
if querySnapshot!.isEmpty
{
print("QuerySnapshot is empty, this is unique")
completionHandler(true)
}
else
{
print("There's a bloody username in here, get a new one")
completionHandler(false)
}
}
}
}

Get Data from firebase for swift

I have below code to get data from my firebase database
db.collection("users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
print("starting name display")
for document in (querySnapshot?.documents)! {
let documentUserId = document.get("uid") as?String
let temp = document.data()["displayName"]
print(temp)
}
}
}
The print statement displays as optional("test name")
Why am i keep getting optional in my string. Same displays on the screen as well.
You need to un-wrap because it's an Optional. Means it could have a value or it could not have a value. So this is one method to handle it:
let temp = document.data()["displayName"] ?? ""
print(temp)
You could also use if let or guard let statements if you need to handle the cases where the value is actually empty.
Note: Take a look at the basics of swift. There is a separate section for Optionals.

Closures for waiting data from CloudKit

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.

Firestore Query Swift 4.0 Missing Return

I have the following query;
fileprivate func observeQuery() {
guard let query = query else { return }
stopObserving()
listener = query.addSnapshotListener({ [unowned self] (snapshot, error) in
guard let snapshot = snapshot else { return }
let models = snapshot.documents.map({ (document) -> Post in
if let model = Post(dictionary: document.data()) {
return model
} else {
print(error as Any)
}
}) //here
self.posts = models
self.documents = snapshot.documents
})
}
I am getting "Missing return in a closure expected to return 'Post'" mentioned as "//here" in the code. I have return model which is of type Post and I cannot access model after the closure. I have used the GitHub files here;
Firestore GitHub iOS Quickstart
This error doesn't make sense to me can someone please shed some light on the matter?
Many thanks as always.
Your issue is that not all code branches return inside the closure of your map statement. You should change map to flatMap, this way you can also get rid of the if statement by simply returning the failable initializer's result inside your closure, since flatMap will filter out all nil return values.
let models = snapshot.documents.flatMap({ document in Post(dictionary: document.data())})