Return 来自异步线程的值

Return value from asynchronous thread

在 playground 文件中,我有一个函数应该 return 一个数组,其中 Lesson 是自定义数据类型。在我屏幕右侧的游乐场中,它确实显示了一个数组,其中包含应该在数组中的值,但如果我尝试将 returned 数组分配给一个变量以供进一步使用,该数组似乎为空。
我在网上读到这与 NSURLSession.sharedSession() 作为异步线程有关,所以我的数组在函数获取所有数据之前被 returned。

代码:

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

您需要将请求完成后要执行的所有内容放入完成块中。您在请求有机会完成之前返回 loadedLesson,因此它 returns 一个空数组。

处理此问题的一种方法是在加载数据时调用另一个函数。

例如:

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

旁注:

您可以使用 ..< 而不是 for lessonList in 0...dayArray.count-1,这将在数组结束前的 1 处停止迭代:

 for lessonList in 0..<dayArray.count {
     // inner loop
 }

正确:您的 HTTP 请求是异步的,这意味着该请求在后台线程上运行,同时允许用户界面操作和其他任何操作在主线程上不受阻碍地继续 运行。为了获得结果数据,您可以将匿名回调函数作为参数传递给 loadLesson,当请求已完全处理后,这将 return 您的数组:

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

现在,您作为参数传递给 loadLesson 的块不会立即尝试访问您的数据,而是等待执行,直到从 httpGet.

中调用它