How to code Nested Loop with synchronous in swift? - ios

In my project, I call getMain() and that has nested loop. That loop call setUp(). My problem is setUp() before finish, upper loop is quit.
Firstly call getMian():
func getMain(){
let entityDescription = NSFetchRequest<NSFetchRequestResult>(entityName: "MainThemeHome")
do{
let results = try context.fetch(entityDescription)
if(results.count) > 0 {
sections.removeAll()
debugPrint(results.count)
outer_count = results.count
for i in 0 ..< (results.count){
let match = results[i] as! NSManagedObject
let associated_url = match.value(forKey: "main_associated_url") as! String
let name = match.value(forKey: "main_name") as! String
//call function
self.setUpViews(associated_url: associated_url, main_name: name, i: i )
self.myGroup.leave()
}
}else{
}
}catch{}
}
Loop call setUp() is below,
private func setUp(associated_url : String , main_name: String,i : Int) {
if Reachability().isInternetAvailable() == true {
self.rest.auth(auth: self.prefs.value(forKey: "access_token") as! String!)
self.rest.get(url: StringResource().mainURL + associated_url , parma: [ "show_min": "true" ], finished: {(result : NSDictionary, status : Int) -> Void in
self.assetsTable.removeAll()
if(status == 200){
let data = result["data"] as! NSArray
for item in 0…data.count - 1 {
let themes : AnyObject = data[item] as AnyObject
let created = themes["created"] as! String
let assets_id = themes["id"] as! Int
let name = themes["name"] as! String
var poster_img_url = themes["poster_image_url"] as! String
let provider_id = themes["provider_id"] as! Int
poster_img_url = StringResource().posterURL + poster_img_url
self.assetsTable.append(AssetsTableItem(created: created, assets_id: assets_id, name: name, poster_image_url: poster_img_url , provider_id: provider_id))
}
}
self.sections.append(SectionsHome(package_name: main_name, package_url: associated_url,i: i,packageTable: self.assetsTable))
self.inner_count = self.sections.count
}else{
}
})
}
}
How to control setUp() is completely finished, loop will be increase count and quit. How to solve this problem. Please help.

You need to use closures in setUp function so that when the setUp if finished you will get the callback
private func setUp(associated_url : String , main_name: String,i : Int, ,sucess:((Void) -> Void)?,failure:((Void?) -> Void)?) {
if Reachability().isInternetAvailable() == true {
self.rest.auth(auth: self.prefs.value(forKey: "access_token") as! String!)
self.rest.get(url: StringResource().mainURL + associated_url , parma: [ "show_min": "true" ], finished: {(result : NSDictionary, status : Int) -> Void in
self.assetsTable.removeAll()
if(status == 200){
let data = result["data"] as! NSArray
for item in 0…data.count - 1 {
let themes : AnyObject = data[item] as AnyObject
let created = themes["created"] as! String
let assets_id = themes["id"] as! Int
let name = themes["name"] as! String
var poster_img_url = themes["poster_image_url"] as! String
let provider_id = themes["provider_id"] as! Int
poster_img_url = StringResource().posterURL + poster_img_url
self.assetsTable.append(AssetsTableItem(created: created, assets_id: assets_id, name: name, poster_image_url: poster_img_url , provider_id: provider_id))
}
}
self.sections.append(SectionsHome(package_name: main_name, package_url: associated_url,i: i,packageTable: self.assetsTable))
self.inner_count = self.sections.count
sucess!()
}else{
failure!()
}
})
}
}
And change the way you call the function as
self.setUp(associated_url: associated_url, main_name: name, i: i, sucess: { () in
debugPrint("SUCCESS")
}) { () in
debugPrint("FAILURE")
}

What I can understand is that you want to make loop wait until the data is not fetched in that particular interation number. If this is the case then you should look for synchronous operation. Like dont let the control passed to next iteration until the operation is finished. But this is a bad practice and makes your app behaviour non responsive.
I would say to work on asynchronous line and create a block or closure and put your code what is expected to perform when you receive your data and put the code outside that block that are not dependent on the data. Blocks anyway capture the environment in that case you should not be worried about the control getting passed.

Related

Swift generic functions issues. Decrease duplicating code

