GCD with nested Parse Queries - ios

func getPosts(skip: Int){
var query = PFQuery(className: self.parseClassName!)
query.includeKey("posted_by")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil && objects != nil {
if let objects = objects as? [PFObject] {
var requestGroup = dispatch_group_create()
for post in objects
{
dispatch_group_enter(requestGroup)
let queryKommentar1 = PFQuery(className:"Comment")
queryKommentar1.whereKey("posted_to", equalTo: post)
queryKommentar1.limit = 3
queryKommentar1.includeKey("written_by")
queryKommentar1.findObjectsInBackgroundWithBlock() {
(commentObjects: [AnyObject]?, error: NSError?) -> Void in
//Creating UITableViewCells from data and store in array
dispatch_group_leave(requestGroup)
}
}
println("Successfully retrieved \(objects.count) posts.")
dispatch_group_notify(requestGroup, dispatch_get_main_queue()) {
println("All done")
}
}
}
}
}
So, I'm not sure if I misunderstood dispatch groups, but my intention is to make two Parse Queries, targeting different classes, and create TableViewCells from the data provided. This works fine, but since I don't want the data to load when the user is scrolling the table, I want to preload the data and create the cells, and store them in an Array. Since I would like to remove any Activity Indication, and reload the table, by the time the fetch is complete I though (after som Googleing) that dispatch groups might be a good solution for this. However, "All done" is never printed in the console.
When I made a dispatch group around the outer query (entering the group just before the query, and leaving as the last line in the block) that worked fine. What am I doing wrong? Is it impossible to use this when nesting asynchronous calls?
FYI, I removed a lot of code, like creating the cells and using the data from Parse, since I would like to spare you from reading that mess.

This dispatch group pattern is basically right.
I would suggest logging some message at dispatch_group_enter and at dispatch_group_leave and see if it's getting called as you think it should and that every enter is offset by a leave.
If the number of occurrences of dispatch_group_leave are less than the number of calls to dispatch_group_enter, the dispatch_group_notify block will not get called.
Perhaps you have some path in that inner findObjectsInBackgroundWithBlock closure that is preventing it from hitting the dispatch_group_leave call.

Related

Handle large JSON files in TableView

I'm trying to parse a large JSON file (approx: 1000 rows containg a tuple with 8 strings) and display this in a UITableView. What I already have is working but I am looking for a more efficient way of displaying them.
At the moment my code looks likes this:
public func GET(request: String, callback: (result: JSON?, response: NSHTTPURLResponse?, error: NSError?) -> Void) {
let session = NSURLSession.sharedSession()
let url = NSURL(string : "SOMEURL")
let task = session.dataTaskWithURL(url!){
(data, response, error) -> Void in
if error != nil {
callback(result: nil, response: response as? NSHTTPURLResponse, error: error!)
} else {
callback(result: JSON(data : data!), response: response as? NSHTTPURLResponse, error: nil)
}
}
task.resume()
}
This does parse the data using SwiftJSON (see JSON(data : data!)), then when it comes to actually filling an array i use a class containing two attributes (one for the Main text in table and one for detail text)
class SomeClass {
let MainText : String
let DetailText : String
init(MainText : String, DetailText : String) {
self.MainText = MainText
self.DetailText = Detailtext
}
}
Now in the UITableView i have a .swift file and in the
override func ViewDidLoad() {
//code
}
I use a loop to get the data from the result callback in the GET method to append to an array of
var rows : [SomeClass] = []
This is very CPU intensive but I did not find another way to deal with this problem. I tried only displaying 50 rows in the table and only creating 50 class items for the rows. But none of that matters, what I fear is that the SwiftyJSON way of dealing with this problem is not the right one but i thought that maybe I am overlooking something.
If I understood your problem, you are worried about CPU / Energy Efficiency.
What you should consider, if it's not how your app already works, is implementing the parsing process in the background thread, make your [SomeClass] array observable and update the table when it changes (aka when the background parsing added an new value to it).
So first make your parsing function run in background (for instance with the Async GCD wrapper) :
func callback(JSON?, response: NSHTTPURLResponse, error: NSError?) {
Async.background {
//Do your JSON parsing stuff here, XXX is a SomeClass object
rows <- rows + [XXX]
}
}
You might have noticed the unusual syntax for the array appending method. That's because making your array "observable" is part of the solution. I advise you to get the Observable-Swift library to make it easier to observe.
Once added to your project, change your array declaration :
var rows = Observable([SomeClass]())
Now implement the method that will be called when your callback parsed a new item (for instance in your viewDidLoad:)
rows.afterChange += { self.table.reloadData() }
where table is your table view
If you want to implement a power-friendly runtime, you might want to update the table every time 50 or 100 objects are added to the array. This can be done so (if you want to do so do not implement the method right above):
rows.afterChange += { if $1.count / 100 = 1 { self.table.reloadData() }}
where 100 is the value of new object required to be added in order to update the table. With Observable-Swift, $0 represents the array before it was updated and $1 the array after its update.
One last thing : the rows array is no longer of type [SomeClass] but Observable<SomeClass>. If you want to access the [SomeClass] value, just replace rows by rows.value
Hope I didn't misunderstood your question. Anyway if I did, I think that can still help providing a better implementation of JSON parsing.
You should not be worried about how much of data you have to display in TableView.
TableView class handles everything for you as long as you pass the json object properly as a Tablesource.
It's actually a pretty good concern about how you use the resources. Normally, we will go with pagination if you don't want to query back whole amount of data from a request. Then, you will implement some proper logic based on the skip and limit in order to get further data.
As for the UITableView, there is nothing to worry about. Because, it's developed in an efficient way. The total number of cell in memory is the total number of cell visible. The UITableView will help populating the data via delegation methods. It's not like: you have 500 rows of data, then it has 500 UITableViewCell. It's reusability.

