Changing AlamoFire Config - ios

In a class my project is using have a var to store the alamofire manager:
var alamoManager: Manager!
A method is called repeatedly in the app to config this manager like so:
func configAlamoManager() {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.timeoutIntervalForRequest = 20
//ETC
alamoManager = Alamofire.Manager(configuration: configuration)
}
I have a HTTP call in my app that is ocasiaonally returning a 999 canceled error code. I suspect this is because the manager currently trying to perform the request is replaced by another one from the configAlamoManager() method. Is there any way to just change the config settings in the manager without creating a new instance? alamoManager.session.configuration has no setter. Any pointers on this would be really appreciated! Thanks

Instead of changing the configuration and creating a new Manager, you should override the configuration in the actual NSURLRequest.
let urlRequest = NSURLRequest(url: url)
urlRequest.timeoutInterval = 20
Alamofire.request(urlRequest).responseJSON { response in
debugPrint(response)
}
For more info on what you can override using an NSURLRequest, I'd check out the docs.

Related

Using URLCache subclasses with URLSession

I have an app which uses URLSession-based networking and URLCache for storing network requests on disk. I noticed that when the storage size of URLCache reaches the diskCapacity, the eviction strategy seems to be to remove all entries, which is a problem in my use case. So I decided to write an URLCache subclass which would provide a custom storage for cached responses and which would implement LRU eviction strategy with better control.
As URLCache's documentation states, subclassing for this purpose should be supported:
The URLCache class is meant to be used as-is, but you can subclass it when you have specific needs. For example, you might want to screen which responses are cached, or reimplement the storage mechanism for security or other reasons.
However, I ran into problems with trying to use this new URLCache subclass with URLSession networking.
I have a test resource which I fetch using HTTP GET. The response headers contain:
Cache-Control: public, max-age=30
Etag: <some-value>
When using the standard, non-subclassed URLCache, the first request loads the data from network as expected (verified with HTTP proxy). The second request doesn't go to the network, if done within first 30 seconds, as expected. Subsequent requests after 30 seconds cause conditional GETs with Etag, as expected.
When using a URLCache subclass, all requests load the data from network - max-age doesn't seem to matter, and no conditional GETs are made.
It seems that the URLCache does something special to the CachedURLResponse instances after they're loaded from its internal storage, and this something affects how URLSession handles the HTTP caching logic. What am I missing here?
I've written a very minimal URLCache subclass implementation to demonstrate this problem. This class stores and loads CachedURLResponse instances using NSKeyedArchiver / NSKeyedUnarchiver, and it supports only zero or one response. Note that there are no calls to super - this is by design, since I want to use my own storage.
class CustomURLCache: URLCache {
let cachedResponseFileURL = URL(filePath: NSTemporaryDirectory().appending("entry.data"))
// MARK: Internal storage
func read() -> CachedURLResponse? {
guard let data = try? Data(contentsOf: cachedResponseFileURL) else { return nil }
return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! CachedURLResponse
}
func store(_ cachedResponse: CachedURLResponse) {
try! (try! NSKeyedArchiver.archivedData(withRootObject: cachedResponse, requiringSecureCoding: false)).write(to: cachedResponseFileURL)
}
// MARK: URLCache Overrides
override func cachedResponse(for request: URLRequest) -> CachedURLResponse? {
read()
}
override func getCachedResponse(for dataTask: URLSessionDataTask, completionHandler: #escaping (CachedURLResponse?) -> Void) {
completionHandler(read())
}
override func storeCachedResponse(_ cachedResponse: CachedURLResponse, for request: URLRequest) {
store(cachedResponse)
}
override func storeCachedResponse(_ cachedResponse: CachedURLResponse, for dataTask: URLSessionDataTask) {
store(cachedResponse)
}
}
My test case:
func test() {
let useEvictingCache = false
let config = URLSessionConfiguration.default
if useEvictingCache {
config.urlCache = CustomURLCache()
} else {
config.urlCache = URLCache(memoryCapacity: 0, diskCapacity: 1024 * 1024 * 100)
}
self.urlSession = URLSession(configuration: config)
let url = URL(string: "https://example.com/my-test-resource")!
self.urlSession?.dataTask(with: URLRequest(url: url), completionHandler: { data, response, error in
if let data {
print("GOT DATA with \(data.count) bytes")
} else if let error {
print("GOT ERROR \(error)")
}
}).resume()
}
Tested on iOS 16.2.
Received a response for my question at Apple's Developer Forums from Quinn “The Eskimo”:
My experience is that subclassing Foundation’s URL loading system classes puts you on a path of pain [1]. If I were in your shoes, I’d do your custom caching above the Foundation URL loading system layer.
[1] Way back in the day the Foundation URL loading system was implemented in Objective-C and existed within the Foundation framework. In that world, subclasses mostly worked. Shortly thereafter — and I’m talking before the introduction of NSURLSession here — the core implementation changed languages and moved to CFNetwork. Since then, the classes you see in Foundation are basically thin wrappers around (private) CFNetwork types. That’s generally OK, except for the impact on subclassing.
So it sounds like one should read the URLCache documentation re: subclassing with a grain of salt.

