Label not change in callback swift 3 - ios

I want to change my label text after getting a response from the server but it doesn't change after getting a response from server!!
here is my code :
func getOrderCost() {
DispatchQueue.main.async {
self.customView?.setOrderPriceEstimate(price: "۰")
var price = 10
if let p_code = self.customView?.getPromotionCode() {
self.orderPriceObject.promotion_code = p_code
}
self.registerOrderVM.getOrderPriceEstimation(orderInfo: self.orderPriceObject).asObservable().subscribe(onNext: { (response) in
if let res = response as? [String: Int], let p = res["price_with_added_value"] {
self.customView?.setOrderPriceEstimate(price: "cost : \(p)")
}
}, onError: { (error) in
self.view.showMessage(error.localizedDescription, type: .error, options: [.hideOnTap(true)])
}).disposed(by: self.disposeBag)
}
}
and this is the setOrderPriceEstimate function in customView :
public func setOrderPriceEstimate(price: String) {
self.orderCostLbl.text = price
}

DispatchQueue.main.async {
self.customView?.setOrderPriceEstimate(price: "cost : \(p)")
}
I think you have to update UI in main thread.

Making DispatchQueue.main.async outer doesn't guarantee that code inside getOrderPriceEstimation will run in main queue as code inside getOrder may be in other thread
func getOrderCost() {
DispatchQueue.main.async {
self.customView?.setOrderPriceEstimate(price: "۰")
var price = 10
if let p_code = self.customView?.getPromotionCode() {
self.orderPriceObject.promotion_code = p_code
}
self.registerOrderVM.getOrderPriceEstimation(orderInfo: self.orderPriceObject).asObservable().subscribe(onNext: { (response) in
if let res = response as? [String: Int], let p = res["price_with_added_value"] {
DispatchQueue.main.async {
self.customView?.setOrderPriceEstimate(price: "cost : \(p)")
}
}
}, onError: { (error) in
self.view.showMessage(error.localizedDescription, type: .error, options: [.hideOnTap(true)])
}).disposed(by: self.disposeBag)
}
}
verify if p exists or not
if let res = response as? [String: Int], let p = res["price_with_added_value"] {
DispatchQueue.main.async {
self.customView?.setOrderPriceEstimate(price: "cost : \(p)") }
}
else
{
print("res or p is not found")
}
Also check if customView is nil

Related

Swift: run function when for in loop completes and responses returned

