Get header when enumerate an array - swift

i try to get header informations about remote files which url are located in an array.
My function to get header works perfectly when called alone but doesn't when inside array enumerate.
Here my functions:
func getHeaderInformations (myUrl: URL, completion: #escaping (_ content: String?) -> ()) {
var request = URLRequest(url: myUrl)
request.httpMethod = "HEAD"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil, let reponse = response as? HTTPURLResponse, let contentType = reponse.allHeaderFields["Content-Type"],let contentLength = reponse.allHeaderFields["Content-Length"]
else{
completion(nil)
return
}
let content = String(describing: contentType) + "/" + String(describing: contentLength)
completion(content)
}
task.resume()
}
// download picture
func downloadPic (){
let minimumSize=UserDefaults.standard.object(forKey: "Minimum_size") as! Int64
var imgExtension:String=""
var type:String=""
var size:Int64=0
for (_,item) in LiensImgArrayURL.enumerated() {
getHeaderInformations(myUrl: item, completion: { content in
let myInfoArray = content?.components(separatedBy: "/")
type=(myInfoArray?[0])!
imgExtension=(myInfoArray?[1])!
size=Int64((myInfoArray?[2])!)!
})
if (type=="image" && size>=minimumSize) {
SaveFileToDirectory(myRemoteUrl: item, myExtension: imgExtension)
}
}
}
How can i write well this code for "getHeaderInformations" works and return good values inside the func "downLoadPic"?
Thanks for your answers...

Since getHeaderInformations works asynchronously put the code to save the file in the closure.
If you don't need the index in the for loop you don't need enumerated() either.
I tweaked the code bit to get rid of ugly question and exclamation marks and parentheses.
for item in LiensImgArrayURL {
getHeaderInformations(myUrl: item, completion: { content in
if let myInfoArray = content?.components(separatedBy: "/") {
type = myInfoArray[0]
imgExtension = myInfoArray[1]
size = Int64(myInfoArray[2])!
if type == "image" && size >= minimumSize {
SaveFileToDirectory(myRemoteUrl: item, myExtension: imgExtension)
}
}
})
}

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)

Function runs twice if nested async calls are executed and once otherwise. Need help pre-determining when this will happen

func handleGetAllPhotoURLs is called from the line below and I have confirmed that the line of code only executes once with breakpoints.
_ = FlickrClient.getAllPhotoURLs(currentPin: self.currentPin, fetchCount: fetchCount, completion: self.handleGetAllPhotoURLs(pin:urls:error:))
According to output from my print statements, the function runs twice because it prints two lines of output if urls.count is non-zero. However, if urls.count is zero then I only get one print statement that states "urls.count ---> 0"
handleGetAllPhotoURLs ---> urls.count ---> 0 //this line is always printed
handleGetAllPhotoURLs ---> urls.count ---> 21 //this line is only printed if the urls parameter is not empty
func handleGetAllPhotoURLs(pin: Pin, urls: [URL], error: Error?){
print("handleGetAllPhotoURLs ---> urls.count ---> \(urls.count)")
let backgroundContext: NSManagedObjectContext! = dataController.backGroundContext
if let error = error {
print("func mapView(_ mapView: MKMapView, didSelect... \n\(error)")
return
}
let pinId = pin.objectID
backgroundContext.perform {
let backgroundPin = backgroundContext.object(with: pinId) as! Pin
backgroundPin.urlCount = Int32(urls.count)
try? backgroundContext.save()
}
for (index, currentURL) in urls.enumerated() {
URLSession.shared.dataTask(with: currentURL, completionHandler: { (imageData, response, error) in
guard let imageData = imageData else {return}
connectPhotoAndPin(dataController: self.dataController, currentPin: pin , data: imageData, urlString: currentURL.absoluteString, index: index)
}).resume()
}
}
In addition, I have a UILabel that only reveals itself when urls.count is zero and I only want to reveal it when urls is empty.
Right now, if urls is not empty, the app is very quickly flashing the empty message UILabel. Which now makes sense to me because print statement shows that urls array is temporarily empty.
Is there a way for me to determine to avoid flashing the empty message UILabel to user when urls.count is non-zero?
edit: Added code below based on request. The function below is called to obtain [URL] in completion handler. Then the completion handler is fed into:
func handleGetAllPhotoURLs(pin: Pin, urls: [URL], error: Error?)
class func getAllPhotoURLs(currentPin: Pin, fetchCount count: Int, completion: #escaping (Pin, [URL], Error?)->Void)-> URLSessionTask?{
let latitude = currentPin.latitude
let longitude = currentPin.longitude
let pageNumber = currentPin.pageNumber
let url = Endpoints.photosSearch(latitude, longitude, count, pageNumber).url
var array_photo_URLs = [URL]()
var array_photoID_secret = [[String: String]]()
var array_URLString = [String]()
var array_URLString2 = [String]()
var count = 0
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let dataObject = data, error == nil else {
DispatchQueue.main.async {
completion(currentPin, [], error)
}
return
}
do {
let temp = try JSONDecoder().decode(PhotosSearch.self, from: dataObject)
temp.photos.photo.forEach{
let tempDict = [$0.id : $0.secret]
array_photoID_secret.append(tempDict)
let photoURL = FlickrClient.Endpoints.getOnePicture($0.id, $0.secret)
let photoURLString = photoURL.toString
array_URLString.append(photoURLString)
getPhotoURL(photoID: $0.id, secret: $0.secret, completion: { (urlString, error) in
guard let urlString = urlString else {return}
array_URLString2.append(urlString)
array_photo_URLs.append(URL(string: urlString)!)
count = count + 1
if count == temp.photos.photo.count {
completion(currentPin, array_photo_URLs, nil)
}
})
}
completion(currentPin, [], nil)
return
} catch let conversionErr {
DispatchQueue.main.async {
completion(currentPin, [], conversionErr)
}
return
}
}
task.resume()
return task
}
In the do block, you are calling completion twice. Please see the correction,
do {
let temp = try JSONDecoder().decode(PhotosSearch.self, from: dataObject)
if temp.photos.photo.isEmpty == false {
temp.photos.photo.forEach{
let tempDict = [$0.id : $0.secret]
array_photoID_secret.append(tempDict)
let photoURL = FlickrClient.Endpoints.getOnePicture($0.id, $0.secret)
let photoURLString = photoURL.toString
array_URLString.append(photoURLString)
getPhotoURL(photoID: $0.id, secret: $0.secret, completion: { (urlString, error) in
guard let urlString = urlString else {return}
array_URLString2.append(urlString)
array_photo_URLs.append(URL(string: urlString)!)
count = count + 1
if count == temp.photos.photo.count {
completion(currentPin, array_photo_URLs, nil)
}
})
}
} else {
completion(currentPin, [], nil)
}
return
}

