New to swiftui and don't understand why the JSONDecoder() line in the first code throws
[SwiftUI] Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
This to me is not updating ui so why is this showing?
do {
// pass the request type, bearer token, and email / password ( which are sent as POST body params )
let request = L.getRequest(requestType:"POST", token: token, email: self.username, password: self.psw)
L.fetchData(from: request) { result in
switch result {
case .success(let data):
// covert the binary data to a swift dictionary
do {
let response = try JSONDecoder().decode(WpJson.self, from: data)
for (key, title) in response.allowedReadings {
let vimeoId = Int( key )!
let vimeoUri = self.buildVimeoUri(vimeoId: key)
self.addReadingEntity(vimeoUri: vimeoUri, vimeoId: vimeoId, title: title)
}
self.writeToKeychain(jwt:response.jwt, displayName: response.displayName)
readings = self.fetchReadings()
}
catch {
self.error = error.localizedDescription
}
case .failure(let error):
self.error = error.localizedDescription
}
}
}
I tried wrapping a main queue around the do-catch in the L.fetchData(from: request) { result in but this did not help
DispatchQueue.main.async { [weak self] in
Here is the Login protocol, again without any ui work:
import Foundation
import SwiftUI
struct Login: Endpoint {
var url: URL?
init(url: URL?) {
self.url = url
}
}
protocol Endpoint {
var url: URL? { get set }
init(url: URL?)
}
extension Endpoint {
func getRequestUrl() -> URLRequest {
guard let requestUrl = url else { fatalError() }
// Prepare URL Request Object
return URLRequest(url: requestUrl)
}
func getRequest(requestType:String="POST", token:String, email:String="", password:String="") -> URLRequest {
var request = self.getRequestUrl()
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
if ( "" != email && "" != password && requestType == "POST") {
let parameters:[String:String?] = [
"email": email,
"password": password
]
// Run the request
do {
// pass dictionary to nsdata object and set it as request body
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
}
}
return request;
}
func fetchData(from request: URLRequest, completion: #escaping (Result<Data, NetworkError>) -> Void) {
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
completion(.success(data))
} else if error != nil {
// any sort of network failure
completion(.failure(.requestFailed))
} else {
// this ought not to be possible, yet here we are
completion(.failure(.unknown))
}
}.resume()
}
}
extension URLSession {
func dataTask(with request: URLRequest, completionHandler: #escaping (Result<(Data, HTTPURLResponse), Error>) -> Void) -> URLSessionDataTask {
return dataTask(with: request, completionHandler: { (data, urlResponse, error) in
if let error = error {
completionHandler(.failure(error))
} else if let data = data, let urlResponse = urlResponse as? HTTPURLResponse {
completionHandler(.success((data, urlResponse)))
}
})
}
}
Do you have any idea on how to fix this?
Wrap it right in place of assignment
catch {
DispatchQueue.main.async {
self.error = error.localizedDescription
}
}
case .failure(let error):
DispatchQueue.main.async {
self.error = error.localizedDescription
}
}
I'm using Alamofire to send a login request to an HTTP server. It returns a JSON response. My problem is that the mainline code finishes before the .responseJSON. How do I wait until the response is returned before returning from the function?
var ret: Bool = true
Alamofire.request(
URL(string: "http://localhost:8081/login/iPhone")!,
method: .post,
parameters: ["email":"test#test.test", "password":"test", "uuid":String(describing: UIDevice.current.identifierForVendor!.uuidString)],
headers: [:])
.validate()
.responseJSON{(response) -> Void in
do {
guard response.result.isSuccess else {
throw FieldError.fetchError(responseError: response.result.error)
}
guard let value = response.result.value as? [String: Any],
let status = value["status"] as? String,
let message = value["message"] as? String else {
throw FieldError.messageFormatError
}
switch status {
case "Login suggess":
break
default:
throw FieldError.fieldServerError(status: status, message: message)
}
} catch {
ret = false
debugPrint(error)
}
}
return ret
You can always use global
typealias DownloadComplete = () -> ()
next in func with you're json
func yourFunc(completed: #escaping DownloadComplete)
and after download use
completed()
I am using this Alamofire request and returning a json
let todoEndpoint: String = "https://api.abc.com/api/v3/products?pid=uid8225&format=json&&offset=0&limit=10"
Alamofire.request(todoEndpoint)
.responseJSON { response in
guard let json = response.result.value as? [String: Any] else {
print("didn't get todo object as JSON from API")
print("Error: \(response.result.error)")
return
}
print(json)
}
Now i have do loop where i want to use this json value but i am getting:
error : Use of unresolved identifier json ?
do {
for (index,subJson):(String, JSON) in json {
print("Index :\(index) Title: \(subJson)" )
} catch
{
print("there was an error")
}
As mentioned :
https://github.com/Alamofire/Alamofire#response-string-handler
" the result of a request is only available inside the scope of a response closure. Any execution contingent on the response or data received from the server must be done within a response closure."
How can i use this json value outside scope of response closure ?
Can you please suggest
Is there any completion handler i need to write and how can it be done ?
Thanks
Better use the SwiftyJson for easy json parsing with Almofire like bellow :
import Alamofire
import SwiftyJSON
static func getRequest(urlString: URL?, Parameter:NSDictionary?, completion: #escaping (_ serverResponse: AnyObject?,_ error:NSError?)->()){
Alamofire.request(urlString!, parameters:nil, headers: nil).responseJSON { response in
if(response.result.value != nil){
let serverResponse = JSON(response.result.value!)
print("Array value is \(serverResponse.arrayValue)")
completion(serverResponse as AnyObject?, nil)
}
else{
completion(nil, response.result.error as NSError?)
}
}
}
You can write this function in a separate class like NetworkLayer and then call it from any class for the WebService call as given bellow :
let url = NSURL(string: yourWebServiceUrlString)
NetworkLayer.getRequest(urlString: url as URL?, Parameter: nil) { (serverResponse, error) in
if (error == nil){
print("Server response: \(serverResponse)")
}
}
Note: You can also pass the parameter dictionary in it.
I am using Alamofire for api call. but not hit the API. And also how to set the header in Alamofire.
func signupApiASyncPostCall(_ url:String, params:[String: AnyObject]?, success successBlock :#escaping apiSuccess,failure failureBlock :#escaping apiFailure) {
let Auth_header = ["Content-Type" : "application/json", "application/json" : "Accept"]
Alamofire.request(url, method: .post, parameters: params, encoding: URLEncoding.default, headers: Auth_header)
.responseJSON { response in
print("\(response.request?.url)") // original URL request
//print(response.response) // URL response
//print(response.data) // server data
print(response.result) // result of response serialization
if response.result.isSuccess {
successBlock(response.result.value as? NSDictionary)
print(successBlock(response.result.value as? NSDictionary))
} else {
let httpError: NSError = response.result.error! as NSError
let statusCode = httpError.code
let error:NSDictionary = ["error" : httpError,"statusCode" : String(statusCode)]
failureBlock(error)
}
}
}
I'm struggling a bit to figure out how to best test an app that uses Alamofire to help sync with server data.
I want to be able to test my code that uses Alamofire and processes JSON responses from a server.
I'd like to mock those tests so that I can feed the expected response data to those tests without incurring real network traffic.
This blog post (http://nshipster.com/xctestcase/) describes how easy it is to Mock an object in Swift - but I'm not sure how to do that with Alamofire and its chained responses.
Would I mock the Manager? the Request? Response? Any help would be appreciated!
I'm adding another answer since I've just found this approach that in my opinion is easier and really simple to read and use.
I've created a dummy Alamofire class that contains only the functions and the types necessary for tests.
Now I include this file in the test target instead of the real Alamofire.
For example I've created my version of the Request class where I define a couple of static variables that I valorise depending on the test, and for this class I've implemented only the init and the responseJSON function.
public class Request {
var request:String?
struct response{
static var data:NSHTTPURLResponse?
static var json:AnyObject?
static var error:NSError?
}
init (request:String){
self.request = request
}
public func responseJSON(options: NSJSONReadingOptions = .AllowFragments, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
completionHandler(NSURLRequest(URL: NSURL(string:self.request!)!), Request.response.data, Request.response.json, Request.response.error)
return self
}
}
Now I can mock a response in a test:
func testMytestFunction(){
var HTMLResponse = NSHTTPURLResponse(URL: NSURL(string: "myurl")!, statusCode: 200, HTTPVersion: "HTTP/1.1", headerFields: nil)
Request.response.data = HTMLResponse
Request.response.json = LoadDataFromJSONFile("MyJsonFile")
request(.POST, "myurl", parameters: nil, encoding: ParameterEncoding.JSON).responseJSON {
(request, response, JSON, error) -> Void in
// the JSON and response variable now contains exactly the data that you have passed to Request.response.data and Request.response.json
}
}
The request function is defined here:
public func request(method: Method, URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request {
return Request(request: URLString.URLString)
}
public func request(URLRequest: URLRequestConvertible) -> Request {
return Request(request: "fakecall")
}
This question is getting old, but I just encountered the same issue, and the solution is very easy when using OHHTTPStubs.
OHHTTPStubs just mocks the responses you get from NSURLSession, so it works well with Alamofire, and you get very good coverage of your code path.
For example, in your test case, just mock the response using:
OHHTTPStubs.stubRequestsPassingTest({
(request: NSURLRequest) -> Bool in
return request.URL!.host == "myhost.com"
}, withStubResponse: {
(request: NSURLRequest) -> OHHTTPStubsResponse in
let obj = ["status": "ok", "data": "something"]
return OHHTTPStubsResponse(JSONObject: obj, statusCode:200, headers:nil)
})
Waiting for an answer by #mattt I post an example of my code.
Let's say that we have a Client class that is responsible for calling a simple web service. This class implements a function called userSignIn that performs a sign in using the WS.
This is the code for the userSignIn function:
func userSignIn(
#email:String,
password:String,
completionHandler: (Bool, String?, NSError?) -> Void
)-> Void
{
var parameters:[String:AnyObject] = [
"email":email,
"password":password,
]
Alamofire.request(.POST, Client.urlPath, parameters: parameters, encoding: ParameterEncoding.JSON).responseJSON {
(request, response, JSON, responseError) -> Void in
// Setup callback params
// HERE WE INJECT THE "FAKE" DATA--------
var operationComplete = false
var accessToken:String?
var error:NSError?
// --------------------------------------
if let statusCode = response?.statusCode {
// Check for errors and build response data
(operationComplete, accessToken, error) = self.checkSignInResponse(statusCode, JSON: JSON)
}
// Call the completion handler
completionHandler(operationComplete, accessToken, error)
}
}
The aim of the function is to get a token from the web service if the information passed by the user are correct.
The function checkSignInResponse (I don't report its code since it's not useful for the answer) has the role to valorise the 3 variables operationComplete, accessToken and error depending on the JSON response received.
Now that the 3 variables have a value we call the completionHandler using them.
How to mock this function?!
To mock the response I override the userSignIn function directly into the test function (as explained by the NSHipster article).
func testUserSignIn_whenParamsAreInvalid(){
class MockClient:Client {
override func userSignIn(#email: String, password: String, completionHandler:
(Bool, String?, NSError?) -> Void) {
// Set callback params
var operationComplete = false
var accessToken:String? = nil
var error:NSError? = NSError(domain: "Testing", code: 99, userInfo: nil)
completionHandler(operationComplete, accessToken, error)
}
}
signInViewController!.client = MockClient()
signInViewController!.loadView()
fillRegisterFieldsWithDataAndSubmit(femail(), password: fpassword())
XCTAssertNotNil(signInViewController!.error, "Expect error to be not nil")
}
then I substitute the client inside the view controller that I'm testing using my "mocked" client. In this case I'm testing that the controller passes to the function information that are not valid so I check that the error property of the controller is not nil. To force this data I simply set operationComplete to false and I manual generate an NSError.
Does it make any sense to you? I'm not sure that this test is a good test... but at least I can verify the data flow.
I believe I have a solution to this for the newer versions of Alamofire. My Swift and DI skills are a bit noob so this can probably be improved but I thought I'd share. The most challenging part of mocking Alamofire is mocking the method chaining in the Network call (request().responseJSON).
The Network call:
let networkManager: NetworkManagerProtocol!
init(_ networkManager: NetworkManagerProtocol = NetworkManagerTest(SessionManager())) {
self.networkManager = networkManager
}
func create(_ params: [String: Any], completion: #escaping (Response<Success,Fail>) -> Void) {
self.networkManager.manager.request(self.url!, method: .post, parameters: params, encoding: URLEncoding.default, headers: nil).responseJSON {
response in
if response.result.isSuccess {
completion(Success())
} else {
completion(Fail())
}
}
}
The manager that you'll inject into the network call class:
The NetworkManagerProtocol provides the get manager functionality to the various types of network managers.
class NetworkManager: NetworkManagerProtocol {
private let sessionManager: NetworkManagerProtocol
init(_ sessionManager: NetworkManagerProtocol) {
self.sessionManager = sessionManager
}
var manager: SessionManagerProtocol {
get {
return sessionManager.manager
}
set {}
}
}
Extend Alamofire's SessionManager class:
This is where we add the protocols and custom functionality to SessionManager. Note the protocol's request method is a wrapper around Alamofire's request method .
extension SessionManager: NetworkManagerProtocol, SessionManagerProtocol {
private static var _manager = SessionManager()
var manager: SessionManagerProtocol {
get {
return SessionManager._manager
}
set {
let configuration = URLSessionConfiguration.default
SessionManager._manager = Alamofire.SessionManager(configuration: configuration, delegate: SessionManager.default.delegate)
}
}
func request(_ url: URLConvertible, method: HTTPMethod, parameters: Parameters, encoding: ParameterEncoding, headers: HTTPHeaders?) -> DataRequestProtocol {
let dataRequest: DataRequest = self.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers)
return dataRequest
}
}
Create a SessionManagerMock for the mock api call:
This class creates a SessionManagerMock object and then retrieves the mock data with its request method.
class SessionManagerMock: NetworkManagerProtocol, SessionManagerProtocol {
private static var _manager = SessionManagerMock()
var manager: SessionManagerProtocol {
get {
return SessionManagerMock._manager
}
set {}
}
func request(_ url: URLConvertible, method: HTTPMethod, parameters: Parameters, encoding: ParameterEncoding, headers: HTTPHeaders?) -> DataRequestProtocol {
return DataRequestMock()
}
}
Extend Alamofire's DataRequest class:
And again, note the protocol's responseJSON class is a wrapper around DataRequests's responseJSON class.
extension DataRequest: DataRequestProtocol {
func responseJSON(completionHandler: #escaping (DataResponse<Any>) -> Void) -> Self {
return self.responseJSON(queue: nil, options: .allowFragments, completionHandler: completionHandler)
}
}
DataRequestMock Class:
This class stores the data for the mock request. It could be built out a little more (add request data, etc) but you get the idea.
class DataRequestMock: DataRequestProtocol {
static var statusCode: Int = 200
var dataResponse = DataResponse<Any>(
request: nil,
response: HTTPURLResponse(url: URL(string: "foo.baz.com")!, statusCode: DataRequestMock.statusCode, httpVersion: "1.1", headerFields: nil),
data: nil,
result: Result.success(true), // enum
timeline: Timeline()
)
func response(completionHandler: #escaping (DataResponse<Any>) -> Void) -> Self {
completionHandler(dataResponse)
return self
}
func responseJSON(completionHandler: #escaping (DataResponse<Any>) -> Void) -> Self {
return response(completionHandler: completionHandler)
}
}
The Protocol Droids:
protocol NetworkManagerProtocol {
var manager: SessionManagerProtocol { get set }
}
protocol SessionManagerProtocol {
func request(_ url: URLConvertible, method: HTTPMethod, parameters: Parameters, encoding: ParameterEncoding, headers: HTTPHeaders?) -> DataRequestProtocol
}
protocol DataRequestProtocol {
func responseJSON(completionHandler: #escaping (DataResponse<Any>) -> Void) -> Self
}
The test method:
A lot of improvements could be made to make this more dynamic but again you get the idea
var sut: UserService?
override func setUp() {
super.setUp()
sut = UserService(NetworkManagerTest(SessionManagerMock()))
}
func testCreateUser201() {
DataRequestMock.statusCode = 201
let params : [String : String] = ["name": "foo baz", "email": "foobaz#gmail.com", "password": "tester123"]
var resultCode: Int!
sut?.create(params) {(response: Response) in
switch response {
case .success(let resp):
resultCode = resp.statusCode
case .failure(let resp):
resultCode = resp.statusCode
}
}
XCTAssertEqual(resultCode, 201, "Status code is wrong")
}