Trying to cache with Alamofire with no results - swift

I'm trying to use Alamofire Cache, so my desired behavior would be to load the first time from web and next times if data is present on cache return it without make more requests...after I'll think about expiration...
My code returns always response nil...any suggestions on this? Thanks
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
config.URLCache = NSURLCache.sharedURLCache()
config.requestCachePolicy = NSURLRequestCachePolicy.ReturnCacheDataElseLoad
let manager = Alamofire.Manager(configuration: config)
manager.request(.GET, url, parameters: parameters, encoding: .URL, headers: ["Token":LocalStorage.read("token") as! String, "Version":"2"]).responseJSON { (response) in
if let json = response.result.value
{
if response.response?.statusCode == 200
{
completionHandler(parser.parseSwifty(JSON(json)), nil)
}
else
{
completionHandler(nil, serverErrorMessage)
}
}
else
{
completionHandler(nil, networkErrorMessage)
}
}

So...I resolved this issue:
in my NetworkHelper I added a computed property like so:
static let manager: Manager = {
let memoryCapacity = 100 * 1024 * 1024; // 100 MB
let diskCapacity = 100 * 1024 * 1024; // 100 MB
let cache = NSURLCache(memoryCapacity: memoryCapacity, diskCapacity: diskCapacity, diskPath: "shared_cache")
let configuration: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = ["Token":LocalStorage.read("token") as! String, "Version":"2"]
configuration.requestCachePolicy = .ReturnCacheDataElseLoad // this is the default
configuration.URLCache = cache
return Manager(configuration: configuration)
}()
and when I have to retrieve some data:
self.manager.request(.GET, url, parameters: parameters, encoding: .URL)
.response { (request, response, data, error) in
debugPrint(request)
debugPrint(response)
debugPrint(error)
}.responseJSON......
I also write a func to preload some data like this:
static func preloadGetRequest(url : String, parameters: Dictionary<String, String>?)
{
self.manager.request(.GET, url, parameters: parameters, encoding: .URL)
.response { (request, response, data, error) in
debugPrint(request)
debugPrint(response)
debugPrint(error)
}
}
Try and let me know :)

It seems the correct way is to create Session Manager early on before calling the request or else the request would always returns nil.
var sessionManager: SessionManager!
override func viewDidLoad() {
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .returnCacheDataElseLoad
sessionManager = SessionManager(configuration: configuration)
}
//later on
#objc private func refreshWeatherData(_ sender: Any) {
// Fetch Weather Data
sessionManager.request("https://jsonplaceholder.typicode.com/posts")
.response { (data) in
self.refreshControl?.endRefreshing()
if let data = data.data {
let json = try? JSON(data: data)
}
}
}
or create a URLRequest with caching policy like so
#objc private func refreshWeatherData(_ sender: Any) {
// Fetch Weather Data
let request = URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/posts")!, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 100)
Alamofire.request(request)
.response { (data) in
self.refreshControl?.endRefreshing()
if let data = data.data {
let json = try? JSON(data: data)
}
}
}

Related

Alamofire and TableView/CollectionView

