Many problems with Swift3: cannot understand new syntax for completion handlers - swift

Yesterday I updated to new Mac OS X Sierra and XCode 8 which forced me to update to Swift 3.0 syntax. In my app I have many functions like the following:
fileprivate func requestFisheFieldWithHandler(_ url:String, completionHandler: #escaping (_ success: NSDictionary?, _ error: NSError?) -> Void) {
let configuration = URLSessionConfiguration.default
let url: URL = URL(string: url)!
let urlRequest: URLRequest = URLRequest(url: url)
let session = URLSession(configuration: configuration)
let task = session.dataTask(with: urlRequest, completionHandler: { (data: Foundation.Data?, response: URLResponse?, error: NSError?) -> Void in
if (error != nil) {
//print(error?.code)
//print(error)
completionHandler(success: nil, error: error)
}
else {
do {
let responseJSON = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions()) as! [String: String]
completionHandler(success: responseJSON, error:nil)
}
catch let error as NSError {
completionHandler(success: nil, error:error)
}
}
} as! (Data?, URLResponse?, Error?) -> Void)
task.resume()
}
and I get this error:
"Cannot convert value of type '(Data?, URLResponse?, Error?) -> Void' to expected argument type '(Data?, URLResponse?, Error?) -> Void'"
Moreover, I also used many associative arrays to collect data from downloaded JSON file, like the following:
for comune in response! {
self.comuni.append(comune["nome"] as! String)
self.comuniWithID[comune["nome"] as! String] = Int(comune["idcomune"] as! String)
}
DispatchQueue.main.async {
self.myPicker.reloadComponent(1)
}
and another error I get is:
"Type 'NSFastEnumerationIterator.Element' (aka 'Any') has no subscript members"
Please, would someone help me to correct them? Because I cannot understand what they mean and my app will be published next 30th September...

The most significant change is that all parameter labels in closures have been removed in Swift 3.
This is your code Swift 3 compatible.
As always, do not cast a Swift collection type to a Foundation counterpart. You will throw away all type information.
And don't use the annotations in the completion block return values, the compiler can infer the types. If you need to look up the actual types ⌥-click on the symbol.
fileprivate func requestFisheFieldWithHandler(_ url:String, completionHandler: #escaping ([String: String]?, NSError?) -> Void) {
let configuration = URLSessionConfiguration.default
let url: URL = URL(string: url)!
let urlRequest = URLRequest(url: url)
let session = URLSession(configuration: configuration)
let task = session.dataTask(with: urlRequest) { (data, response, error) -> Void in
if (error != nil) {
completionHandler(nil, error as NSError?)
}
else {
do {
let responseJSON = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions()) as! [String: String]
completionHandler(responseJSON, nil)
}
catch let error as NSError {
completionHandler(nil, error)
}
}
}
task.resume()
}
Regarding the second error you have to cast response! to something more meaningful than Any, I guess ... in response as! [[String:Any]]

