Custom, content based validator for Alamofire (in Swift) - ios

I know you can add a status code and content type validators, but I'd really love to be able to write my own validator based on the result content - basically I want to make sure the json I'm getting back contains some fields, and that their value is valid.
The way the app I'm working on is currently designed is there's a Server class that handles all the api calls, and the response object is returned to whoever called it, so they can do their logic / update ui, etc.
Now I have a status code validator on all the requests, so I don't need to have it on all external, but I have several apis, that require that custom validation logic, which means I have to add it in all the places that call it, AND that I can't use this amazing syntax:
switch resp.result {
case .Success(let value):
print("yay")
case .Failure:
print("nay")
}
I'd love any answer/pointer that can help me find a solution,
Thank you all so much in advance! :)

I wound up having this exact same question and found out what you want to do is write your own response serializer and stop using .validate().
The serializer I'm using is very, very close to the out-of-the-box JSONResponseSerializer, except I make a check for an error.
The only change I make to the stock serializer is within the do-catch statement:
do {
let JSON = try NSJSONSerialization.JSONObjectWithData(validData, options: options)
if let responseDict = JSON as? NSDictionary, apiError = NSError.APIErrorFromResponse(responseDict) {
return .Failure(apiError)
}
return .Success(JSON)
} catch {
return .Failure(error as NSError)
}
APIErrorFromResponse is simply an extension method on NSError that checks the JSON for an error dictionary and populates a custom NSError out of that.
Hopefully this points you in the right direction if you haven't already found a solution!

Related

