How to make an observe at the moment in Swift? - ios

I have a problem in this code:
func calculaGastos() -> Float{
let idUser = Auth.auth().currentUser?.displayName
var total : Float = 0
let ref = Database.database().reference().child("Users").child(idUser!).child("Gastos")
ref.observeSingleEvent(of: .value) { (snapshot) in
let value = snapshot.value as? NSDictionary
if(value != nil){
for i in value!{
let j = i.value as? NSDictionary
let precio = j?["precio"] as? Float
let fecha = j?["Fecha"] as? String
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM d, yyyy"
let date = dateFormatter.date(from: fecha!)
if(((date?.timeIntervalSinceNow)! * -1) < (30*24*3600)){
print("Total:" ,total)
total += precio!
}
}
}
print("Calculing in the function", total)
}
return total
}
Which is called in a override function in another view controller and the logs shows that in the print of the viewdidload is 0, but in the function print is show that is printed as 30 but return 0 all time, I believe that the problem is that it returns before enter in the observer but I'm not sure any solutions for this?
override func viewDidLoad() {
super.viewDidLoad()
nomUser.text = id?.displayName!
correoLabel.text = id?.email!
print("Calculing in View Controller", calculo.calculaBenef(), calculo.calculaGastos())
gastosField.text = String(calculo.calculaGastos())
benefField.text = String(calculo.calculaBenef())
// Do any additional setup after loading the view.
}
Here is my log:
Log

Within an app I'm currently working on, I ran into a similar issue. The solution was to implement a dispatch group in the function. I also changed the way your function returns total so that it's now being returned by a completion handler.
Try this instead:
func calculaGastos(completionHandler: #escaping (Float) -> Void){
let idUser = Auth.auth().currentUser?.displayName
var total : Float = 0
let ref = Database.database().reference().child("Users").child(idUser!).child("Gastos")
ref.observeSingleEvent(of: .value) { (snapshot) in
let value = snapshot.value as? NSDictionary
if(value != nil){
let myGroup = DispatchGroup()
for i in value!{
myGroup.enter()
let j = i.value as? NSDictionary
let precio = j?["precio"] as? Float
let fecha = j?["Fecha"] as? String
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM d, yyyy"
let date = dateFormatter.date(from: fecha!)
if(((date?.timeIntervalSinceNow)! * -1) < (30*24*3600)){
print("Total:" ,total)
total += precio!
}
myGroup.leave()
}
myGroup.notify(queue: .main) {
print("Calculing in the function", total)
completionHandler(total)
}
}}
}
Call the function and use total like this:
override func viewDidLoad() {
super.viewDidLoad()
nomUser.text = id?.displayName!
correoLabel.text = id?.email!
print("Calculing in View Controller", calculo.calculaBenef())
calculo.calculaGastos { (total) in
print("calculaGastos total: \(total)")
gastosField.text = String(total)
benefField.text = String(calculo.calculaBenef())
}
// Do any additional setup after loading the view.
}
To my understanding:
observeSingleEvent is asynchronous, so it may or may not complete by the time return is called. Additionally, the for i in value starts only after observeSingleEvent is complete, so return is even more likely to be called before the tasks are completed. That's where DispatchGroup() and the completion handler come in.
When myGroup.enter() is called, it notifies DispatchGroup that a task has started. When myGroup.leave() is called, DispatchGroup is notified that the task has been completed. Once there have been as many .leave()s as .enter()s, the group is finished. Then myGroup notifies the main queue that the the group is finished, and then the completionHandler is called which returns total.
The completionHandler is also beneficial because of the way in which you're using calculaGastos. You're calling the function, and then you're using the return value to be displayed in a textField. Now that the completion handler is added, the textField.text is only being set after calculaGastos is complete and has returned total:
calculo.calculaGastos { (total) in
print("calculaGastos total: \(total)")
gastosField.text = String(total)
benefField.text = String(calculo.calculaBenef())
}
Hope that makes some sense! Glad the code worked for you.

Related

My weather data is not showing up in my view controller

