I have these two functions below, using a completion handler. The questions is highlighted in comments of the 2nd function... why is the result part getting executed even before the asynchronous call in function checforViolationStatus() been completed.
func checkViolationStatus(usr: PFUser, completion: (result: Int32) -> Void) {
var violations: Int32 = 0
var query = PFQuery(className: PF_BLOCKEDUSERS_CLASS_NAME)
query.whereKey(PF_BLOCKEDUSERS_USER, equalTo: usr)
query.countObjectsInBackgroundWithBlock {
(count: Int32, error: NSError?) -> Void in
if error == nil {
print("Result = \(count)")
//The result here returned is 4, I can see it but always ZERO(0) gets printed in the main function. Unable to understand why.
violations = count
}
}
completion(result: violations)
}
func goToMainMenu() {
if PFUser.currentUser() != nil {
self.mCould.checkViolationStatus(PFUser.currentUser()!) {
(result: Int32) in
//QUESTION: result is getting returned as ZERO even before the actual asynchronous call in the checkforViolation function has been completed - why????
if result < 4 {
//Go to Main Menu Screen
print("result<=4 so calling segue")
self.performSegueWithIdentifier("segueLoginVCToMainVC", sender: nil)
} else {
print("result >=4, so should not be doing anything")
}
print("Number of Violations Received Back: \(result)")
}
}
}
Try change your function to this,you should call completion in the countObjectsInBackgroundWithBlock,this method is async.
Or this function return before countObjectsInBackgroundWithBlock is finished
func checkViolationStatus(usr: PFUser, completion: (result: Int32) -> Void) {
var violations: Int32 = 0
var query = PFQuery(className: PF_BLOCKEDUSERS_CLASS_NAME)
query.whereKey(PF_BLOCKEDUSERS_USER, equalTo: usr)
query.countObjectsInBackgroundWithBlock {
(count: Int32, error: NSError?) -> Void in
if error == nil {
print("Result = \(count)")
//The result here returned is 4, I can see it but always ZERO(0) gets printed in the main function. Unable to understand why.
violations = count
completion(result: violations)
}
}
}
Related
I'm working with Modbus protocol and after successfully setup TCP connection, I am able to connect with the hardware and start performing continuous reading operations and everything working fine while the application is in foreground.
As soon as the application moves to background state, all my queries stop working. To verify the same, I added logs but logs were also not showing in application background state.
I am stuck with this issue now and not able to identify the actual root cause. Can anyone help me in this. Below is the code which I'm using:
class DemoFile: NSObject {
var mb: OpaquePointer?
var modbusQueue: DispatchQueue?
var ipAddress: NSString?
init(ipAddress: NSString, port: Int32, device: Int32) {
super.init()
modbusQueue = DispatchQueue(label: "com.iModbus.modbusQueue")
let _ = self.setupTCP(ipAddress: ipAddress, port: port, device: device)
}
func setupTCP(ipAddress: NSString, port: Int32, device: Int32) -> Bool {
self.ipAddress = ipAddress
mb = modbus_new_tcp(ipAddress.cString(using: String.Encoding.ascii.rawValue) , port)
var modbusErrorRecoveryMode = modbus_error_recovery_mode(0)
modbusErrorRecoveryMode = modbus_error_recovery_mode(rawValue: MODBUS_ERROR_RECOVERY_LINK.rawValue | MODBUS_ERROR_RECOVERY_PROTOCOL.rawValue)
modbus_set_error_recovery(mb!, modbusErrorRecoveryMode)
modbus_set_slave(mb!, device)
return true
}
private func buildNSError(errno: Int32, errorString: NSString) -> NSError {
var details: [String: Any] = [:]
details[NSLocalizedDescriptionKey] = errorString
let error = NSError(domain: "Modbus", code: Int(errno), userInfo: details)
return error
}
private func buildNSError(errno: Int32) -> NSError {
let errorString = NSString(utf8String: modbus_strerror(errno))
return self.buildNSError(errno: errno, errorString: errorString!)
}
//Step 1 First I make a connection with my modbus RTU
/**
Function to establish modbus TCP connection with success and error handled
*/
func connect(success: #escaping () -> Void, failure: #escaping (NSError) -> Void) {
modbusQueue?.async {
let ret = modbus_connect(self.mb!)
if ret == -1 {
let error = self.buildNSError(errno: errno)
DispatchQueue.main.async {
failure(error)
}
}
else {
DispatchQueue.main.async {
success()
}
}
}
}
//Step 2 I start perform read operations with my connected Modbus RTU
/**
Function to read register from the start of the address and till (according to the count)
- parameter startaddress: the address of the starting register
- parameter count: the number of consecutive registers to be read
*/
func readRegistersFrom(startAddress: Int32, count: Int32, success: #escaping ([AnyObject]) -> Void, failure: #escaping (NSError) -> Void) {
modbusQueue?.async {
let tab_reg: UnsafeMutablePointer<UInt16> = UnsafeMutablePointer<UInt16>.allocate(capacity: Int(count))
if modbus_read_registers(self.mb!, startAddress, count, tab_reg) >= 0 {
let returnArray: NSMutableArray = NSMutableArray(capacity: Int(count))
for i in 0..<Int(count) {
returnArray.add(Int(tab_reg[i]))
}
DispatchQueue.main.async {
success(returnArray as [AnyObject])
}
}
else {
let error = self.buildNSError(errno: errno)
DispatchQueue.main.async {
failure(error)
}
}
}
}
//Step 3 It will gives me response
//Step 4 With the help of for Loop I am continue reading the values
////Calling the readInput Function for getting continue response
func readModbusData() {
for _ in 0..<2000 {
self.readRegistersFrom(startAddress: 51, count: 1) { (result) in
print("Success: \(result)")
} failure: { (error) in
print("Error: \(error.localizedDescription)")
}
}
}
/*
Problem Statement: When my app goes to active state to background state It's stop the execution of modbus query and also, I am not able to print my communication logs also
*/
}
Ok I don't get this. I have written some code for forward geocoding, I have an UITextField that you write name of a city in and after you press the enter button it is dismissed and at the same time the function is called to determine if the UITextField contains a valid input. If there is an error it is saved in a bool variable which value is changed in the function. I have print statements all over the place and from the console output I can see that the function ran after the if condition, but it is called before... what? Can somebody explain me what is going on? Code:
var locationError: Bool?
func textFieldShouldReturn(textField: UITextField) -> Bool {
self.view.endEditing(true)
forwardGeocoding(textField.text!)
print("forward geocoding ran 1st time")
print(locationError)
if locationError == true {
print("Error")
} else if locationError == false {
print("Success")
} else if locationError == nil {
print("No value for locationError")
}
return false
}
func forwardGeocoding(address: String) -> CLLocation? {
var userLocation: CLLocation?
CLGeocoder().geocodeAddressString(address, completionHandler: { (placemarks, error) in
if error != nil {
print("Geocoding error: \(error)")
self.locationError = true
return
}
if placemarks?.count > 0 {
print("Placemark found")
self.locationError = false
let placemark = placemarks?.first
let location = placemark?.location
let coordinate = location?.coordinate
print("Settings location: \(coordinate!.latitude), \(coordinate!.longitude)")
if let unwrappedCoordinate = coordinate {
let CLReadyLocation: CLLocation = CLLocation(latitude: unwrappedCoordinate.latitude, longitude: unwrappedCoordinate.longitude)
userLocation = CLReadyLocation
}
}
})
return userLocation
}
Console output:
forward geocoding ran 1st time
nil
No value for locationError
Placemark found
Settings location: 48.8567879, 2.3510768
Try multi threading it...
let queue = NSOperationQueue()
queue.addOperationWithBlock() {
NSOperationQueue.mainQueue().addOperationWithBlock() {
}
}
You need to add the completion handler as your function parameter:
func forwardGeocoding(address: String, completionHandler: (placemarks: String? or [Array of any type], error: NSError?) -> ()) -> CLLocation?
modify your if block:
if error != nil {
print("Geocoding error: \(error)")
self.locationError = true
completionHandler(nil, error)
return
}
then call it like
forwardGeocoding(textField.text!){(placemarks, error) in
//your code.
}
When you call geocodeAddressString, it is executed in a separate thread (#AppleDoc This method submits the specified location data to the geocoding server asynchronously and returns). So effectively you have 2 threads running in parallel. geocodeAddressString in thread 2 will take more time to execute since it makes a server call and the block will be executed when the call returns. During this time thread 1 will finish its execution and will print the log statements.
If you want to handle this issue, locationError if-else condition logic should be implemented in such a way that it should be triggered once your callback is executed.
I have a function Admin that runs asynchronously in the background.
Is there a way to make sure that the function is completed before calling the code after it?
(I am using a flag to check the success of the async operation. If the flag is 0, the user is not an admin and should go to the NormalLogin())
#IBAction func LoginAction(sender: UIButton) {
Admin()
if(bool.flag == 0) {
NormalLogin()
}
}
func Admin() {
let userName1 = UserName.text
let userPassword = Password.text
let findTimeLineData2:PFQuery = PFQuery(className: "Admins")
findTimeLineData2.findObjectsInBackgroundWithBlock { (objects: [AnyObject]?, error: NSError?) -> Void in
if !(error != nil){
for object in objects as! [PFObject] {
let userName2 = object.objectForKey("AdminUserName") as! String
let userPassword2 = object.objectForKey("AdminPassword") as! String
if(userName1 == userName2 && userPassword == userPassword2) {
//hes an admin
bool.flag = 1
self.performSegueWithIdentifier("AdminPage", sender: self)
self.UserName.text = ""
self.Password.text = ""
break;
}
}
}
}
}
You need to look into completion handlers and asynchronous programming. Here's an example of an async function that you can copy into a playground:
defining the function
notice the "completion" parameter is actually a function with a type of (Bool)->(). Meaning that the function takes a boolean and returns nothing.
func getBoolValue(number : Int, completion: (result: Bool)->()) {
if number > 5 {
// when your completion function is called you pass in your boolean
completion(result: true)
} else {
completion(result: false)
}
}
calling the function
here getBoolValue runs first, when the completion handler is called (above code) your closure is run with the result you passed in above.
getBoolValue(8) { (result) -> () in
// do stuff with the result
print(result)
}
applying the concept
You could apply this concept to your code by doing this:
#IBAction func LoginAction(sender: UIButton) {
// admin() calls your code, when it hits your completion handler the
// closure {} runs w/ "result" being populated with either true or false
Admin() { (result) in
print("completion result: \(result)") //<--- add this
if result == false {
NormalLogin()
} else {
// I would recommend managing this here.
self.performSegueWithIdentifier("AdminPage", sender: self)
}
}
}
// in your method, you pass a `(Bool)->()` function in as a parameter
func Admin(completion: (result: Bool)->()) {
let userName1 = UserName.text
let userPassword = Password.text
let findTimeLineData2:PFQuery = PFQuery(className: "Admins")
findTimeLineData2.findObjectsInBackgroundWithBlock { (objects: [AnyObject]?, error: NSError?) -> Void in
if !(error != nil){
for object in objects as! [PFObject] {
let userName2 = object.objectForKey("AdminUserName") as! String
let userPassword2 = object.objectForKey("AdminPassword") as! String
if(userName1 == userName2 && userPassword == userPassword2) {
// you want to move this to your calling function
//self.performSegueWithIdentifier("AdminPage", sender: self)
self.UserName.text = ""
self.Password.text = ""
// when your completion handler is hit, your operation is complete
// and you are returned to your calling closure
completion(result: true) // returns true
} else {
completion(result: false) // returns false
}
}
}
}
}
Of course, I'm not able to compile your code to test it, but I think this will work fine.
I am using Parse.com as my backend. I would like to put all functions/code related to accessing Parse.com in a single class that I can call from different ViewControllers.
Problem is that - since many of these functions from Parse.com are asynchronous, how does one return a value from these functions to update the UI?
For example, in the following function I am going and getting Earnings information of the current user. Since this function is using the asynchronous method findObjectsInBackgroundWithBlock from parse.com, I cannot really return anything from this function.
Is there a workaround to this problem? Currently, I am having to place this in function in the same ViewController class. (and hence having to repeat this same function in multiple viewControllers. Would like to have it in a single function only)
Only solution I see is to go to the synchronous method findObjects. Is there any other way?
func getcurrUserEarnings() {
/// Get details of currentUser from Earnings Class
///
/// :param - NSInterval
/// :returns - Int
func loadEarningsInfo() {
if (PFUser.currentUser() != nil) {
var query = PFQuery(className:"Earnings")
query.whereKey("user", equalTo: PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
if let objects = objects as? [PFObject] {
for object in objects {
println(object.objectId)
//WANT TO UPDATE UI HERE WITH THE VALUES THAT WERE JUST RETURNED
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
}
}
You can use callback to pass in something.
For example:
func doSomething(callBack:(String)->())->(){
callBack("abc")
}
doSomething { (str:String) -> () in
println(str)
}
Also, do not forget to update UI on main thread
For example
func loadEarningsInfo(callBack:([PFObject])->()) {
if (PFUser.currentUser() != nil) {
var query = PFQuery(className:"Earnings")
query.whereKey("user", equalTo: PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
if let objects = objects as? [PFObject] {
callBack(objects)
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
}
Then when you use
loadEarningsInfo { (objects:[PFObject]) -> () in
//Update UI with objects
}
You can also handle error in callback as well,I just post a simple example
How can I wait until data is retrieved from parse.com?
This is the function I have that returns an empty string since the response from parse.com is too slow. If I put a breakpoint inside the success area it will break "long" after the data is needed. I guess there is a way to get the data synchronous so it will wait?
func getObjectId(localPersonId:NSString) -> NSString{
var currentObjectId:NSString = ""
var query = PFQuery(className:"myClass")
query.whereKey("personId", equalTo:localPersonId)
query.whereKey("groupId", equalTo:self.currentGroupId)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// should not use a for loop since this should
// only return one row
for object in objects {
currentObjectId = object["objectId"] as NSString
}
} else {
// Log details of the failure
NSLog("Error: %# %#", error, error.userInfo!)
}
}
return currentObjectId
}
In this case the getObjectId function will return an empty string. Anyone?
I realize this is 3 months old but although the Parse docs are incredibly good/useful, there isn't a whole lot out there answering IOS Parse related questions.
This should work. It uses a completion handler, which is a simple way of dealing with this issue.
(for more on completion handlers in asynch context: https://thatthinginswift.com/completion-handlers/ )
func getObjectId(localPersonId:NSString, completionHandler: (currentObjectId: [String]) -> ()){
var currentObjectId:NSString = ""
var query = PFQuery(className:"myClass")
query.whereKey("personId", equalTo:localPersonId)
//query.whereKey("groupId", equalTo:self.currentGroupId)
query.findObjectsInBackgroundWithBlock {
(objects, error) -> Void in
if error == nil {
// should not use a for loop since this should
// only return one row
for object in objects {
completionHandler(currentObjectId: currentObjectId)
}
} else {
// Log details of the failure
NSLog("Error: %# %#", error!, error!.userInfo!)
}
}
}