Alamofire request or Firebase query is not working in a non UIViewController Class - swift

Imperial trying to perform a request to a website using alamofire and my problem is the following:
When I use the corresponding code in an ViewController cocoaTOuch class viewDidLoad() function everything works fine.(here is the code)
super.viewDidLoad()
let loginActionUrl = url
do{
let parameters = [
"p_user":user,
"p_password": password
]
AF.request(loginActionUrl, method: .post, parameters: parameters).responseJSON
{response in
if let header = response.response?.allHeaderFields as? [String: String],
let responseUrl = response.request?.url{
let sessionCookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: responseUrl)
......
If I repeat the same code inside a private function on a swift (non cocoa touch) class, then,I have no response, While debugging it tries to perform the request task twice and then jumps out of the {response in code block.
The code is the following:
private func checkInWithAeA(withLogIn: String, password: String) -> (Bool){
var companyUSerRecognized: Bool = false
var startIndex: String.Index!
let loginActionUrl = url
do{
let parameters = [
"p_user" : withLogIn,
"p_password": password
]
AF.request(loginActionUrl, method: .post, parameters: parameters).responseJSON
{response in
if let header = response.response?.allHeaderFields as? [String: String],
let responseUrl = response.request?.url{
let sessionCookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: responseUrl)
companyUSerRecognized = true
......
I don't now what is happening but is the second time I have the same problem. What I'm dong is trying to avoid to set up to much code in the viewController using other support classes, following best practices, but I already tried to do this with firebase, and I have the same problem, the query to the database only worked in UIViewcontroller classes (in certain) and now is the same, I am not able to obtain any result when I execute the code in the clean swift file.
Is there any kind of limitation on this. Why I cannot do anything like an alamofire request or a firebase query to the realtime database out of a UIViewController class?
Here I add some information:
var myConnectionController: ConnectionController = ConnectionController()
let (companyUSerRecognized, error) = myConnectionController.chekUserIDandPassWord(forTheCompany: self.companyName, logInName: self.companyLogIn, password: self.companyPassword)
This call to the ConnectionController class (that is a swift plain class) asks for a connexion to a web page. If the response is good, then a true is obtained and the process is continued.
The function called has a switch statement:
public func chekUserIDandPassWord(forTheCompany: String, logInName: String, password: String) -> (Bool, String){
var companyUSerRecognized: Bool!
var error: String!
switch forTheCompany {
case "(AEA)":
companyUSerRecognized = checkInWithAeA(withLogIn: logInName, password: password)
break
.....
This is what calls Check in With AeA. (The function I just mentioned before). What I want to is get the cookies of the connection in return check them and if they are good, true is returned.
I already have done this in the viewDidLoad() function of a ViewController, In fact I can parse the response with SwiftSoup, etc. But If I do it this way I am not able to do it.
Thanks again

I finally made up the solution by reviewing some bibliography. I did not notice that, any alamofire call opens a pipeline. That is, we are obviously talking about asynchronous operations. There are two ways to handle with this kind of operations in swift. One is the use of future objects. This option allows the continuation of the execution by substituting the results from the async call when they are ready. Depending on the situation this is not so good. The other is to make the execution wait for the response of the async call. This is done with a closure. I took this lastoption.
The closer is to be performed by using a completion handler function that is called at the end of the async call block to return any value you need from the async call. In this case. This is what I called completion
private func checkInWithAeA(completion: #escaping (Bool)-> Void){
let loginActionUrl = url1
let postLoginUrl = url2
let parameters = [
"p_user" : logInName,
"p_password": password
]
AF.request(loginActionUrl, method: .post, parameters: parameters).responseData
{(response) in
if let header = response.response?.allHeaderFields as? [String: String],
let responseUrl = response.request?.url{
let sessionCookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: responseUrl)
let cookieToSend = sessionCookies[0]
//Debug
print(cookieToSend)
AF.session.configuration.httpCookieStorage?.setCookie(cookieToSend)
completion(true)
}else{
completion(false)
}
}
}
That's it. Hope it helps
BTW. I think that this is the same problem with the firebase queries.
Thanks!

Related

Returning parsed JSON data using Alamofire?

Hello new to Swift and Alamofire,
The issue i'm having is when I call this fetchAllUsers() the code will return the empty users array and after it's done executing it will go inside the AF.request closure and execute the rest.
I've done some research and I was wondering is this is caused by Alamofire being an Async function.
Any suggestions?
func fetchAllUsers() -> [User] {
var users = [User]()
let allUsersUrl = baseUrl + "users/"
if let url = URL(string: allUsersUrl) {
AF.request(url).response { response in
if let data = response.data {
users = self.parse(json: data)
}
}
}
return users
}
You need to handle the asynchrony in some way. This this means passing a completion handler for the types you need. Other times it means you wrap it in other async structures, like promises or a publisher (which Alamofire also provides).
In you case, I'd suggest making your User type Decodable and allow Alamofire to do the decoding for you.
func fetchAllUsers(completionHandler: #escaping ([User]) -> Void) {
let allUsersUrl = baseUrl + "users/"
if let url = URL(string: allUsersUrl) {
AF.request(url).responseDecodable(of: [User].self) { response in
if let users = response.value {
completionHandler(users)
}
}
}
}
However, I would suggest returning the full Result from the response rather than just the [User] value, otherwise you'll miss any errors that occur.

How to get original requests in Alamofire 5?

I made a wrapper for Alamofire which makes the data request first and then it prints the details of original URLRequest.
let dataRequest = session.request(url, method: .get, parameters: parameters)
let originalRequest = dataRequest.request
// Now print somehow the details of original request.
It worked fine on Alamofire 4.9, but it stopped in the newest 5.0 version. The problem is that dataRequest.request is nil. Why this behavior has changed? How can I access URLRequest underneath DataRequest?
URLRequests are now created asynchronously in Alamofire 5, so you're not going to be able to access the value immediately. Depending on what you're doing with the URLRequest there may be other solutions. For logging we recommend using the new EventMonitor protocol. You can read our documentation to see more, but adding a simple logger is straightforward:
final class Logger: EventMonitor {
let queue = DispatchQueue(label: ...)
// Event called when any type of Request is resumed.
func requestDidResume(_ request: Request) {
print("Resuming: \(request)")
}
// Event called whenever a DataRequest has parsed a response.
func request<Value>(_ request: DataRequest, didParseResponse response: DataResponse<Value, AFError>) {
debugPrint("Finished: \(response)")
}
}
let logger = Logger()
let session = Session(eventMonitors: [logger])
I had to obtain the URLRequest in a test case. Solved it by adding .responseData and using XCTestExpectation to wait for the async code to return:
func testThatHeadersContainContentEncoding() {
let exp = expectation(description: "\(#function)\(#line)")
let request = AF.request("http://test.org",
method: .post, parameters: ["test":"parameter"],
encoding: GZIPEncoding.default,
headers: ["Other":"Header"])
request.responseData { data in
let myHeader = request.request?.value(forHTTPHeaderField: additionalHeader.dictionary.keys.first!)
// .. do my tests
exp.fulfill()
}
waitForExpectations(timeout: 10, handler: nil)
}

How to correct the order of execution of code in Swift 5?

The code within the function is executed in a different order than it is expected. I wanted to change the state of the login Boolean variable inside the if statement, but the function returns the initial value before if statement is completed.
Code sample:
class ClassName {
func loginRequest (name: String, pwd: String) -> Bool {
var login:Bool
//Initial value for login
login = false
let task = session.uploadTask(with: request, from: jsonData) { data, response, error in
if let httpResponse = response as? HTTPURLResponse {
print(httpResponse.statusCode)
if (httpResponse.statusCode) == 200 {
//Change the value of login if login is successful
login = true
if let data = data, let dataString = String(data: data, encoding: .utf8) {
do {
...
} catch {print(error.localizedDescription)}
}
}
}
}
task.resume()
//Problem return false in any case because return is completed before if statement
return login
}
}
Completion Handlers is your friend
The moment your code runs task.resume(), it will run your uploadTask and only when that function is finished running it will run the code where you change your login variable.
With That said: That piece of code is running asynchronously. That means your return login line of code won't wait for your network request to come back before it runs.
Your code is actually running in the order it should. But i myself wrote my first network call like that and had the same problem. Completion Handles is how i fixed it
Here is a very nice tutorial on Completion Handlers or you might know it as Callbacks :
Link To Completion Handlers Tutorial
If i can give you a little hint - You will have to change your function so it looks something like this: func loginRequest (name: String, pwd: String, completionHandler: #escaping (Bool) -> Void)
And replace this login = true with completionHandler(true)
Wherever it is you call your function it will look something like this:
loginRequest(name: String, pwd: String) {didLogIn in
print("Logged In : \(didLogIn)")
}
One last thing... You're actually already using Completion Handlers in your code.
let task = session.uploadTask(with: request, from: jsonData) { data, response, error in
... ... But hopefully now you understand a little bit better, and will use a completion handler approach when making network calls.
GOOD LUCK !

Create a Swift HTTP mock with alternate data

I have a mocked HTTPManager, and I want it to either return a userIDResonse or a tokenResponse.
To be able to do this I made the mock conform to a protocol to allow this to be set within the test.
let userIDResponse = """
{\"user_id\":\"5a7ab957a225856b38f49bb4\"}
"""
let tokenResponse = """
{\"access_token\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjczMUE3OUEyMjY3QjY4Q0EwNTc5QjYzRjdFMkY0QjlBQkZFMENEMTUiLCJ0eXAiOiJKV1QiLCJ4NXQiOiJjeHA1b2laN2FNb0ZlYllfZmk5TG1yX2d6UlUifQ.eyJuYmYiOjE1MTI5NjU3NTgsImV4cCI6MTUxMjk2OTM1OCwiaXNzIjoiaHR0cHM6Ly9pZG0uYWxwaGFub3ZhLnNnIiwiYXVkIjpbImh0dHBzOi8vaWRtLmFscGhhbm92YS5zZy9yZXNvdXJjZXMiLCJhcGkyIl0sImNsaWVudF9pZCI6ImNhcGFwb3QtbmciLCJzdWIiOiI1YTFjMWU5MjY0MjUzYjFlMWU2N2ZhZDIiLCJhdXRoX3RpbWUiOjE1MTI5NjU3NTgsImlkcCI6ImFscGhhbm92YSIsImZpcnN0X25hbWUiOiJTdGV2ZW4iLCJsYXN0X25hbWUiOiJDdXJ0aXMiLCJuYW1lIjoiU3RldmVuIEN1cnRpcyIsImVtYWlsIjoic3RldmVuQGFscGhhbm92YS5zZyIsInNjb3BlIjpbImNhcGFwb3QucHJvZmlsZSIsImVtYWlsIiwib3BlbmlkIiwiYXBpMi5yZWFkX29ubHkiLCJvZmZsaW5lX2FjY2VzcyJdLCJhbXIiOlsicGFzc3dvcmQiXX0.q4-SF5KBVSwN4bFhcQ88icR9X2jzz_JH2K4EpDgS-oZjjppNruckxfTjauVqcwG8zPR0eGzx5CBXiAfMeg9akShWajqBZ9rkCsqjXw6Ef74J9cTBDhxTEUL0v7P0zm_fVNOutM_UJQ-DiQr2gAO0mfAxMhOiQ_uXlKoM2RYGKjfMkH6Ym7kBjtRAhho8pPVmtQiBmVFI5OUVXNU3rPVgB7sx-I1LZmUZBZoy7T4s14TAuE4yiUyTBgO5joyRsZtMdFybna8CRK_ylS3WC6wOBNm74O9IrZlbsiradtLzMG-9E8AnjbvH4RYR68H2xpt562PfnGD_VC9NXFQ7iRrRMw\"}
"""
Used by the Mock
protocol HTTPManagerMockProtocol {
func setResponse(response: String.UTF8View)
}
typealias HTTPMock = HTTPManagerProtocol & HTTPManagerMockProtocol
class HTTPManagerMock: HTTPMock {
var data = Data(userIDResponse.utf8)
func setResponse(response: String.UTF8View) {
data = Data(response)
}
func get(urlString: String, parameters: [String : String], completionBlock: #escaping (Result<Data, Error>) -> Void) {
completionBlock(.success(data))
}
}
So then in my test I have to set the reponse:
let httpMock = HTTPManagerMock()
httpMock.setResponse(response: tokenResponse.utf8)
sut = Login(serverString: "serverURL", headers: [:], httpManager: httpMock )
In some ways this seems ok, however it means I cannot use the setup function in my tests which results in repeated code within my test classes.
Which approach can mean I can have a mock with different output without generating extra test code?
Make a parameterized helper method to create your System Under Test.
private func makeLogin(response: String) -> Login {
let httpMock = HTTPManagerMock()
httpMock.setResponse(response: response.utf8)
return Login(serverString: "serverURL", headers: [:], httpManager: httpMock)
}
That way, you can vary the response across different tests. And if you have tests where you don't really care about the response and want to provide dummy data, that can be a default value in the helper.

Method call asks for method as parameter

I'm pretty new to swift so this might be a really simple question, but I am trying to create a method that returns a list upon completion but when I try to call the method, it says I am missing the escaping parameter which I do not know how to satisfy.
Here is the method:
func fillFromFile(completionBlock: #escaping ([Asset_Content]) -> ()) {
let url = "URL STRING"
LoadJSONFile(from: url) { (result) in
// The code inside this block would be called when LoadJSONFile is completed. this could happen very quickly, or could take a long time
//.map is an easier way to transform/iterate over an array
var newContentArray = [Asset_Content]()
for json in result{
let category = json["BIGCATEGORY"] as? String
let diagnosis = json["DIAGNOSIS"] as? String
let perspective = json["PERSPECTIVE"] as? String
let name = json["NAME"] as? String
let title = json["Title"] as? String
let UnparsedTags = json["TAGS"] as? String
let filename = json["FILENAME"] as? String
let tagArray = UnparsedTags?.characters.split(separator: ",")
for tag in tagArray!{
if(!self.ListOfTags.contains(String(tag))){
self.ListOfTags.append(String(tag))
}
}
let asset = Asset_Content(category!, diagnosis!, perspective!, name!, title!, filename!)
// This is a return to the map closure. We are still in the LoadJSONFile completion block
newContentArray.append(asset)
}
print("return count ", newContentArray.count)
// This is the point at which the passed completion block is called.
completionBlock(newContentArray)
}
}
here is the method call:
self.ListOfFiles = fillFromFile()
and the error is "Missing argument for parameter 'completionblock' in call"
The way you expect the response of a method with completionBlock is like this:
fillFromFile { (response) in
self.ListOfFiles = response
}
Like this you are setting your ´ListOfFiles´ variable, with the new variable that comes in the method.
In the return of your function you should have a DispatchQueue
DispatchQueue.main.async {
completionBlock(newContentArray)
}
Notice that the fillFromFile function doesn't return anything. It's an asynchronous function. This means that it does its work independently of the main control flow of the thread it was called from. It'll return (nothing) almost immediately, and perform its work some unknown time thereafter.
To obtain the result of this function, you're expected to given a completion handler. This is a closure that will be called by the code when it eventually completes its work. As a parameter to this closure, it will pass in the result of the work (an Array<Asset_Content>).
Here's simple example of how to satisfy this method signature:
fillFromFile { (response) in
print(response)
}
I suggest you read the language guide, especially its section on closures.