Swift: How to pass in a closure as a function argument - ios

I'm trying to figure out the syntax for passing in a closure (completion handler) as an argument to another function.
My two functions are:
Response Handler:
func responseHandler(response: NSURLResponse!, data: NSData!, error: NSError!) -> Void {
var err: NSError
var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
println("AsSynchronous\(jsonResult)")
}
Query Function
public func queryAllFlightsWithClosure( ) {
queryType = .AllFlightsQuery
let urlPath = "/api/v1/flightplan/"
let urlString : String = "http://\(self.host):\(self.port)\(urlPath)"
var url : NSURL = NSURL(string: urlString)!
var request : NSURLRequest = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:responseHandler)
}
I'd like to modify the Query to something like:
public fund queryAllFlightsWithClosure( <CLOSURE>) {
so that I can externally pass the closure into the function. I know there is some support for training closures but I"m not sure if thats the way to go either. I can't seem to get the syntax correct...
I've tried:
public func queryAllFlightsWithClosure(completionHandler : {(response: NSURLResponse!, data: NSData!, error: NSError!) -> Void} ) {
but it keeps giving me an error

It might help defining a type alias for the closure:
public typealias MyClosure = (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void
that makes the function signature "lighter" and more readable:
public func queryAllFlightsWithClosure(completionHandler : MyClosure ) {
}
However, just replace MyClosure with what it is aliasing, and you have the right syntax:
public func queryAllFlightsWithClosure(completionHandler : (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void ) {
}

OOPS nevermind...
public func queryAllFlightsWithClosure(completionHandler : (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void ) {
took out the {} and it seems to work?

Related

Mocking URLSession for API Testing is giving unrecognized selector sent to instance

I want to test an API without making a server call so I am mocking URLSession and URLSessionDataTask so that I can inject it in my API class.
class MockURLSession: URLSession {
private let mockTask: MockTask
var cachedUrl: URL?
init(data: Data?, urlResponse: URLResponse?, error: Error?) {
mockTask = MockTask(data: data, urlResponse: urlResponse, error:
error)
}
override func dataTask(with url: URL, completionHandler: #escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
self.cachedUrl = url
mockTask.completionHandler = completionHandler
return mockTask
}
}
class MockTask: URLSessionDataTask {
private let data: Data?
private let urlResponse: URLResponse?
var completionHandler: ((Data?, URLResponse?, Error?) -> Void)!
init(data: Data?, urlResponse: URLResponse?, error: Error?) {
self.data = data
self.urlResponse = urlResponse
}
override func resume() {
DispatchQueue.main.async {
self.completionHandler(self.data, self.urlResponse, self.error)
}
}
}
Here when I am running this test it's going in my API class calling the getMovies method. Instance I am getting over there is kind of MockURLSession which is fine. Next moment it gives this ApiTests testGetMoviesSuccessReturnsMovies] : failed: caught "NSInvalidArgumentException", "-[MyAppTests.MockTask error]: unrecognized selector sent to instance 0x600002010500
func testGetMoviesSuccessReturnsMovies() {
let jsonData = "[{\"title\": \"Spider Man Far From Home\",\"detail\": \"The first Spider-Man featuring Tom Holland in the iconic role\"}]".data(using: .utf8)
var mockURLSession = MockURLSession(data: jsonData, urlResponse: nil, error: nil)
let apiRespository = APIRepository(session: mockURLSession)
let moviesExpectation = expectation(description: "movies")
var moviesResponse: Result<[Movie]>?
apiRespository.getMovies { (movies) in
moviesResponse = movies
moviesExpectation.fulfill()
}
waitForExpectations(timeout: 10) { (error) in
XCTAssertNotNil(moviesResponse)
}
}
Here is my protocol extension for api
extension Gettable {
func get<T:Decodable>(with decodingType: T.Type, url: String, session: URLSession, completion:#escaping(Result<T>) -> Void) {
let dataTask = session.dataTask(with: URL(string: url)!) { (data, response, error) in
guard data != nil && error == nil else {
return
}
do {
let decoder = JSONDecoder()
let parsedObj = try decoder.decode(T.self, from: data ?? Data())
completion(Result.success(parsedObj))
}
catch let parsedError {
completion(Result.failure(parsedError))
}
}
dataTask.resume()
}
}
Your help will be highly appreciated.
You are not overriding the method dataTask(with:completionHandler:) in your MockURLSession. So, the original dataTask(with:completionHandler:) of URLSession is called, which internally calls dataTaskForRequest:completion:.
If you are successfully overriding existing methods in the parent class, Swift compiler claims to prefix override keyword.
Move your nested dataTask(with:completionHandler:) out of init(data:urlResponse:error:).
init(data: Data?, urlResponse: URLResponse?, error: Error?) {
mockTask = MockTask(data: data, urlResponse: urlResponse, error:
error)
}
override func dataTask(with url: URL, completionHandler: #escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
self.cachedUrl = url
mockTask.completionHandler = completionHandler
return mockTask
}
For edited part...
Seems inheritance of error property is not working properly. (Which may be a bug of Swift compiler related to bridging NSError and Error.)
Please try this:
class MockTask: URLSessionDataTask {
private let data: Data?
private let urlResponse: URLResponse?
private let _error: Error?
override var error: Error? {
return _error
}
var completionHandler: ((Data?, URLResponse?, Error?) -> Void)!
init(data: Data?, urlResponse: URLResponse?, error: Error?) {
self.data = data
self.urlResponse = urlResponse
self._error = error
}
override func resume() {
DispatchQueue.main.async {
self.completionHandler(self.data, self.urlResponse, self.error)
}
}
}

Swift 1.2 to swift 2: Cannot convert value of type to expected argument type

I'm trying to create a NSURLSession task based on a tutorial I found online (https://www.raywenderlich.com/85528/user-accounts-ios-ruby-rails-swift#next_section) and I am getting the following error:
Cannot convert value of type '(NSData!, NSURLResponse!, NSError!) -> ()' to expected argument type '(NSData?, NSURLResponse?, NSError?) -> Void
at this block of code:
let task = session.dataTaskWithRequest(request) { (data: NSData!, response: NSURLResponse!, error: NSError!) in
The function where the issue belongs to can be found here
func sendRequest(request: NSURLRequest, completion:(NSData!, NSError!) -> Void) -> () {
// Create a NSURLSession task
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data: NSData!, response: NSURLResponse!, error: NSError!) in
if error != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(data, error)
})
return
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if let httpResponse = response as? NSHTTPURLResponse {
if httpResponse.statusCode == 200 {
completion(data, nil)
} else {
var jsonerror:NSError?
if let errorDict = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error:&jsonerror) as? NSDictionary {
let responseError : NSError = NSError(domain: "HTTPHelperError", code: httpResponse.statusCode, userInfo: errorDict as? [NSObject : AnyObject])
completion(data, responseError)
}
}
}
})
}
The full code block can be found here (https://codeshare.io/uJPcX) at line 68.
Change
data:NSData!, response: NSURLResponse!, error: NSError!
to
data: NSData?, response: NSURLResponse?, error: NSError?
when using data or response etc further down you may have to write is as data! to unwrap the variable, but be careful because if the variable is nil it will crash, so you must check that it is not nil first

How to return JSON in swift from HTTPPost

I am new to iOS developing and need some help with JSON and what to be returning. I have the following function in my modal:
func loginRequest(username: String, password: String, completionHandler: ((NSURLResponse!, JSON, NSError?) -> Void)) {
var request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: ""correct post url"\(username)/\(password)")
request.HTTPMethod = "POST"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let httpResponse = response as? NSHTTPURLResponse
var json = JSON(data: data!)
println(json)
})
}
This does successfully return the JSON if I print it inside this function. However, the following code in my view controller yields no errors but fails to return the JSON at all.
#IBAction func signIn(sender: UIButton) {
modal.loginRequest("Test", password: "Pass") { (response, json, error) -> Void in
println(json)
println("Hello")
if (json != nil) {
Do parsing stuff
}
}
In my ViewController, json does not return nil, it doesn't return at all. The code prints in from my modal but does not show in the VC. How am I calling the function wrong?
Your function doesn't call the completion handler closure which is passed as param. If you want access the data however, you have to call the completionHandler closure. This is how your code should be:
func loginRequest(username: String, password: String, completionHandler: ((NSURLResponse!, JSON, NSError?) -> Void)) {
var request : NSMutableURLRequest = NSMutableURLRequest()
request.URL = NSURL(string: ""correct post url"\(username)/\(password)")
request.HTTPMethod = "POST"
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue(), completionHandler:{ (response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
var error: AutoreleasingUnsafeMutablePointer<NSError?> = nil
let httpResponse = response as? NSHTTPURLResponse
var json = JSON(data: data!)
println(json)
// call the closure argument here, to pass the asynchrounsly retrieved vallues
// back to the caller of loginRequest
completionHandler(response, json, error)
})
}