Value type of BFTask has no member Generator: Swift 2.0

override func viewDidAppear(animated: Bool) {
var query = PFQuery(className: "_User")
query.whereKey("username", equalTo: userName)
var objects = query.findObjectsInBackground()
self.resultsImageFiles.removeAll(keepCapacity: false)
**for object in objects {**
self.resultsImageFiles.append(object["photo"] as! PFFile)
self.resultsImageFiles[0].getDataInBackgroundWithBlock {
(imageData:NSData?, error:NSError?) -> Void in
}
}
}
So I've spent this morning looking around the site/googling and maybe I am just dense but I can't seem to find the answer to my error. The error is, as I stated in the title: value type of BFTask has no member generator.
It is located in the line for object in objects. I have tried to unwrap it, but it did not work (maybe because the option is not meant to be nil)? Any ideas? Thanks,
Brock
findObjectInBackground() returns an object representing a background task, not an array. At the place in your code you’ve put the for loop, the background task may not have even completed yet.
You need to wait for it to complete and then extract its results by using continueWithBlock, which will give you the array you’re looking for.

Query class in Parse (swift)

I have a strange issue. The following block of code is placed in my viewDidAppear section of the first View Controller, and when it runs, println(latArray), println(longArray), println(addressArray), all return no value. In the console it returns [] for all three arrays. HOWEVER, when I go to another view controller, and go back, it will populate with the data from Parse. Why is this? Why wont latArray, longArray, and addressArray populate when the app is loaded the first time with the viewWillAppear method?
var query = PFQuery(className: "NewLog")
// Add a where clause if there is a search criteria
query.whereKey("Type", containsString: newRecordCreated)
println(query)
query.findObjectsInBackgroundWithBlock({
(objects, error) -> Void in
if error == nil {
// Results were successfully found
if let objects = objects as? [PFObject] {
for object in objects {
//println(object["follower"])
latArray.append(object["Lat"] as! Double)
longArray.append(object["Long"] as! Double)
addressArray.append(object["Address"] as! String)
}
}
// self.tableView.reloadData()
} else {
println("error")
// The network was inaccessible and we have no cached data for
// this query.
}
})
println(latArray)
println("^^Latitude values")
println(longArray)
println("^^Longitude values")
println(addressArray)
println("^^Address values")
}
The query is being executed in the background. The code in the block (closure) is executed when the query is finished. Note: this might be and most likely is after viewDidAppear finishes. If you put your print statements after your for loop, you should see values. If you uncomment your reloadData method, the table should be updated with new information when the query finishes.

Sequence in codes inside a function - Swift

I have a function which will be triggered when a like button is clicked, something like the facebook "like" buttons. I have the first part, and the second part as commented below. The problem is that, when the code runs, there is no sequence in the codes. For example I need the first part to run and then the second part, but sometimes the second part runs first. How can I add a sequence as priority to run?
#IBAction func likeBtn(sender: AnyObject) {
/* first part */
if likeTitle == "Like" {
var likeObj = PFObject(className: "likes")
likeObj["userName"] = PFUser.currentUser()!.username
likeObj["tweetObjectId"] = objectid.text
likeObj.save()
likeBtn.setTitle("Liked", forState: UIControlState.Normal)
}
/* second part */
var likeCount = PFQuery(className: "likes")
likeCount.whereKey("ObjectId", equalTo: objectid.text!)
var likedUsersCount = likeCount.countObjects()
var addLikeCountQuery = PFQuery(className: "comments")
addLikeCountQuery.whereKey("objectId", equalTo: objectid.text!)
addLikeCountQuery.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
for object in objects! {
var ob:PFObject = object as! PFObject
ob["numberOfLikes"] = likedUsersCount
ob.save()
}
}
}
The first part will only run if likeTitle == "Like", so if likeTitle != "Like" then your first part won't run, but the second part will always run.
Another problem could (keyword here is "could" :-)) be that in your first part, you call likeObj.save(). I don't know what your code looks like, but this could be asynchronous, meaning that it'll save whatever needs to be saved in a background thread to not block the main thread, and meanwhile your second part code will continue running on the main thread.
If that's the case, then maybe you should consider some sort of callback to be executed when the save operation is completed, and in that callback, you invoke your second part.

UITableViewController with Section and Parse as BackEnd server

I don't find any good and update answers about loading data from Parse to an UITableViewController with sections.
I know that using PFQueryTableViewController is only possible for 1 section.
I have a class Recipes with the following columns in Parse:
Section; Name; Calories
Hence my database looks like this
Morning; Eggs; 120
Morning, Bacon; 250
Lunch; Meat; 340
....
I compute a function to query the data from Parse like this:
func queryData(){
var query = PFQuery(className: self.recipesClass as String)
//query.cachePolicy = .CacheElseNetwork
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// Results were successfully found, looking first on the
// network and then on disk.
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
self.tableSections.addObject(object)
}
} else {
// The network was inaccessible and we have no cached data for
// this query.
}
}
}
Where tableSections is a NSMutableArray.
From here, I'm a bit lost on how to proceed to achieve the required results.
Please help,
Thank you in advance
You will want to create different sections based on the 'Section' property of each returned object. This is a bit tricky for someone new to UITableView, but it can be accomplished in around an hour with Sensible TableViews (http://sensiblecocoa.com/).
Sensible TableViews will let you take an array of objects and separate it into several sections. It even interfaces directly with Parse.com if you so desire. I use it in almost all of my apps for its simple, clean approach to tables and cloud data.
Start with their online guide here:
http://sensiblecocoa.com/usermanual/latest/
You can also skip right to Parse.com Integration here: http://sensiblecocoa.com/usermanual/latest/#ExploringParseComBinding

Resources