Reputation: 5083
In a playground file, I have a function which is supposed to return a array where Lesson is a custom data type. In the playground at the right hand of my screen it does display a array with the values that where supposed to be in the array though if I try to assign the returned array to a variable for further use, the array seems to be empty.
I’ve read around the web that this has to do with NSURLSession.sharedSession() being a asynchronous thread soo my array being returned before the function has gotten al the data.
Code:
//
// CCApp
//
// Created by Milo Cesar on 02-03-15.
// Copyright (c) 2015 Experium. All rights reserved.
//
import Foundation
import XCPlayground
/*
Custom Data Types
Day: Used to easily transition from day to int
Lesson: Used to store all the necessary info to be displayed to the user
*/
//Day Enum
enum Day: Int{
case Maandag = 0, Dinsdag, Woensdag, Donderdag, Vrijdag
}
//Lesson Struct
struct Lesson {
var day:Day
var start:String
var end:String
var lesson:String
var room:String
var teacher:String
var groups:String
var type:String
var isBreak:Bool
var isCanceled:Bool
}
/*
Code from medium.com used to get & parse JSON
source: https://medium.com/swift-programming/learn-nsurlsession-using-swift-ebd80205f87c
*/
func httpGet(request: NSURLRequest!, callback: (String, String?) -> Void) {
var session = NSURLSession.sharedSession()
var task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if error != nil {
callback("", error.localizedDescription)
} else {
var result = NSString(data: data, encoding:
NSASCIIStringEncoding)!
callback(result, nil)
}
}
task.resume()
}
func parseJSON(inputData: NSData) -> Array<AnyObject>{
var error: NSError?
var boardsDictionary = NSJSONSerialization.JSONObjectWithData(inputData, options: NSJSONReadingOptions.MutableContainers, error: &error) as Array<AnyObject>
return boardsDictionary
}
/*
Code from medium.com mixed with own input and adaptations.
*/
var request = NSMutableURLRequest(URL: NSURL(string: "http://api.ccapp.it/v1/student/110919/schedule/10")!)
func loadLesson() -> Array<Lesson>{
var loadedLesson:Array<Lesson> = []
//Get JSON Data using http://api.ccapp.it/v1/student/110919/schedule/10 as JSON location
httpGet(request) {
(data, error) -> Void in
//Check for Errors
if error != nil {
println(error)
} else {
//If there are no errors debug (print data) and transistion from JSON to Lesson Struct
println(data)
loadedLesson = setupLesson(data)
}
}
return loadedLesson
}
func setupLesson(data: String) -> Array<Lesson>{
//Clear Array when we get load a new schedule
var loadedLesson:Array<Lesson> = []
//Encode NSString to NSData
let jsonData = data.dataUsingEncoding(NSASCIIStringEncoding)
//parse NSData and return Array<AnyObject>
//AnyObject is here a Array<NSDictionary>
var jsonArray = parseJSON(jsonData!)
//Loop through the avaidable days in the schedule
for days in 0...jsonArray.count-1{
//Validate the before stated Array<NSDictionary>
if let day: AnyObject = jsonArray[days] as? Array<NSDictionary>{
//I think this line and the previous line should be able to be one line of code but I got no clue how so just cast it as a Array<NSDictionary> (again?)
if let dayArray:Array<NSDictionary> = day as AnyObject? as Array<NSDictionary>? {
//Loop through the lessons of the day
for lessonList in 0...dayArray.count-1{
//Get all values and store them in a local variable for easy access
var day:Day
var start:String = dayArray[lessonList].valueForKey("start") as String
var end:String = dayArray[lessonList].valueForKey("end") as String
var lessonID:String = dayArray[lessonList].valueForKey("lesson") as String
var room:String = dayArray[lessonList].valueForKey("room") as String
var teacher:String = dayArray[lessonList].valueForKey("teacher") as String
var groups:String = dayArray[lessonList].valueForKey("groups") as String
var type:String = dayArray[lessonList].valueForKey("type") as String
var isBreak:Bool = dayArray[lessonList].valueForKey("break") as Bool
var isCanceled:Bool = dayArray[lessonList].valueForKey("canceled") as Bool
//Create a new Lesson Value for easy storage
var lesson = Lesson(day: Day.Maandag, start:start, end:end, lesson:lessonID, room:room, teacher:teacher, groups:groups, type:type, isBreak:isBreak, isCanceled:isCanceled)
//Add the lesson to the array
loadedLesson.append(lesson)
}
}
}
}
//return the now filled array of lessons
return loadedLesson
}
//Receive a empty Array List
var lessonList = loadLesson()
lessonList.isEmpty
XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true)
Upvotes: 1
Views: 2212
Reputation: 5014
You need to put everything that you want to execute after the request finishes into the completion block. You're returning loadedLesson before the request has a chance to finish, so it returns an empty array.
One way to handle this is to call another function when the data is loaded.
For example:
func finishedLoading(array: Array<Lesson>) {
// Do something with the array
}
func loadLesson() {
//Get JSON Data using http://api.ccapp.it/v1/student/110919/schedule/10 as JSON location
httpGet(request) {
(data, error) -> Void in
//Check for Errors
if error != nil {
println(error)
} else {
//If there are no errors debug (print data) and transistion from JSON to Lesson Struct
println(data)
finishedLoading(setupLesson(data))
}
}
}
Side note:
Instead of for lessonList in 0...dayArray.count-1
, you can use ..<
, which will stop iteration at 1 before the end of the array:
for lessonList in 0..<dayArray.count {
// inner loop
}
Upvotes: 1
Reputation: 12853
That's correct: Your HTTP request is asynchronous, which means that the request runs on a background thread while allowing user interface actions and whatever else to continue running unimpeded on the main thread. In order to get your resulting data you can pass a anonymous callback function as a parameter to loadLesson
, that will return your array when the request has been fully processed:
func loadLesson(onComplete: ([Lesson]) -> ()) {
httpGet(request) {
(data, error) -> Void in
if error != nil {
println(error)
} else {
let lessons = setupLesson(data)
onComplete(lessons)
}
}
}
loadLesson { (lessons) in
for lesson in lessons {
println(lesson)
}
}
Now, instead of attempting to access your data right away, the block you passed as a parameter to loadLesson
waits to execute until it is called from within httpGet
.
Upvotes: 3