The completion handler of dataTask(with:completionHandler:) in Swift 3 is changed to completionHandler: #escaping (Data?, URLResponse?, Error?) -> Void so use Error instead of NSError.
let task = session.dataTask(with: urlRequest, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in
For more about completion Handler check Apple Documentation.
For your error
Type 'NSFastEnumerationIterator.Element' (aka 'Any') has no subscript members"
You need to specify the type of your response to [[String:Any]] then all goes to normal.
if let array = response as? [[String: Any]] {
for comune in array {
self.comuni.append(comune["nome"] as! String)
self.comuniWithID[comune["nome"] as! String] = Int(comune["idcomune"] as! String)
}
}

Related

Cannot convert value of type '()' to expected argument type '(Data?, URLResponse?, Error?) -> Void'

I can't solve this error I hope can I find the solution here
the error is in the line 22 and it's "Cannot convert value of type () to expected argument type (Data?, URLResponse?, Error?) -> Void"
struct WeatherManager {
let weatherURL = ""
func fetchWeather(cityName : String){
let urlString = "\(weatherURL)&q=\(cityName)"
performRequest(urlString: urlString)
}
func performRequest(urlString : String){
//1- Create a URL
if let url = URL(string: urlString){
//2- Create a URLSession
let session = URLSession(configuration: .default)
//3- Give the session a task
let task = session.dataTask(with: url, completionHandler:handel(data: <#T##Data?#>, urlSession: <#T##URLSession?#>, error: <#T##Error?#>))
//4- Start the Task
task.resume()
}
}
func handel(data:Data? , urlSession : URLSession? , error : Error?){
if error != nil {
print(error!)
return
}
if let safeData = data{
let dataString = String(data: safeData, encoding: .utf8)
print(dataString!)
}
}
}
here is the error :
let task = session.dataTask(with: url, completionHandler:handel(data: <#T##Data?#>, urlSession: <#T##URLSession?#>, error: <#T##Error?#>))
A few observations:
The signature of “handle” is incorrect. The second parameter is a URLResponse, not a URLSession:
func handle(data: Data?, response: URLResponse?, error: Error?) {
if let error = error {
print(error)
return
}
if let data = data, let string = String(data: data, encoding: .utf8) {
print(string)
}
}
As Leo pointed out, you should avoid force-unwrapping with the ! operator, too (especially when converting the Data to a UTF8 String).
You should not supply parameters when you pass handle to dataTask:
func performRequest(urlString: String) {
//1- Create a URL
guard let url = URL(string: urlString) else { return }
//2- Use existing URLSession
let session = URLSession.shared // (configuration: .default)
//3- Give the session a task
let task = session.dataTask(with: url, completionHandler: handle)
//4- Start the Task
task.resume()
}
Unrelated, please note that I have not created a URLSession in performRequest. Sessions will leak memory if you create them but do not invalidate them. In this case, it is easier to use the existing shared session, rather than creating a new one.
If you really needed to create one (which you obviously do not in this case ... use shared instead), you would explicitly tell it to invalidate itself when the request was done.
func performRequest(urlString: String) {
//1- Create a URL
guard let url = URL(string: urlString) else { return }
//2- Create URLSession (but only if absolutely necessary)
let session = URLSession(configuration: .default)
//3- Give the session a task
let task = session.dataTask(with: url, completionHandler: handle)
//4- Start the Task
task.resume()
//5- If you really must create a session, invalidate it when the request(s) are finished
session.finishTasksAndInvalidate()
}
But this is an anti-pattern because it is so inefficient. You should reuse a single session, like shown in the previous code snippet, if at all possible.

how to pass variable value to outside of URLSession async - swift 3

I have this code :
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil {
print(error!)
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let getDetail = parseJSON["detail"] as? String
returnDetail = getDetail!.base64Decoded()
} // parse json end
} // do end
catch {
print(error)
}
} // let task end
returnDetail has been defined previously. I did anything to set returnDetail value to getDetail!.base64Decoded() but it only works inside let task = ...
How can I pass it to the outer scope?
You have several methods to tackle the issue of returning a value from inside an asynchronous function. One of them is to wrap the asynchronous network call inside a function and make it return a completionHandler.
Some general advice: don't use force unwrapping unless you are 100% sure that your optional value won't be nil. With network requests, the data can be nil even if there's no error, so never force unwrap data, use safe unwrapping with if let or guard let. Don't use .mutableContainers in Swift when parsing a JSON value, since it has no effect. The mutability of the parsed JSON object is decided by using the let or var keyword to declare the variable holding it. Also don't use NSDictionary, use its native Swift counterpart, Dictionary ([String:Any] is a shorthand for the type Dictionary<String,Any>).
func getDetail(withRequest request: URLRequest, withCompletion completion: #escaping (String?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil {
completion(nil, error)
return
}
else if let data = data {
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else {completion(nil, nil);return}
guard let details = json["detail"] as? String else {completion(nil, nil);return}
completion(details, nil)
}
catch {
completion(nil, error)
}
}
}
task.resume()
}
Then you can call this function by
getDetail(withRequest: request, withCompletion: { detail, error in
if error != nil {
//handle error
} else if detail = detail {
//You can use detail here
}
})
I would suggest to use a completion handler.
func foo(withCompletion completion: (String?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil {
completion(nil, error)
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let details = parseJSON["detail"] as? String
completion(details, nil)
} // parse json end
} // do end
catch {
completion(nil, error)
}
} // let task end
}
I think, you use CallBack(Clourse) of Swift to return data when getDetail have data.