I can't figure out why my view controller is not showing the data, even though I can see it in the output window.
Output:
Muḩāfaz̧at Al Jīzah
Clear
88.0
my code:
override func viewDidLoad() {
super.viewDidLoad()
loadCurrentWeather = currentWeatherData()
loadCurrentWeather.downloadWeatherData {
//setting uo UI to download data
self.updateTodayUI()
}
}
func updateTodayUI() {
locationLabel.text = loadCurrentWeather.cityName
weatherTypeLabel.text = loadCurrentWeather.weatherType
currentTempLabel.text = "\(loadCurrentWeather.currentTemp)"
weatherTypeImage.image = UIImage(named: loadCurrentWeather.weatherType)
}
My view controller in Xcode:
My view controller on iphone:
currentweatherData the code where I'm downloading the data form.
import UIKit
import Alamofire
class currentWeatherData {
var cityNameone: String!
var dateone: String!
var weatherTypeone: String!
var currentTempone: Double!
var cityName: String {
if cityNameone == nil {
cityNameone = ""
}
return cityNameone
}
var date: String {
if dateone == nil {
dateone = ""
}
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .none
let currentDate = dateFormatter.string(from: Date())
self.dateone = "Today, \(currentDate)"
return dateone
}
var weatherType: String{
if weatherTypeone == nil{
weatherTypeone = ""
}
return weatherTypeone
}
var currentTemp: Double {
if currentTempone == nil {
currentTempone = 0.0
}
return currentTempone
}
func downloadWeatherData(completed: DownloadComplete){
// to tell alamofire where to download the data
let weatherURL = URL (string: currentWeatherURL)!
Alamofire.request(weatherURL).responseJSON{ response in
let result = response.result
if let dictionary = result.value as? Dictionary<String, AnyObject>{
if let name = dictionary["name"] as? String {
self.cityNameone = name.capitalized
print(self.cityNameone ?? "No city name")
}
if let weather = dictionary["weather"] as? [Dictionary<String, AnyObject>]{
if let main = weather[0]["main"] as? String {
self.weatherTypeone = main.capitalized
print(self.weatherTypeone ?? "No weather type")
}
}
if let main = dictionary["main"] as? Dictionary<String, AnyObject> {
if let currentTemperature = main["temp"] as? Double {
let kelvintoFarenheit = (currentTemperature * (9/5) - 459.67)
let totalKelvinToFarenheit = Double(round(10 * kelvintoFarenheit/10))
self.currentTempone = totalKelvinToFarenheit
print(self.currentTempone ?? .nan)
}
}
}
}
completed()
}
}
Is problem with my code or my view controller? Is it something wrong with my constraints?
I can't seem to figure it out.
You are calling completed too early - before the JSON response arrives. You have to call it inside the closure of the responseJSON call instead:
Alamofire.request(weatherURL).responseJSON { response in
let result = response.result
// ...
completed()
}
I cannot see all of your code to troubleshoot, but you may have a concurrency issue. Try putting the call to updateTodayUI inside of viewDidLoad(_:) inside of an async block like this:
DispatchQueue.main.async {
updateTodayUI()
}
You can find more information on dispatch queues and concurrency in the documentation.

Swift Firebase Multithreading Issue

