When the post button is pressed, the function below executes. In the function, all the objects that are retrieved using the Parse backend are appended to the groupConversation array, which is a global array. However, when I reference the array in the UITableViewController that is popped to towards the end of the function and use println() to print the content of the array, the array is empty. However, when I use println() in the UIViewController that contains this function the array is shown to contain one object. In the console, the println() of the UITableViewController that is popped to once the button is pressed, is executed before the println() of the UIViewController that contains the function below. How can I make the functon below execute completely before popping to the UITableViewController.
#IBAction func postButtonPressed(sender: AnyObject) {
//Adds Object To Key
var name=PFObject(className:currentScreen)
name["userPost"] = textView.text
name.saveInBackgroundWithBlock {
(success: Bool!, error: NSError!) -> Void in
if success == true {
self.textView.text=""
} else {
println("TypeMessageViewController Error")
}
}
//Gets all objects of the key
var messageDisplay = PFQuery(className:currentScreen)
messageDisplay.selectKeys(["userPost"])
messageDisplay.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil{
for object in objects {
var textObject = object["userPost"] as String
groupConversation.append(textObject)
}
} else {
// Log details of the failure
}
println("Type message \(groupConversation)")
}
navigationController!.popToViewController(navigationController!.viewControllers[1] as UIViewController, animated: true)
}
The problem is here messageDisplay.findObjectsInBackgroundWithBlock. As you are doing this in background thread, it will be separated from main thread. And your main thread will execute as it should be.
So it before finishing the task you main thread popping the view.
messageDisplay.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil{
for object in objects {
var textObject = object["userPost"] as String
groupConversation.append(textObject)
}
} else {
// Log details of the failure
}
println("Type message \(groupConversation)")
dispatch_async(dispatch_get_main_queue()) {
self.navigationController!.popToViewController(navigationController!.viewControllers[1] as UIViewController, animated: true)
return
}
}
Pushing and popping in background thread may cause problem. So get the main thread after executing the task in background and then pop in main thread.
In swift single statement closures automatically return the statement return value. In your specific case, it's attempting to return an instance of [AnyObject]?, which is the return value of popToViewControllerAnimated. The closure expected by dispatch_afteris Void -> Void instead. Since the closure return type doesn't match, the compiler complains about that.
Hope this helps.. ;)
You are running into a very common issue with asynchronous code. Both your ...InBackgroundWithBlock {} methods run something in the background (async).
The best example I have found to explain it is this:
When you start an async code block, it is like putting eggs on to boil. You also get to include something that should be done when they finish boiling (the block). This might be something like remove the shell and slice the eggs.
If your next bit of code is "butter bread, put eggs on bread" you might get unexpected results. You don't know if the eggs have finished boiling yet, or if the extra tasks (removing shell, slicing) has finished yet.
You have to think in an async way: do this, then when it is finished do this, etc.
In terms of your code, the call to popToViewController() should probably go inside the async block.
Related
I wrote two Swift functions (just a multiple choice quiz app)
func createQuestions() // this goes to Parse, and fetches the questions data that are going to question the users and store them into local arrays
func newQuestion() // this also fetches some other data (for example, some incorrect choices) from Parse and read local variables and finally set the labels to correctly display to the users
I want in ViewDidLoad, first execute createQuestion(), after it is fully completed then run newQuestion(). Otherwise the newQuestion() has some issues when reading from local variables that were supposed to be fetched. How am I going to manage that?
EDIT: I learned to use closure! One more follow up question. I am using a for loop to create questions. However, the problem is that the for loop does not execute orderly. Then my check for repeated function (vocabTestedIndices) fails and it would bring two identical questions. I want the for loop to execute one by one, so the questions created will not be overlapped.
code image
try
override func viewDidLoad() {
super.viewDidLoad()
self.createQuestions { () -> () in
self.newQuestion()
}
}
func createQuestions(handleComplete:(()->())){
// do something
handleComplete() // call it when finished stuff what you want
}
func newQuestion(){
// do other stuff
}
What about swift defer from this post?
func deferExample() {
defer {
print("Leaving scope, time to cleanup!")
}
print("Performing some operation...")
}
// Prints:
// Performing some operation...
// Leaving scope, time to cleanup!
Since you are new. I don't know if you do know closures or not so i have placed simple solution for you. (solution is similar to #Paulw11 commented on your question)
just call in viewDidLoad:
self.createQuestions()
The task you want to perform depends on the Parse response:
only after response arrives you want to call newQuestion function.
Here is the Parse Documentation for swift: https://www.parse.com/docs/ios/guide#objects-retrieving-objects
func createQuestions() {
var query = PFQuery(className:"GameScore")
query.whereKey("playerName", equalTo:"Sean Plott")
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
self.newQuestion()
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
}
func newQuestion() {
//here is your code for new question function
}
Closure will help you to achieve this functionality.
Create your createQuestions function as below.
func createQuestions(completion:((Array<String>) -> ())){
//Create your local array for storing questions
var arrayOfQuestions:Array<String> = []
//Fetch questions from parse and allocate your local array.
arrayOfQuestions.append("Question1")
//Send back your questions array to completion of this closure method with the result of question array.
//You will get this questions array in your viewDidLoad method, from where you called this createQuestions closure method.
completion(arrayOfQuestions)
}
viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//Calling createQuestions closure method.
self.createQuestions { (arrayOfQuestions) -> () in
//Pass your local questions array you received from createQuestions to newQuestion method.
self.newQuestion(arrayOfQuestions)
}
}
New Question Method
func newQuestion(arrayOfQuestions:Array<String>){
//You can check your questions array here and process on it according to your requirement.
print(arrayOfQuestions)
}
I am creating a framework for web services used in my project. I have uploaded template in GitHub. https://github.com/vivinjeganathan/ErrorHandling
It has various layers. Layer 1 for validation. Layer 2 for formation of request. Layer 3 for the actual network call.
View Controller <----> Layer 1 <---> Layer 2 <---> Layer 3
Data flows between layers through closures, if error happens at any layer it needs to be gracefully passed to the ViewController.
I have referred to this link for error handling in async calls - http://appventure.me/2015/06/19/swift-try-catch-asynchronous-closures/
Created a branch in the same repo - name - ErrorHandling-Method1.
I was able to transfer error from layer 3 to layer 2(Single Level - Returning response through functions in closures - as mentioned in the link). But face difficulties in transferring back across multi layers.
Can anyone assist with the sample application provided in public GitHub?
First of all, I don't think it's necessary to stack the layers the way you did, for example, by adding the validation functionality as a layer you are increasing coupling making that layer dependant of the layers below (parsing, networking, etc.), instead, why don't you separate validation to make it only dependant of the data?:
class ViewController: UIViewController {
var validator = InputValidator()
override func viewDidLoad() {
super.viewDidLoad()
do {
try validator.validateInput("INPUT")
try Fetcher.requestDataWithParams("INPUT")
}
catch {
handleError(error)
}
}
}
Now the validation functionality is not dependant of the other layers, so communication would flow like this:
View Controller <---> ParsingLayer <---> NetworkingLayer
I did rename the layers but they are not necessarily have to be like this, you can add or remove layers.
I think is going to be kind of complicated if I try to explain my approach, so I'm going to give an example using the previous layers, first the bottom layer:
class NetworkingLayer {
class func requestData(params: AnyObject, completion: (getResult: () throw -> AnyObject) -> Void) -> Void {
session.dataTaskWithURL(url) { (data, urlResponse, var error) in
if let error = error {
completion(getResult: { throw error })
} else {
completion(getResult: { return data })
}
}
}
}
I have omitted some sections of code, but the idea is to do any necessary step to make the layer work (create session, etc.) and to always communicate back through the completion closure; a layer on top would look like this:
class ParsingLayer {
class func requestObject(params: AnyObject, completion: (getObject: () throw -> CustomObject) -> Void) -> Void {
NetworkingLayer.requestData(params, completion: { (getResult) -> Void in
do {
let data = try getResult()
let object = try self.parseData(data)
completion(getObject: { return object })
}
catch {
completion(getObject: { throw error })
}
})
}
}
Notice that the completion closures are not the same, since every layer adds functionality, the returned object can change, also notice that the code inside the do statement can fail in two ways, first if the network call fails and then if the data from the networking layer cannot be parsed; again the communication to the layer on top is always done through the completion closure.
Finally the ViewController can call the next layer using the closure expected by the Parsing layer in this case, and is able to handle errors originated in any layer:
override func viewDidLoad() {
super.viewDidLoad()
do {
try validator.validateInput("INPUT")
try ParsingLayer.requestObject("INPUT", completion: { (getObject) in
do {
let object = try getObject()
try self.validator.validateOutput(object)
print(object)
}
catch {
self.handleError(error)
}
})
catch {
handleError(error)
}
}
Notice that there is a do catch inside the completion closure, this is necessary since the call is made asynchronously, now that the response has gone through all the layers and have actually change to be of a more specialised type you can even validate the result without having the necessity to make a layer for the validation functionality.
Hope it helps.
Personally I would use notifications passing the NSError as the object of the notification in the layers and observe the notification in the view controller.
In the layers:
NSNotificationCenter.defaultCenter().postNotificationName("ErrorEncounteredNotification", object: error)
In the view controller
NSNotificationCenter.defaultCenter().addObserver(self, selector: "errorEncountered:", name: "ErrorEncounteredNotification", object: nil)
the selector method:
func errorEncountered(notification: NSNotification!) {
let error: NSError! = notification.object as! NSError
NSLog("error: \(error)")
}
You correctly identified a nasty problem with error handling in asynchronous code.
It seems to be easy with synchronous functions - which just return an error code, or have an extra error parameter, or use the new Swift throws syntax. Here is an synchronous function:
func computeSome() throws -> Some
And this is a viable function signature for an asynchronous function:
func computeSomeAsync(completion: (Some?, NSError?) -> ())
The asynchronous function returns Void and does not throw. If it fails, it calls its completion function with the error parameter set.
However, completion handlers become quickly cumbersome, especially in nested code.
The solution is to use a Future:
func computeSomeAsync() -> Future<Some>
This function is asynchronous and does not throw - and returns a Future. So, what's a future?
Well a future represents the eventual result of an asynchronous function. When you call the asynchronous function, it immediately returns and you get a placeholder for the result. This, called a future, will be eventually completed by the underlying background task that computes the value.
When the underlying task eventually succeeded, this future contains the computed value of the function. When it failed it will contain the error.
Depending on the actual implementation and the API of a Future Library, you can obtain the result by registering continuations:
let future = computeSomeAsync()
future.onSuccess { value in
print("Value: \(value)")
}
future.onFailure { error in
print("Error: \(error)")
}
It may look weird at first, but you can do awesome things with futures:
fetchUser(id).flatMap { user in
fetchProfileImage(user.profileImageUrl).flatMap { image in
cacheImage(image)
}
}
.onFailure { error in
print("Something went wrong: \(error)")
}
The above statement is asynchronous - as well as function fetchUser, fetchProfileImage and cacheImage. Error handling included.
Why declare your method throws if you never throw or even try to catch? You could throw the errors using the throwable declaration through all the layers, and even change the throwable type at each level.
UPDATE: Didnt think of throwing dont work in async operations. Using NSNotification is one good route, or you could take a look at RXSwift or similar to solve it too. My personal recommendation would be to use RxSwift. This keeps you out of callback hell, which you are currently travelling down into.
I have a strange issue. The following block of code is placed in my viewDidAppear section of the first View Controller, and when it runs, println(latArray), println(longArray), println(addressArray), all return no value. In the console it returns [] for all three arrays. HOWEVER, when I go to another view controller, and go back, it will populate with the data from Parse. Why is this? Why wont latArray, longArray, and addressArray populate when the app is loaded the first time with the viewWillAppear method?
var query = PFQuery(className: "NewLog")
// Add a where clause if there is a search criteria
query.whereKey("Type", containsString: newRecordCreated)
println(query)
query.findObjectsInBackgroundWithBlock({
(objects, error) -> Void in
if error == nil {
// Results were successfully found
if let objects = objects as? [PFObject] {
for object in objects {
//println(object["follower"])
latArray.append(object["Lat"] as! Double)
longArray.append(object["Long"] as! Double)
addressArray.append(object["Address"] as! String)
}
}
// self.tableView.reloadData()
} else {
println("error")
// The network was inaccessible and we have no cached data for
// this query.
}
})
println(latArray)
println("^^Latitude values")
println(longArray)
println("^^Longitude values")
println(addressArray)
println("^^Address values")
}
The query is being executed in the background. The code in the block (closure) is executed when the query is finished. Note: this might be and most likely is after viewDidAppear finishes. If you put your print statements after your for loop, you should see values. If you uncomment your reloadData method, the table should be updated with new information when the query finishes.
I have a function which will be triggered when a like button is clicked, something like the facebook "like" buttons. I have the first part, and the second part as commented below. The problem is that, when the code runs, there is no sequence in the codes. For example I need the first part to run and then the second part, but sometimes the second part runs first. How can I add a sequence as priority to run?
#IBAction func likeBtn(sender: AnyObject) {
/* first part */
if likeTitle == "Like" {
var likeObj = PFObject(className: "likes")
likeObj["userName"] = PFUser.currentUser()!.username
likeObj["tweetObjectId"] = objectid.text
likeObj.save()
likeBtn.setTitle("Liked", forState: UIControlState.Normal)
}
/* second part */
var likeCount = PFQuery(className: "likes")
likeCount.whereKey("ObjectId", equalTo: objectid.text!)
var likedUsersCount = likeCount.countObjects()
var addLikeCountQuery = PFQuery(className: "comments")
addLikeCountQuery.whereKey("objectId", equalTo: objectid.text!)
addLikeCountQuery.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
for object in objects! {
var ob:PFObject = object as! PFObject
ob["numberOfLikes"] = likedUsersCount
ob.save()
}
}
}
The first part will only run if likeTitle == "Like", so if likeTitle != "Like" then your first part won't run, but the second part will always run.
Another problem could (keyword here is "could" :-)) be that in your first part, you call likeObj.save(). I don't know what your code looks like, but this could be asynchronous, meaning that it'll save whatever needs to be saved in a background thread to not block the main thread, and meanwhile your second part code will continue running on the main thread.
If that's the case, then maybe you should consider some sort of callback to be executed when the save operation is completed, and in that callback, you invoke your second part.
I declare arr1 and arr2 as arrays globally in my viewcontroller.
In the following function, I add to them in a parse query. Xcode gets buggy when I don't include "self" before arr1 inside the query, so I included it. Outside the query, in the space where I have marked below, I try to access arr1 and arr2. But regardless of whether I try self.arr1 or arr1, the array turns out to be empty at that point. I think this is some sort of scope issue with swift, and I've had quite a bit of trouble with it so any help would be much appreciated.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "vF") {
var destVC = segue.destinationViewController as vF
destVC.namePassed = fV
var query = PFQuery(className:"class")
query.whereKey("createdBy", equalTo:fV)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// The find succeeded.
NSLog("Successfully retrieved \(objects.count) records.")
// Do something with the found objects
for object in objects {
self.arr1.append(object["field1"]! as Int)
self.arr2.append(object["field2"]! as String)
}
} else {
// Log details of the failure
}
}
// I want to access arr1 and arr2 right here, but when I do they come up empty
// I have tried accessing with self.arr1 and arr1 but neither works
}
}
The findObjectsInBackgroundWithBlock is async so it will happen in the background thread while your program still running in the main thread, by the point your background thread comeback with the data you need you program is why passed the point you indicate.
One of the best solutions for your problem is to add observers for the arr1 and arr2 so you will be notified when it happens. Add observers to array can be a little bit trick and I want to keep it simple for you, so I would recommend you to create a boolean variable that tells you when the value finish change. To do so you will need to create the variable like this
var arrayDidChange:Bool = false {
didSet{
if arrayDidChange{
callFuncDataReadArrayHere()
}
}
}
Now everytime you change the values (add or edit) in the array you set arrayDidChange to true and in the end of callFuncDataReadArrayHere after do all you need to do you set arrayDidChange to false.
This way you will always be sure you will access the values in the array after it be populate.
I hope that helped you!
You should connect the segue directly from the controller instead of the cell. Implement didSelectRowAtIndexPath, and put the query there, and call performSegueWithIdentifier:sender: inside the completion block of the asynchronous method. Now, when you implement prepareForSegue, the arrays will be available to pass to the destination view controller.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var query = PFQuery(className:"class")
query.whereKey("createdBy", equalTo:fV)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// The find succeeded.
NSLog("Successfully retrieved \(objects.count) records.")
// Do something with the found objects
for object in objects {
self.arr1.append(object["field1"]! as Int)
self.arr2.append(object["field2"]! as String)
}
self.performSegueWithIdentifier("vF", sender: self)
} else {
// Log details of the failure
}
}
}
There will be some delay before the segue actually proceeds with this approach, because it won't happen until findObjectsInBackgroundWithBlock returns its results. If that's not acceptable, then you could still pass the arrays in prepareForSegue (in the same place I show after the "for object in objects" clause), but then the arrays will not be immediately available in the destination view controller, and you will have to deal with that.