Handling Asynchronous Call by Alamofire - ios

I am using Swift 3 with XCode 8
I am pretty new to IOS development and using Swift. I am currently having a problem where some required code does not run after the asynchronous call has successfully been completed.
In my constants file:
typealias DownloadComplete = () -> ()
In my WeatherVC.swift file:
var currentWeather = CurrentWeather()
override func viewDidLoad() {
super.viewDidLoad()
TableView.delegate = self
TableView.dataSource = self
currentWeather.downloadWeatherDetails{
//setup UI to load downloaded data
print("Done 2")
self.updateMainUI()
}
}
In my CurrentWeather.swift class:
func downloadWeatherDetails(completed: #escaping DownloadComplete){
//Alamofire download
let currentWeatherURL = URL(string: CURRENT_WEATHER_URL)!
Alamofire.request(currentWeatherURL).responseJSON { response in
let result = response.result
if let dict = result.value as? Dictionary<String, AnyObject>{
if let name = dict["name"] as? String{
self._cityName = name.capitalized
print(self._cityName)
}
if let weather = dict["weather"] as? [Dictionary<String, AnyObject>]{
if let main = weather[0]["main"] as? String{
self._weatherType = main.capitalized
print(self._weatherType)
}
}
if let main = dict["main"] as? Dictionary<String, AnyObject>{
if let currentTemperature = main["temp"] as? Double {
let kelvinToCelsius = currentTemperature - 273.15
self._currentTemp = kelvinToCelsius
print(self._currentTemp)
}
}
}
print("Done 1")
}
completed() //Make sure to tell download is done
}}
When Executing the code, "Done 2" is being printed out first, before "Done 1", when I want it to be the other way around.
How can I fix this? (FYI: Following Weather App Tutorial on Udemy)

Update: It was just simply moving completed inside the responseJSON closure.

Related

Swift: Trying to set a ui button title using a completion handler

I am trying to use data from firebase to populate buttons on the UI. Everything works as expected except the button title is not updating. Any ideas on how to fix this?
#IBAction func addNewTapped(_ sender: Any) {
readOneDay2(lastMonday(trackerDate), completion: { message in
let lastHourRead = message
print(message)
self.lastHour1.setTitle(lastHourRead, for: UIControl.State.application)
})
}
func readOneDay2 (_ mydate: Date, completion: #escaping (_ message: String) -> Void){
var db: DatabaseReference!
db = Database.database().reference()
var totalComb: Double = 0.0
let userID = Auth.auth().currentUser?.uid
db.child("TimesheetData").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let dict = snapshot.value as? NSDictionary
for (key, value) in dict! {
let myvalue = value as? [String: Any]
let compDate: String = myvalue!["timeSheetDate"]! as! String
if compDate == dateStringer(mydate) {
let sHours: String = myvalue!["hours"]! as! String
let sOverTime: String = myvalue!["overTime"]! as! String
let sDoubleTime: String = myvalue!["doubleTime"]! as! String
let dHours: Double = Double(sHours)!
let dOverTime: Double = Double(sOverTime)!
let dDoubleTime: Double = Double(sDoubleTime)!
totalComb = totalComb + dHours + dOverTime + dDoubleTime
print(key)
}
}
print("First Sum " + String(totalComb))
DispatchQueue.main.async {
completion(String(totalComb))
}
}) { (error) in
print(error.localizedDescription)
}
}
As per #Virender said,
Change UIControl.State.application to UIControl.State.normal
DispatchQueue.main.async {
self.lastHour1.setTitle(lastHourRead, for:.normal)
}

IOS : message doesn't appear immediately in tableview from firebase database