Type of expression is ambiguous without more context in dataTask

I am converting my code to swift 3 but getting "Type of expression is ambiguous without more context" error in the following code .
open class func performGetRequest(_ targetURL: URL!, completion: #escaping (_ data: Data?, _ HTTPStatusCode: Int, _ error: NSError?) -> Void) {
var request = URLRequest(url: targetURL)
request.httpMethod = "GET"
let sessionConfiguration = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfiguration)
let task = session.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async(execute: {
completion(data, response.statusCode,error)
})
}
task.resume()
}
The compiler wants Error not NSError.
Further in Swift 3 omit the parameter labels in the completion handler and don't pass IUO* parameters if you actually mean non-optional:
open class func performGetRequest(_ targetURL: URL, completion: #escaping (Data?, Int, Error?) -> Void) {
Finally you don't need an URLRequest in this case, GET is the default, so just pass the URL to dataTask and delete the two lines related to the request.
(*) Implicit Unwrapped Optional
There is two problem in your code first response.statusCode, response is type of URLResponse and it doesn't have property statusCode so you need to type cast it to HTTPURLResponse to get the statusCode. Second is from Swift 3 use native Error instead of NSError, So your whole code would be like this.
open class func performGetRequest(_ targetURL: URL!, completion: #escaping (_ data: Data?, _ HTTPStatusCode: Int, _ error: Error?) -> Void) {
var request = URLRequest(url: targetURL)
request.httpMethod = "GET"
let sessionConfiguration = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfiguration)
let task = session.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async(execute: {
completion(data, (response as! HTTPURLResponse).statusCode,error)
})
}
task.resume()
}

Converting Swift 2.3 to Swift 3.0 - Error, Cannot invoke 'dataTask' with an argument list of type'

