Escaping completion handler that takes optional argument sometimes crashes Swift - swift

I have this code:
func function(completion: #escaping (_ error: Error?) -> Void){
getSomethingFromUrl {(result) in
guard let documentData = result.property else {
completion(nil) //crashes with error Thread 1: EXC_BREAKPOINT (code=1, subcode=0x102ba1774)
return
}
}
}
sometimes it crashes on the completion(nil) line, with the error code
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x102ba1774)
I have no idea why it is crashing considering the argument is optional, and I am simply passing nil to it.
Here is the exact code that I am using to call the completion handler. It is in the completion block of a firestore transaction:
let docSizesRef = FirebaseHelper.References.firestoreReference.collection(FirestoreCollections.onlineUsers).document(FirestoreDocuments.documentSizeTracker)
FirebaseHelper.References.firestoreReference.runTransaction({ (transaction, errorPointer) -> Any? in
let docSizesDocument: DocumentSnapshot
do {
try docSizesDocument = transaction.getDocument(docSizesRef)
} catch let fetchError as NSError {
errorPointer?.pointee = fetchError
return nil
}
let oldCount = docSizesDocument.data()?[documentIdToWriteTo] as? Int ?? 0
transaction.updateData([documentIdToWriteTo: oldCount + 1], forDocument: docSizesRef)
return nil
}) { (object, error) in
completion(error)
if let error = error {
print("Transaction failed: \(error)")
} else {
print("Transaction successfully committed!")
}
}
Andhere is the code that deals with completion
func addUserToOnlineDocs(){
User.shared.managers.accountManager.addToOnlineDocs(completion: { (error) in
if let error = error{
self.createTwoButtonAlert(title: AlertErrors.Account.Titles.errorAddingToOnlineList, message: error.localizedDescription, rightButtonText: self.retryButton, leftButtonText: self.ignoreButton, rightButtonStyle: .cancel, leftButtonStyle: .default, completion: { (buttonPressed) in
if buttonPressed == self.retryButton{
connectionSetup()
}
})
return
}
self.loadingCompleteDispatchGroup.leave()
})
}
could it be to do with the transaction calling the completion handler multiple times?

Try to add ;
else { completion(nil); return }

Related

Why does the CKFetchRecordsOperation not work?

This operation does not retrieve anything. The CkFetchRecordsCOmpletion block does not get called? Thanks for your time!
public func getRecord(recordID: CKRecord.ID, completion: #escaping (CKRecord?, CKError?) -> Void) {
let operation = CKFetchRecordsOperation(recordIDs: [recordID])
operation.fetchRecordsCompletionBlock = { (records, error) in
// Checking for potential errors
if let error = error {
completion(nil, error as? CKError)
print(error)
}
if let record = records?[recordID] {
completion(record, nil)
}
}
operation.qualityOfService = .utility
let privateDatabase = CKContainer(identifier: "something").privateCloudDatabase
privateDatabase.add(operation)
}

series of cloudkit callbacks - when to popover to previous controller