Swift: Using parsed JSON Data outside of a closure [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed last year.
I am building a mobile app with swift, and am having some syntax issues as I am not a developer. The structure and logic of the application is really rough and surely incorrect, however we just need something that functions. (It is a school project and my team got no devs).
Anyways, we have a MySQL database that will be used as a middleman between our badge server/admin app, and our mobile app. Currently when you go to https://gatekeeperapp.org/service.php , you will see the current database data, taken by a php script and hosted there as JSON. Currently in Swift I have a struct with a function that takes this JSON data, and maps it to variables. The idea is to then pass these pulled variables into a separate set of functions that will check the pulled long/lat against the mobile devices location, and then return whether they match or not. This value would be updated, re-encoded to JSON, and pushed to a web service that would go about changing the values in the database so the badge server could use them.
Where I am currently I can see that values are being pulled and mapped and I can set a variable in a separate function to the pulled value, but then I can only seem to output this value internally, rather than actually use it in the function. I get a type error saying that the pulled values are of type (). How can I properly use these values? Ultimately I think I would want to convert the () to a double, so I could properly compare it to the Long/Lat of the device, and then will need to re-encode the new values to JSON.
Swift Code -- struct function
Swift code -- JSON struct
Swift code -- using pulled data
Your closure is called asynchronously, which means that the outer function where you are expecting to use the values has already returned by the time the closure is called. Instead, you probably need to call some other function from the closure, passing the values you've received.
class MyClass {
func fetchUserData() {
UserData().fetchUser { [weak self] user, error in
DispatchQueue.main.async {
if let user = user {
self?.handleSuccess(userID: user)
} else if let error = error {
self?.handleError(error)
}
}
}
}
private func handleSuccess(userID: String) {
print(userID)
// Do something with userID. Maybe assign it to a property on the class?
}
private func handleError(_ error: Error) {
print(error)
// Handle the error. Maybe show an alert?
}
}

Swift access dictionary value

I have a Rails api that I am using to develop my iOS application with. I am currently using alamofire to perform HTTP requests and am running into an issue when trying to access dictionary keys.
Type of the response from Alamofire is __NSDictionaryI.
AF.request("\(url)/wishlist", method: .post, parameters: parameters, encoding: URLEncoding.default).responseJSON { response in
switch response.result {
case .failure(let err):
print(err)
case .success(let res):
print(type(of: res))
print(res["message"])
}
}
I am coming from Ruby where we access hash keys like hash["key"] but am running into issues when trying to do that here. The response from this request prints
{
message = "Care was created";
status = ok;
}
I was hoping I could do res["message"] to access that value to pass to a toast message. However, I am unable to compile due to the following error
Value of type 'Any' has no subscripts
Can anybody explain what is happening here and why I am unable to capture this value?
Your first step should be to learn Swift and forget about any assumptions into which Ruby may have led you. Ruby and Swift are opposites. Ruby has no typing ("duck typing") and any variable can adopt any value. Swift has strict typing and a variable must declare its type at the outset and can never change that type.
You can supply a type by casting the response result to a definite type. For instance, you might cast it to [String:Any]. That's a dictionary (similar to Ruby hash) and can be subscripted.
It would be better, however, to decode the response content into a struct with a message property and status property, each of those being properly typed, rather than settling for the typeless responseJSON. You shouldn't say Any unless you really have to, and you rarely have to.

XCTests and PHPhotoLibrary

I have functionality in the app that saved picture in photo gallery. An I wonder how to test this code:
func saveInPhotoGallery() {
guard self.cameraOutput != nil else { return }
if self.cameraOutput is UIImage {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAsset(from: (self.cameraOutput as? UIImage)!)
}, completionHandler: { (saved, error) in
guard error == nil else {
self.unsucessfullSavingOperation(error)
return
}
})
}
}
Let's assume now that I want to test in my case scenario that self.cameraOutput is and UIImage and sth went wrong and there is an error in completionHandler so I ended up in self.unsucessfullSavingOperation(error) method. This has separate tests of course, but what I want to cover is:
Make sure whenever something will went wrong with inserting image in Camera Roll I will end up calling this method
And when I try to call saveInPhotoGallery() in test target it produce Alert that this require access to your photo library (doh!). But there is a way to skip this alert in Unit Tests or check whanever it popup and press allow? (like I said, for this test, let's assume that I have this permissions)
Or there is a way to mock this behaviour?
Yes, I'd mock PHPhotoLibrary. The main thing you'll need to replace is your use of PHPhotoLibrary.shared() which creates a dependency to a concrete instance. Instead, we can depend on an abstraction, that is, a protocol.
Then production code can supply PHPhotoLibrary.shared() as the instance to use. Test code can supply a mock object.
Let me know if you need more elaboration on breaking the dependency, or on making a mock object, or both.

Swift 3 and JSON – Updating the database by running a URL in the background

1. Clicking the link causes a database update.
There is a certain link I have access to (let's pretend it's www.google.com), such that when I open it up in my browser, it updates a certain section of the JSON code in my database. Based on the numbers that make up a portion of the link, it adjusts a certain value in the data.
2. How do I run this link in the background of my iOS app?
I need to be able to "open" this link within the app, without actually opening up a UIWebview and visually visiting the site. I just need this JSON data inside the database to update on its own (with the user unaware that it even happened).
The problem I'm having here is that I simply don't know how this is done. How do I cause this link to be visited without opening up a Safari browser?
The best approach I've found for such functions is to treat them as if they were "AJAX" (or "REST", "API", etc.) - while these terms are often used (and for more seasoned programmers instantly give a certain thought), the end result is that they take information from your 'originator' and send to the 'server' for processing, which then replies with a 'response'. Once you get that concept in your head, this becomes a fairly simple activity.
(for our example, I will call this "API", as that does really suit {as #Mortiz suggested} this question best)
For Swift 3, there are several ways to do this, I'll show you two I've found and use for various functions:
DispatchQueue
For a 'one-time shot to a url that I know exists and will connect reliability', this is a good one to use (think of it as a 'quick-n-dirty' if you like....!)
DispatchQueue.global().async {
let data = try? Data(contentsOf: theURL!) //make sure your url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
// do stuff here with the data if you need
// you can get the response from the server and parse it out, set buttons in the app, etc.
}
}
Alamofire
For Swift 3, Alamofire is extremely popular and does a lot of great stuff. Check it out if you haven't already!
Alamofire.request("\(theURL!)").responseJSON { response in
print("result is ", response.result)
switch response.result {
case .success(let value):
// do stuff with the returned data
// like updating your internal database, etc.
print(value)
case .failure(let error):
print("There was an error")
// you can see the error response in various ways....
print(requested)
print(error)
print(response)
print(response.result)
}
}
Once you have your buttons in place (from your description it sounds like that is what your #1 is about), then in the function you call when it is clicked, drop in the code from above and 'do stuff' as you need.
This will make the update to the server automatically in the background (answering your #2) - the user won't notice anything unless there are connection issues to the internet, etc. (much too complex to get into here, though if you expect to have much of it, Alamofire is a great choice as it automatically retries, etc. (part of the great features you should check out)
A key piece of this is that you can take the response from the URL (send various bits of JSON data back from the server, then break it back down in the phone) and do 'whatever' with it.
Some things you can do (to hopefully give you more ideas - which is just about anything.....):
update data in the app (local storage, local variables, etc.)
update text (color, background) inside Buttons or Labels
process Alerts to the user (not your case, but sometimes you want to let them know what went on - certainly if it was an error in updating your server)
change Images (various things)
switch Views
Well, the list is as long as "things you can do in an app", so decide for yourself what you need to mod/update - this is "the" way to do it!
You could also use the UIWebView without ever showing it, like this (Swift 3):
func webView() {
let theWebView: UIWebView
theWebView = UIWebView(frame: UIScreen.main.bounds)
theWebView.delegate = self
if let theURL = URL(string: "your URL") {
let request = URLRequest(url: theURL)
theWebView.loadRequest(request)
}
}
Just don't add it to the view.

Reading Websites in iOS

I am trying to read data from my API. I am not using JSON data because the API doesn't return an array, just a line of text. Anyways, I am using the following code to read the text from the API.
func contactVetApi(url:String){
let nsUrl = NSURL(string:url)
let task = NSURLSession.sharedSession().dataTaskWithURL(nsUrl!){
(data, response, error) in
print(data)
}
task.resume()
}
I am calling this function in the ViewDidLoad function of my ViewController file. As you can see, it takes a parameter that is a string. The parameter is the URL to read. It then translates the string into a NSUrl so it can be used with the sharedSession. I then initialize the shared session and create a data task with that url. I then print out the data it returns. The only issue is that the output isn't what I am expecting. What I am expecting is for it to say, "Future home of something quite cool." Although, this is what I am getting.
Optional(<46757475 72652068 6f6d6520 6f662073 6f6d6574 68696e67 20717569 74652063 6f6f6c>)
Optional(<46757475 72652068 6f6d6520 6f662073 6f6d6574 68696e67 20717569 74652063 6f6f6c>)
I need help figuring out why it is printing that out instead of what I am expecting. In case it is needed, the api url is http://apis.wilsonfamily5.org/vet/about.php. Before anybody asks though, I did add into the info.plist file the disabling of the iOS 9 app transport security. If you need any extra information to help me solve this problem, I would be more then happy to give it to you. I want to thank you in advance.
You currently are printing a NSData object, which will always look like that jibberish. What you actually want however is to convert the NSData to a NSString or String to create a human readable form:
var dataAsString = NSString(data: data, encoding: NSUTF8StringEncoding)
Taken from this answer.

Resources