when i add first message it's appear immediately in the tableview but next messages don't appear i have to back and return to the same chat to get them
retrieve messages method
my database
viewDidLoad
`
func fetchMessage(key1:String){
let mydatabase = FIRDatabase.database().reference().child("message")
let query = mydatabase.queryOrderedByKey().queryEqual(toValue: key1)
query.observe(.childAdded) { (snapshot) in
for dict in snapshot.children{
let childsnap = dict as! FIRDataSnapshot
let dict2 = childsnap.value as! [String: Any]
var message = chatmssage()
let body = dict2["MesageBody"]! as? String
let email = dict2["from"]! as? String
message.body = body
message.email = email
self.chatmessagearr.append(message)
self.mytableview.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
mytableview.delegate = self
mytableview.dataSource = self
self.tabBarController?.tabBar.isHidden = true
messageTXTF.delegate = self
mytableview.separatorStyle = .none
let curID = (user!["id"] as? String)!
let toID = self.reciever.uid!
key = (curID < toID ? curID+toID : toID+curID )
fetchMessage(key1:key!)
}
You should call self.myTableView.reloadData() from the main thread after your fetch completes. Like this,
DispatchQueue.main.async {
self.myTableView.reloadData()
}

Synchronized code in iOS/Swift [duplicate]

This question already has answers here:
How to return value from Alamofire
(5 answers)
Closed 5 years ago.
I am new with iOS programming. I am trying to make a piece of code in my function be synchronized, but it doesn't seem to work:
func fetchLocationsList(searchText:String)->Array<String> {
print ("searched text:\(searchText)")
let url = URL(string:"http://api.openweathermap.org/data/2.5/find?q=\(searchText)&type=like&sort=name&cnt=9&APPID=a33aa72")
//Using Alamofire to handle http requests
Alamofire.request(url!).responseJSON {response in
guard let jsonResponse = response.result.value as? [String:Any]
else { print ("error in json response")
return}
guard let list = jsonResponse["list"] as? NSArray else {return}
let lockQueue = DispatchQueue(label:"Et.My-Weather-App.queue1")
_ = lockQueue.sync{
for index in 0..<list.count {
print ("index is: \(index)")
guard let listElement = list[index] as? [String:Any] else {return}
let id = listElement["id"] as! Int
print ("id is: \(id)")
let cityName = listElement["name"] as! String
print ("cityName is: \(cityName)")
let sys = listElement["sys"] as! [String:Any]
let country = sys["country"] as! String
print ("country is: \(country)")
let element = "\(cityName), \(country), \(id)"
print ("\(element)")
self.resultsArray.append(element)
}
}
}
if self.resultsArray.count==0 {
print ("results array is also zero!")
}
return self.resultsArray
}
When I run it, I see that the line "results array is also zero!" is printed before the "for" loop fills the resultArray with elements, so the returned resultArray is always empty!
What am I doing wrong?
I suggest you do this as async tasks are a pain and this works quite well.
func fetchLocationsList(searchText:String, completion: #escaping (_ results:Array<String>)->()){
print ("searched text:\(searchText)")
let url = URL(string:"http://api.openweathermap.org/data/2.5/find?q=\(searchText)&type=like&sort=name&cnt=9&APPID=a33aa72")
//Using Alamofire to handle http requests
Alamofire.request(url!).responseJSON {response in
guard let jsonResponse = response.result.value as? [String:Any] else { print ("error in json response"); return}
guard let list = jsonResponse["list"] as? Array<Dictionary<String,Any>> else { return }
var array = Array<String>() // create an array to store results.
for item in list {
let id = item["id"] as! Int
let cityName = item["name"] as! String
let sys = item["sys"] as! Dictionary<String,Any>
let country = sys["country"] as! String
let element = "\(cityName), \(country), \(id)"
array.append(element) // add to that array.
}
completion(array) //send the array via the completions handler.
}
}
So in your viewDidLoad or whatever.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
fetchLocationsList(searchText: "Whatever this string is") { (results) in
self.resultsArray.append(contentsOf: results)
// Then do anything else you need to do after this function has completed within this closure.
}
}

Completion handler not working as expected with Dispatch Group and Concurrent Queue

i've created a Dispatch group in which three concurrent queues are running and then notifies group for update, which is going good and all this i've put in a function with completion handler.now issue i'm facing is completion handler get called before queue execution completes .how can i resolve this, please advice?
func loadCompaniesFromSynch(_ data: Data, completionHandler: #escaping(String) -> ())
{
var companyFile = ""
companies = [Company]()
let batchGroup = DispatchGroup()
let queue = DispatchQueue(label: "Batch Queue", qos: .background, attributes: .concurrent)
if !FileManager.default.fileExists(atPath: self.fileMgr.getDocumentPath()) {
self.fileMgr.createFileDirectory(self.constants!.APP_FOLDER)
}
companyFile = self.fileMgr.getDocumentFilePath(self.fileMgr.getCacheData(constants!.COMPANIES_LAST_SYNCH_DATE) as! String)
let dataOld: Data = try! Data(contentsOf: URL(fileURLWithPath: companyFile),options: NSData.ReadingOptions.uncached)
let oldCompanies: NSArray! = (try? JSONSerialization.jsonObject(with: dataOld, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [[String:Any]] as NSArray!
let newCompanyObj: NSDictionary? = (try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers)) as? NSDictionary
var company: Company?
if newCompanyObj?.count > 0 {
if let companies = oldCompanies
{
for com in companies as! [[String: AnyObject]]
{
company = Company()
company!.orgCode = com["ORG_CODE"] as? String
company!.orgDescription = com["ORG_DESCRIPTION"] as? String
if let btlOrg = com["OBX_BTL_CODE"] as? String
{
company!.orgBtlCode = btlOrg
company!.orgBtlDescription = com["BTL_DESCRIPTION"] as? String
}
company!.orgStatus = com["ORG_STATUS"] as! String?
self.companies!.append(company!)
company = nil
}
}
print("loadCompaniesFromSynch >> oldCompanies >>\(oldCompanies.count) Comapnies Count \(self.companies!.count)")
var dataDict = Dictionary<String,String>()
if let json = newCompanyObj as NSDictionary!
{
if let companies = json["RESULTS"] as? NSDictionary
{
if let companiesNew = companies["COMPANIES"] as? [[String: AnyObject]]
{
// for com in companiesNew
let addArray = companiesNew.filter { $0["ORG_STATUS"] as! String == ComapnyStatus.ADD.rawValue}
let deleteArray = companiesNew.filter { $0["ORG_STATUS"] as! String == ComapnyStatus.DELETED.rawValue}
let updateArray = companiesNew.filter { $0["ORG_STATUS"] as! String == ComapnyStatus.UPDATE.rawValue}
print(addArray.count)
print(deleteArray.count)
print(updateArray.count)
var addCompanies: [Company]?
var updateCompanies:[Company]?
var comapnySet = Set(self.companies!)
batchGroup.enter()
queue.async(group: batchGroup)
{
if (addArray.count > 0 )
{
addCompanies = [Company]()
for (index,item) in addArray.enumerated()
{
let company = self.returnComapnyOjectfromDictionary(item as NSDictionary)
addCompanies!.append(company)
print("add loop----\(index)")
}
}
batchGroup.leave()
}
batchGroup.enter()
queue.async(group: batchGroup) {
if updateArray.count > 0
{
updateCompanies = [Company]()
for (index,item) in updateArray.enumerated()
{
let company = self.returnComapnyOjectfromDictionary(item as NSDictionary)
updateCompanies!.append(company)
print("update loop----\(index)")
}
}
batchGroup.leave()
}
batchGroup.enter()
queue.async(group: batchGroup) {
for (_,item) in deleteArray.enumerated()
{
let company = self.returnComapnyOjectfromDictionary(item as NSDictionary)
_ = self.removeObject(&self.companies!,object: company)
print("looop2")
}
batchGroup.leave()
}
batchGroup.notify(queue: .global(qos: .background))
{
if updateCompanies?.count == updateArray.count{
//self.companies = Array(comapnySet)
print("count before \(self.companies?.count)")
comapnySet.subtract(Set(updateCompanies!))
self.companies = Array(comapnySet)
// self.companies = Array(comapnySet.intersection(Set(updateCompanies!)))
print("after delete \(self.companies?.count)")
self.companies!.append(contentsOf: updateCompanies!)
print("update array count \(updateArray.count) ----- and update Companies count --\(self.companies?.count)")
updateCompanies = nil
}
if addCompanies?.count == addArray.count
{
self.companies!.append(contentsOf: addCompanies!)
print("add array count \(addArray.count) ----- and add Companies count --\(addCompanies?.count)")
addCompanies = nil
}
}
batchGroup.wait()
}
}
//**Below code is executed before queue completion**
if let status = json["STATUS"] as? String
{
dataDict[self.constants!.defaultsKeys.RESPONSE_STATUS] = status
}
if let message = json["MESSAGE"] as? String
{
dataDict[self.constants!.defaultsKeys.RESPONSE_MESSAGE] = message
}
}
var newCompanyArray:Array<AnyObject> = []
var dict = Dictionary<String,String>()
for cmp in self.companies!
{
dict["ORG_CODE"] = cmp.orgCode
dict["ORG_DESCRIPTION"] = cmp.orgDescription
dict["OBX_BTL_CODE"] = cmp.orgBtlCode
dict["BTL_DESCRIPTION"] = cmp.orgBtlDescription
dict["ORG_STATUS"] = cmp.orgStatus
newCompanyArray.append(dict as AnyObject)
}
let isValidJson = JSONSerialization.isValidJSONObject(newCompanyArray)
if newCompanyArray.count > 0 && isValidJson
{
let companyCount = newCompanyArray.count - oldCompanies.count
let replaceComCount = self.utility!.replace(self.constants!.logs.LOG_COMPANY_SYNC_END,originalString: "<COUNT>",withString: "\(companyCount)")
self.parser!.setLogValueToXml(replaceComCount, logType:
self.constants!.logs.LOG_TYPE_ACTIVITY, fileLogType: "")
let dataFinal:Data = try! JSONSerialization.data(withJSONObject: newCompanyArray, options: [])
self.fileMgr.removeFile(self.fileMgr.getDocumentFilePath(self.fileMgr.getCacheData(self.constants!.COMPANIES_LAST_SYNCH_DATE) as! String))
let compniesFileName = "Companies_\(self.dateUtil.getCurrentDateTime())" //logic is to be use in synch
self.fileMgr.setCacheData(compniesFileName as AnyObject, key: self.constants!.COMPANIES_LAST_SYNCH_DATE)
self.fileMgr.writeFile(NSString(data: dataFinal, encoding: String.Encoding.utf8.rawValue)!,fileName :self.fileMgr.getCacheData(self.constants!.COMPANIES_LAST_SYNCH_DATE) as! String,documentDir:self.fileMgr.getDocumentPath())
}
}
completionHandler(companyFile)
}
DispatchQueue.global(qos: .background).async
{
self.loadCompaniesFromSynch(jNsData, completionHandler:
{
companyFile in
if !companyFile.isEmpty
{
self.doPropertySync()
}
else
{
}
})
}
You are mixing up a lot of things. You are trying to get notified, but also you are trying to wait.
Your completion()handler and the code marked with
//Below code is executed before queue completion"
is called/running outside your notification block...
AFAICS, there is no reason to use notify or wait whithin loadCompaniesFromSynch(), as you do do not call any async tasks in it.
How I understand, what you want to do is to do your heavy io stuff in the background. In this case, delete all the DispatchGroup stuff and dispatch the hole function. No need to wait/notify, as you are using a completion handler. Check the following to get the idea:
func loadCompaniesFromSynch(_ data: Data, completionHandler: #escaping(String) -> ()) {
// dispatch the whole thing to the global background queue
DispatchQueue.global(qos: .background).async {
// your original code with all DispatchGroup stuff deleted
// call completion handler on main queue, so the caller does not have to care
DispatchQueue.main.async {
completionHandler(companyFile)
}
}
}
self.loadCompaniesFromSynch(jNsData) {
companyFile in
// do stuff after loadCompaniesFromSynch finished
}
Hope this helps.

iOS Swift App, Openweather API

I'm making a weather app using OpenweatherAPI. I download data from web, but when I want to pass it to another class it show as nil. Here is the code:
So that's the class I'm getting data from the API
var currentTemp: Double {
if _currentTemp == nil {
_currentTemp = 120.0
}
return _currentTemp
}
func downloadWeatherDetails(completed: #escaping DownloadComplete) {
Alamofire.request(CURRENT_WEATHER_URL).responseJSON { response in
let result = response.result
if let dict = result.value as? Dictionary<String, Any> {
if let main = dict["main"] as? Dictionary<String, Any> {
if let currentTemperature = main["temp"] as? double_t {
let kelvinToCelsiusPreDivision = (currentTemperature - 273.15)
let kelvinToCelsius = Double(round((10 * kelvinToCelsiusPreDivision) / 10))
self._currentTemp = kelvinToCelsius
print(self._currentTemp)
}
}
}
completed()
} }
And the mainVC - ViewController:
func updateMainUI() {
currentTempLabel.text = "\(currentWeather.currentTemp)"
}
I'm of course calling updateMainUI in ViewDidLoad, but IMHO I think the method that updates UI is probably called before getting the data from the JSON.
The Label on the App shows 120 - so it is considered as nil...
Sorry about parenthesis if there is something wrong.
Thanks for help in advice :)
EDIT:
Ohh and I forgot to add.. The data from API is perfectly fine, so the call to API is working :)
// I Hope that helps
func downloadWeatherDetails(completed: #escaping DownloadComplete) {
//Download Current Weather Data
Alamofire.request(CURRENT_WEATHER_URL).responseJSON { response in
let result = response.result
if let dict = result.value as? Dictionary<String, AnyObject> {
if let main = dict["main"] as? Dictionary<String, AnyObject> {
if let currentTemperature = main["temp"] as? Double {
let kelvinToFarenheitPreDivision = (currentTemperature * (9/5) - 459.67)
let kelvinToFarenheit = Double(round(10 * kelvinToFarenheitPreDivision/10))
self._currentTemp = kelvinToFarenheit
print(self._currentTemp)
}
}
}
completed()
}
}

Resources