Swift 2 OSX 如何在包含数据任务的一系列完成的末尾使用循环?

Swift 2 OSX How do I use a loop at the end of a series of completions which includes a data task?

我正在为我的每个函数使用完成块(以避免使用 while isDoingSomething 循环)。

当所有块都完成时,我得到了预期的数组。但是当我尝试遍历这个最终数组时,它会按预期不断循环,但不会在每次迭代中恢复 Request.sendRequest(..) 中的 NSURLSessionDataTask

ViewController.swift

import Cocoa

class ViewController: NSViewController {
    @IBOutlet weak var runButton: NSButton!
    @IBOutlet weak var visitsTextField: NSTextField!

    var accessToken = ""
    var cookies = [NSHTTPCookie]()
    var data = NSData?()
    var userIds = [String]()
    var usernames = [String]()
    var contentsOfURL = NSString()

    @IBAction func runButtonAction(sender: AnyObject) {
        run({
            // if I remove the loop and visit only one profile, it completes OK 
            for username in self.usernames {
                let profileVisitor = ProfileVisitor(profile: username)
                profileVisitor.visit({ 

                })
            }
        })
    }

    func run(completion: () -> Void) {
        let runManager = RunManager(desiredVisits: Int(visitsTextField.stringValue)!)
        runManager.parseJSON({
            self.usernames = runManager.usernames
            completion()
        })
    }
}

RunManger.swift

import Cocoa

class RunManager: NSObject {
    var data = NSData?()
    var desiredVisits = Int()
    var usernames = [String]()
    var userIds = [String]()

    init(desiredVisits: Int) {
        self.desiredVisits = desiredVisits
    }


    func parseJSON(completion: () -> Void) {
        let jsonLimit = 40
        var profileNames = [String]()
        let finalVisits = desiredVisits % jsonLimit
        let repeats = (desiredVisits / jsonLimit) + 1
        let json: [String:NSObject] = [..., "limit":jsonLimit]

        let url = "https://www.awebsite.com/1/path1/path2/path3"
        let URL = NSURL(string: url)!

        let vis = URLVisitor(URL: URL, params: "", method: "POST", jsonParams: json as! [String : NSObject])

        vis.execute({
            for i in 1..<repeats {

                if vis.data != nil {
                    do {
                        let data = vis.data!
                        let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)

                        if let _usernames = json["data"] as? [[String: AnyObject]] {
                            for username in _usernames {
                                if let username = username["username"] as? String {
                                    self.usernames.append(username)
                                }
                            }   
                        }
                    } catch {

                    }
                }   
            }
            completion()
        })
    }
}

URLVisitor

import Cocoa

class URLVisitor: NSOperation {
    var authorizationHeader = ""
    var contentsOfURL = NSString()
    var jsonParams = [String: NSObject]()
    var isConnected = false
    var method = String()
    var params = String()
    var statusCode = Int()
    var cookies = [NSHTTPCookie]()
    var URL: NSURL?
    var isVisiting = false
    var task = NSURLSessionDataTask()
    var data = NSData?()

    init(URL: NSURL, params: String, method: String, jsonParams: [String:NSObject]) {
        self.URL = URL
        self.params = params
        self.method = method
        self.jsonParams = jsonParams
    }

    func execute(completion: () -> Void) {
        let request = Request(URL: URL!, params: params, method: method, jsonParams: jsonParams)

        if !self.cookies.isEmpty {
            request._setCookies(self.cookies)
        }

        request._setAuthorizationHeader(self.authorizationHeader)
        request.sendRequest ({
            self.contentsOfURL = request.contentsOfURL
            self.statusCode = request.getStatusCode()
            self.data = request.data
            completion()
        })
    }
}

请求

import Cocoa

class Request: NSOperation {
    var authorizationHeader = ""
    var contentsOfURL = NSString()
    var data: NSData?
    var jsonParams = [String:NSObject]()
    var isConnected = false
    var method = String()
    var params = String()
    var statusCode = NSHTTPURLResponse().statusCode
    var session = NSURLSession.sharedSession()
    var url = String()
    var URL = NSURL()
    var cookies = [NSHTTPCookie]()

    init(URL: NSURL, params: String, method: String, jsonParams: [String:NSObject]) {
        self.jsonParams = jsonParams
        self.method = method
        self.params = params
        self.URL = URL
    }

    func sendRequest(completion: () -> Void) {
        let session = NSURLSession.sharedSession()
        let request = NSMutableURLRequest(URL: URL)
        request.HTTPMethod = method

        if jsonParams.count != 0 {
            do {
                let jsonData = try NSJSONSerialization.dataWithJSONObject(jsonParams, options: .PrettyPrinted)

                request.setValue("aplication/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
                request.HTTPBody = jsonData
            } catch {

            }
        } else {
            request.HTTPBody = self.params.dataUsingEncoding(NSUTF8StringEncoding)
        }

        let task = session.dataTaskWithRequest(request) {
            (data, response, error) in

            NSHTTPCookieStorage.sharedHTTPCookieStorage().setCookies(self.cookies, forURL: self.URL, mainDocumentURL: nil)
        if data != nil {
            self.data = data!
            print(data)
            do {
                swiftlet responseHeaders = response as! NSHTTPURLResponse
                self.statusCode = responseHeaders.statusCode

                switch self.statusCode {
                case 200:
                    print("200: OK. getting contentsOfURL and cookies")
                    self.contentsOfURL = try NSString(contentsOfURL: self.URL, encoding: NSUTF8StringEncoding)
                    self.cookies = NSHTTPCookieStorage.sharedHTTPCookieStorage().cookiesForURL(self.URL)!

                    case 400:
                        print("400: page not found on web")

                    case 404:
                        print("404: page not found on server")

                    case 407:
                        print("407: failed authenticate proxy credentials")

                    default:
                        print("unable to get statusCode")
                    }
                } catch {

                }
            } else {
                print("\(self.statusCode): unable to get response ")
            }
            print("completing")
            completion() // continue inside request call
        }
        task.resume()
    }

    func _setAuthorizationHeader(authorizationHeader: String) {
        self.authorizationHeader = authorizationHeader
    }

    func _setCookies(cookies: [NSHTTPCookie]) {
        self.cookies = cookies
    }

    func getStatusCode() -> Int {
        return self.statusCode
    }

    func getContentsOfURL() -> NSString {
        return self.contentsOfURL
    }
}

我猜您不打算在请求的 sendRequest 方法中为会话声明局部变量 class。

 let session = NSURLSession.sharedSession()

本地会话变量隐藏了您的 class 的会话成员,并且会在函数完成后立即超出范围(这会丢弃整个会话和任务)。

[编辑] 我刚刚注意到你的局部变量正在使用 sharedSession 所以即使它超出范围,任务也应该保持活动状态,因为会话应该保持对它的强引用(根据文档) .

一定是其他问题。

我最终使用了信号量调度,这成功了。

func sendRequest() { 让信号量 = dispatch_semaphore_create(0)

let task = session.dataTaskWithRequest(request) {
    // ...
    dispatch_semaphore_signal(semaphore)
}
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)

}