I've been trying multiple ways to run a function in a for in loop and when all have returned to run another function but for some reason it appears the final function is running before one or more of the others have returned a result.
This is my latest attempt: (both functions work it is just the order which is the issue)
var counter: Int = 0
for owner in arrOwnerList {
self.removeDevice(device: self.device, account: owner as! String) { (isSuccess) -> Void in
print(isSuccess)
if isSuccess {
}
}
}
if self.arrOwnerList.count == self.counter {
self.removeDeviceFromServer(device: self.device)
self.sendEmail(to:"gordon#myemail.co.uk", subject:self.device+" has been removed", text:self.device+" has been removed from the server, please check the sim for bar and termination")
}
}
func removeDevice(device: String, account: String, completion: #escaping (Bool) -> Void) {
let dictHeader : [String:String] = ["username":username,"password":password]
let dictArray = [device]
self.counter += 1
WebHelper.requestPUTAPIRemoveDevice(baseURL+"rootaccount/removedevices/"+account+"?server=MUIR", header: dictHeader, dictArray: dictArray, controllerView: self, success: { (response) in
print(response)
if response.count == 0 {
self.Label1.alpha = 1
print("response count == 0")
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: Messages.ServerError, on: self)
}
}
else {
}
}) { (error) in
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: error?.localizedDescription ?? Messages.ServerError, on: self)
}
}
let isSuccess = true
self.Label1.alpha = 1
completion(isSuccess)
}
func removeDeviceFromServer(device: String) {
let dictHeader : [String:String] = ["username":username,"password":password]
WebHelper.requestDELETEAPI(baseURL+"defaultdevice/"+device+"?server=MUIR", header: dictHeader, controllerView: self, success: { (response) in
if response.count == 0 {
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: Messages.ServerError, on: self)
}
}
else {
if response.count != 0 {
self.Label2.alpha = 1
DispatchQueue.main.async {
self.Label2.alpha = 1
}
}
else{
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: Messages.NoDataFound, on: self)
}
}
}
}) { (error) in
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: error?.localizedDescription ?? Messages.ServerError, on: self)
}
}
}
DispatchGroup will solve the problems. (https://developer.apple.com/documentation/dispatch/dispatchgroup)
let dispatchGroup = DispatchGroup()
for owner in arrOwnerList {
dispatchGroup.enter()
self.removeDevice(device: self.device, account: owner as! String) { (isSuccess) -> Void in
defer {
dispatchGroup.leave()
}
print(isSuccess)
if isSuccess {
}
}
}
// This block execute when the loop is completed.
dispatchGroup.notify(queue: .main) { [weak self] in
guard let self = self else { return }
self.removeDeviceFromServer(device: self.device)
self.sendEmail(to:"gordon#myemail.co.uk", subject:self.device+" has been removed", text:self.device+" has been removed from the server, please check the sim for bar and termination")
}

How to make the for loop stop for the asynchronous request Swift 5

Here's the func that I want the for loop to wait for the completion from func getVenueDetails and if completion is true break out of the loop, and if not continue with the next venue id.
func searchVenues(lat: Double, lng: Double) {
let parameter: [String: String] = [
"ll": "\(lat),\(lng)",
"radius": "600",
"limit": "10",
"intent": "browse",
"categoryId": "4bf58dd8d48988d1e4931735,4bf58dd8d48988d1f1931735,4deefb944765f83613cdba6e,4bf58dd8d48988d17f941735,52e81612bcbc57f1066b79eb,4bf58dd8d48988d181941735,4bf58dd8d48988d1f4931735,4bf58dd8d48988d189941735,4bf58dd8d48988d182941735,4bf58dd8d48988d17b941735,4bf58dd8d48988d163941735,4bf58dd8d48988d164941735,4bf58dd8d48988d165941735,56aa371be4b08b9a8d57356a,4bf58dd8d48988d12f941735"
];
var isSent2: Bool = false
client.request(path: "venues/search", parameter: parameter) { result in
switch result {
case let .success(data):
let jsonResponse = try! JSONSerialization.jsonObject(with: data, options: [])
let json = JSON(jsonResponse)
let name = json["response"]["venues"][0]["name"].string
print("NAME FROM JSON: ", name)
let id = json["response"]["venues"][0]["id"].string
print("id of foursquare", id)
let group = DispatchGroup()
group.notify(queue: .main) {
for (key,subJson):(String, JSON) in json["response"]["venues"] {
//group.enter()
let placeName = subJson["name"].string
print("place name:",placeName.unsafelyUnwrapped)
let placeId = subJson["id"].string
print("place id:",placeId.unsafelyUnwrapped)
group.enter()
self.getVenueDetails(id: placeId!) { (isSent) in
print("isSent", isSent)
isSent2 = isSent
group.leave()
}
if (isSent2){
print("linaaa")
//group.leave()
break
}
}//end of for loop
}
// print("json == ", jsonResponse)
case let .failure(error):
// Error handling
switch error {
case let .connectionError(connectionError):
print(connectionError)
case let .responseParseError(responseParseError):
print(responseParseError) // e.g. JSON text did not start with array or object and option to allow fragments not set.
case let .apiError(apiError):
print(apiError.errorType) // e.g. endpoint_error
print(apiError.errorDetail) // e.g. The requested path does not exist.
}
}
}
}
here's the other function, where the request for venue details is made, so I want when the isSent returned true the for loop in searchVenues to stop (break) and if returned false to continue with next one.
func getVenueDetails(id: String, completionHandler: #escaping (Bool)->Void) {
var isSent: Bool = false
let parameter: [String: String] = [
"VENUE_ID": "\(id)",
];
client.request(path: "venues/\(id)", parameter: parameter) { result in
switch result {
case let .success(data):
let jsonResponse = try! JSONSerialization.jsonObject(with: data, options: [])
let json = JSON(jsonResponse)
// print("json == ", jsonResponse)
let name = json["response"]["venue"]["name"].string
if let rating:Double = json["response"]["venue"]["rating"].double {
print("rating from: ", rating)
//rat = rating
if (rating > 2) {
self.foursquareNotification(name: name!)
print("here inside lol")
isSent = true
DispatchQueue.main.async {
completionHandler(isSent)
print("isSent hereee", isSent)
}
} //end if
else {
isSent = false
DispatchQueue.main.async {
completionHandler(isSent)
}
}//end else
} //end if rating
// rat = json["response"]["venue"]["rating"].double!
// print("rating from: ", rat)
// //rat = rating.unsafelyUnwrapped
case let .failure(error):
// Error handling
switch error {
case let .connectionError(connectionError):
print(connectionError)
case let .responseParseError(responseParseError):
print(responseParseError) // e.g. JSON text did not start with array or object and option to allow fragments not set.
case let .apiError(apiError):
print(apiError.errorType) // e.g. endpoint_error
print(apiError.errorDetail) // e.g. The requested path does not exist.
}
}
}
//return isSent
}//end getVenueDetails
You need to remove the for loop
var res = json["response"]["venues"]
Then
var current = 0
func start(item:ItemType) {
let placeName = subJson["name"].string
let placeId = subJson["id"].string
self.getVenueDetails(id: placeId!) { (isSent) in
if isSent {
counter += 1
start(nextItem)
}
}

How to merge two datasourcemodel using mvvm in swift

I have two datasource model.I am doing in mvvm.
My datasourcemodel is as below.
class QuestionDataSourceModel: NSObject {
var dataListArray:Array<QuestionListModel>? = []
var list:Array<OptionsModel>? = []
init(array :Array<[String:Any]>?) {
super.init()
var newArray:Array<[String:Any]> = []
if array == nil{
// newArray = self.getJsonDataStored22()
}
else{
newArray = array!
}
var datalist:Array<QuestionListModel> = []
for dict in newArray{
let model = QuestionListModel(dictionary: dict)
datalist.append(model)
}
self.dataListArray = datalist
print(self.dataListArray)
}
}
the above is first datasourcemodel.
Next datasourcemodel is as below.
class DummyDataSourceModel: NSObject {
var dataListArray:Array<DummyDataModel>? = []
var list:Array<DummyDataModel>? = []
init(array :Array<[String:Any]>?) {
super.init()
var newArray:Array<[String:Any]> = []
if array == nil{
// newArray = self.getJsonDataStored22()
}
else{
newArray = array!
}
var datalist:Array<DummyDataModel> = []
for dict in newArray{
let model = DummyDataModel(dictionary: dict)
datalist.append(model)
}
self.dataListArray = datalist
print(self.dataListArray)
}
}
In my view controller :-
questionViewModel.loadData { (isSuccess) in
if(isSuccess == true)
{
let sec = self.questionViewModel.numberOfSections()
for _ in 0..<sec
{
self.questionViewModel.answers1.add("")
self.questionViewModel.questions1.add("")
self.questionViewModel.questionlist1.add("")
}
//questionViewModel.numberOfSections()
self.activityindicator.stopAnimating()
self.activityindicator.isHidden = true
self.tableview.refreshControl = refreshControl
self.tableview .allowsMultipleSelection = false
self.tableview.reloadData()
// self.questionViewModel.loadData2{ (isSuccess) in
self.dummyDataViewModel.loadData1{ (isSuccess) in
if(isSuccess == true)
{
print(self.questionViewModel.datasourceModel.dataListArray?.count)
self.questionViewModel.totaldata()
self.tableview2.allowsMultipleSelection = false
self.tableview2.reloadData()
}
else{
self.viewDidLoad()
}
}
}
else{
self.activityindicator.stopAnimating()
self.activityindicator.isHidden = true
let controller = UIAlertController(title: "No Internet Detected", message: "This app requires an Internet connection", preferredStyle: .alert)
// Create the actions
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
NSLog("OK Pressed")
self.viewDidLoad()
}
controller.addAction(okAction)
self.present(controller, animated: true, completion: nil)
}
}
MY QuestionViewModel:-
func loadFromWebserviceData(completion :#escaping (QuestionDataSourceModel?) -> ()){
Alamofire.request("http://www.example.com").validate(statusCode: 200..<300).validate(contentType: ["application/json"]).responseJSON{ response in
let status = response.response?.statusCode
print("STATUS \(status)")
print(response)
switch response.result{
case .success(let data):
print("success",data)
let result = response.result
print(result)
if let wholedata = result.value as? [String:Any]{
print(wholedata)
if let data = wholedata["data"] as? Array<[String:Any]>{
print(data)
print(response)
for question in data {
let typebutton = question["button_type"] as? String
print(typebutton)
self.type = typebutton
let options = question["options"] as! [String]
// self.dataListArray1 = [options]
self.tableArray.append(options)
// self.savedataforoptions(completion: <#T##(NH_OptionslistDataSourceModel?) -> ()#>)
self.no = options.count
}
print(self.tableArray)
let newDataSource:QuestionDataSourceModel = QuestionDataSourceModel(array: data)
completion(newDataSource)
}
}
case .failure(let encodingError ):
print(encodingError)
// if response.response?.statusCode == 404{
print(encodingError.localizedDescription)
completion(nil)
}
}}
func loadData(completion :#escaping (_ isSucess:Bool) -> ()){
loadFromWebserviceData { (newDataSourceModel) in
if(newDataSourceModel != nil)
{
self.datasourceModel = newDataSourceModel!
completion(true)
}
else{
completion(false)
}
}
}
my dummyViewModel is below:-
func loadFromDummyData(completion :#escaping (DummyDataSourceModel?) -> ()){
if let path = Bundle.main.path(forResource: "jsonData", ofType: "json") {
do {
let jsonData = try NSData(contentsOfFile: path, options: NSData.ReadingOptions.mappedIfSafe)
do {
let jsonResult: NSDictionary = try JSONSerialization.jsonObject(with: jsonData as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
if let people = jsonResult["data"] as? Array<[String:Any]> {
// self.dict = people
for person in people {
let options = person["options"] as! [String]
self.tableArray.append(options)
let name = person ["question"] as! String
self.tableArray.append(options)
}
let newDataSource:DummyDataSourceModel = DummyDataSourceModel(array: people)
completion(newDataSource)
}
} catch {}
} catch {}
}
}
func loadData1(completion :#escaping (_ isSucess:Bool) -> ()){
loadFromDummyData{ (newDataSourceModel) in
if(newDataSourceModel != nil)
{
self.datasourceModel = newDataSourceModel!
completion(true)
}
else{
completion(false)
}
}
}
Now i need to merge this two datasourceModel.
I am using tableview to display the data .
So first i need to display the data from the JSON.then below i need to display the data from the json.file.So how to merge this two datasourcemodel.
First the data from JSON has 10 sections.
In json.file it has 3 sections.So total 13 sections .So i need to display the 13 sections together in the tableview.How to do?
This the json.file data:-
{
"data":[
{
"question": "Gender",
"options": ["Male","Female"],
"button_type":"2"
},
{
"question": "How old are you",
"options": ["Under 18","Age 18 to 24","Age 25 to 40","Age 41 to 60","Above 60"],
"button_type":"2"
},
{
"button_type":"2",
"question": "I am filling the Questionnaire for?",
"options": ["Myself","Mychild","Partner","Others"]
}
]
}
And same format forJSON from api.
{
"data":[
{
"question": "Gender",
"options": ["Male","Female"],
"button_type":"2"
},
{
"question": "How old are you",
"options": ["Under 18","Age 18 to 24","Age 25 to 40","Age 41 to 60","Above 60"],
"button_type":"2"
},
{
"button_type":"2",
"question": "I am filling the Questionnaire for?",
"options": ["Myself","Mychild","Partner","Others"]
}
]
}
class OptionsModel: NSObject {
var options: [String]?
var question: String?
var button_type: String?
}
class OptionsViewModel: NSObject {
var optionList: Array<OptionsModel>?
func callApi() {
yourApiCall() { webResponseArray in
for res in webResponseArray {
let model = OptionsModel(json: res)
if optionList == nil {
optionList = [OptionsModel]()//Initialize Array if not initialized
}
optionList.append(model)// You are appending Option getting from web Api Response
}
//After Completely fetching all models from webResponseArray you should load Data from Local JSON
loadLocalJSON()//After loading Data from Web You are loading Data from local JSON.
}
}
func loadLocalJSON() {
let localJsonArray = someMethodToLoadJsonFromLocalFileAndReturnsJSON()
for json in localJsonArray {
let model = OptionsModel(json: json)
if optionList == nil {
optionList = [OptionsModel]()//Initialize Array if not initialized
}
optionList.append(model)// You are appending Option getting from Local JSON
}
//Now you have a single Array containing all local and remote option models.
}
}

Firebase Cloud functions is slow

We use Firebase as a backend for our service. It has a PeopleViewController that displays users within a radius of the current user. The radius can vary from 1 to 40 miles. Also there is a check for blocked users, age, sex. At the very beginning of the development of the project, a temporary implementation was made for this capability within the application. I created several managers who fulfilled these requests, and then gave the result to PeopleViewController. At the beginning of the project, because of the tight deadlines, pagination was not implemented, but the pagination was not particularly needed since the users even in the radius were less than 100. Now, in one radius there can be more than 600 users and I think it is not right that these requests are performed inside application. We move these functions from the app Side to the cloud function so that we can adjust how many users should be in each issue.
The problem is that even a warmed-up cloud function works slower than all functions related to this capability inside the application. On average, the code inside the application for a radius of 40 miles and the inclusion of all age groups returns 646 users in 1.5 seconds, while the best result is cloud functions 3.6. Below I will give examples of the time and code with the app side and cloud funciton.
The results are obtained using the code that is executed in the application:
1 number of users 646, 1.6775569915771484 seconds
2 number of users 646, 1.4022901058197021 seconds
3 number of users 646, 1.5957129001617432 seconds
The results with the cloud function time are specified in milliseconds. The parameters and location are the same as the code that is run from the application:
1 "timers": {
"afterVerify": 1,
"gotIdsFromGeofire": 1372,
"gotBlockedUsersIDs": 2463,
"gotBlockedByUsersIDs": 2467,
"gotUserSearchLocationModels": 5256,
"gotAllInitData": 5256,
"filtersAndSortsDone": 6168
},
2 "timers": {
"afterVerify": 6,
"gotIdsFromGeofire": 1209,
"gotBlockedUsersIDs": 1909,
"gotBlockedByUsersIDs": 1913,
"gotUserSearchLocationModels": 3800,
"gotAllInitData": 3800,
"filtersAndSortsDone": 4207
},
3 "timers": {
"afterVerify": 1,
"gotIdsFromGeofire": 812,
"gotBlockedUsersIDs": 1415,
"gotBlockedByUsersIDs": 1419,
"gotUserSearchLocationModels": 3912,
"gotAllInitData": 3913,
"filtersAndSortsDone": 4417
},
In the managers who request a list of users and filter the received data within the application.
extension PeopleListViewController {
#objc fileprivate func requestPeopleNearBy() {
let firPeopleSearchDatabaseManagerStart = Date().timeIntervalSince1970
firPeopleSearchDatabaseManager.searchPeople(success: { [weak self](userSearchLocationModels) in
let firPeopleSearchDatabaseManagerEnd = Date().timeIntervalSince1970
let resultTimeStamp = firPeopleSearchDatabaseManagerEnd - firPeopleSearchDatabaseManagerStart
debugPrint("firPeopleSearchDatabaseManager userSearchLocationModels", userSearchLocationModels.count, "resultTimeStamp", resultTimeStamp)
}) { (error) in
}
}
}
class FIRPeopleSearchDatabaseManager {
fileprivate enum MainGateways {
case userSearchLocationModel, userLocations
var description: String {
switch self {
case .userSearchLocationModel:
return "userSearchLocationModel"
case .userLocations:
return "userLocations"
}
}
}
func saveUserSearchLocationModel(_ userSearchLocationModel: UserSearchLocationModel, success: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
DispatchQueue.global(qos: .background).async {
let ref = Database.database().reference().child(MainGateways.userSearchLocationModel.description).child(userSearchLocationModel.userID)
let json = userSearchLocationModel.toJSON()
ref.updateChildValues(json, withCompletionBlock: { (error, ref) in
guard error == nil else {
fail?(error!)
return
}
success?()
})
}
}
private func downloadUserSearchLocationModel(_ userID: String, success: ((_ userSearchLocationModel: UserSearchLocationModel) -> Void)?, notExist: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
DispatchQueue.global(qos: .background).async {
let ref = Database.database().reference().child(MainGateways.userSearchLocationModel.description).child(userID)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.value is NSNull {
notExist?()
return
}
guard let json = snapshot.value as? [String : Any] else {
debugPrint("doest exist", userID)
notExist?()
return
}
guard let userSearchLocationModel = Mapper<UserSearchLocationModel>().map(JSON: json) else {
debugPrint("doest exist", userID)
notExist?()
return
}
guard !userSearchLocationModel.userID.isEmpty else {
notExist?()
return
}
success?(userSearchLocationModel)
}, withCancel: { (error) in
fail?(error)
})
}
}
func searchPeople(success: ((_ searchLocationModels: [UserSearchLocationModel]) -> Void)?, fail: ((_ error: Error) -> Void)?) {
let realmManager = RealmManager()
guard let currentUserID = realmManager.getCurrentUser()?.id else { return }
guard let location = LocationManager.shared.currentLocation else { return }
let realmUserSettingsManager = RealmUserSettingsManager()
let miles = realmUserSettingsManager.getMaxDistanceInMiles() ?? 15
let distanceConverter = DistanceConvertor()
let radius = distanceConverter.convertedMilesToMetersForFIRRequest(miles)
DispatchQueue.global(qos: .background).async {
let usersGeofireRef = Database.database().reference().child(MainGateways.userLocations.description)
guard let geoFire = GeoFire(firebaseRef: usersGeofireRef) else { return }
guard let circleQuery = geoFire.query(at: location, withRadius: radius) else { return }
var usersListIDs = [String]()
var generalBlockModel = [BlockModel]()
let dispatchGroup = DispatchGroup()
let blockSystemManager = BlockSystemManager()
dispatchGroup.enter()
blockSystemManager.requestBlockingUserIDs(completion: { (blockModels, error) in
generalBlockModel = blockModels
dispatchGroup.leave()
})
dispatchGroup.enter()
circleQuery.observe(.keyEntered, with: { (key, location) in
if let _key = key {
if _key != currentUserID {
usersListIDs.append(_key)
}
}
})
circleQuery.observeReady({
circleQuery.removeAllObservers()
dispatchGroup.leave()
})
dispatchGroup.notify(queue: .global(qos: .background), execute: {
if usersListIDs.count == 0 {
circleQuery.removeAllObservers()
success?([])
return
}
var unblockedUserListIDs = [String]()
for usersListID in usersListIDs {
let isContains = generalBlockModel.contains(where: { $0.userID == usersListID })
if !isContains {
unblockedUserListIDs.append(usersListID)
}
}
self.downloadUsers(usersListIDs: unblockedUserListIDs, success: success)
})
}
}
private func downloadUsers(usersListIDs: [String], success: ((_ searchLocationModels: [UserSearchLocationModel]) -> Void)?) {
var userIDsCount = usersListIDs.count
var userSearchLocationModels = [UserSearchLocationModel]()
for userID in usersListIDs {
self.downloadUserSearchLocationModel(userID, success: { (userSearchLocationModel) in
userSearchLocationModels.append(userSearchLocationModel)
if userIDsCount == userSearchLocationModels.count {
self.filter(userSearchLocationModels, success: { (sortedUserSearchLocationModels) in
success?(sortedUserSearchLocationModels)
})
}
}, notExist: {
userIDsCount -= 1
if userIDsCount == userSearchLocationModels.count {
self.filter(userSearchLocationModels, success: { (sortedUserSearchLocationModels) in
success?(sortedUserSearchLocationModels)
})
}
}, fail: { (error) in
userIDsCount -= 1
if userIDsCount == userSearchLocationModels.count {
self.filter(userSearchLocationModels, success: { (sortedUserSearchLocationModels) in
success?(sortedUserSearchLocationModels)
})
}
})
}
}
private func filter(_ userSearchLocationModels: [UserSearchLocationModel], success: ((_ sortedUserSearchLocationModels: [UserSearchLocationModel]) -> Void)?) {
filterByUserSearchFilter(userSearchLocationModels) { (searchFilteredUserSearchLocationModels) in
self.filterByOnlineAndRecentLaunchApp(searchFilteredUserSearchLocationModels, success: { (onlineRecentFilteredUserSearchLocationModels) in
success?(onlineRecentFilteredUserSearchLocationModels)
})
}
}
private func filterByUserSearchFilter(_ userSearchLocationModels: [UserSearchLocationModel], success: ((_ sortedUserSearchLocationModels: [UserSearchLocationModel]) -> Void)?) {
DispatchQueue.main.async {
let timeManager = TimeManager()
let realmUserSettingsManager = RealmUserSettingsManager()
guard let cuPeopleFilterSettings = realmUserSettingsManager.getUserPeopleFilterSettings() else { return }
var sortedUserSearchLocationModels = [UserSearchLocationModel]()
for userSearchLocationModel in userSearchLocationModels {
let userYearAge = timeManager.getUserAgeFromBirthdayTimeStamp(userSearchLocationModel.birthdayTimeStamp)
if cuPeopleFilterSettings.filterGenderModeIndex != 2 {
// sorting with mode
if cuPeopleFilterSettings.maxAgeValue != UserPeopleFilterSettings.Standard.maxAge {
if userYearAge >= cuPeopleFilterSettings.minAgeValue && userYearAge <= cuPeopleFilterSettings.maxAgeValue && userSearchLocationModel.genderIndex == cuPeopleFilterSettings.filterGenderModeIndex {
sortedUserSearchLocationModels.append(userSearchLocationModel)
}
} else {
// max age settings
if userYearAge >= cuPeopleFilterSettings.minAgeValue && userSearchLocationModel.genderIndex == cuPeopleFilterSettings.filterGenderModeIndex {
sortedUserSearchLocationModels.append(userSearchLocationModel)
}
}
} else {
if cuPeopleFilterSettings.maxAgeValue != UserPeopleFilterSettings.Standard.maxAge {
if userYearAge >= cuPeopleFilterSettings.minAgeValue && userYearAge <= cuPeopleFilterSettings.maxAgeValue {
sortedUserSearchLocationModels.append(userSearchLocationModel)
}
} else {
// max age in settings
if userYearAge >= cuPeopleFilterSettings.minAgeValue {
sortedUserSearchLocationModels.append(userSearchLocationModel)
}
}
}
}
//sorting by online status and last seen timestamp
let onlineUsers = sortedUserSearchLocationModels.filter { $0.isOnline }
let otherUsersSortedByTimeStamp = sortedUserSearchLocationModels.filter { !$0.isOnline }.sorted { $0.lastSeenTimeStamp > $1.lastSeenTimeStamp }
var resultSortedArray = [UserSearchLocationModel]()
resultSortedArray.append(contentsOf: onlineUsers)
resultSortedArray.append(contentsOf: otherUsersSortedByTimeStamp)
success?(resultSortedArray)
}
}
/// 1. online users sorted by most recently active on the app 2. offline users sorted by most recently active on the app
private func filterByOnlineAndRecentLaunchApp(_ userSearchLocationModels: [UserSearchLocationModel], success: ((_ sortedUserSearchLocationModels: [UserSearchLocationModel]) -> Void)?) {
DispatchQueue.global(qos: .background).async {
let sortedUsers = userSearchLocationModels.sorted(by: { $0.recentActivityTimeStamp > $1.recentActivityTimeStamp })
success?(sortedUsers)
}
}
}
From BlockSystemManager
extension BlockSystemManager {
func requestBlockingUserIDs(completion: ((_ blockModels: [BlockModel], _ error: Error?) -> Void)?) {
DispatchQueue.main.async {
guard let currentUserID = RealmManager().getCurrentUser()?.id else { return }
DispatchQueue.global(qos: .background).async {
let dispatchGroup = DispatchGroup()
var generalBlockModels = [BlockModel]()
let blockedUsersRef = Database.database().reference().child(MainPath.blockingInfo.description).child(currentUserID).child(SubPath.blockedUsers.description)
dispatchGroup.enter()
blockedUsersRef.observeSingleEvent(of: .value, with: { (snap) in
if snap.value is NSNull {
dispatchGroup.leave()
return
}
guard let dict = snap.value as? [String : [String : Any]] else {
dispatchGroup.leave()
return
}
guard let blockModelsDict = Mapper<BlockModel>().mapDictionary(JSON: dict)?.values else {
dispatchGroup.leave()
return
}
let blockModels = Array(blockModelsDict)
generalBlockModels += blockModels
dispatchGroup.leave()
}, withCancel: { (error) in
dispatchGroup.leave()
})
let blockedByUsersRef = Database.database().reference().child(MainPath.blockingInfo.description).child(currentUserID).child(SubPath.blockedByUsers.description)
dispatchGroup.enter()
blockedByUsersRef.observeSingleEvent(of: .value, with: { (snap) in
if snap.value is NSNull {
dispatchGroup.leave()
return
}
guard let dict = snap.value as? [String : [String : Any]] else {
dispatchGroup.leave()
return
}
guard let blockModelsDict = Mapper<BlockModel>().mapDictionary(JSON: dict)?.values else {
dispatchGroup.leave()
return
}
let blockModels = Array(blockModelsDict)
generalBlockModels += blockModels
dispatchGroup.leave()
}, withCancel: { (error) in
dispatchGroup.leave()
})
dispatchGroup.notify(queue: .global(qos: .background), execute: {
completion?(generalBlockModels, nil)
})
}
}
}
}
This function is located in the PeopleSearchManager and calls the cloud function
func searchPeopleFirstRequest(success: ((_ searchLocationModels: [UserSearchLocationModel]) -> Void)?, fail: ((_ error: Error) -> Void)?) {
DispatchQueue.main.async {
guard let location = LocationManager.shared.currentLocation else { return }
let realmUserSettingsManager = RealmUserSettingsManager()
let miles = realmUserSettingsManager.getMaxDistanceInMiles() ?? 15
let parameters = ["miles" : miles, "latitude" : location.coordinate.latitude, "longitude" : location.coordinate.longitude] as [String : Any]
DispatchQueue.global(qos: .background).async {
Auth.auth().currentUser?.getIDTokenForcingRefresh(true, completion: { (token, error) in
if let error = error {
// Handle error
fail?(error)
return
}
guard let _token = token else { return }
let headers = ["X-Auth-MyApp-Token" : _token]
guard let url = URL(string: BackendEndPoint.searchPeopleFirstRequest.path) else { return }
AlamofireMyAppManager.shared.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON(completionHandler: { (response) in
switch response.result {
case .success(let value):
guard let dict = value as? [String : Any] else {
success?([])
return
}
guard let result = dict["result"] as? [[String : Any]] else {
success?([])
return
}
let userSearchLocationModels = Mapper<UserSearchLocationModel>().mapArray(JSONArray: result)
success?(userSearchLocationModels)
// for second request
if let lastUserSearchLocationModelID = dict["lastUserSearchLocationModelID"] as? String {
debugPrint("lastUserSearchLocationModelID", lastUserSearchLocationModelID)
self.peopleSecondRequestModel = PeopleSecondRequestModel(lastUserSearchLocationModelID: lastUserSearchLocationModelID, location: location, radius: miles, offset: userSearchLocationModels.count)
}
case .failure(let error):
debugPrint(error)
fail?(error)
}
})
})
}
}
}
Cloud function
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const GeoFire = require('geofire');
const moment = require('moment');
const map = require('lodash').map;
const filter = require('lodash').filter;
const sortBy = require('lodash').sortBy;
const last = require('lodash').last;
module.exports = functions.https.onRequest((req, res) => {
const token = req.header('X-Auth-MyApp-Token');
const miles = req.query['miles'];
const latitude = req.query['latitude'];
const longitude = req.query['longitude'];
let currentUserID;
let outputUsers;
const startTime = moment();
const timers = {};
admin.auth().verifyIdToken(token)
.then((decodedToken) => {
timers.afterVerify = moment() - startTime;
currentUserID = decodedToken.uid;
console.log('userID', currentUserID);
const getUsersSearchModelsByLocationPromise = new Promise((resolve, reject) => {
try {
const userLocationRef = admin.database().ref().child('userLocations');
const geoFire = new GeoFire(userLocationRef);
const geoQuery = geoFire.query({
center: [Number(latitude), Number(longitude)],
radius: Number(miles) * 1.609
});
const usersIDs = [];
const keyListener = geoQuery.on('key_entered', (key) => usersIDs.push(key));
geoQuery.on('ready', () => {
keyListener.cancel();
geoQuery.cancel();
timers.gotIdsFromGeofire = moment() - startTime;
resolve(usersIDs)
});
} catch (error) {
reject(error)
}
})
.then((userIDs) => {
return Promise.all(map(userIDs, (id) => admin.database().ref().child('userSearchLocationModel').child(id)
.once('value')
.then((snapshot) => snapshot.val())
))
.then((users) => {
timers.gotUserSearchLocationModels = moment() - startTime;
return Promise.resolve(users);
})
});
const getBlockedUsersIDsPromise = Promise.all([
admin.database().ref().child('blockingInfo').child(currentUserID).child('blockedUsers')
.once('value')
.then((snapshot) => snapshot.val())
.then((users) => {
timers.gotBlockedUsersIDs = moment() - startTime;
return map(users, 'userID')
}),
admin.database().ref().child('blockingInfo').child(currentUserID).child('blockedByUsers')
.once('value')
.then((snapshot) => snapshot.val())
.then((users) => {
timers.gotBlockedByUsersIDs = moment() - startTime;
return map(users, 'userID')
}),
]);
const getFilterSettingsPromise = admin.database().ref().child('userPeopleFilterSettings').child(currentUserID)
.once('value')
.then((snapshot) => snapshot.val());
return Promise.all([
getUsersSearchModelsByLocationPromise,
getBlockedUsersIDsPromise,
getFilterSettingsPromise
]);
})
.then(([users, blockedUsersIds, filterSettings]) => {
timers.gotAllInitData = moment() - startTime;
outputUsers = users;
// filter nulls
outputUsers = filter(outputUsers, (user) => user);
// filter blocked
outputUsers = filter(outputUsers, (user) => blockedUsersIds.indexOf(user.userID) === -1);
// filter by gender
outputUsers = filterSettings.filterGenderModeIndex !== 2 ?
filter(outputUsers, (user) => user.genderIndex === filterSettings.filterGenderModeIndex) :
outputUsers;
// filter by age
outputUsers = filter(outputUsers, (user) => {
const userAge = moment().diff(user.birthdayTimeStamp * 1000, 'years');
if (filterSettings.maxAgeValue === 55 && userAge >= 55) return true;
return (userAge >= filterSettings.minAgeValue && userAge <= filterSettings.maxAgeValue);
});
// sort by recent activity timestamp
outputUsers = sortBy(outputUsers, 'recentActivityTimeStamp');
// slice
outputUsers = outputUsers.slice(0, 99);
timers.filtersAndSortsDone = moment() - startTime;
return res.status(200).send({
timers: timers,
result: outputUsers,
lastUserSearchLocationModelID: last(outputUsers)
});
})
.catch((error) => {
console.log('Error: ', error);
return res.status(500).send({
code: error.code,
message: error.message,
stack: error.stack
});
});
});
Also I want to say that I read this post, but as I wrote at the beginning, that even the heated function works like this. I also asked my question here. What is the reason for such a big difference in the result? How can this be remedied or what are the solutions to this problem?