Alamofire background upload fails

I have a working Alamofire upload request using the following:
Alamofire.upload(multipartFormData: { multipartFormData in
...
}
Now I want the request to be launched in background, as it's embedded in a Share extension.
Looking at Alamofire page, I simply added:
let configuration = URLSessionConfiguration.background(withIdentifier: "com...")
let sessionManager = Alamofire.SessionManager(configuration: configuration)
sessionManager.upload(multipartFormData: { multipartFormData in
...
}
Now when executed I instantly get:
Operation couldn't complete. NSURLErrorDomain error -995
I can't find any reference to this error and what it means. Any ideas? Thanks a lot!

Create a unique identifer in a webview

Go easy on me I am new to I OS and Swift :). I am trying to create a IOS app using swift. I have a web view display that is working correctly, displaying the website. YAY!!
What I need to do now is create a unique identifier that is stored locally and when the app is opened is sent to the remote server. I see i can use this...
UIDevice.currentDevice().identifierForVendor!.UUIDString
However i would like to store it locally for future use and send it to the remote server every time the app is opened. I have done research on this and have come upon answers for other objects just not a web view.
If someone knows of a tutorial or example code for this solution i would greatly appreciate it.
UPDATE
let uuid = UIDevice.currentDevice().identifierForVendor!.UUIDString
and for the url im using
let url= NSURL (string:"https://example.com");
Could i do something like this? Or like it?
let url= NSURL (string:"https://example.com");
let requestobj= NSURLRequest(URL:url! ADD VAR HERE? );
Where ADD VAR HERE is the uuid to pass to the server which i can catch with a php script?
Latest update..
Im having a hard time integrating that into my existing code. Where would be the best place to put it?
import UIKit
class ViewController: UIViewController {
let uuid = UIDevice.currentDevice().identifierForVendor!.UUIDString
#IBOutlet weak var WebView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = NSURL (string: "https://example.com");
let requestObj = NSURLRequest(URL: url?)
WebView.loadRequest(requestObj);
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here is the answer i was looking for. Thanks for your help everyone!
let device_uuid = UIDevice.currentDevice().identifierForVendor!.UUIDString
let api_host = "https://example.com?uuid=" + device_uuid
let url = NSURL(string: api_host)
let req = NSURLRequest(URL: url!)
WebView.loadRequest(req);
Apparently what i needed to do was build my URL into a variable. Then i can structure it using the NSURL and use it from there. This guide helped me. Just ignore the ruby on rails part if that's not what your doing.
http://ericlondon.com/2015/12/09/sending-messages-between-a-swift-webview-and-a-rails-backend-using-javascript.html
You will need to check on the webserver side to confirm exactly what you need to pass in - but if you are developing that side as well, then you should have control :-)
Should be something like this - please not that you don't need ; in swift
let request= NSURLRequest(URL:url)
var bodyData = "myUUID=\(uuid)&otherData=value1"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
Keep in mind that this identifier will change if a user uninstalls the application. If you need to persist it then I'd recommend to store it on the keychain so the id is always the same for the same phone even if the app is uninstalled.
Check this other question: How to preserve identifierForVendor in ios after uninstalling ios app on device?

Download images via AlamofireImage framework with Content-Type: 'binary/octet-stream'

I try to download images from Amazon S3 server via AlamofireImage framework.
The Images on the S3 server, save with 'Content-Type' = 'binary/octet-stream'.
In the beginning I got the Error:
Failed to validate response due to unacceptable content type.
So, I tried to change/update the HTTP Header's request in order to support with binary/octet-stream'
I updated the method:
private func URLRequestWithURL(URL: NSURL) -> NSURLRequest
In the UIImageView+AlamofireImage.swift file to:
private func URLRequestWithURL(URL: NSURL) -> NSURLRequest {
let mutableURLRequest = NSMutableURLRequest(URL: URL)
mutableURLRequest.addValue("binary/octet-stream", forHTTPHeaderField: "Content-Type")
return mutableURLRequest
}
And is still not working, Just after I added the:
let contentTypes: Set<String> = ["Content-Type", "binary/octet-stream"]
Request.addAcceptableImageContentTypes(contentTypes)
The problem was solved, But I really don't like the fact that I changed a private method in the AlamofireImage framework.
I wonder if there is an elegant way to solve this problem, given I can't change the images 'Content-Type' in the S3 server.
Thanks
Doing Request.addAcceptableImageContentTypes(["binary/octet-stream"]) should be all that you need to get it to work.
If you were using af_setImageWithURL, there was a bug that it wasn't using the acceptableImageContentTypes. AlamofireImage 2.2.0 fixes that.
In Swift 3.2 it's slightly different.
let request = URLRequest(url: URL)
DataRequest.addAcceptableImageContentTypes(["binary/octet-stream"])
AlamoDownloader.shared.imageDownloader.download(request){ response in
DataRequest is an Alamofire public class.

Create a Class that Makes HTTPRequest in Swift

I am making an app that has the need to make HTTP Request in many of its ViewControllers.
I ended up copy and pasting these codes in to each of the ViewControllers and listen to the delegates callback of NSURLConnectionDelegate and NSURLConnectionDataDelegate
func makeRequest()
{
//Base64
var username = "testUsername";
var password = "testPassword";
var loginString = NSString(format: "%#:%#", username, password);
var loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!;
var base64LoginString = loginData.base64EncodedStringWithOptions(nil);
var url: NSURL = NSURL(string: self.HTTP_REQUEST_STRING)!;
var urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: url);
urlRequest.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization");
urlRequest.HTTPMethod = "POST";
urlConnection = NSURLConnection(request: urlRequest, delegate: self)!;
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!)
{
self.resultData.appendData(data);
}
func connectionDidFinishLoading(connection: NSURLConnection!)
{
//Do Something
}
func connection(connection: NSURLConnection, didFailWithError error: NSError)
{
//Do Something
}
I am wondering if there is a better approach than this, rather than copy and pasting the codes into every ViewControllers?
Is it possible to put these codes into a class? But then, how do we know if the connection has finished loading?
I am sorry for this question, I lack the knowledge of good Object Oriented design.
Thank you
To be up to date, you should be using NSURLSession as your request class. You're not necessarily required to listen in for the delegation callbacks as there is a closure callback that will provide you with and error and data according to how you configured your session. Regarding placement, it depends on your code and what you want. You can place this code in the viewController if it makes sense, some people create proxy classes to make all their requests and report statuses back to them. It all depends on flexibility, robustness and structure of your application. If you're making the same network request from 3 different viewControllers, it's likely that your should be placing the network request in a type of proxy class to prevent duplicate code. Look into the proxy and singleton design patterns if you'd like to know more about code design.
Here's a nice tutorial on NSURLSession to get you started:
Raywenderlich tutorial
Proxy Design Pattern
Singleton Design Pattern
Like others have mentioned, you have far more learning to do than a single answer on SO can provide. In the mean time, take a look at Alamofire, a fairly comprehensive networking library built around NSURLSession. In it you will find code for creating requests and much more. Alamofire provides a prebuilt, OO way of performing network requests, suitable for one off requests or an entire network manager class.

Resources