I'm trying to convert one of my projects from Swift 2.3 to Swift 3.0 but some reason I get the following error...
Cannot invoke 'dataTask' with an argument list of type'(with: NSMutableURLRequest, completionHandler:(Data?, UIRLResponse?, NSError) -> Void)'
Overloads for ‘dataTask’ exist with these partially matching parameter lists: (with: URLRequest, completionHandler:#escaping(Data?, URLResponse?, Error?) -> Void), (with: URL, completionHandler: #escaping(Data?, URLResponse?, Error?) -> Void)
The error shows in this line from the code below...
let task = session.dataTask(with: request, completionHandler: { (responseData:Data?, response:URLResponse?, error:NSError?) -> Void in
Any suggestion?
Here is the whole code for function where the error occurs.
func verifyReceipt(_ transaction:SKPaymentTransaction?){
let receiptURL = Bundle.main.appStoreReceiptURL!
if let receipt = try? Data(contentsOf: receiptURL){
//Receipt exists
let requestContents = ["receipt-data" : receipt.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))]
//Perform request
do {
let requestData = try JSONSerialization.data(withJSONObject: requestContents, options: JSONSerialization.WritingOptions(rawValue: 0))
//Build URL Request
let storeURL = URL(string: "https://buy.itunes.apple.com/verifyReceipt")// production URL
//let storeURL = NSURL(string: "https:/sandbox.itunes.apple.com/verifyReceipt") // Testing URL
let request = NSMutableURLRequest(url: storeURL!)
request.httpMethod = "Post"
request.httpBody = requestData
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { (responseData:Data?, response:URLResponse?, error:NSError?) -> Void in
//
do {
let json = try JSONSerialization.jsonObject(with: responseData!, options: .mutableLeaves) as! NSDictionary
print(json)
if (json.object(forKey: "status") as! NSNumber) == 0 {
//
if let latest_receipt = json["latest_receipt_info"]{
self.validatePurchaseArray(latest_receipt as! NSArray)
} else {
let receipt_dict = json["receipt"] as! NSDictionary
if let purchases = receipt_dict["in_app"] as? NSArray{
self.validatePurchaseArray(purchases)
}
}
if transaction != nil {
SKPaymentQueue.default().finishTransaction(transaction!)
}
DispatchQueue.main.sync(execute: { () -> Void in
self.delegate?.managerDidRestorePurchases()
})
} else {
//Debug the receipt
print(json.object(forKey: "status") as! NSNumber)
}
} catch {
print(error)
}
})
task.resume()
} catch {
print(error)
}
} else {
//Receipt does not exist
print("No Receipt")
}
}
The compiler wants URLRequest and Error
...
var request = URLRequest(url: storeURL!)
request.httpMethod = "Post"
...
let task = session.dataTask(with: request,
completionHandler: { (responseData:Data?,
response:URLResponse?,
error:Error?) -> Void in
I recommend to omit all type annotations
let task = session.dataTask(with: request,
completionHandler: { (responseData, response, error) -> Void in
var request = URLRequest(url: storeURL!)
request.httpMethod = "Post"
let task = session.dataTask(with: request,
completionHandler: { (responseData:Data?,
response:URLResponse?,
error:Error?) -> Void in
let task = session.dataTask(with: request,
completionHandler: { (responseData, response, error) -> Void in
https://swift.org/migration-guide-swift3/

Swift 2.0 : Cannot invoke with an argument list of type... (HTTP Request)

Since I upgraded to Xcode 7 beta I have an error that I can't fix.
Here's the full code from my DataManager.swift
import Foundation
var TopAppURL:String = String()
var numberAsked:String = String()
class DataManager {
class func getInfo(ID : String){
TopAppURL = "http://sweetapi.com/?=\(ID)"
numberAsked = ID
}
class func loadDataFromURL(url: NSURL, completion:(data: NSData?, error: NSError?) -> Void) {
var session = NSURLSession.sharedSession()
// Use NSURLSession to get data from an NSURL
let loadDataTask = session.dataTaskWithURL(url, completionHandler: { (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
if let responseError = error {
completion(data: nil, error: responseError)
} else if let httpResponse = response as? NSHTTPURLResponse {
if httpResponse.statusCode != 200 {
var statusError = NSError(domain:"com.raywenderlich", code:httpResponse.statusCode, userInfo:[NSLocalizedDescriptionKey : "HTTP status code has unexpected value."])
completion(data: nil, error: statusError)
} else {
completion(data: data, error: nil)
}
}
})
loadDataTask.resume()
}
class func getDataFromSweetApiOk(success: ((IDdata: NSData!) -> Void)) {
//1
print("DataManager loads \(TopAppURL)")
loadDataFromURL(NSURL(string: TopAppURL)!, completion:{(data, error) -> Void in
//2
if let urlData = data {
//3
success(IDdata: urlData)
}
})
}
}
So I got this error : "Cannot invoke 'dataTaskWithURL' with an argument list of type '(NSURL, completionHandler: (NSData!, NSURLResponse!, NSError!) -> Void)'"
I searched everywhere how to fix this but like Swift 2.0 is very new, I didn't found any solution.
func dataTaskWithURL(_ url: NSURL,
completionHandler completionHandler: ((NSData!,
NSURLResponse!,
NSError!) -> Void)?) -> NSURLSessionDataTask
has changed to
func dataTaskWithURL(_ url: NSURL,
completionHandler completionHandler: (NSData?,
NSURLResponse?,
NSError?) -> Void) -> NSURLSessionDataTask?
in iOS9. The completionHandler no longer is optional, and all parameters in the completionHandler are now optionals instead of implicitly unwrapped optionals.
Now, to help with this in future changes to the optional system, try to avoid (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in, you can simply use data, response, error in and then option-click for more details.
This will remove bloat from your code, and thus improve readability.
To solve your problem in the comments, check out this question.