I created a reusable Alamofire request which works smoothly. I am trying to get the data from the request Decode it(works fine too) and append it to one of my arrays to display in tableView/collectionView.
I am using MVVM and I append my data in viewModel(you can see below). The thing is I have tableView in my viewController and inside my tableView methods( viewForSupplementaryElementOfKind for instance) the 'stories'(from viewModel) are always empty.
In my viewDidLoad method I call getMainPageData(from viewModel) first and then create my tableView. I assure you the request itself is a success, the only problem is displaying the data.
Please keep in mind that the project has many API calls so I need a solution which will work in all cases when I have to deal with "lists". Thank you in advance
class NetworkManager {
let keychain = KeychainManager()
let base = "SomeBase"
let storageManager = StorageManager()
func setupRequest(path: Paths, method: RequestMethod, body: Encodable? = nil, params: [String: Any]? = nil, header: HeaderType, completion: #escaping((Result<Data,NetworkError>) -> Void)) {
var queries = ""
if let params = params {
queries = params.passingQuery()
}
let url = URL(string: base + path.rawValue + queries)
var request = URLRequest(url: url!)
request.httpMethod = method.rawValue
request.setValue(header.value[0].value, forHTTPHeaderField: "Content-Type")
if let userToken = keychain.getAccessToken(), userToken.count > 0 {
request.setValue("Bearer " + userToken, forHTTPHeaderField: "Authorization")
}
if let body = body {
if let jsonData = body.toJSONData() {
request.httpBody = jsonData
}
}
AF.request(request).validate().responseJSON { response in
if (200...299) ~= response.response?.statusCode ?? -1 {
self.handlingHeaders(response: response)
completion(.success(response.data!))
} else {
do {
if let data = response.data {
let json = try JSONDecoder().decode(ErrorResponse.self, from: data)
completion(.failure(.responseError(json.message)))
}
} catch {
completion(.failure(.serverError))
}
}
}
}
private func handlingHeaders(response: AFDataResponse<Any>) {
let headers = response.response?.headers
if let accessToken = headers?.dictionary["Authorization"] {
keychain.saveToken(token: accessToken)
}
}
}
extension Encodable {
func toJSONData() -> Data? { try? JSONEncoder().encode(self) }
}
var stories = [Story]()
func getMainPageData(completion: #escaping(Result<Void, NetworkError>) -> ()) {
networkManager.setupRequest(path: .mainPageData, method: .get, body: nil, params: nil, header: .application_json) { [self] result in
switch result {
case .success(let data):
do {
let homePageData = try JSONDecoder().decode(MainPageResponse.self, from: data)
stories.append(contentsOf: homePageData.model.stories)

Alamofire: How to conditionally change cache policy based on network status?

The end result I would like to achieve is to use cached data when the network is unavailable and load data from the server when the network is available.
The closest thing I've found is this thread, but I am still having trouble getting it to work using Alamofire.
How to cache response in iOS and show only when internet is not available?
I have two classes, one to check the network status and another that configures an Alamofire url session. Detecting the network status seems to be working, but it's not changing the session configuration. What am I doing wrong? Thanks!
NetworkReachability.shared.startNetworkMonitoring() is run when the app loads in didFinishLaunchingWithOptions.
class NetworkReachability {
static let shared = NetworkReachability()
let reachabilityManager = NetworkReachabilityManager(host: "www.google.com")
func startNetworkMonitoring() {
reachabilityManager?.startListening(onUpdatePerforming: { status in
switch status {
case .notReachable:
ApiManager.configuration.requestCachePolicy = .returnCacheDataDontLoad
case .reachable(.cellular):
ApiManager.configuration.requestCachePolicy = .reloadIgnoringCacheData
case .reachable(.ethernetOrWiFi):
ApiManager.configuration.requestCachePolicy = .reloadIgnoringCacheData
case .unknown:
print("Unknown network state")
}
})
}
}
A custom api manager to configure the cache.
class ApiManager {
static let shared = ApiManager()
static let configuration = URLSessionConfiguration.af.default
public let sessionManager: Alamofire.Session = {
let responseCacher = ResponseCacher(behavior: .modify { _, response in
let userInfo = ["date": Date()]
return CachedURLResponse(
response: response.response,
data: response.data,
userInfo: userInfo,
storagePolicy: .allowed)
})
return Session(
configuration: configuration,
cachedResponseHandler: responseCacher)
}()
}
I'm writing my APIClient here which I've written using Alamofire.
import Alamofire
class NetworkLogger: EventMonitor {
let queue = DispatchQueue(label: "com.ketan.almofire.networklogger")
func requestDidFinish(_ request: Request) {
print(request.description)
}
func request<Value>(
_ request: DataRequest,
didParseResponse response: DataResponse<Value, AFError>
) {
guard let data = response.data else {
return
}
if let json = try? JSONSerialization
.jsonObject(with: data, options: .mutableContainers) {
print(json)
}
}
}
class APIClient {
private var manager = Session()
init() {
configureSession()
}
private func manageCachePolicy() {
if NetworkReachabilityManager()?.isReachable ?? false {
manager.sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
} else {
manager.sessionConfiguration.requestCachePolicy = .returnCacheDataElseLoad
}
}
private func configureSession() {
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .returnCacheDataElseLoad
if NetworkReachabilityManager()?.isReachable ?? false {
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
}
manager = Session(configuration: configuration, eventMonitors: [NetworkLogger()])
///When we don't want network logs
// manager = Session(configuration: configuration)
}
// MARK: - Request
func requestData(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil,
completion: #escaping (Result<Data, ErrorResult>) -> Void) {
manageCachePolicy()
manager.request(convertible,
method: method,
parameters: parameters,
encoding: encoding,
headers: headers).validate().responseData
{ (response) in
switch response.result {
case .success(let data):
completion(.success(data))
case .failure(let error):
completion(.failure(.network(string: error.localizedDescription)))
}
}
}
}

How to save the result of a #escaping closure function into a variable?

I currently have a class which is below:
class Anton {
//URL to web service (Internal)
let URL_DISPLAY_MENU = "http://192.168.1.100/api/DisplayMenu.php"
func displayMenu(completion: #escaping ([[String:Any]]) ->()) {
let requestURL = URL(string: URL_DISPLAY_MENU)
var request = URLRequest(url: requestURL!)
request.httpMethod = "POST"
var menu: [[String:Any]]?
let task = URLSession.shared.dataTask(with: request) { data,response,error in guard let data = data, error == nil else {
print("error=\(String(describing: error))")
return
}
do {
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
} else {
menu = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] ?? []
var dictionary = [Int:Any]()
for (index,item) in menu!.enumerated() {
let uniqueID = index
dictionary[uniqueID] = item
}
completion(menu!)
}
} catch let error as NSError {
print(error)
}
}
task.resume()
}
}
At the moment I use the class & contained function as follows:
var anton = Anton()
anton.displayMenu { menu in
print(menu)
}
What I want to do is have a way of saving the #escaping result into a global variable. I'm very new to escaping closures so not sure how to go about this.
You can save your value directly in completion.
Example:
var anton = Anton()
anton.displayMenu { menu in
self.myMenu = menu
}

function with dataTask returning a value

I wan't to check if my url statusCode equals to 200, I created a function returning a Boolean if the statusCode equals to 200, I'm using a dataTask, but I don't know how to return a value:
class func checkUrl(urlString: String) -> Bool{
let urlPath: String = urlString
var url: NSURL = NSURL(string: urlPath)!
var request: NSURLRequest = NSURLRequest(url: url as URL)
var response: URLResponse?
let session = Foundation.URLSession.shared
var task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let data = data{
print("data =\(data)")
}
if let response = response {
print("url = \(response.url!)")
print("response = \(response)")
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
if httpResponse.statusCode == 200{
return true
} else {
return false
}
}
})
task.resume()
}
The returns in if else are returning an error:
Unexpected non-void return value in void function
in order to return value you should use blocks. Try declaring your function like this:
class func checkUrl(urlString: String, finished: ((isSuccess: Bool)->Void) {
let urlPath: String = urlString
var url: NSURL = NSURL(string: urlPath)!
var request: NSURLRequest = NSURLRequest(url: url as URL)
var response: URLResponse?
let session = Foundation.URLSession.shared
var task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let data = data{
print("data =\(data)")
}
if let response = response {
print("url = \(response.url!)")
print("response = \(response)")
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
if httpResponse.statusCode == 200{
finished(isSuccess: true)
} else {
finished(isSuccess: false)
}
}
})
task.resume()
}
And then call it like this:
checkUrl("http://myBestURL.com", finished { isSuccess in
// Handle logic after return here
})
Hope that this will help.
Consider semaphore if you want to keep your original return pattern.
func checkUrl(urlString: String) -> Bool {
if let url = URL(string: fileUrl) {
var result: Bool!
let semaphore = DispatchSemaphore(value: 0) //1. create a counting semaphore
let session = URLSession.shared
session.dataTask(with: url, completionHandler: { (data, response, error) in
result = true //or false in case
semaphore.signal() //3. count it up
}).resume()
semaphore.wait() //2. wait for finished counting
return result
}
return false
}
Swift4, work in my case
Try to add guard let data = data else { return } in dataTask like:
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
print("get some data")
}.resume()
You're returning a value from a Void function that is the completionHandler closure of dataTask(_:, _:)
Regarding your code, there is something wrong: you can't return that value because it's executed on a different thread, it's an asynchronous operation. Please take a look at this thread: Returning data from async call in Swift function