I want to understand how can decrease amount of duplicated code. I have two almost the same functions. The differences are next:
firs function returns array of [ExerciseEntity] and second function returns array of [WorkoutEntity]
func replaceExercisesIdentifiers(from jsonObjects: [[String: Any]], transaction: BaseDataTransaction) -> [ExerciseEntity] {
for jsonObject in jsonObjects {
if let mobileLocalId = jsonObject["mobileLocalId"] as? String {
if mobileLocalId.contains("<x-coredata://") {
if let managedObject = try? transaction.fetchOne(From<ExerciseEntity>()
.where(
format: "%K == %#",
#keyPath(BaseMO.id),
mobileLocalId)
) {
let editObject = transaction.edit(managedObject)
if let identifier = jsonObject["id"] as? String {
editObject?.id = identifier
}
}
}
}
}
let managedObjects = try! transaction.importUniqueObjects(
Into<ExerciseEntity>(),
sourceArray: jsonObjects)
return managedObjects
}
func replaceWorkoutsIdentifiers(from jsonObjects: [[String: Any]], transaction: BaseDataTransaction) -> [WorkoutEntity] {
for jsonObject in jsonObjects {
if let mobileLocalId = jsonObject["mobileLocalId"] as? String {
if mobileLocalId.contains("<x-coredata://") {
if let managedObject = try? transaction.fetchOne(From<WorkoutEntity>()
.where(
format: "%K == %#",
#keyPath(BaseMO.id),
mobileLocalId)
) {
let editObject = transaction.edit(managedObject)
if let identifier = jsonObject["id"] as? String {
editObject?.id = identifier
}
}
}
}
}
let managedObjects = try! transaction.importUniqueObjects(
Into<WorkoutEntity>(),
sourceArray: jsonObjects)
return managedObjects
}
This is a similar question related to how to use generic function I asked before.
I implemented this in my code but:
func importArray<T: ImportableUniqueObject>(from exercisesDict: [[String: Any]], transaction: BaseDataTransaction) -> [T] where T.ImportSource == [String: Any] {
let managedObjects = try? transaction.importUniqueObjects(Into<T>(), sourceArray: jsonObjects)
}
But here is few things, with T type
First - I can't add this code: editObject?.id = identifier
as there is no id in T type.
Second when I debug these generic functions debugger every time crashes:
Message from debugger: The LLDB RPC server has crashed. The crash log is located in ~/Library/Logs/DiagnosticReports and has a prefix 'lldb-rpc-server'. Please file a bug and attach the most recent crash log.
If interesting here is a file with log. I have not submitted it yet.
For sure I can add a lot of prints to track behavior, though it's a but annoying) But main task is to get rid of duplication.
Try this (I have not tested):
protocol MyProtocol {
var id: Int { get set }
}
struct ExerciseEntity {
var id: Int
}
struct WorkoutEntity {
var id: Int
}
func replaceWorkoutsIdentifiers<T: MyProtocol>(from jsonObjects: [[String: Any]], transaction: BaseDataTransaction) -> [T] {
for jsonObject in jsonObjects {
if let mobileLocalId = jsonObject["mobileLocalId"] as? String {
if mobileLocalId.contains("<x-coredata://") {
if let managedObject = try? transaction.fetchOne(From<T>()
.where(
format: "%K == %#",
#keyPath(BaseMO.id),
mobileLocalId)
) {
let editObject = transaction.edit(managedObject)
if let identifier = jsonObject["id"] as? String {
editObject?.id = identifier
}
}
}
}
}
let managedObjects = try! transaction.importUniqueObjects(
Into<T>(),
sourceArray: jsonObjects)
return managedObjects as! T
}
Using:
let array: [ExerciseEntity] = replaceWorkoutsIdentifiers(from jsonObjects: ..., transaction: ...)

Contextual type 'Void' cannot be used with dictionary literal

