I'm trying to create a Simple HTTP Framework.
At first I have the following typealias:
typealias successBock = ([Any]) -> ()
typealias errorBlock = (NSError) -> ()
typealias requestResponseTuple = (con: NSURLConnection, success: successBock?, error: errorBlock?)
And I have an error in this method:
func performHttpRequest<T>(method: HTTPRequestMethod, path: String?, parameter:Dictionary<String, String>,
success:successBock?, error:errorBlock?) -> Int {
var url = NSURL(string: baseURL.stringByAppendingPathComponent((path != nil) ? path! : ""))
var request = NSURLRequest(URL: url!, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData, timeoutInterval: 20.0)
var con = NSURLConnection(request: request, delegate: self, startImmediately: true)
var requestId = newRequestId()
currentActiveRequests[requestId] = requestResponseTuple(con, successBock.self, errorBlock.self)
return requestId;
}
The error goes here:
requestResponseTuple(con, success, errorBlock.self)
Error: "'successBock.Type' is not convertible to 'successBock'"
I want to pass the block, so that i'm able to call it later. In objective-c i never had a problem like that.
To be honest, i have no idea why this occurs. I checked several pages, but didn't found a solution.
Best regards,
Maik
edit2:
changed one method:
func performHttpRequest<T>(method: HTTPRequestMethod, path: String?, parameter:Dictionary<String, String>,
success:successBock?, error:errorBlock?) -> Int {
var url = NSURL(string: baseURL.stringByAppendingPathComponent((path != nil) ? path! : ""))
var request = NSURLRequest(URL: url!, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData, timeoutInterval: 20.0)
var con = NSURLConnection(request: request, delegate: self, startImmediately: true)
var requestId = newRequestId()
currentActiveRequests[requestId] = requestResponseTuple(con, success, error)
return requestId;
}
Error now: Could not find an overload for 'subscript' that accepts the supplied arguments
Edit:
CodeLink:
http://pastebin.com/c5a0fn1N http://pastebin.com/d0QGQ2AR
Ok if i understand well you just have to change
requestResponseTuple(con, successBock.self, errorBlock.self)
by
requestResponseTuple(con!, success, error)
Your parameter Names are success and error. successBock and errorBlock are the types.
So i Suggest you to capitalize them.
Hope it helps you.
edit: Since NSURLConnection returns an optional, you have to unwrap it.
You should check if con is equal to nil before unwrapping it.
if let con = NSURLConnection(request: request, delegate: self, startImmediately: true)
{
currentActiveRequests[requestId] = RequestResponseTuple(con, success, error)
}
else
{
// there was an error, the NSURLConnection has not been created
}
Related
I have been skimming the StackOverflow questions trying to figure out where I'm going wrong with my code, but I just can't seem to! I am trying to convert my Swift 1.2 project to Swift 2.0, and am having an issue with my class that downloads JSON data.
I am continually receiving the error Unexpected non-void return value in void function.
Here is the code, somewhat truncated, that I am using;
...
class func fetchMinionData() -> [Minion] {
var myURL = "http://myurl/test.json"
let dataURL = NSURL(string: myURL)
let request = NSURLRequest(URL: dataURL!, cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 5.0)
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
let minionJSON = JSON(data!)
var minions = [Minion]()
for (_, minionDictionary) in minionJSON {
minions.append(Minion(minionDetails: minionDictionary))
}
return minions
//THIS IS WHERE THE ERROR OCCURS
}).resume()
}
...
Perhaps I am overlooking something simple, but I'm unsure why my function would be considered void at all. Any thoughts would be immensely appreciated! Thank you!
You have a problem because your line:
return minions
does not return from your function. Instead, it returns from the completion handler in dataTaskWithRequest. And it shouldn't be doing so because that closure is a void function.
The problem which you have results from the fact that dataTaskWithRequest is an asynchronous operation. Which means that it can return later after executing your function.
So, you need to change your design pattern.
One way of doing that would be the following:
static var minions:[Minion] = [] {
didSet {
NSNotificationCenter.defaultCenter().postNotificationName("minionsFetched", object: nil)
}
}
class func fetchMinionData() {
var myURL = "http://myurl/test.json"
let dataURL = NSURL(string: myURL)
let request = NSURLRequest(URL: dataURL!, cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 5.0)
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
let minionJSON = JSON(data!)
var minions = [Minion]()
for (_, minionDictionary) in minionJSON {
minions.append(Minion(minionDetails: minionDictionary))
}
self.minions = minions
//THIS IS WHERE THE ERROR OCCURS
}).resume()
}
Then before calling your function you should register to listen for NSNotification with name "minionsFetched". And only after you get that notification you should process the minions as if they were fetched.
I fixed mine by creating a completion handler. You can do this instead of using notifications:
class func fetchMinionData(completionHandler: (minions: [Minion]) -> Void) {
var myURL = "http://myurl/test.json"
let dataURL = NSURL(string: myURL)
let request = NSURLRequest(URL: dataURL!, cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 5.0)
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
let minionJSON = JSON(data!)
var minions = [Minion]()
for (_, minionDictionary) in minionJSON {
minions.append(Minion(minionDetails: minionDictionary))
}
completionHandler(minions: minions)
//THIS IS WHERE YOUR PREVIOUS ERROR OCCURRED
}).resume()
}
I am using URLSession's dataTask method with a completion handler. The error in response is nil, but the data object returns something, it returns 0 bytes of data.
I was using Alamofire library firstly, I thought there is something wrong with it because I started using newer version so I stated using my own implementation of Alamofire just so I don't have to rewrite all the calls I already have in my app.
It still returns 0 bytes of data.
When I use the same URL in the Playground with a simple URLSession call, it works and returns the data, do you have any idea what might go wrong?
My implementation of Alamofire (Srsly quick and dirty within 30 minutes):
import Foundation
typealias DataResponse = (data: Data?, response: URLResponse?, error: Error?, request: URLRequest?, result: Result)
public class Alamofire: NSObject {
enum Method: String {
case get = "GET"
case post = "POST"
}
fileprivate static let session = URLSession(configuration: URLSessionConfiguration.default)
public struct request {
var request: URLRequest? = nil
var url: URL? = nil
let method: Alamofire.Method?
let parameters: Parameters?
let headers: Headers?
init(_ request: URLRequest, method: Alamofire.Method? = nil, parameters: Parameters? = nil, headers: Headers? = nil) {
self.request = request
self.url = request.url
self.method = method
self.parameters = parameters
self.headers = headers
}
init(_ url: URL, method: Alamofire.Method? = nil, parameters: Parameters? = nil, headers: Headers? = nil) {
self.url = url
self.request = URLRequest(url: url)
self.method = method
self.parameters = parameters
self.headers = headers
}
}
}
typealias Parameters = [String: Any?]
typealias Headers = [String: String]
extension Alamofire.request {
func responseData(completionHandler: #escaping (_ dataResponse: DataResponse) -> Void) {
guard let request = request else { return }
Alamofire.session.dataTask(with: request) { (data, response, error) in
let result: Result
if let error = error {
result = Result.failure(error)
completionHandler((data, response, error, request, result))
return
}
completionHandler((data, response, error, request, Result.success))
}.resume()
}
}
enum Result {
case success
case failure(Error)
}
So the resolution to my problem was a realisation that the data provider cut me off of data :) Can happen too. That's probably not a "solution" but an option you have to consider as I will from now on :D Thank you all
Did you create an App Transport Security Execption for Allows Arbitrary loads in the info.plist?
I don't understand what went wrong over here,
// Now actually send the request to the server.
NSURLConnection.sendAsynchronousRequest(request as? URLRequest ?? URLRequest(url: URL), queue: OperationQueue.main, completionHandler: {(_ response: URLResponse, _ data: Data, _ connectionError: Error?) -> Void in
var resultText: String? = nil
if connectionError != nil {
resultText = "** ERROR = \(connectionError)"
print("**Error \(unsuccessLabel.text)")
uploadPleaseWaitLabel.hidden = true
//****** 12-17
unsuccessLabel.hidden = false
//****** 12-17
sessionUploadedYorN = "N"
self.useDocument()
}
else {
Look at this line:
request as? URLRequest ?? URLRequest(url: URL)
You are attempting to create an URLRequest with the argument URL. Just pass there the real URL instance, not the type URL.
Possible solution:
let actualRequest = request as? URLRequest ?? URLRequest(url: URL(string:"http://www.google.com")!)
NSURLConnection.sendAsynchronousRequest(actualRequest,
queue: OperationQueue.main,
completionHandler:
{ (_ response: URLResponse, _ data: Data, _ connectionError: Error?) -> Void in
var resultText: String? = nil
if connectionError != nil {
resultText = "** ERROR = \(connectionError)"
print("**Error \(unsuccessLabel.text)")
uploadPleaseWaitLabel.hidden = true
//****** 12-17
unsuccessLabel.hidden = false
//****** 12-17
sessionUploadedYorN = "N"
self.useDocument()
}
UPDATE
As of Swift4, your code wouldn't compile even with that fix (there is a signature issue within the closure). More than that, NSURLConnection is deprecated since iOS9 and is discouraged to use.
You should refactor your code to use with Swift-native URLSession and its dataTask instance method. It does practically the same.
I have a problem when trying to parse a simple website in Swift 3. I combined what I found on this website, but still can't extract loginText from the other function, and got a 'instance member can't be used on .. type' error when trying to put everything into a class.
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
extension String {
func slice(from: String, to: String) -> String? {
return (range(of: from)?.upperBound).flatMap { substringFrom in
(range(of: to, range: substringFrom..<endIndex)?.lowerBound).map { substringTo in
substring(with: substringFrom..<substringTo)
}
}
}
}
var loginText = ""
func getToken(completionHandler: (String) -> () ) {
var request = URLRequest(url: URL(string: [MY URL])!)
request.httpMethod = "GET"
var loginText = ""
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let task = session.dataTask(with: request) {data, response, err in
loginText = (String(data: data!, encoding: String.Encoding.utf8)!)
}
task.resume()
}
func viewDidLoad() {
getToken {
loginText in
loginText.slice(from: "LT", to: "1s1")
print("View Controller: \(loginText)")
}
}
print(viewDidLoad())
print(loginText)
Thanks !
I got you.
You are coincidentally using the wrong loginText.
You called the completionHandler string loginText while also having loginText declared outside the function.
But you were on the right path.
Your problem will be solved if you call self.loginText, check this out:
getToken {
loginText in //you named it loginText
self.loginText.slice(from: "LT", to: "1s1")
print("View Controller: \(loginText)")
}
The thing is, this STILL never gets called. Because you are never calling that completionHandler, also -
forget about self.loginText, because you have a completionHandler that just passes your string.
Use this and don't even change the getToken method. It will be called, and will work fine:
let task = session.dataTask(with: request) {data, response, err in
let loginText = (String(data: data!, encoding: String.Encoding.utf8)!)
completionHandler(loginText)
}
Also I would delete the var loginText = "" if you don't want to use it outside of getToken. But even if you are, I would suggest just having a function which takes in a String, rather then having it declared just outside. But you know why you need it outside if you do - so yeah. Cheers
EDIT: You also seem to be missing the #escaping
func getToken(completionHandler: #escaping (String) -> () ) {}
There are a lot of discussion about this and I understand the solution to use the delegate method and check the response "404":
var request : NSURLRequest = NSURLRequest(URL: url)
var connection : NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)!
connection.start()
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
//...
}
But I would like to have a simple solution like:
var exists:Bool=fileexists(sURL);
Because I will have a lot of request in the same class with the delegate and I only want to check the response with my function fileexists().
Any hints ?
UPDATE
I guess I'll have to do a synchronious request like the following, but I get always 0x0000000000000000 as a response::
let urlPath: String = sURL;
var url: NSURL = NSURL(string: urlPath)!
var request1: NSURLRequest = NSURLRequest(URL: url)
var response: AutoreleasingUnsafeMutablePointer<NSURLResponse?
>=nil
var error: NSErrorPointer = nil
var dataVal: NSData = NSURLConnection.sendSynchronousRequest(request1, returningResponse: response, error:nil)!
var err: NSError
println(response)
Swift 3.0 version of Martin R's answer written asynchronously (the main thread isn't blocked):
func fileExistsAt(url : URL, completion: #escaping (Bool) -> Void) {
let checkSession = Foundation.URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0 // Adjust to your needs
let task = checkSession.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if let httpResp: HTTPURLResponse = response as? HTTPURLResponse {
completion(httpResp.statusCode == 200)
}
})
task.resume()
}
Checking if a resource exists on a server requires sending a HTTP
request and receiving the response. TCP communication can take some
amount of time, e.g. if the server is busy, some router between the
client and the server does not work
correctly, the network is down etc.
That's why asynchronous requests are always preferred. Even if you think
that the request should take only milliseconds, it might sometimes be
seconds due to some network problems. And – as we all know – blocking
the main thread for some seconds is a big no-no.
All that being said, here is a possible implementation for a
fileExists() method. You should not use it on the main thread,
you have been warned!
The HTTP request method is set to "HEAD", so that the server sends
only the response header, but no data.
func fileExists(url : NSURL!) -> Bool {
let req = NSMutableURLRequest(URL: url)
req.HTTPMethod = "HEAD"
req.timeoutInterval = 1.0 // Adjust to your needs
var response : NSURLResponse?
NSURLConnection.sendSynchronousRequest(req, returningResponse: &response, error: nil)
return ((response as? NSHTTPURLResponse)?.statusCode ?? -1) == 200
}
Improved Vito's solution so the completion is always called:
func fileExists(at url: URL, completion: #escaping (Bool) -> Void) {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0 // Adjust to your needs
URLSession.shared.dataTask(with: request) { _, response, _ in
completion((response as? HTTPURLResponse)?.statusCode == 200)
}.resume()
}
// Usage
fileExists(at: url) { exists in
if exists {
// do something
}
}
async/await
func fileExists(at url: URL) async throws -> Bool {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0 // Adjust to your needs
let (_, response) = try await URLSession.shared.data(for: request)
return (response as? HTTPURLResponse)?.statusCode == 200
}
// Usage
if try await fileExists(at: url) {
// do something
}
// or if you don't want to deal with the `throw`
if (try? await fileExists(at: url)) ?? false {
// do something
}