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()
}
Related
I have found learning swift to be more or less unbearable to do anything, something that would be done in a single line in Python becomes a whole task in swift.
I am trying to return the data from a http request and cannot find a single source that explains how. The only things I can find prints the data instead of returning it, either as a dictionary (from using JSONSerialization) or simply as a string.
let url = URL(string: "url")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print("error: \(error)")
} else {
if let response = response as? HTTPURLResponse {
print("statusCode: \(response.statusCode)")
}
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("data: \(dataString)")
}
}
}
task.resume()
func makePostRequest(){
let urlPath: String = "http://www.swiftdeveloperblog.com/http-post-example- script/"
var url: NSURL = NSURL(string: urlPath)!
var request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
var stringPost="firstName=James&lastName=Bond" // Key and Value
let data = stringPost.dataUsingEncoding(NSUTF8StringEncoding)
request.timeoutInterval = 60
request.HTTPBody=data
request.HTTPShouldHandleCookies=false
let queue:NSOperationQueue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let jsonResult: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error: error) as? NSDictionary
if (jsonResult != nil) {
// Success
println(jsonResult)
let message = jsonResult["Message"] as! NSString
println(message)
}else {
// Failed
println("Failed")
}
})
}
I am new to swift and doing a project in swift 4.0 to acquire data form Fitbit API and got a Strange problem, my url task does not send any urlrequest any more but skip all the code until task.resume, and do not give anything back. Can anyone helps me plz. The code is shown below
import UIKit
class FitbitAPI{
static let sharedInstance : FitbitAPI = FitbitAPI()
var parsedJson : [Any]? = nil
func authorize(with token: String){
let accessToken = token
let baseURL = URL(string: "https://api.fitbit.com/1/user/-/activities/steps/date/today/1m.json")
let request = NSMutableURLRequest(url:baseURL!)
let bodydata = "access_token=\(String(describing: accessToken))"
request.httpMethod = "GET"
request.setValue("Bearer \(String(describing: accessToken))", forHTTPHeaderField: "Authorization")
request.httpBody = bodydata.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {[weak self] (data, response, error) in
if let error = error {
print(error)
}
if let data = data, error == nil{
do {
self?.parsedJson = (try JSONSerialization.jsonObject(with: data, options: []) as? [Any] )
print(String(describing: self?.parsedJson))
}catch _{
print("Received not-well-formatted JSON")
}
}
if let response = response {
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
}
As #Larme implied in his comment, all of that code between the let task = line and the task.resume() line is a callback. Meaning it won't get called until the task completes. Put breakpoints inside of that callback (like on your if let error = error line), and see if they get hit.
ALso, your URL task is a local variable in this method. That means it's entirely possible that its getting released from memory right at the end of this method, before the callback can even be executed. You'll need a reference to the task outside of the method if you want to guarantee that it stays alive in memory long enough to hit the completion callback.
I have access the "dictionary" moviedb for
example : https://www.themoviedb.org/search/remote/multi?query=exterminador%20do%20futuro&language=en
How can i catch only the film's name and poster from this page to my project in Swift ?
It's answer :)
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
reload()
}
private func reload() {
let requestUrl = "https://www.themoviedb.org/search/remote/multi?query=exterminador%20do%20futuro&language=en"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let request = NSURLRequest(URL: NSURL(string: requestUrl)!)
let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
if let error = error {
println("###### error ######")
}
else {
if let JSON = NSJSONSerialization.JSONObjectWithData(data,
options: .AllowFragments,
error: nil) as? [NSDictionary] {
for movie in JSON {
let name = movie["name"] as! String
let posterPath = movie["poster_path"] as! String
println(name) // "Terminator Genisys"
println(posterPath) // "/5JU9ytZJyR3zmClGmVm9q4Geqbd.jpg"
}
}
}
})
task.resume()
}
}
You need to include your api key along with the request. I'd just try something like this to see if it works or not. If it does, then you can go about using the api key in a different way to make it more secure. I wouldn't bother as it's not an api with much sensitive functionality.
let query = "Terminator+second"
let url = NSURL(string: "http://api.themoviedb.org/3/search/keyword?api_key=YOURAPIKEY&query=\(query)&language=ββen")!
let request = NSMutableURLRequest(URL: url)
request.addValue("application/json", forHTTPHeaderField: "Accept")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { data, response, error in
if let response = response, data = data {
print(response)
//DO THIS
print(String(data: data, encoding: NSUTF8StringEncoding))
//OR THIS
if let o = NSJSONSerialization.JSONObjectWithData(data, options: nil, error:nil) as? NSDictionary {
println(dict)
} else {
println("Could not read JSON dictionary")
}
} else {
print(error)
}
}
task.resume()
The response you'll get will have the full list of properties. You need the poster_path and title (or original_title) property of the returned item.
Before the Swift 2.0 Update this code worked perfectly to download my JSON File from the Server with a PHP Script:
let url = NSURL(string: webAdress)
let cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData
var request = NSMutableURLRequest(URL: url!, cachePolicy: cachePolicy, timeoutInterval: 5.0)
var response: NSURLResponse? = nil
var error: NSError? = nil
let reply = NSURLConnection.sendSynchronousRequest(request, returningResponse:&response, error:&error)
After the Update Xcode asked me to do some changes. I did and the code had no Error, but it always throws...
let url = NSURL(string: webAdress)
let cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData
let request = NSMutableURLRequest(URL: url!, cachePolicy: cachePolicy, timeoutInterval: 5.0)
var response: NSURLResponse? = nil
var reply = NSData()
do {
reply = try NSURLConnection.sendSynchronousRequest(request, returningResponse:&response)
} catch {
print("ERROR")
}
Looking forward to your solutions!
Here's an example using the new NSURLSession - apparently NSURLConnection has been deprecated in iOS 9.
let url = NSURL(string: webAddress)
let request = NSURLRequest(URL: url!, cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 5.0)
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
print(data)
print(response)
print(error)
})?.resume()
I think it's super clean, there's just not much documentation on it. Let me know if you have any trouble getting this to work.
Maximilian hi,
I have the same unsolved issue, the proposed solution by Sidetalker using NSURLSession.dataTaskWithRequest is not what you looking for since the NSURLSession API is highly asynchronous (according with Apple documentation) and the code you had implemented in swift 1.2 was synchronous.
in the other hand, NSURLConnection has been deprecated in iOS 9 therefore the code you wrote is probably not building, right?
my suggested solution is:
let url = NSURL(string: webAdress)
let request: NSURLRequest = NSURLRequest(URL: url!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
var responseCode = -1
let group = dispatch_group_create()
dispatch_group_enter(group)
session.dataTaskWithRequest(request, completionHandler: {(_, response, _) in
if let httpResponse = response as? NSHTTPURLResponse {
responseCode = httpResponse.statusCode
}
dispatch_group_leave(group)
})!.resume()
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
//rest of your code...
please let me know if its now OK
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
}