Array is null after setting data in it

I have a JSON request that gets data from the Darksky API, I get the data properly and it is showing on the screen. However, When i'm trying to set the data from the array I get from the JSON call in another array, it stays empty.
This is my code:
just declaring the array:
var mForecastArray = [Weather]()
this is the function that calls the API:
func getForecast(){
Weather.forecast(withLocation: "37.8267,-122.4233") { (arr) in
DispatchQueue.main.async {
self.mForecastArray = arr
self.mTodayWeather = arr[0]
self.mCollectionView.reloadData()
}
}
}
The weird part is that it does work, and the data do shows on screen, but still, mForecastArray seems null.
This is the API call itself:
static func forecast(withLocation location: String, completion: #escaping ([Weather]) -> ()){
let url = basePath + location
let request = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
var forecastArray: [Weather] = []
if let data = data{
do{
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any]{
if let dailyForecast = json["daily"] as? [String:Any]{
if let dailyData = dailyForecast["data"] as? [[String:Any]]{
for dataPoint in dailyData{
if let weatherObject = try? Weather(json: dataPoint){
forecastArray.append(weatherObject)
}
}
}
}
}
}catch{
print(error.localizedDescription)
}
completion(forecastArray)
}
}
task.resume()
}
It's a visual asynchronous illusion.
The static method forecast works asynchronously.
Most likely your code looks like
getForecast()
print(self.mForecastArray)
This cannot work because the array is populated much later.
Move the print line into the completion handler of the static method
func getForecast(){
Weather.forecast(withLocation: "37.8267,-122.4233") { (arr) in
DispatchQueue.main.async {
self.mForecastArray = arr
print(self.mForecastArray)
self.mTodayWeather = arr[0]
self.mCollectionView.reloadData()
}
}
}

Accessing a variable outside a given function

