Sequence in codes inside a function - Swift - ios

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.

Related

How to filter large array in iOS swift 2 for uisearchbar

I am having a UISearchBar with more than 80000 elements in an array and I have to filter this array according to the user input.
But while typing in search view its working very slow means its taking too much time for typing values in keyboard.
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.characters.count == 0 {
searchActive = false
} else {
searchActive = true;
filtered.removeAllObjects()
dispatch_to_background_queue {
for sumber in self.data {
let nameRange: NSRange = sumber.rangeOfString(searchText, options: [NSStringCompareOptions.AnchoredSearch,NSStringCompareOptions.CaseInsensitiveSearch])
if nameRange.location != NSNotFound {
self.filtered.addObject(sumber)
}
}//end of for
self.dispatch_to_main_queue {
/* some code to be executed on the main queue */
self.tableView.reloadData()
}
} //end of dispatch
}
}
func dispatch_to_main_queue(block: dispatch_block_t?) {
dispatch_async(dispatch_get_main_queue(), block!)
}
func dispatch_to_background_queue(block: dispatch_block_t?) {
let q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(q, block!)
}
There are two approaches to combine here for the best result:
First, keep long-running operations off the main (UI) thread
You can dispatch the filtering to a background thread using dispatch_async, or even to a background thread after some delay using dispatch_after.
Second, don't filter the array immediately after every key press
It's a waste of time because usually the user will type several keys before waiting to see what pops up. You want to therefore delay the filtering operation, and only perform it after some small amount of time has passed since the last key press. This is called "debouncing".
Here's a neat way to do all of this in Swift:
func debounce(delay:NSTimeInterval, queue:dispatch_queue_t, action: (()->())) -> (()->()) {
var lastFireTime:dispatch_time_t = 0
let dispatchDelay = Int64(delay * Double(NSEC_PER_SEC))
return {
lastFireTime = dispatch_time(DISPATCH_TIME_NOW,0)
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
dispatchDelay
),
queue) {
let now = dispatch_time(DISPATCH_TIME_NOW,0)
let when = dispatch_time(lastFireTime, dispatchDelay)
if now >= when {
action()
}
}
}
}
class ViewController {
lazy var debouncedFilterArray : () -> () = debounce(0.3, queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), action: self.filterArray)
func filterArray() {
// do filtering here, but don't call this function directly
}
}
The debounce function itself returns a function that when called will exhibit this "debouncing" behaviour, running no more often than the delay interval passed to it.
To use, simply call debouncedFilterArray(). It will in turn call filterArray, but always on a background thread and never more often than every 0.3 seconds.
I want to add a couple of thoughts.
You already seem to do async processing, which is great. It won't make the search faster, but the app keeps responsive. Consider making it stoppable. If the user types three letters, you will queue up three searches and will get the relevant results only after the last run finished. This could be done using some sort of a boolean stop flag that gets checked within the search. If a new search is started, kill the old one first.
Show partial results. The user won't be watching at thousands of cells at once, but only at the first 20 or so. Depending on the order of your input and output, this may be very easy to do and fast as hell.
Build on your previous search. Searching for "Ab" will only be successful if searching for "A" (or "b" for that matter if the search wasn't anchored) was successful at well. So if your last search was a substring from your current search, take the output array of your previous search as an input. Obviously, be careful with stopped searches here.
Check if performance is really as bad. Do you run with optimizations switched on? The debug mode might be considerable slower, but that wouldn't matter.
Where does the data come from? That's a rather huge amount of data to keep around in memory. If it's coming from a database, using database functions might be easier (and most words above still comply).
Still too slow? Index your data set. If you know upfront which elements contain "A", the number of needed searches could drop significantly. And you'd have the results for the first search already
As you're using anchored search, working on a sorted array could give a much better performance characteristic. Just find the first and last element of your search term with binary search and use that range. Maybe without even copying into a new array. This approach puts some workload upfront (maybe before the user even started typing). If your search data is within larger objects, some sort of index tables would do.
You could perform the filtering on a background thread so to leave the main thread (which does manage the UI) responsive.
func filter(list:[String], keyword:String, completion: (filteredList:[String]) -> ()) {
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
dispatch_async(queue) {
let filtered = list.filter { $0.containsString(keyword) }
dispatch_async(dispatch_get_main_queue()) {
completion(filteredList: filtered)
}
}
}
Example
let data = ["dog", "cat", "eagle"]
filtered(data, keyword: "do") { (filteredList) -> () in
// update the UI here
}
80000! That is a lot of data indeed. One solution which could considerably speed things is to shrink the search array after each key stroke AND to cancel the search if many keystrokes are typed in a row while caching the search in case keystrokes are erased. You could combine it with appzYouLife's answer and you would already have a more solid framework. Here is an example of how that could work, the token is necessary so that you update the UI in accordance with the search:
var dataToSearch = [AnyObject]()
var searchCache = NSCache()
var currentSearchToken = 0
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
performSearch(searchText, searchToken: ++currentSearchToken)
}
func performSearch(searchText: String, searchToken: Int) {
if let searchResults = searchCache.objectForKey(searchText) as? [AnyObject] { //If the search is cached, we simply pull the results
guard searchToken == currentSearchToken else {return} //Make sure we don't trigger unwanted UI updates
performListUpdate(searchResults)
return
}
var possiblePreviousSearch = searchText //We're going to see if we can build on any of previous searches
while String(possiblePreviousSearch.characters.dropLast()).characters.count > 0 { //While we still have characters
possiblePreviousSearch = String(possiblePreviousSearch.characters.dropLast()) //Drop the last character of the search string
if let lastSearch = searchCache.objectForKey(possiblePreviousSearch) as? [AnyObject]{ //We found a previous list of results
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
dispatch_async(queue) {
let newResults = lastSearch.filter {object in /**put your conditions here instead of return true*/ return true} //Sort on top of a previous search
self.searchCache.setObject(newResults, forKey: searchText)
guard searchToken == self.currentSearchToken else {return} //We don't want to trigger UI Updates for a previous search
dispatch_async(dispatch_get_main_queue()) {
self.performListUpdate(newResults)
return
}
}
}
}
//If we got to this point, we simply have to search through all the data
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
dispatch_async(queue) {
let newResults = self.dataToSearch.filter {object in /**put your conditions here instead of return true*/ return true} //Sort on top of a previous search
self.searchCache.setObject(newResults, forKey: searchText)
guard searchToken == self.currentSearchToken else {return} //We don't want to trigger UI Updates for a previous search
dispatch_async(dispatch_get_main_queue()) {
self.performListUpdate(newResults)
return
}
}
} //end of perform search
Of course this answer isn't perfect. It assumes your lists can be sorted on top of smaller ones (for example the results of searching for "abc" will be a subset of the results of searching "ab" and "a").
EDIT Combine this with debouncing as shown in another answer and you have an ok performance!