This Error comes up after I relaunched my Project without any changes, never heard of it before.
func toDictionary() -> [String : Any] {
let newPostRef = Database.database().reference().child("posts").childByAutoId()
let newPostKey = newPostRef.key
// 1. save image
if let imageData = image.jpegData(compressionQuality: 0.5) {
let storage = Storage.storage().reference().child("images/\(newPostKey)")
storage.putData(imageData).observe(.success, handler: { (snapshot) in
self.downloadURL = snapshot.metadata?.downloadURL()?.absoluteString
return [ <- The Error appears here!!!
"text" : text,
"imageDownloadURL" : downloadURL,
"numberOfLikes" : numberOfLikes,
"numberOfDislikes" : numberOfDislikes
]
}
)}
}
Maybe the following lines of Code help, as I only read something that this Error occurs because of any false String or something like that...
var text: String = ""
private var image: UIImage!
var downloadURL: String?
var numberOfLikes = 0
var numberOfDislikes = 0
let ref: DatabaseReference!
init(snapshot: DataSnapshot){
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
text = value["text"] as! String
downloadURL = value["imageDownloadURL"] as? String
numberOfLikes = value["numberOfLikes"] as! Int
numberOfDislikes = value["numberOfDislikes"] as! Int
}
}
The issue is that the Firebase function observe is asynchronous, so you cannot use the synchronous return statement to return values from it. You need to declare your function to use a completion handler.
func toDictionary(completion: #escaping ([String:Any])->()) {
let newPostRef = Database.database().reference().child("posts").childByAutoId()
let newPostKey = newPostRef.key
// 1. save image
if let imageData = image.jpegData(compressionQuality: 0.5) {
let storage = Storage.storage().reference().child("images/\(newPostKey)")
storage.putData(imageData).observe(.success, handler: { (snapshot) in
self.downloadURL = snapshot.metadata?.downloadURL()?.absoluteString
let snapShotDict = ["text" : text, "imageDownloadURL" : downloadURL, "numberOfLikes" : numberOfLikes, "numberOfDislikes" : numberOfDislikes ]
completion(snapShotDict)
}
)}
}
Then access it like this:
toDictionary(completion: { dict in
// You can only use `dict` inside the completion handler closure
print(dict)
})

Trying to get data from firebase Swift

Ok, I have a function that has to find the average of the ratings, so I store the total amount of ratings and a total sum of ratings in my firebase (which works fine). I am trying to retrieve the data, but it seems that it doesn't even enter the codeblock of the .observeSingleEvent I am using the same approach when trying to update the values, which means I get them and I add the new rating to them and then I use the code below to update the values:
let ratingObject = [
"uid" : (user?.uid)! as String,
"totalRatings" : newRating as Int,
"amountOfRatings" : newAmountOfRating as int
] as [String : Any]
dbRef.setValue(ratingObject)
It doesn't give an error and I am just lost
I tried to do it based on this tutorial: https://www.youtube.com/watch?v=JV9Oqyle3iE
The answers given in this thread : how parsing Firebase FDatasnapshot json data in swift are just crashing the app
private func FindAverage(uid: String) -> Int {
var totalRatings = 0
var amountOfRatings = 1
let dbRef = Database.database().reference().child("ratings").child(uid)
dbRef.observeSingleEvent(of: .value, with: { snapshot in
let dict = snapshot.value as? [String:Any]
totalRatings = dict?["totalRatings"] as? Int ?? 0
amountOfRatings = dict?["amountOfRatings"] as? Int ?? 1
}){ (error) in
print(error.localizedDescription)
}
return((Int)(totalRatings/amountOfRatings))
}
Database structure
Any tips and help is very appreciated!
You are trying to return value out of completion handler.
Your function should be like:
private func findAverage(byUid uid: String, completion: #escaping (_ average: Int) -> Void) {
// ...
dbRef.observeSingleEvent(of: .value, with: { snapshot in
guard let dict = snapshot.value as? [String: Any] else {
return
}
totalRatings = dict["totalRatings"] as? Int ?? 0
amountOfRatings = dict["amountOfRatings"] as? Int ?? 1
completion((Int)(totalRatings / amountOfRatings))
})
}
Something like this, check Swift docs (about completion handlers etc.).
Try below code:
private func FindAverage(uid: String) -> Int {
var totalRatings = 0
var amountOfRatings = 1
let dbRef = Database.database().reference().child("ratings").child(uid)
dbRef.observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
guard let dict = snapshot.value as? [String: Any] else {
return
}
totalRatings = dict?["totalRatings"] as? Int ?? 0
amountOfRatings = dict?["amountOfRatings"] as? Int ?? 1
}
}){ (error) in
print(error.localizedDescription)
}
return((Int)(totalRatings/amountOfRatings))
}

Firebase listen for changes in number of likes and display it on screen