Are nested completion blocks a sign of bad design?

I have a method that calls three functions that each make a request to Firebase and pass back data in a completion handler. Once the data from 2 of the completion handlers is sent back, I call another method to pass in the data and send back a valid result. Is nesting blocks like this a sign of poor design?
func fetchNearbyUsers(for user: User, completionHandler: usersCompletionHandler?) {
self.fetchAllUsers(completionHandler: { (users: [User]) in
ChatProvider.sharedInstance.fetchAllChatrooms(completionHandler: { (chatrooms: [Chatroom]) in
self.validateNewUsers(currentUser: user, users: users, chatrooms: chatrooms, completionHandler: { (validUsers: [User]) in
guard validUsers.isEmpty == false else {
completionHandler?([])
return
}
completionHandler?(validUsers)
})
})
})
}
// Other Methods
func fetchAllUsers(completionHandler: usersCompletionHandler?) {
var users: [User] = []
let group = DispatchGroup()
let serialQueue = DispatchQueue(label: "serialQueue")
DatabaseProvider.sharedInstance.usersDatabaseReference.observe(.value, with: {
snapshot in
guard let data = snapshot.value as? [String: AnyObject] else { return }
for (_, value) in data {
guard let values = value as? [String: AnyObject] else { return }
guard let newUser = User(data: values) else { return }
group.enter()
newUser.fetchProfileImage(completionHandler: { (image) in
serialQueue.async {
newUser.profileImage = image
users.append(newUser)
group.leave()
}
})
}
group.notify(queue: .main, execute: {
completionHandler?(users)
})
})
}
func fetchAllChatrooms (completionHandler: chatroomsHandler?) {
var chatrooms = [Chatroom]()
let group = DispatchGroup()
let serialQueue = DispatchQueue(label: "serialQueue")
DatabaseProvider.sharedInstance.chatroomsDatabaseReference.observe(.value, with: { snapshot in
guard let data = snapshot.value as? [String: AnyObject] else { return }
for (_, value) in data {
guard let value = value as? [String: AnyObject] else { return }
guard let newChatroom = Chatroom(data: value) else { return }
group.enter()
self.fetchChatroomUsers(chatroom: newChatroom, completionHandler: { (chatroom) in
serialQueue.async {
chatrooms.append(newChatroom)
group.leave()
}
})
}
group.notify(queue: .main, execute: {
completionHandler?(Array(Set<Chatroom>(chatrooms)))
})
})
}
private func validateNewUsers(currentUser: User, users: [User], chatrooms: [Chatroom], completionHandler: usersCompletionHandler?) {
let chatPartners = self.chatPartners(currentUser: currentUser, chatrooms: chatrooms)
let validUsers = users
.filter { currentUser.id != $0.id }
.filter { currentUser.distanceFromUser(atLocation: $0.coordinates) <= 8046.72 }
.filter { !chatPartners.contains($0.id) }
completionHandler?(validUsers)
}
private func chatPartners (currentUser: User, chatrooms: [Chatroom]) -> Set<String> {
var results = [String]()
for chatroom in chatrooms {
if currentUser.id == chatroom.firstUserId {
results.append(chatroom.secondUserId)
} else if currentUser.id == chatroom.secondUserId {
results.append(chatroom.firstUserId)
}
}
return Set(results)
}

Resources