URL request using Swift

I have access the "dictionary" moviedb for
example : https://www.themoviedb.org/search/remote/multi?query=exterminador%20do%20futuro&language=en
How can i catch only the film's name and poster from this page to my project in Swift ?
It's answer :)
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
reload()
}
private func reload() {
let requestUrl = "https://www.themoviedb.org/search/remote/multi?query=exterminador%20do%20futuro&language=en"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let request = NSURLRequest(URL: NSURL(string: requestUrl)!)
let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
if let error = error {
println("###### error ######")
}
else {
if let JSON = NSJSONSerialization.JSONObjectWithData(data,
options: .AllowFragments,
error: nil) as? [NSDictionary] {
for movie in JSON {
let name = movie["name"] as! String
let posterPath = movie["poster_path"] as! String
println(name) // "Terminator Genisys"
println(posterPath) // "/5JU9ytZJyR3zmClGmVm9q4Geqbd.jpg"
}
}
}
})
task.resume()
}
}
You need to include your api key along with the request. I'd just try something like this to see if it works or not. If it does, then you can go about using the api key in a different way to make it more secure. I wouldn't bother as it's not an api with much sensitive functionality.
let query = "Terminator+second"
let url = NSURL(string: "http://api.themoviedb.org/3/search/keyword?api_key=YOURAPIKEY&query=\(query)&language=‌​en")!
let request = NSMutableURLRequest(URL: url)
request.addValue("application/json", forHTTPHeaderField: "Accept")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { data, response, error in
if let response = response, data = data {
print(response)
//DO THIS
print(String(data: data, encoding: NSUTF8StringEncoding))
//OR THIS
if let o = NSJSONSerialization.JSONObjectWithData(data, options: nil, error:nil) as? NSDictionary {
println(dict)
} else {
println("Could not read JSON dictionary")
}
} else {
print(error)
}
}
task.resume()
The response you'll get will have the full list of properties. You need the poster_path and title (or original_title) property of the returned item.