When user doubletap on the image,the number of likes(heart) will increase for each post.
I tried this code but it does not work as expected(For eg when i double tap,it will load one post and duplicate it with different number of likes(Eg Post 1-1like,Post 1-2like,Post 1-3like).How do i display the updated number of likes without duplicating the post?(Post 1- Displaying the X number of likes where X=Incremental)
func loadPosts() {
let ref = Database.database().reference()
ref.child("posts").observe(.childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? [String: Any] {
guard let titleText = dict["title"] as? String else{return}
let locationDetails = dict["location"] as! String
let captionText = dict["caption"] as! String
let photoUrlString = dict["photoUrl"] as! String
let priceText = dict["price"] as! String
let categoryText = dict["category"] as! String
let usernameLabel = dict["username"] as! String
let profileImageURL = dict["pic"] as! String
let heartInt = dict["heart"] as! Int
let timestampString = dict["timestamp"] as! String
let post = Post(titleText: titleText, captionText: captionText, locationDetails: locationDetails, photoUrlString: photoUrlString, priceText: priceText,categoryText: categoryText, usernameLabel: usernameLabel, profileImageURL: profileImageURL, heartInt: heartInt, timestampString: timestampString)
//append(post) to array
self.posts.append(post)
print(self.posts)
self.collectionView.reloadData()
}
}
}
func delayCompletionHandler(completion:#escaping (() -> ())) {
let delayInSeconds = 0.5
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
completion()
}
}
//CollectionView
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
cell.reloadEvents = {
self.delayCompletionHandler {
self.loadPosts()
}
}
}
To detect the heartTapped which is in another file:
#objc func heartTapped(){
print(" I heart u")
let ref = Database.database().reference()
heartInt1 += 1
ref.child("posts").child(timestamp).observeSingleEvent(of: .value, with: { (snapshot) in
if let dic = snapshot.value as? [String : AnyObject]{
var heartString = dic["heart"] as! Int
heartString += 1
ref.child("posts").child(self.timestamp).updateChildValues(["heart" : heartString])
}
})
reloadEvents?()
}
}
Silly me I should have added self.posts.removeAll()in loadPosts()
Solved.
Your solution is bad practice. When you deal with databases you have to think at scale, if Kim Kardashian that gets a heart every second uses your app, whats going to happen then, load and delete everything for every heart? Does that sound efficient?
You need to find a way to load only the number of hearts not everything in order to get the number of hearts.
A solution would be to add an extra child ref.child("hearts") and then have a loadHearts() where you will get just the number of hearts.

Swift JSON with Alamofire - Unexpectedly found nil while unwrapping an Optional value

