Code
var promos = [PromoGroup]()
var adsImages = Array<UIImage>()
override func viewDidLoad() {
super.viewDidLoad()
setupData()
//print 1
println(self.promos.count)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func setupData() {
getAllDataPromos { (PromoGroup, error) -> Void in
if error == nil {
self.promos = PromoGroup!
//print 2
println(self.promos.count)
for i in 0...self.promos.count-1 {
self.adsImages.append(UIImage(data: NSData(contentsOfURL: NSURL(string:"https://abs.com/content/bb2.jpg")!)!)!)
}
}
}
}
but adsImage can't show image, but when i try move adsImage to viewDidLoad and also it works, and result println
print 1 result
0
print 2 result
3
count data works if its on getAllDataPromos,
what should i do if i want to show picture in adsImage...
Thanks
(Adapted from this previous answer of mine: https://stackoverflow.com/a/28207368/2274694)
By invoking println(self.promos.count) immediately after the call to setupDataand thus getAllDataPromos contained within that method, you're attempting to print self.promos.count before it's been set in the asynchronous getAllDataPromos block.
An async block performs in the background, so as the code written immediately after your block executes on the main thread, the code within the async block will not yet be complete. That's why println(self.promos.count) prints correctly inside the async block -- because at that point, the code has definitely been executed; but you cannot print the updated value directly outside of the async block as you've done, because the async code hasn't finished executing yet.
Similarly, if you're trying to display an image in an adsImage UIImageView using a value from your adsImages array, you can't set that image until the asynchronous block is complete, so I'd recommend setting it within the async block.
Related
}
Hey guys, I have a problem setting a value for the label. The label should display the number of elements in the array inside my JSON (link - followers_url variable). I call alamo and make a request with that url. When I print the value inside parseData() method I get the right result. When I print it inside configureView() and viewDidLoad() I always get 0.
Setting the label text also works only inside parseData() method. Any ideas how I can get it to work?
Alamofire.request(url).validate().responseJSON { response in
self.parseData(data: response.data!)
}
This above request runs on another background thread.
So when you call the function callAlamo the response is received in the completion block ( { response in ). So when you call print() after callAlamo. the response has not yet been received and print is called so value is not updated. So please perform the operation on the response only through completion block.
If you want to set a label write you set label code after self.parseData in completion block ({response in). Make sure you set it in main queue as the UI operation needs to be performed on main queue only
Following question will help to set label on main thread.
In Swift how to call method with parameters on GCD main thread?
You need to understand multithreading concept to get a better understanding of this. Follow this https://medium.com/#gabriel_lewis/threading-in-swift-simply-explained-5c8dd680b9b2
You should learn something about iOS Parsing techniques. Then learn how to create Model using class or struct. Then you will get Idea.
You should look into Object Mapper as well.
You're dealing with an asynchronous operation. Asynchronous operations are "actions" that are dispatched and require you to wait before they complete. Think about loading a website in Safari. Once you type, let's say, stackoverflow.com in your browser, a loading spinner will notify that something is loading. While the page is loading, you obviously cannot see what's on the webpage. There's only an empty, white page.
The same is happening with your request. When you call the callAlamo function you're telling the app to start loading something. This is requiring you to wait until the task is done. If you count the elements in the followersAndFollowingArray right after the server call, then you'll get it empty, because the request is still waiting to be completed. It's like pretending to view the stackoverflow.com website immediately after having typed the URL. You can't.
That's where closures come in handy. You can use closures to execute something when another action has been completed. In this case, I would fire the web request, display a loading spinner to notify the user that something is loading, and finally populate the followersLabel along with stopping the animation. You can do something like that
func callAlamo(url: String, completion: #escaping ([User]) -> Void) {
if Connectivity.isConnectedToInternet {
Alamofire.request(url).validate().responseJSON { response in
let userData = self.parseData(data: response.data!)
completion(userData)
}
}
}
Additionally you need to let the parseData method to return the parsed array of Users, so the callAlamo function could use it.
func parseData(data : Data) -> [User] {
do {
return try JSONDecoder().decode([User].self, from: data)
} catch let jsonErr {
print("Error serializing", jsonErr)
return [User]()
}
}
Finally, you can execute the callAlamo function on inside the configureView method, performing an action when the server request has been completed. In our case, we want to populate the label.
private func configureView(){
followersLabel.text = String(followers)
// Starting the loading animation
startAnimation()
callAlamo(url: "Hello") { userData in
// Assigning the callAlamo result to your followers array
// once the server request has been completed
self.followersAndFollowingArray = userData
// This will return the number you'd expect
print(self.followersAndFollowingArray.count)
// Stopping the loading animation
stopAnimation()
}
}
Right now you probably won't have the startAnimation and stopAnimation methods, but you can feel free to implement them, I just wanted to give you an idea of a classic implementation.
This question already has answers here:
Waiting for Asynchronous function call to complete
(2 answers)
Closed 5 years ago.
I have a callback function that returns an NSDictionary:
override func viewDidLoad()
{
super.viewDidLoad()
var nd: NSDictionary = [:]
parseJSON(callback: {dict in
nd = dict
//print(nd) This one prints the filled dictionary correctly
})
//print(nd) This one prints an empty dictionary
}
I want to store the values returned from the callback function in "nd", but the print statement outside the callback is still printing an empty NSDictionary.
What am I doing wrong here, and how can I fix it?
EDIT:
var nd: NSDictionary! = nil
override func viewDidLoad()
{
super.viewDidLoad()
loadData()
print(self.nd)
}
func loadData()
{
parseJSON(callback: {dict in
self.nd = dict
print(self.nd)
})
}
I changed it to this, so the function completes its loading, and then prints. However, the print statement in viewDidLoad() prints before the one in loadData(), and the print statement in viewDidLoad() still prints nil. Why does this happen, and what do I need to change specifically?
Looks like the parseJSON runs in the background and might only complete after your viewDidLoad method ends. As such, nd will stay empty immediately after the call.
My suggestion: add your dictionary processing logic inside the aforementioned callback (and then copy the results to a variable outside your viewDidLoadmethod).
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'm learning swift now and having hard time understanding the multithreading issues..
specific problem Im having is that i'm loading data from the internet and
trying to return an array ("broadcasts") containing this data while using dispatch_async .
My problem is that the return execution with the empty array happens before
the array is filled with the data (this line "println(broadcasts)" happens but the array returns empty.. )
Here is my code:
import UIKit
public class BroadcastRequest {
func requestNewBroadcasts() -> [BroadcastModel] {
var broadcasts = [BroadcastModel]()
var sectionsOfBroadcasts = [[BroadcastModel]]()
DataManager.getBroadcastsFrom׳TweeterWithSuccess { (youTubeData) -> Void in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let json = JSON(data: youTubeData)
if let broadcastsArray = json["result"].array {
for broadcastDict in broadcastsArray{
var broadcastID: String? = broadcastDict["id"].string
var broadcastURL: String? = broadcastDict["broadcast"].string
DataManager.getBroadcastDetailsFromYouTubeWithSuccess(broadcastURL!) { (youTubeData) -> Void in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let json2 = JSON(data: youTubeData)
if let broadcastDetailsDict = json2["result"].dictionary {
var cover: String! = broadcastDetailsDict["cover"]!.string
var caption: String! = broadcastDetailsDict["caption"]!.string
var broadcast = BroadcastModel(id: broadcastID, broadcastURL: broadcastURL, cover: cover, caption: caption)
broadcasts.append(broadcast)
**println(broadcasts)**
}
}
}
}
}
}
}
**return broadcasts**
}
}
After looking at answers i have changed the code to this:
import UIKit
public class BroadcastRequest {
func requestNewBroadcasts() {
var broadcasts = [BroadcastModel]()
var sectionsOfBroadcasts = [[BroadcastModel]]()
DataManager.getBroadcastsFrom׳TweeterWithSuccess { (youTubeData) -> Void in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let json = JSON(data: youTubeData)
if let broadcastsArray = json["result"].array {
for broadcastDict in broadcastsArray{
var broadcastID: String? = broadcastDict["id"].string
var broadcastURL: String? = broadcastDict["broadcast"].string
DataManager.getBroadcastDetailsFromYouTubeWithSuccess(broadcastURL!) { (youTubeData) -> Void in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let json2 = JSON(data: youTubeData)
if let broadcastDetailsDict = json2["result"].dictionary {
var cover: String! = broadcastDetailsDict["cover"]!.string
var caption: String! = broadcastDetailsDict["caption"]!.string
var broadcast = BroadcastModel(id: broadcastID, broadcastURL: broadcastURL, cover: cover, caption: caption)
broadcasts.append(broadcast)
println(broadcasts)
}
}
}
}
}
}
}
**TableViewController.requestFinished(broadcasts)**
}
}
And in class TableViewController-
public class TableViewController: UITableViewController {
...
...
public func requestFinished(requestedBroadcasts: [BroadcastModel]) {
self.broadcasts = requestedBroadcasts
self.tableView.reloadData()
}
Now i get the error:
Cannot invoke 'requestFinished' with an argument list of type '([(BroadcastModel)])'
Your code is wrong. A call to a GCD function like dispatch_async returns immediately, before the code in the closure has even begun executing. That's the nature of concurrent programming.
You can't write async code that makes a call and then has the answer on the next line. Instead you need to change your logic so that you make the call to dispatch async and then return and forget about the request until the block completes. Then you put the code that handles the result inside the closure. You can add a call inside your closure that calls out to your app on the main thread to notify it that processing is complete.
EDIT:
Your new code has multiple problems:
Your call to dispatch_async is using the main queue, which is a queue on the main thread. If your goal is to get this code to run in the background, this code fails to do that.
The call to TableViewController.requestFinished(broadcasts) is still in the wrong place. It needs to be inside the block, after the data has been fetched. it also needs to be performed on the main thread.
The call to TableViewController.requestFinished(broadcasts) appears to be sending a message to the TableViewController class rather than to an instance of the TableViewController class, which is wrong. You can't fix that unless your block has access to the instance of TableViewController that you want to send the message to.
You should probably refactor your requestNewBroadcasts method as follows:
Make it not return anything. (The result won't be available until after the async block completes anyway.)
Make requestNewBroadcasts take a completion block (or rather a closure, as they are called in Swift). Get rid of the TableViewController.requestFinished(broadcasts) call entirely, and instead have your network code call the completion block once the network request is complete.
Make your call to dispatch_async use a background queue rather than the main queue so that your task actually runs in the background.
Make your requestNewBroadcasts method invoke the completion block on the main thread.
Each of those steps is going to require work and research on your part.
Figuring out how to add a closure as a parameter will take digging. (See the Swift Programming Language iBook. It explains it well.)
Figuring out how to get a background queue will take work. (See the Xcode docs documentation on GCD. In particular look at dispatch_get_global_queue)
Figuring out how to make a call to the main thread from a background thread will take research (again see the Xcode docs on GCD. Hint: Your current call to dispatch_async is sending your block to a queue on the main thread.).
return broadcasts is executed before the loop that appends data to your array because of DataManager.getBroadcastsFromTweeterWithSuccess's closure.
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.