Can't access value outside of completion handler - swift

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)
}
}

Related

Escaping completion handler that takes optional argument sometimes crashes 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 }

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
})
}

Async issue in call using ObjectiveDropboxOfficial SDK

I'm experiencing an issue with a function that should return an encrypted file from dropbox, but is instead returning the empty dictionary I initialized to receive the data.
I'm almost certain it's a race condition issue since an async call has to be made to the Dropbox API, but so far I have been unable to resolve the issue using GCD. Any help would be most appreciated:
func loadDropboxAccounts() {
let retreiveDataGroup = dispatch_group_create()
var dictionary = [String:String]()
dispatch_group_enter(retreiveDataGroup)
if DropboxClientsManager.authorizedClient() == nil {
DropboxClientsManager.authorizeFromController(UIApplication.sharedApplication(), controller: self, openURL: {(url: NSURL) -> Void in
UIApplication.sharedApplication().openURL(url)
}, browserAuth: true)
}
if let client = DropboxClientsManager.authorizedClient() {
client.filesRoutes.downloadData("/example/example.txt").response({(result: AnyObject?, routeError: AnyObject?, error: DBError?, fileContents: NSData) -> Void in
if (fileContents.length != 0) {
let cipherTextData = fileContents
let plainTextByteArray = CryptoHelper.accountDecrypt(cipherTextData, fileName: "vault")
let plainTextString = plainTextByteArray.reduce("") { $0 + String(UnicodeScalar($1)) }
let plainTextData = dataFromByteArray(plainTextByteArray)
do {
try dictionary = NSJSONSerialization.JSONObjectWithData(plainTextData, options: []) as! [String:String]
for (key, value) in dictionary {
let t = dictionary[key]
print(t)
}
} catch let error as NSError{
print("loadAccountInfo:", error)
}
} else {
print("\(routeError)\n\(error)\n")
}
}).progress({(bytesDownloaded: Int64, totalBytesDownloaded: Int64, totalBytesExpectedToDownload: Int64) -> Void in
print("\(bytesDownloaded)\n\(totalBytesDownloaded)\n\(totalBytesExpectedToDownload)\n")
})
}
dispatch_group_notify(retreiveDataGroup, dispatch_get_main_queue()) {
return dictionary
}
}
Just for reference, this the pod that I am using in the project:
https://github.com/dropbox/dropbox-sdk-obj-c

Alamofire library Void is not convertible to Response<AnyObject, NSError> -> Void

I have a function which sets the JSON results into an NSDictionary. It then uses this value to call a few other functions. I am using Alamofire and since I wrote this app in Swift 1, some this has changed which is giving me errors.
Here is the function:
func fetchApiData() {
// I believe this is the problem code below.
let _requestURL1 = Alamofire.request(.GET,dataSourceURL!)
_requestURL1.responseJSON { (_requestUrl, _requestResponse, _objJSON1, error) -> Void in
if(_objJSON1 != nil)
{
let jsonResult1 = _objJSON1 as NSDictionary;
self.checkIP(jsonResult1)
self.checkGeo(jsonResult1)
}
else{
return
}
}
let _requestURL2 = Alamofire.request(.GET,self.dataSourceURL2!)
_requestURL2.responseJSON { (_requestUrl, _requestResponse, _objJSON2, error) -> Void in
if(_objJSON2 != nil)
{
let jsonResult2 = _objJSON2 as NSDictionary;
self.checkDNS(jsonResult2)
NSTimer.scheduledTimerWithTimeInterval(self.refreshFrequencyInt, target: self, selector: Selector("fetchApiData"), userInfo: nil, repeats: false)
}
else{
return
}
}
}
Apparently now, this line:
_requestURL1.responseJSON { (_requestUrl, _requestResponse, _objJSON1, error) -> Void in
gives me this error:
'(_, _, _, _) -> Void' is not convertible to 'Response<AnyObject, NSError> -> Void'
I have tried the solutions for this issue but I can't seem to get the code working in the same way it was before.
Please help! :)
Try this:
Alamofire.request(.GET, url)
.responseJSON(completionHandler: {
(JSON) -> Void in
let JSONDictonary = JSON.result.value as! NSDictionary
let photoInfos = (JSONDictonary.valueForKey("photos") as! [NSDictionary]).filter({
($0["nsfw"] as! Bool) == false
}).map{
PhotoInfo(id: $0["id"] as! Int, url: $0["image_url"] as! String)
}

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.