I've been delving into swift and using the Parse SDK and was wondering if anyone could shed some light on the following:
I am trying to delete an object in the Parse DB, and have set the method up to fail - but it fails to fail.
func destroy(onComplete: Bool -> Void) {
let object = PFObject(className: "ClassName")
object.deleteInBackgroundWithBlock({
(success: Bool, error: NSError?) -> Void in
NSLog("Error: \(error)")
if let error = error {
onComplete(false)
NSLog("Error: \(error)")
} else {
onComplete(success)
}
})
}
When I set the objectId property the object is destroyed fine, but here I am omitting it so, obviously, nothing is destroyed, but when the closure runs, success is always equal to true, and error is always nil.
Does anyone know if this is intended behaviour, because if no object is destroyed, surely, either success should equal false, or error should be non-nil?
Thanks
Paul
Related
I'm using a CKQueryOperation that apparently works fine for most of my users. However, for some of them it is not working. The issue is that queryCompletionBlock is not being called.
Analysing user logs I can see that it works fine for most of the users, but it doesn't work for a few of them. It fails in all kind of iPhone models. But iOS is always iOS 14.2 on the failing devices. Unfortunately, I can not reproduce the issue on my device and this makes impossible to debug it.
I've already checked that the issue is not related with the internet connection type (wifi or data)
Any idea?
This is the code
func fetchTeams(_ success: #escaping (_ result: [CKRecord]?) -> Void,
failure: #escaping (_ error: NSError) -> Void) {
bfprint("fetchTeams starts")
let type = RecordType.Team
let predicate = NSPredicate(format: "TRUEPREDICATE")
let query = CKQuery(recordType: type.rawValue, predicate: predicate)
let operation = CKQueryOperation(query: query)
allTeamsRecords = []
executeQueryOperation(operation,
success: success,
failure: failure)
}
private func executeQueryOperation(_ queryOperation: CKQueryOperation,
success: #escaping (_ result: [CKRecord]?) -> Void,
failure: #escaping (_ error: NSError) -> Void) {
bfprint("executeQueryOperation starts")
let configuration = CKOperation.Configuration()
configuration.qualityOfService = .userInitiated
queryOperation.configuration = configuration
queryOperation.queuePriority = .veryHigh
queryOperation.recordFetchedBlock = { [weak self] (record) in
guard let strongSelf = self else {
bfprint("CloudKitDataProvider was deallocated before we got all team records")
return
}
strongSelf.allTeamsRecords.append(record)
}
queryOperation.queryCompletionBlock = { [weak self] (cursor, error) in
bfprint("fetch teams operation completion block called")
if let cursor = cursor {
bfprint("We got a new cursor fetching teams")
let newOperation = CKQueryOperation(cursor: cursor)
guard let strongSelf = self else {
bfprint("CloudKitDataProvider was deallocated before we got all team records")
return
}
strongSelf.executeQueryOperation(newOperation,
success: success,
failure: failure)
}
else if let error = error {
DispatchQueue.main.async(execute: {
failure(error as NSError)
bfprint("Cloud Query Error - Fetching Teams): \(error)")
})
}
else {
DispatchQueue.main.async(execute: {
bfprint("Get teams finished successfully")
guard let strongSelf = self else {
bfprint("CloudKitDataProvider was deallocated before we execute success closure")
return
}
success(strongSelf.allTeamsRecords)
})
}
}
Self.publicDB.add(queryOperation)
bfprint("query added to database")
}
I don't know what's specifically wrong in your situation, but I might offer some general guidance with CloudKit as I've worked with it over the years. CloudKit is really reliable, but it's also a little unreliable. :)
Here are some tips:
Build in mechanisms to repeatedly check that you have the latest data.
Background notifications don't always come. Have a way to get data that may have been missed.
Development and production behave a little differently in that dev seems to be a bit less reliable overall.
The CloudKit Dashboard needs to be refreshed (like the whole page in your web browser) from time-to-time because the state shown can get stale even when using the reload and query buttons in the interface.
So in your case, you might have a way to repeatedly try the CKQueryOperation so that it gets reattempted if something is haywire on CloudKit. Maintaining a local cache that syncs with CloudKit is the best way I've found to make sure your data is accurate.
I'm 99% sure it was an iOS issue. After users updated to iOS 14.3 the problem disappeared
This question already has answers here:
What does "Fatal error: Unexpectedly found nil while unwrapping an Optional value" mean?
(16 answers)
Closed 6 years ago.
I am trying to download image from Firebase storage.
func downloadThumbnail(thumbnail: String) -> URL {
var thumb: URL!
let _ = DataService.dataService.TAG_PHOTO_REF.child("\(thumbnail)").downloadURL { (thumbnailUrl, error) in
if error != nil {
print(error?.localizedDescription as Any)
} else {
thumb = thumbnailUrl
}
}
return thumb
}
cell.photo.kf.setImage(with: downloadThumbnail(thumbnail: selectedTag.thumbnail))
When I run this code I got
fatal error: unexpectedly found nil while unwrapping an Optional value
with return thumb line.
But if I run only print(thumbnailUrl) instead of return, it prints correct thumbnail url. Can anyone know why I got this error?
Thanks.
You can not guarantee that thumb will never be nil. Because of this, you should not be using !. Because you have no control over it and have not set it manually, you need to make it an optional.
var thumb: URL?
Secondly, you have an internet call. You are returning thumb before you get a response from that call, because of that, thumb is nil, but you told us with the ! that that is impossible, so you crash.
If you put in breakpoints, you should notice that you will hit return thumb on your method before you hit the if error != nil line. You can't use a return for this, because the method will always return before it gets a response from firebase, so your URL will always be nil. I would instead send a URL in a completion.
I haven't checked the firebase code, but if all is right with it, this is the order you want.
So:
func downloadThumbnail(thumbnail: String,withCompletion comp: #escaping (URL?, Error?) -> ()) {
let _ = DataService.dataService.TAG_PHOTO_REF.child("\(thumbnail)").downloadURL { (thumbnailUrl, error) in
if error != nil {
print(error?.localizedDescription as Any)
comp(nil, error)
} else {
comp(thumbnailUrl, nil)
}
}
}
So when you call it somewhere else:
func getMyImage(cell: UITableViewCell) {
downloadThumbnail(thumbnail: selectedTag.thumbnail) { (thumbnailUrl, error) in
if error != nil {
//show some sort of alert for the user here? or do something to handle the error?
} else {
//the url is an optional URL, so check to make sure it isn't nil
if let url = thumbnailUrl {
cell.photo.kf.setImage(with: url)
} else {
//you didn't get an error from your firebase response
//but the thumbnail url it gave you is broken for some reason
//so again, do something about your error here
}
}
}
If this doesn't match the design pattern of your app, let me know. I assumed you were using a tableview and that these methods might be in different classes.
Let us say that I have a method:
func getData(id:Int, completion: (object: object, error: Error?) -> ()){
// some code
let error = ErrorParser.parseData(data!)
if error?.statusCode <= 200 {
// do sth
}
else {
completion(object: object, error: error)
}
}
}
My question is, what happens when I call this method from another class and the completion block is never called (the calling class never gets the block returned)? Is this safe?
It depends on the part you've skipped in // some code.
Let's imagine you are using some 3rd party lib that is performing async request:
func getData(id:Int, completion: (object: object, error: Error?) -> ()){
MyLibrary.doRequest(id: id) { data in
let error = ErrorParser.parseData(data!)
if error?.statusCode <= 200 {
// do sth
}
else {
completion(object: object, error: error)
}
}
}
Here you are passing a new closure (let's name it B) as a parameter to MyLibrary.doRequest method. And your completion parameter is captured by the B closure, and won't be released until this closure is released.
Then MyLibrary stores a strong reference to B closure somewhere and most likely it will release B closure after the request is completed (or failed) and the B closure is executed.
Alternatively you can have some synchronous code here:
func getData(id:Int, completion: (object: object, error: Error?) -> ()){
let data = OtherLib.loadDataFromDisk(id)
let error = ErrorParser.parseData(data!)
if error?.statusCode <= 200 {
// do sth
}
else {
completion(object: object, error: error)
}
}
In this case, your completion block will be released after function returns.
I am using Firebase SDK (2.4.2) for iOS. The following code is part of the didSelectPost() function used in SLComposeServiceViewController object. It's very important to indicate that this behavior is not the same when used in a regular UIViewController (weird I know).
Assuming the following basic code:
override func presentationAnimationDidFinish() {
// Retrieving content and identifying type here
}
override func didSelectPost() {
self.myRootRef.runTransactionBlock({
(currentData:FMutableData!) in
currentData.value = "test"
// Finalizing the transaction
return FTransactionResult.successWithValue(currentData)
}
)
self.extensionContext!.completeRequestReturningItems([], completionHandler: nil)
}
When the transaction is complete node_value disappears completely from Firebase. The value is not set and the node is deleted. This is a very weird and unexpected behavior!
On the other hand, the following code works as expected.
override func didSelectPost() {
self.myRootRef.setValue("test", withCompletionBlock: {
(error:NSError?, ref:Firebase!) in
if (error != nil) {
print("Data could not be saved.")
} else {
print("Data saved successfully!")
}
})
self.extensionContext!.completeRequestReturningItems([], completionHandler: nil)
}
This problem did not exist a couple of days ago, last I ran the code above. Any insights as to what the problem is?
If you set nil to a path in a Firebase database, it will do the same as a .removeValue().
When running a transaction you should check for nil. Since transactions can be called with nil if no default value was written.
myRootRef.runTransactionBlock({
(currentData:FMutableData!) in
var value = currentData.value as? Int
if value == nil {
value = 0
}
currentData.value = value! + 1
return FTransactionResult.successWithValue(currentData)
})
Read the Firebase Docs for more information.
After the recent Swift update, I have been trying to debut a few lines of code and don't seem to be understanding what's wrong..
The lines are
PFGeoPoint.geoPointForCurrentLocationInBackground {
with the error message "Cannot invoke 'geoPointForCurrentLocationInBackground' with an argument list of type '((PFGeoPoint", NSError!) -> Void)'"
The second line is
PFUser.logInWithUsernameInBackground(username:usernameTextField.text, password:passwordTextField.text, target: self) {
With the error "Extra argument 'target' in call"
I've tried looking online and debugging these, but I honestly have no idea what's going on. It seems to be an error in the parse code and I'm not sure why that is...
Edit: I fixed second error I was having. code:
PFUser.logInWithUsernameInBackground(usernameTextField.text, password:passwordTextField.text) {
Start from Swift 1.2, the Failable Casts is introduced. you can use the PFGeoPoint.geoPointForCurrentLocationInBackground method like the following:
If you're quite sure that the downcasting will succeed, you can use as! to force the cast:
PFGeoPoint.geoPointForCurrentLocationInBackground {
(point:PFGeoPoint!, error:NSError!) -> Void in
if (error == nil) {
println(point)
} else {
println(error)
}
}
If you're not sure if the casting will succeed, just use the as? operator. By using as?, it returns an optional value, but in case the downcasting fails, the value will be nil.
PFGeoPoint.geoPointForCurrentLocationInBackground {
(point:PFGeoPoint?, error:NSError!) -> Void in
if (error == nil) {
if let myPoint = point {
println(myPoint)
}
} else {
println(error)
}
}