I'm trying to run a couple of for loops inside of a function that should return an array of strings.
Where I'm having trouble is with getting the correct results BEFORE the next for loop is run...and then again returning that results BEFORE I need to return the array of strings to complete the function.
In the first case, I have a for loop that's getting data from Firebase. I was able to use a dispatch group to get the value to print out - but then with the other loop after this - I was having issues from using the dispatch group in the prior task.
The code all works perfectly if executed with the correct values but I'm not sure how to go about this with regards to threading. Would really appreciate any help.
func findTopSpots() -> [String] {
var topFive = [String]()
var locationRatingDictionary = [String:Double]()
let myGroup = DispatchGroup()
let locationsArray = ["wyoming", "kansas", "arkansas", "florida", "california"]
// Use the days to find the most common month
let calendar = NSCalendar.current
var monthArray = [String]()
var date = self.departureDate!
let endDate = self.returnDate!
// Formatter for printing the month name
let fmt = DateFormatter()
fmt.dateFormat = "MMMM"
// Add each days month to an array
while date <= endDate {
date = calendar.date(byAdding: .day, value: 1, to: date)!
monthArray.append(fmt.string(from: date))
}
// Return the primary month from function
let primaryMonth = findMostCommonMonthInArray(array: monthArray).lowercased()
// Create a dictionary of location:rating for the primary month
for doc in locationsArray {
self.db.collection("locations").document(doc).collection("historic").document(primaryMonth).getDocument { (document, err) in
if let document = document, document.exists {
let rating = document["rating"] as? Double
locationRatingDictionary[doc] = rating
} else {
print("Document does not exist")
}
}
}
//---- THE CODE BELOW WILL NOT PRINT WITH ANY VALUES ----//
print(locationRatingDictionary)
// Sort the tuple array by rating
let locationRatingTupleArray = locationRatingDictionary.sorted{ $0.value > $1.value }
// Return 5 results
for (location,rating) in locationRatingTupleArray.prefix(5) {
print(location,rating)
topFive.append(location)
}
print("top five are \(topFive)")
return topFive
}
The issue here is that the firebase returns with query results asynchronously and you are not waiting for it to return.
I can see that you have instantiate DispatchGroup but have not used it. Lets try to use it to solve your issue. Also, you would need to change the method signature to take a closure. This avoids blocking thread to return function output.
func findTopSpots(completionHandler:([String])->Void) {
var topFive = [String]()
var locationRatingDictionary = [String:Double]()
let myGroup = DispatchGroup()
let locationsArray = ["wyoming", "kansas", "arkansas", "florida", "california"]
// Use the days to find the most common month
let calendar = NSCalendar.current
var monthArray = [String]()
var date = self.departureDate!
let endDate = self.returnDate!
// Formatter for printing the month name
let fmt = DateFormatter()
fmt.dateFormat = "MMMM"
// Add each days month to an array
while date <= endDate {
date = calendar.date(byAdding: .day, value: 1, to: date)!
monthArray.append(fmt.string(from: date))
}
// Return the primary month from function
let primaryMonth = findMostCommonMonthInArray(array: monthArray).lowercased()
// Create a dictionary of location:rating for the primary month
for doc in locationsArray {
myGroup.enter() self.db.collection("locations").document(doc).collection("historic").document(primaryMonth).getDocument { (document, err) in
if let document = document, document.exists {
let rating = document["rating"] as? Double
locationRatingDictionary[doc] = rating
} else {
print("Document does not exist")
}
myGroup.leave()
}
}
myGroup.notify(queue:.main) {
//---- THE CODE BELOW WILL NOT PRINT WITH ANY VALUES ----//
print(locationRatingDictionary)
// Sort the tuple array by rating
let locationRatingTupleArray = locationRatingDictionary.sorted{ $0.value > $1.value }
// Return 5 results
for (location,rating) in locationRatingTupleArray.prefix(5) {
print(location,rating)
topFive.append(location)
}
print("top five are \(topFive)")
completionHandler(topFive)
}
}
Your code is asynchronous fastest way is dispatchGroup with completion
//
func findTopSpots(completion:#escaping(_ arr:[string])->void){
let dispatchGroup = DispatchGroup()
var topFive = [String]()
var locationRatingDictionary = [String:Double]()
let locationsArray = ["wyoming", "kansas", "arkansas", "florida", "california"]
// Use the days to find the most common month
let calendar = NSCalendar.current
var monthArray = [String]()
var date = self.departureDate!
let endDate = self.returnDate!
// Formatter for printing the month name
let fmt = DateFormatter()
fmt.dateFormat = "MMMM"
// Add each days month to an array
while date <= endDate {
date = calendar.date(byAdding: .day, value: 1, to: date)!
monthArray.append(fmt.string(from: date))
}
// Return the primary month from function
let primaryMonth = findMostCommonMonthInArray(array: monthArray).lowercased()
// Create a dictionary of location:rating for the primary month
for doc in locationsArray {
dispatchGroup.enter()
self.db.collection("locations").document(doc).collection("historic").document(primaryMonth).getDocument { (document, err) in
if let document = document, document.exists {
let rating = document["rating"] as? Double
locationRatingDictionary[doc] = rating
} else {
print("Document does not exist")
}
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
//---- THE CODE BELOW WILL NOT PRINT WITH ANY VALUES ----//
print(locationRatingDictionary)
// Sort the tuple array by rating
let locationRatingTupleArray = locationRatingDictionary.sorted{ $0.value > $1.value }
// Return 5 results
for (location,rating) in locationRatingTupleArray.prefix(5) {
print(location,rating)
topFive.append(location)
}
print("top five are \(topFive)")
completion(topFive)
}
}

Solve memory issue with migrating a big bunch of data

In my previous version of my app I stored all user data into .dat files (with NSKeyedArchiver), but in my new version I want to upgrade to a real(m) database.
I'm trying to import all of this data (and that can be a LOT) into Realm. But it's taking so much memory that the debugger eventually kill my app before the migration has finished. The 'strange' thing is that the data on hard disk is only 1.5 mb big, but it's taking memory for more than 1gb so I'm doing something wrong.
I also tried to work with multiple threads, but that didn't help. Well it speeded up the migration process (which is good), but it also took the same amount of memory.
Who can help me out? See my code below for more information..
FYI Async can be found here https://github.com/duemunk/Async
import Async
let startDate = NSDate(timeIntervalSince1970: 1388534400).startOfDay // Start from 2014 jan 1st
let endDate = NSDate().dateByAddingTimeInterval(172800).startOfDay // 2 days = 3600 * 24 * 2 = 172.800
var pathDate = startDate
let calendar = NSCalendar.currentCalendar()
let group = AsyncGroup()
var allPaths = [(Int, Int)]()
while calendar.compareDate(pathDate, toDate: endDate, toUnitGranularity: .Month) != .OrderedDescending {
// Components
let currentMonth = calendar.component(.Month, fromDate: pathDate)
let currentYear = calendar.component(.Year, fromDate: pathDate)
allPaths.append((currentYear, currentMonth))
// Advance by one month
pathDate = calendar.dateByAddingUnit(.Month, value: 1, toDate: pathDate, options: [])!
}
for path in allPaths {
group.background {
// Prepare path
let currentYear = path.0
let currentMonth = path.1
let path = (Path.Documents as NSString).stringByAppendingPathComponent("Stats_\(currentMonth)_\(currentYear).dat")
print(path)
if NSFileManager.defaultManager().fileExistsAtPath(path) {
NSKeyedUnarchiver.setClass(_OldStatisticsDataModel.self, forClassName: "StatisticsDataModel")
if let statistics = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [_OldStatisticsDataModel] {
// Loop through days
for i in 1...31 {
let dateComponents = NSDateComponents()
dateComponents.year = currentYear
dateComponents.month = currentMonth
dateComponents.day = i
dateComponents.hour = 0
dateComponents.minute = 0
// Create date from components
let userCalendar = NSCalendar.currentCalendar() // user calendar
guard let date = userCalendar.dateFromComponents(dateComponents) else {
continue
}
// Search for order items
let filtered = statistics.filter {
if let date = $0.date {
let dateSince1970 = date.timeIntervalSince1970
return date.startOfDay.timeIntervalSince1970 <= dateSince1970 && date.endOfDay.timeIntervalSince1970 >= dateSince1970
}
return false
}
if filtered.isEmpty == false {
// Create order
let transaction = Transaction()
transaction.employee = Account.API().administratorEmployee()
let order = Order()
order.status = PayableStatus.Paid
order.createdDate = date.timeIntervalSince1970
order.paidDate = date.timeIntervalSince1970
// Loop through all found items
for item in filtered {
// Values
let price = (item.price?.doubleValue ?? 0.0) * 100.0
let tax = (item.tax?.doubleValue ?? 0.0) * 100.0
// Update transaction
transaction.amount += Int(price)
// Prepare order item
let orderItem = OrderItemm()
orderItem.amount = item.amount
orderItem.price = Int(price)
orderItem.taxPercentage = Int(tax)
orderItem.name = item.name ?? ""
orderItem.product = Product.API().productForName(orderItem.name, price: orderItem.price, tax: orderItem.taxPercentage)
// Add order item to order
order.orderItems.append(orderItem)
}
if order.orderItems.isEmpty == false {
print("\(date): \(order.orderItems.count) order items")
// Set transaction for order
order.transactions.append(transaction)
// Save the order
Order.API().saveOrders([order])
}
}
}
}
}
}
}
group.wait()
As in the comments of my question was suggested by #bdash, autoreleasepool did the trick.
I use AsyncSwift as syntactic sugar for grand central dispatch, but when using block groups the group keeps a reference to the block which caused that the memory wasn't released. I still make use of a group now but I leave the group after it's finished.
I provided my code example below, to make things clearer. Using multiple threads gave me incredibly (like 10 times faster) more performance while memory won't go above 150MB. Previously app was crashing at 1,3GB due to memory pressure.
let group = AsyncGroup()
var allPaths = [(Int, Int)]()
// Some logic to fill the paths -> not interesting
for path in allPaths {
group.enter() // The following block will be added to the group
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
autoreleasepool { // Creating an autoreleasepool to free up memory for the loaded statistics
// Stripped unnecessary stuff
if NSFileManager.defaultManager().fileExistsAtPath(path) {
// Load the statistics from .dat files
NSKeyedUnarchiver.setClass(_OldStatisticsDataModel.self, forClassName: "StatisticsDataModel")
if let statistics = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [_OldStatisticsDataModel] {
// Loop through days
for i in 1...31 {
autoreleasepool {
// Do the heavy stuff here
}
}
}
}
}
group.leave() // Block has finished, now leave the group
}
}
group.wait()