I tried the completion handler as suggested by Gurdev and got something. It does return the values of my array in the MAIN function. But the issue is the same: I have to use Sleep() function for 3 seconds for the HTTP request to complete. This is like hard coding as it could take less or more than 3 seconds to complete. When I remove the Sleep() command, I end up returning a VOID array.
The relevant code is pated below.
Thanks!
--------Web Class--------
import Foundation
import UIKit
class Web {
var ar1 = [Double]()
var ar2 = [Double]()
func getData(str: String, completion: (_ result: [[Double]]) -> Void) {
let request = NSMutableURLRequest(url: URL(string: str)!)
httpGet(request as URLRequest!){
(data, error) -> Void in
if error != nil {
print(error ?? "Error")
} else {
let delimiter = "\t"
let lines:[String] = data.components(separatedBy: CharacterSet.newlines) as [String]
for line in lines {
var values:[String] = []
if line != "" {
values = line.components(separatedBy: delimiter)
let str1 = (values[0])//FIRST COLUMN
let str2 = (values[1])//SECOND COLUMN
let db1 = NumberFormatter().number(from: str1)?.doubleValue
self.ar1.append(db1!)
let db2 = NumberFormatter().number(from: str2)?.doubleValue
self.ar2.append(db2!)
}
}
}
}//end of request
sleep(3) // This delay sends data to MAIN, Otherwise NOT
let dta = [self.ar1, self.ar2]
completion(dta)
}
func httpGet(_ request: URLRequest!, callback: #escaping (String, String?) -> Void) {
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
if error != nil {
callback("", error!.localizedDescription)
} else {
let result = NSString(data: data!, encoding:
String.Encoding.ascii.rawValue)!
callback(result as String, nil)
}
})
task.resume()
}
}
--------Web Class--------
-------Call In Class--------
Web().getData (str: "https://dl.dropboxusercontent.com/s/f6j7w7zeqaavzqw/s02.txt?dl=0")
{
(result: [[Double]]) in
let x = result[0]
let y = result[1]
}
-------Call In Class--------
Essentially, I am trying to access my variable "ar1" at a certain point in my code but I cannot. I am trying to return the value too but it returns NULL at that point.
What is wrong here ?
I tried like the below code :
import Foundation
import UIKit
class Web {
var ar1 = [Double]()
var ar2 = [Double]()
func getData(str: String) -> [Double] {
let request = NSMutableURLRequest(url: URL(string: str)!)
httpGet(request as URLRequest!){
(data, error) -> Void in
if error != nil {
print(error ?? "Error")
} else {
let delimiter = "\t"
let lines:[String] = data.components(separatedBy: CharacterSet.newlines) as [String]
for line in lines {
var values:[String] = []
if line != "" {
values = line.components(separatedBy: delimiter)
let str1 = (values[0])//FIRST COLUMN
let str2 = (values[1])//SECOND COLUMN
let db1 = NumberFormatter().number(from: str1)?.doubleValue
self.ar1.append(db1!)
let db2 = NumberFormatter().number(from: str2)?.doubleValue
self.ar2.append(db2!)
}
}
dump (self.ar1) // "ar1" accessible HERE (returns all elements)
}
}//end of request
//BUT I WANT TO RETURN "ar1" HERE !
// So that I can use it in my MAIN class
dump (self.ar1) // "ar1" not accessible here (returns ZERO elements)
return self.ar1
}
func httpGet(_ request: URLRequest!, callback: #escaping (String, String?) -> Void) {
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
if error != nil {
callback("", error!.localizedDescription)
} else {
let result = NSString(data: data!, encoding:
String.Encoding.ascii.rawValue)!
callback(result as String, nil)
}
})
task.resume()
}
}
In your code you are returning the ar1 from the return statement of your method whereas the value to ar1 is being set in Aysnchronous callback completion handler. You should use the completion handler block where you are trying to access the value of ar1. HTTPGet function is running in Async mode and value in ar1 is set in the callback handler block.
Let me know if you need any further help on this.

Requests and handlers in swift

I need to get data from url. For it I have get method in "HTTPClient" class.
func getRequest(url: String, parameters: String = "", completion: (NSData) -> ()) {
var request = NSMutableURLRequest(URL: NSURL(string: url)!)
if parameters != "" {
request = NSMutableURLRequest(URL: NSURL(string: url + "?" + parameters)!)
}
request.HTTPMethod = "GET"
request.HTTPShouldHandleCookies = true
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
(data, response, error) in
if error != nil {
print(error?.localizedDescription)
return
} else {
completion(data!)
}
}
task.resume()
}
But when I call it from "MainService" class I don't get data in handler. (I don't come in handler)
func getAvailableCoins() -> [Coin]? {
var coins = [Coin]?()
httpClient.getRequest("http://shapeshift.io/getcoins", completion: { data in
coins = self.shapeShiftService.availableCoins(data)
})
return coins
}
What problem can be there?
I just realised that you return from your getAvailableCoins() method BEFORE your handler gets a chance to be called. Consequently you return an empty array.
So basically the thing is that getAvailableCoins() is an asynchronous operation and you can't use return statement to return result. What you could do is declare a special method and call it when your coins are fetched:
func getAvailableCoins() {
var coins = [Coin]?()
httpClient.getRequest("http://shapeshift.io/getcoins", completion: { data in
coins = self.shapeShiftService.availableCoins(data)
coinsAreReady(coins)
})
}
func coinsAreReady(coins: [Coin]?) {
//do what you need with your coins here
}
Now coinsAreReady will be called when your data is loaded and you can work with it from there.