I have series of sequential cloud kit calls to fetch records each based on previous fetch. Any one of these fetches may fail and then I have to popover to previous controller. Since there are so many places the fetches can fail, I have to embed popViewController to previous controller in many locations. Can I avoid this and call popover only once if it is possible?
func iCloudSaveMeterHubPrivateDbCz() {
self.clOps.iCloudFetchRecord(recordName: locId, databaseScope: CKDatabaseScope.private, customZone: true, completion: { (locationRecord, error) in
guard error == nil else {
self.navigationController!.popViewController(animated: true)
return
}
self.iCloudFetchMeter(withLocationCKRecord: locationRecord!) { records, error in
if (error != nil ) {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
self.navigationController!.popViewController(animated: true)
}
if let _ = records?.first {
self.clOps.iCloudFetchRecord(recordName: contactId, databaseScope: CKDatabaseScope.private, customZone: true, completion: { (contactRecord, error) in
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
self.navigationController!.popViewController(animated: true)
}
DispatchQueue.main.async {
if let record = contactRecord {
record.setObject("true" as NSString, forKey:"assignedEEP")
}
}
}
self.navigationController!.popViewController(animated: true)
}
}
})
}
Whenever I get into nested callbacks like this, I try to consolidate handling a response to a single point in the code. The popular motto of coders helps in this case: "don't repeat yourself"
Here's a suggestion for consolidating error handling and popping to a single place by making your main function have a closure:
func iCloudSaveMeter(finished: #escaping (_ success: Bool, _ error: CKError?) -> Void){
self.clOps.iCloudFetchRecord(recordName: locId, databaseScope: CKDatabaseScope.private, customZone: true, completion: { (locationRecord, error) in
if error != nil {
//:::
finished(false, error)
}
self.iCloudFetchMeter(withLocationCKRecord: locationRecord!) { records, error in
if error != nil {
//:::
finished(false, error)
}
if let _ = records?.first {
self.clOps.iCloudFetchRecord(recordName: contactId, databaseScope: CKDatabaseScope.private, customZone: true, completion: { contactRecord, error in
//:::
finished(false, error)
DispatchQueue.main.async {
if let record = contactRecord {
record.setObject("true" as NSString, forKey:"assignedEEP")
}
}
}
}
//:::
finished(true, nil)
}
})
}
//----
//Call it like this
iCloudSaveMeter(){ success, error in
//Pop
if !success{
self.navigationController!.popViewController(animated: true)
}
//Hande error
if let error = error{
self.aErrorHandler.handleCkError(ckerror: error)
}
}

Call completionHandler in function after embedded function returns value? Swift

What is the proper way to call a completionHandler() in a function that contains another function? Basically, I want to wait until the embedded function is done before calling the completionHandler.
func somefunc {
transferManager?.download(downloadRequest).continue( {(task: AWSTask) -> AnyObject! in
// Your handler code here
if (task.error != nil) {
print("- Error while downloading!")
print(task.error)
}
else if (task.result != nil) {
//let downloadOutput: AWSS3TransferManagerDownloadOutput = task.result as! AWSS3TransferManagerDownloadOutput
do {
let dFile = try NSString(contentsOf: downloadingFileURL as URL, encoding: String.Encoding.utf8.rawValue)
print(dFile)
modelsParseCSV(contentsOfURL: downloadingFileURL)
}
catch {
print("- Error: Unable to retrieve contents of csv file")
}
}
else {
print("- Uknown error: AWSS3 get file")
}
print("------------ AWS Get Models File End ----------")
return nil
})
completionHandler()
}
I was able to solve my own problem. The following code calls the completion handler and after the embedded function is complete:
func AWS_getModelsFile(AWSMake: String, completionHandler: (() -> Void)! ) {
.........
transferManager?.download(downloadRequest).continue( {(task: AWSTask) -> AnyObject! in
// Your handler code here
if (task.error != nil) {
print("- Error while downloading!")
print(task.error)
}
else if (task.result != nil) {
//let downloadOutput: AWSS3TransferManagerDownloadOutput = task.result as! AWSS3TransferManagerDownloadOutput
do {
let dFile = try NSString(contentsOf: downloadingFileURL as URL, encoding: String.Encoding.utf8.rawValue)
print(dFile)
modelsParseCSV(contentsOfURL: downloadingFileURL)
}
catch {
print("- Error: Unable to retrieve contents of csv file")
}
}
else {
print("- Uknown error: AWSS3 get file")
}
print("------------ AWS Get Models File End ----------")
completionHandler()
return nil
})
}

Can't access value outside of completion handler

The following code gives me an error as 'return self.myID' should be at the last curly bracket. Although if I do that, it will claim self.myID as an unresolved identifier since it is outside of the closure. How can I make it so that I can access self.myID outside of the completion handler and place the return value.
func chatPartnerId() -> String? {
nextrequest.startWithCompletionHandler { (connection: FBSDKGraphRequestConnection! , result: AnyObject!, error: NSError!) -> Void in
self.myID = result["id"] as! String
return self.myID
}
}
Instead of returning a String use a completion handler:
func chatPartnerId(completion: (result: AnyObject?, error: NSError?)->()) {
nextrequest.startWithCompletionHandler { (connection: FBSDKGraphRequestConnection! , result: AnyObject!, error: NSError!) -> Void in
if((error) != nil){
completion(result:nil,error:error)
}
else{
self.myID = result["id"] as! String
completion(result:self.myID, error:nil)
}
}
}
Call it as
chatPartnerId() { (result,error) -> Void in
if let error = error{
print(error)
}
if result != nil {
print(result)
}
}

Can't get throws to work with function with completion handler

I'm trying to add a throws to my existing function with a completion handler but I keep getting a warning saying no calls throwing functions occur within try expression. In the section where I throw the errors, I get an error saying
invalid conversion from throwing function of type '() throwing -> Void' to non-throwing function type.
enum LoginError: ErrorType {
case Invalid_Credentials
case Unable_To_Access_Login
case User_Not_Found
}
#IBAction func loginPressed(sender: AnyObject) {
do{
try self.login3(dict, completion: { (result) -> Void in
if (result == true)
{
self.performSegueWithIdentifier("loginSegue", sender: nil)
}
})
}
catch LoginError.User_Not_Found
{
//deal with it
}
catch LoginError.Unable_To_Access_Login
{
//deal with it
}
catch LoginError.Invalid_Credentials
{
//deal with it
}
catch
{
print("i dunno")
}
}
func login3(params:[String: String], completion: (result:Bool) throws -> Void)
{
//Request set up
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves) as? NSDictionary
if let parseJSON = json
{
let userID = parseJSON["user_id"] as? Int
let loginError = parseJSON["user_not_found"] as? String
let validationError = parseJSON["invalid_credentials"] as? String
let exception = parseJSON["unable_to_access_login"] as? String
var responseArray = [(parseJSON["user_id"] as? Int)]
if userID != nil
{
dispatch_async(dispatch_get_main_queue()) {
completion(result:true)
}
}
else if loginError != ""
{
dispatch_async(dispatch_get_main_queue()){
completion(result: false)
self.loginErrorLabel.text = loginError
throw LoginError.User_Not_Found
}
}
else if validationError != ""
{
dispatch_async(dispatch_get_main_queue()){
completion(result:false)
self.validationErrorLabel.text = validationError
throw LoginError.Invalid_Credentials
}
}
else if exception != nil
{
dispatch_async(dispatch_get_main_queue()){
completion(result:false)
self.exceptionErrorLabel.text = "Unable to login"
throw LoginError.Unable_To_Access_Login
}
}
}
else
{
}
}
catch let parseError {
// Log the error thrown by `JSONObjectWithData`
})
task.resume()
}
What you can do is encapsulating the error into a throwable closure like in the following code to achieve what you want:
func login3(params:[String: String], completion: (inner: () throws -> Bool) -> ()) {
let task = session.dataTaskWithRequest(request, completionHandler: { data, response, error -> Void in
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves) as? NSDictionary
if let parseJSON = json {
let userID = parseJSON["user_id"] as? Int
let loginError = parseJSON["user_not_found"] as? String
let validationError = parseJSON["invalid_credentials"] as? String
let exception = parseJSON["unable_to_access_login"] as? String
var responseArray = [(parseJSON["user_id"] as? Int)]
if userID != nil {
dispatch_async(dispatch_get_main_queue()) {
completion(inner: { return true })
}
}
else if loginError != ""
{
dispatch_async(dispatch_get_main_queue()) {
self.loginErrorLabel.text = loginError
completion(inner: { throw LoginError.User_Not_Found })
}
}
else if validationError != ""
{
dispatch_async(dispatch_get_main_queue()) {
self.validationErrorLabel.text = validationError
completion(inner: {throw LoginError.Invalid_Credentials})
}
}
else if exception != nil
{
dispatch_async(dispatch_get_main_queue()){
self.exceptionErrorLabel.text = "Unable to login"
completion(inner: {throw LoginError.Unable_To_Access_Login})
}
}
}
else
{
}
}
task.resume()
}
And the you can call it like in the following way:
self.login3(dict) { (inner: () throws -> Bool) -> Void in
do {
let result = try inner()
self.performSegueWithIdentifier("loginSegue", sender: nil)
} catch let error {
print(error)
}
}
The trick is that the login3 function takes an additional closure called 'inner' of the type () throws -> Bool. This closure will either provide the result of the computation, or it will throw. The closure itself is being constructed during the computation by one of two means:
In case of an error: inner: {throw error}
In case of success: inner: {return result}
I strongly recommend you an excellent article about using try/catch in async calls Using try / catch in Swift with asynchronous closures
I hope this help you.
You're asking for X and I'm answering Y, but just in case...
There's always the possibility to add the throwing capability to your function instead of the completion handler:
func login3(params:[String: String], completion: (result:Bool) -> Void) throws {
...
}
Then you can call it from inside IBAction:
do {
try self.login3(dict) { result -> Void in
...
}
} catch {
print(error)
}
Read the below with a grain of salt. I haven't worked much with Swift 2.0 yet:
You created a "dataTask" task who's's completion handler has a throw in it, but the only actual code in your login3 method is task.resume(). The completion handler won't get executed until after login3 returns. (In fact, it's a parameter to another object, so the compiler has no idea what's going to happen with that code.)
As I understand it, the actual top-to-bottom body of your login3 method must contain a throw. Since it's an async method, you can't do that. Thus, don't make your login3 function throw. Instead have it pass an error object to it's completion handler.
In my impression this is caused by the signature of your functions. In #IBAction func loginPressed(sender: AnyObject) you don't have to use try when calling login3 as the function itself is not marked as throwing but the completion handler is. But in fact the completion closure of login3 will never throw as you execute all throwing functions inside a do {} catch {} block, so you could try to remove the throws annotation from the login3 completion closure and also call the closure if you catch an error in login3 with an according result. This way you handle all throwing functions inside of login3 in a do {} catch {} block and call the completion handler with a suitable value.
Generally I am also not aware that you can catch without a preceding do {} block like you did in the #IBAction.