Swift to Swift 2 and Cannot Invoke error. What is wrong?

I am using an API that provided the following code that is supposed to provide the song currently playing on the radio station.
func getSongs() {
let url = NSURL(string: "http://api.vicradio.org/songs/current")!
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data: NSData!, response: NSURLResponse!, error: NSError!) in
if error != nil {
// Handle error...
return
}
println(error)
println(response)
println(NSString(data: data, encoding: NSUTF8StringEncoding))
}
task.resume()
}
This code was written for Swift 1 I believe. I'm getting an error that says:
Cannot invoke "dataTaskWithRequest" with an argument of list type "NSMutableURLRequest, (NSData!, NSURLResponse!, NSError!) -> _ "
I'm rather new to Swift, so maybe someone could explain how I could correct this error?
Change:
(data: NSData!, response: NSURLResponse!, error: NSError!)
to:
(data: NSData?, response: NSURLResponse?, error: NSError?)

How to get data from NXOAuth2Request response

I'm writing my first iOS app. It includes API calls through OAuth2Client.
The problem is when calling the AdvAPI getUser function. A GET request is made through NXOAuth2Request which deals with the response data in responseHandler and variable result is set to an NSDictionary. The result however is not accessible outside the XOAuth2Request function. How can I get the result and return it from getUser?
Thanks!
import Foundation
class AdvAPI {
var store : NXOAuth2AccountStore
var account : NXOAuth2Account?
init(){
self.store = NXOAuth2AccountStore.sharedStore() as NXOAuth2AccountStore
self.store.setClientID(
"test",
secret: "test",
authorizationURL: NSURL.URLWithString("http://localhost:3000/oauth/authorize"),
tokenURL: NSURL.URLWithString("http://localhost:3000/oauth/token"),
redirectURL: NSURL.URLWithString("http://localhost:3000/oauth/connect"),
forAccountType: "AdventureApp"
)
self.account = self.store.accountsWithAccountType("AdventureApp")[0]
}
func getUser(parameters : NSDictionary=[String: AnyObject]()) -> NSDictionary {
NXOAuth2Request.performMethod("GET",
onResource: NSURL.URLWithString("http://localhost:3000/api/v1/me"),
usingParameters: parameters,
withAccount: self.account,
sendProgressHandler: nil,
responseHandler: {(response: NSURLResponse?, responseData: NSData?, error: NSError?) in
var jsonError: NSError
var result = NSJSONSerialization.JSONObjectWithData(responseData, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
}
)
return result
}
}
The getUser function returns before the NXOAuth2Request is completed and therefore never sets the result variable.
To get around this the only option appears to be to call a callback from within responseHandler when the request is completed as such.
func getUser(parameters : NSDictionary=[String: AnyObject]()) {
NXOAuth2Request.performMethod("GET",
onResource: NSURL.URLWithString("http://localhost:3000/api/v1/me"),
usingParameters: parameters,
withAccount: self.account,
sendProgressHandler: nil,
responseHandler: {(response: NSURLResponse?, responseData: NSData?, error: NSError?) in
var jsonError: NSError
var result = NSJSONSerialization.JSONObjectWithData(responseData, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
self.delegate.didReceiveAPIResult(result)
}
)
}

Resources