GCD with nested Parse Queries

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.

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.

Array index out of range on refresh

I have a Posts class that I am querying in a UITableViewController using Parse as my backend.
In my viewDidAppear I call my loadData() function. And then I have a var refresher = UIRefreshControl() that is responsible for my refresh() function.
After a few time reloading the data I get a a fatal error: Array index out of range and the following line of code highlighted which is in my cellForRowAtIndexPath.
What was interesting is when I printed out what was in my
index path using println(drive), all the posts were there. But then there were instances where some posts appeared twice then the app would crash.
timelineData.removeAll(keepCapacity: false)
I thought that having this should clear everything so I am not sure why this is happening.
Here is my code for my refresh function.
func refresh()
{
println("refresh table from pull")
timelineData.removeAll(keepCapacity: false)
var findItemData:PFQuery = PFQuery(className:"Posts")
findItemData.addDescendingOrder("createdAt")
findItemData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]? , error:NSError?) -> Void in
if error == nil
{
self.timelineData = objects as! [PFObject]
self.newsFeedTableView.reloadData()
}
}
self.refresher.endRefreshing()
}
I tried using Parse's query.cachePolicy but that didn't matter because the crash kept happening. https://parse.com/docs/ios/guide#queries-querying-the-local-datastore
I also thought it was because I have Parse.enableLocalDatastore() but still no luck.
I do call my other function loadData in my viewDidAppear as mentioned earlier, not sure if this might be the problem, but I don't know how to check for data when there is an update. Still not sure if this is the source of the problem.
EDIT 1
I have attached my timelinedata count in several functions. Second image is when I print the count in my cellForRowIndexPath
Try to:
findItemData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]? , error:NSError?) -> Void in
if error == nil
{
timelineData.removeAll(keepCapacity: false)
self.timelineData = objects as! [PFObject]
self.newsFeedTableView.reloadData()
}
}
It could happen that you have inconsistent data before you actually populate your list. This way you will have your data if some kind of error occurs, so you are safe from that point as well.