Array of custom object is not being set

I am trying to store an Object, CustomPinAnnotation into an array. I have declared it like this: var pinArray = [CustomPinAnnotation]()
I am calling Firebase in a function which gets a lot of locations in which I construct my CustomPin.
func loadStuff()
_REF_BASE.child("Objects").observeSingleEventOfType(.Value, withBlock: { snapshot in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot]{
for snap in snapshots {
if let dict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let obj = objModel(lobbyKey: key, dictionary: pokeDict)
for location in obj.locations {
let coord = location.locationCoordinate
let customPin = CustomPinAnnotation()
customPin.coordinate = coord.coordinate
customPin.title = obj.name
let time = NSDate(timeIntervalSince1970: location.time)
let dayTimePeriodFormatter = NSDateFormatter()
dayTimePeriodFormatter.dateFormat = "MMM dd YYYY hh:mm a"
let dateString = dayTimePeriodFormatter.stringFromDate(time)
customPin._detailedDescription = "Seen at " + dateString
customPin.subtitle = "Seen at \(dateString)"
customPin._pokemonName = obj.name
customPin._imageName = obj.name
customPin._successRate = location.successRate
customPin._upVotes = location.upVotes
customPin._downVotes = location.downVotes
customPin._caughtAtTime = dateString
self.pinToPass = customPin // send this custom pin with info
self.pinArray.append(customPin)
// self.mapView.addAnnotation(customPin);
}
}
}
}
}
})
My ViewDidLoad method
self.PokeMapView.zoomEnabled = true
loadStuff()
self.mapView.addAnnotations(pinArray)
self.mapView.showAnnotations(pinArray, animated: true)
The commented line //self.MapView.addAnnotation(customPin) currently works, but this is very inefficient, as it would pin objects on the map that are not in the current scope of the map itself. However, when I try adding the list of Annotations like so: MapView.addAnnotations(pinArray), nothing works and via the debugger i'm told that the array contains 0 elements.
It'd be greatly appreciated if somebody could help me, as i've been stuck on this for a while and it really causes poor performance.

