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)
}
我正在为我的每个函数使用完成块(以避免使用 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)
}