popToViewController Executing First

When the post button is pressed, the function below executes. In the function, all the objects that are retrieved using the Parse backend are appended to the groupConversation array, which is a global array. However, when I reference the array in the UITableViewController that is popped to towards the end of the function and use println() to print the content of the array, the array is empty. However, when I use println() in the UIViewController that contains this function the array is shown to contain one object. In the console, the println() of the UITableViewController that is popped to once the button is pressed, is executed before the println() of the UIViewController that contains the function below. How can I make the functon below execute completely before popping to the UITableViewController.
#IBAction func postButtonPressed(sender: AnyObject) {
//Adds Object To Key
var name=PFObject(className:currentScreen)
name["userPost"] = textView.text
name.saveInBackgroundWithBlock {
(success: Bool!, error: NSError!) -> Void in
if success == true {
self.textView.text=""
} else {
println("TypeMessageViewController Error")
}
}
//Gets all objects of the key
var messageDisplay = PFQuery(className:currentScreen)
messageDisplay.selectKeys(["userPost"])
messageDisplay.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil{
for object in objects {
var textObject = object["userPost"] as String
groupConversation.append(textObject)
}
} else {
// Log details of the failure
}
println("Type message \(groupConversation)")
}
navigationController!.popToViewController(navigationController!.viewControllers[1] as UIViewController, animated: true)
}
The problem is here messageDisplay.findObjectsInBackgroundWithBlock. As you are doing this in background thread, it will be separated from main thread. And your main thread will execute as it should be.
So it before finishing the task you main thread popping the view.
messageDisplay.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil{
for object in objects {
var textObject = object["userPost"] as String
groupConversation.append(textObject)
}
} else {
// Log details of the failure
}
println("Type message \(groupConversation)")
dispatch_async(dispatch_get_main_queue()) {
self.navigationController!.popToViewController(navigationController!.viewControllers[1] as UIViewController, animated: true)
return
}
}
Pushing and popping in background thread may cause problem. So get the main thread after executing the task in background and then pop in main thread.
In swift single statement closures automatically return the statement return value. In your specific case, it's attempting to return an instance of [AnyObject]?, which is the return value of popToViewControllerAnimated. The closure expected by dispatch_afteris Void -> Void instead. Since the closure return type doesn't match, the compiler complains about that.
Hope this helps.. ;)
You are running into a very common issue with asynchronous code. Both your ...InBackgroundWithBlock {} methods run something in the background (async).
The best example I have found to explain it is this:
When you start an async code block, it is like putting eggs on to boil. You also get to include something that should be done when they finish boiling (the block). This might be something like remove the shell and slice the eggs.
If your next bit of code is "butter bread, put eggs on bread" you might get unexpected results. You don't know if the eggs have finished boiling yet, or if the extra tasks (removing shell, slicing) has finished yet.
You have to think in an async way: do this, then when it is finished do this, etc.
In terms of your code, the call to popToViewController() should probably go inside the async block.

Resources