Swift Make For Loop wait for Asynchronous function to end

im trying to work with firebase, i made this function that is saving "Offers" to an Array and then sending this array to another viewcontroller to show them on a tableView.
i found a problem that the for loop is ending before the data is coming from the servers and the callback of firebase is coming, where u can see the line "fir (self.goodOffer...)" is the end of the firebase callback.
maybe someone know how can i make the for loop to wait for the firebase callback to end?
thx.
geocodeAddress(totalFromLocation, callback: { (location) in
if location != nil {
fromLocation = location
self.geocodeAddress(totalToLocation, callback: { (location) in
if location != nil {
toLocation = location
mainDistance = Int(self.distanceCalculate(fromLocation!, locationTwo: toLocation!))
print(mainDistance)
REF_BUSSINES_OFFERS.queryOrderedByKey().queryEqualToValue("north").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
print("in snap")
if let value = snapshot.value {
if self.isAlavator {
if let area = value["north"] {
if let data = area!["withAlavator"]! {
if let bids = data["bids"] as? Int {
for i in 1..<bids+1 {
if let bid = data["\(i)"] {
let bidFromSize = bid!["fromSize"] as! Int
let bidToSize = bid!["toSize"] as! Int
let bidFromDistance = bid!["fromDistance"] as! Int
let bidToDistance = bid!["toDistance"] as! Int
if mainDistance >= bidFromDistance && mainDistance <= bidToDistance {
if Int(totalSize) >= bidFromSize && Int(totalSize) <= bidToSize {
let price = String(bid!["price"])
let name = bid!["name"] as! String
let phoneNumber = bid!["phoneNumber"] as! String
var logoImageData: NSData!
LOGOS_REF.child("\(phoneNumber)").dataWithMaxSize(3 * 1024 * 1024, completion: { (data, error) in
if error != nil {
logoImageData = UIImagePNGRepresentation(UIImage(named: "company")!)
} else {
logoImageData = data
}
let offer = Offer(logoImage: logoImageData, name: name, price: price)
self.goodOffers.append(offer)
print("fir \(self.goodOffers[0].price)")
})
}
}
}
print(i)
}//end of for
print("out")
//self.changgeViewToTable()
}
Can't help you much because your code is such a pain to read. Use ObjectMapper to reduce the level (and the effort) to map these fields. Anyhow, you can use dispatch_group to synchronize the various async calls:
let group = dispatch_group_create()
for i in 1..<bids+1 {
// ...
dispatch_group_enter(group)
LOGOS_REF.child("\(phoneNumber)").dataWithMaxSize(3 * 1024 * 1024) {
// ...
dispatch_group_leave(group)
}
}
// Wait for all async calls to complete before proceeding
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
Each dispatch_group_enter() should be balanced with a dispatch_group_leave() when the async task is complete.

Resources