I'm trying to parse a JSON in my app and it's not working.
Here is a look at the JSON I'm trying to parse:
Check out the following screenshots. I get the error "Unexpectedly found nil while unwrapping an optional value" on the line
editorialElement.title = nodeElement!.valueForKey("title") as! String
Can anyone help me properly parse this JSON ?
EDIT 1: Here is more code. It shows what class I'm using (i.e. what objects I am creating based on the JSON file). I imagine this is what you meant by where I initialize JSON objects.
Here is my router as well (build using Alamofire). I feel like something might be wrong with it but at the same time, it makes a lot of sense and I don't know what missing:
EDIT 2: Here is the actual code:
func populateEditorials() {
if populatingEditorials {
return
}
populatingEditorials = true
Alamofire.request(GWNetworking.Router.Editorials(self.currentPage)).responseJSON() { response in
if let JSON = response.result.value {
/*
if response.result.error == nil {
*/
/* Creating objects for every single editorial is long running work, so we put that work on a background queue, to keep the app very responsive. */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
/*
// Making an array of all the node IDs from the JSON file
let nodeIDArray : [String]
var nodeCounter : Int = 0 */
for node in JSON as! NSDictionary {
var nodeElement = JSON.valueForKey(String(node))
self.nodeIDArray.addObject(String(node.key))
var editorialElement : EditorialElement = EditorialElement(title: "init", nodeID: 0, timeStamp: 0, imageURL: "init", author: "init", issueNumber: "init", volumeNumber: "init", articleContent: "init")
editorialElement.title = nodeElement!.valueForKey("title") as! String
editorialElement.nodeID = nodeElement!.valueForKey("nid") as! Int
editorialElement.timeStamp = nodeElement!.valueForKey("revision_timestamp") as! Int
editorialElement.imageURL = nodeElement!.valueForKey("image_url") as! String
editorialElement.author = nodeElement!.valueForKey("author") as! String
editorialElement.issueNumber = nodeElement!.valueForKey("issue_int") as! String
editorialElement.volumeNumber = nodeElement!.valueForKey("volume_int") as! String
editorialElement.articleContent = nodeElement!.valueForKey("html_content") as! String
self.editorialObjects.addObject(editorialElement)
/*
nodeIDArray[nodeCounter] = jsonValue{nodeCounter}.string
let editorialInfos : EditorialElement = ((jsonValue as! NSDictionary).valueForKey("\(nodeIDArray[nodeCounter])") as! [NSDictionary]).map { EditorialElement(title: $0["title"] as! String, nodeID: $0["nid"] as! Int, timeStamp: $0["revision_timestamp"] as! Int, imageURL: $0["image_url"] as! String, author: $0["author"], issueNumber: $0["issue_int"] as! Int, volumeNumber: $0["volume_int"] as! Int, articleContent: $0["html_content"] as! String) /* I am going to try to break this line down to simplify it and fix the build errors */
*/
}
print(self.editorialObjects)
}
/* Sorting the elements in order of newest to oldest (as the array index increases) */
self.editorialObjects.sort({ $0.timeStamp > $1.timeStamp })
let lastItem = self.editorialObjects.count
let indexPaths = (lastItem..<self.editorialObjects.count).map { NSIndexPath(forItem: $0, inSection: $0) }
dispatch_async(dispatch_get_main_queue()) {
self.editorialsTableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Automatic) // Animation implemented for testing, to be removed for version 1.0
}
self.currentPage++
/* } */
}
self.populatingEditorials = false
}
}
Here is the code for my Class and Router:
class EditorialElement: NSObject {
var title: String // title
var nodeID: Int // nid
var timeStamp: Int // revision_timestamp
var imageURL: String? // image_url
var author: String // author
var issueNumber: String // issue_int
var volumeNumber: String // volume_int
var articleContent: String // html_content
/* To get an NSDate objec from Unix timestamp
var date = NSDate(timeIntervalSince1970: timeStamp) */
init(title: String, nodeID: Int, timeStamp: Int, imageURL: String, author: String, issueNumber: String, volumeNumber: String, articleContent: String) {
self.title = title
self.nodeID = nodeID
self.timeStamp = timeStamp
self.imageURL = imageURL
self.author = author
self.issueNumber = issueNumber
self.volumeNumber = volumeNumber
self.articleContent = articleContent
}
override func isEqual(object: AnyObject!) -> Bool {
return (object as! EditorialElement).nodeID == self.nodeID
}
override var hash: Int {
return (self as EditorialElement).nodeID
}
}
enum Router : URLRequestConvertible {
static let baseURLString = MyGlobalVariables.baseURL
case Issue
case Editorials(Int)
case News(Int)
case Random(Int)
case Pictures(Int)
var URLRequest: NSMutableURLRequest {
let path : String
let parameters: [String: AnyObject]
(path) = {
switch self {
case .Issue:
return ("/issue")
case .Editorials (let editorialsSection): /* If section == 0, this will return the first ten editorials. If section == 1, then section * 10 = 10, and we will get the ten editorials after that. */
return ("/list/editorials/\(editorialsSection * 10)")
case .News (let newsSection):
return ("/list/news/\(newsSection * 10)")
case .Random (let randomSection):
return ("/list/random/\(randomSection * 10)")
case .Pictures (let page):
return ("/list/pictures/\(page)")
}
}()
let URL = NSURL(string: Router.baseURLString)
let GoldenWordsURLRequest = NSMutableURLRequest(URL: URL!.URLByAppendingPathComponent(path))
/* let encoding = Alamofire.ParameterEncoding.URL */
return GoldenWordsURLRequest
/* return encoding.encode(URLRequest, parameters: parameters).0 */
}
}
}
That's because you take an optional reference (editorialElement returned from a failable initializer) and call valueForKey("title") on it. It stumbles over the access to "title" because it goes first in you code while the target of the call is nil. I would recommend organizing your code as follows:
if let editorialElement = EditorialElement(title:..., nodeID: and such)
{
... assigning new values to the properties
}
and you will notice that you don't enter the if-let scope. You will avoid the crash and make sure the problem is inside the arguments you initialize the editorialElement with.

Resources