Parse via Swift with Complex Queries - ios

Im having some difficulty with creating complex queries in Parse. What I'm looking to achieve is searching the _Users class and not returning myself as a result and if an Invite already exists in the Invites class, show Pending text rather than Add Button and if both myself and user have already accepted invite, dont show them at all.
I've achieved some of it but I'm not sure i'm doing it the most efficient way. For instance, I first query the _User class and find any users that match the search terms, then loop through and if the objectId == currentUser().objectId, skip that record. Then I run another query in that loop on each record to see if there are any pending invites, if so, i set a flag to true for Pending however i've done that search not in the background because i was having isses with the first query block finishing before the second and my flag not getting set first. So would I then do a third query to see if the field is accepted? Or is there a way to make this all one big query? My code is below for the searching:
func performSearchForText(text: String, completion: SearchComplete) {
state = .Loading
// Query Parse
var containsDisplayName = PFQuery(className:"_User")
containsDisplayName.whereKey("displayName", containsString: text)
var containsUsername = PFQuery(className: "_User")
containsUsername.whereKey("username", containsString: text)
var query = PFQuery.orQueryWithSubqueries([containsUsername, containsDisplayName])
query.findObjectsInBackgroundWithBlock {
(results: [AnyObject]?, error: NSError?) -> Void in
//self.state = .NotSearchedYet
var success = false
if error == nil {
// Found results
// Set Result state to either Results or NoResults
if let results = results {
//println(results)
if results.count == 0 {
self.state = .NoResults
} else {
// Read Results into UserSearchResult Array
// Dont show self in results
// If user has already accepted a request, dont show
// If they have already invited, show Pending instead of button
var userSearchResults = [UserSearchResult]()
var searchResult = UserSearchResult()
for result in results {
if result.objectId != PFUser.currentUser()?.objectId {
// Query invites table to see if they already are accepted with user
// or if a pending invite exists
// Set invite or accepted respectively
var invitedQuery = PFQuery(className: "Invites")
invitedQuery.whereKey("pending", equalTo: true)
invitedQuery.whereKey("inviteToUser", equalTo: result.objectId!!)
var invitedQueryResults = invitedQuery.findObjects()
if invitedQueryResults?.count > 0 {
self.pendingInvite = true
}
searchResult.displayName = result["displayName"] as! String
searchResult.emailAddress = result["username"] as! String
searchResult.inviteUserID = result.objectId!!
searchResult.invited = self.pendingInvite
searchResult.accepted = false
userSearchResults.append(searchResult)
}
}
if userSearchResults.count > 0 {
self.state = .Results(userSearchResults)
} else {
self.state = .NoResults
}
}
success = true
}
dispatch_async(dispatch_get_main_queue()) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
completion(success)
}
} else {
// Error, print it
println(error)
}
}
}

Unless there's a lot more to your invites table, I'd suggest removing it and adding an "invitePending" field to your user table.
Then you can just further refine your other queries by using wherekey("invitePending", equalTo: true).
Additionally, you can do something like this at the start so you don't have to check for currentUser in the query completion block:
containsDisplayName.whereKey("objectId", notEqualTo: PFUser.currentUser()!.objectId!)
But you definitely don't need the invites table if it isn't storing lots of other info. If you have to keep it as a separate table, using a pointer (for 1 to 1) or a PFRelation (for 1 to many) would save you the headache of that inner query.

Related

Faster way to check if entry exists in Core Data

In my app when data is synced i can get 20k entries (from given timestamp) from the server that should be synced to the local device. For every entry i try to fetch it (if it exist already) and if doesn't i create new. The problem is that the whole operation is too slow - for 20k on iphone 5 is 10+ mins. Another solution that i though is to delete all entries from the given timestamp and create new entries for all returned entries and there will be no need to perform fetch for every single entry ? If someone have any advice will be nice. Here is sample code for the current state:
var logEntryToUpdate:LogEntry!
if let savedEntry = CoreDataRequestHelper.getLogEntryByID(inputID: inputID, fetchAsync: true) {
logEntryToUpdate = savedEntry
} else {
logEntryToUpdate = LogEntry(entity: logEntryEntity!, insertInto: CoreDataStack.sharedInstance.saveManagedObjectContext)
}
logEntryToUpdate.populateWithSyncedData(data: row, startCol: 1)
Here is the actual request method:
class func getLogEntryByID(inputID:Int64, fetchAsync:Bool) ->LogEntry? {
let logEntryRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "LogEntry")
logEntryRequest.predicate = NSPredicate(format: "inputId == %#", NSNumber(value: inputID as Int64))
logEntryRequest.fetchLimit = 1
do {
let mocToFetch = fetchAsync ? CoreDataStack.sharedInstance.saveManagedObjectContext : CoreDataStack.sharedInstance.managedObjectContext
if let fetchResults = try mocToFetch.fetch(logEntryRequest) as? [LogEntry] {
if ( fetchResults.count > 0 ) {
return fetchResults[0]
}
return nil
}
} catch let error as NSError {
NSLog("Error fetching Log Entries by inputID from core data !!! \(error.localizedDescription)")
}
return nil
}
Another thing that i tried is to check the count for specific request but again is too slow.
class func doesLogEntryExist(inputID:Int64, fetchAsync:Bool) ->Bool {
let logEntryRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "LogEntry")
logEntryRequest.predicate = NSPredicate(format: "inputId == %#", NSNumber(value: inputID as Int64))
//logEntryRequest.resultType = .countResultType
logEntryRequest.fetchLimit = 1
do {
let mocToFetch = fetchAsync ? CoreDataStack.sharedInstance.saveManagedObjectContext : CoreDataStack.sharedInstance.managedObjectContext
let count = try mocToFetch.count(for: logEntryRequest)
if ( count > 0 ) {
return true
}
return false
} catch let error as NSError {
NSLog("Error fetching Log Entries by inputID from core data !!! \(error.localizedDescription)")
}
return false
}
Whether fetching the instance or getting the count, you're still doing one fetch request per incoming record. That's going to be slow, and your code will be spending almost all of its time performing fetches.
One improvement is to batch up the records to reduce the number of fetches. Get multiple record IDs into an array, and then fetch all of them at once with a predicate like
NSPredicate(format: "inputId IN %#", inputIdArray)
Then go through the results of the fetch to see which IDs were found. Accumulate 50 or 100 IDs in the array, and you'll reduce the number of fetches by 50x or 100x.
Deleting all the entries for the timestamp and then re-inserting them might be good, but it's hard to predict. You'll have to insert all 20,000. Is that faster or slower than reducing the number of fetches? It's impossible to say for sure.
Based on Paulw11's comment, I came up with the following method to evaluate Structs being imported into Core Data.
In my example, I have a class where I store search terms. Within the search class, create a predicate which describes the values of the stuff within my array of structs.
func importToCoreData(dataToEvaluateArray: [YourDataStruct]) {
// This is what Paul described in his comment
let newDataToEvaluate = Set(dataToEvaluateArray.map{$0.id})
let recordsInCoreData = getIdSetForCurrentPredicate()
let newRecords = newDataToEvaluate.subtracting(recordsInCoreData)
// create an empty array
var itemsToImportArray: [YourDataStruct] = []
// and dump records with ids contained in newRecords into it
dataToEvaluateArray.forEach{ record in
if newRecords.contains(record.id) {
itemsToImportArray.append(record)
}
}
// THEN, import if you need to
itemsToImportArray.forEach { struct in
// set up your entity, properties, etc.
}
// Once it's imported, save
// You can save each time you import a record, but it'll go faster if you do it once.
do {
try self.managedObjectContext.save()
} catch let error {
self.delegate?.errorAlert(error.localizedDescription, sender: self)
}
self.delegate?.updateFetchedResultsController()
}
To instantiate recordsInCoreData, I created this method, which returns a Set of unique identifiers that exist in the managedObjectContext:
func getIdSetForCurrentPredicate() -> Set<String> {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "YourEntity")
// searchQuery is a class I created with a computed property for a creating a predicate. You'll need to write your own predicate
fetchRequest.predicate = searchQuery.predicate
var existingIds: [YourEntity] = []
do {
existingIds = try managedObjectContext.fetch(fetchRequest) as! [YourEntity]
} catch let error {
delegate?.errorAlert(error.localizedDescription, sender: self)
}
return Set<String>(existingIds.map{$0.id})
}

How to sort NSDate in descending order from Parse in ios swift

I am looking to get the Parse data of my past meetings. My issue is I want to see the most recent events first. If I fetch the data with following code, am getting it in ascending order. i.e. most recent meeting goes to the last and the oldest comes first.
//let oldDate = NSDate(timeIntervalSinceReferenceDate: -123456789.0)
let query:PFQuery = PFQuery(className: "Events")
//query.whereKey("EventSTDTime", greaterThan: oldDate)
query.whereKey("EventSTDTime", lessThan: NSDate())
query.findObjectsInBackgroundWithBlock {
(object, error) -> Void in
if object != nil
{
if(object!.count != 0)
{
for messageObject in object! {
let meetingName:String! = (messageObject as! PFObject)["MeetingName"] as? String
}
}
}
I tried with removing the comment, I still dont get any difference. I want the most recent meetings at first and then oldest event at the last.
Please help
Add this to your query.
[query addDescendingOrder:#"EventSTDTime"];

How to stop unwrapping nil values from Parse Array?

I'm having trouble getting my information back from a saved array in Parse. I'm saving the information like this.....
videoARY = [videoId, vidTitleText, vidDescription, vidIMG]
let videoSave = PFObject(className:"UserVideos")
videoSave["userObjectId"] = PFUser.currentUser()!.objectId
videoSave["vid\(saveValueLBL.text!)user"] = PFUser.currentUser()!.username
videoSave["vid\(saveValueLBL.text!)"] = videoARY
videoSave.saveInBackgroundWithBlock { (success, error ) -> Void in
if success == true
{
print("Success")
}
}
In a different viewcontroller I'm initializing an array like this....
var vid1array = [String]!()
and retrieving this way.......
let query = PFQuery(className: "UserVideos")
query.whereKey("userObjectId", equalTo: PFUser.currentUser()!.objectId!)
query.findObjectsInBackgroundWithBlock { (vid:Array?, error:NSError?) -> Void in
if !(error != nil)
{
for items in vid!
{
if let myfav1 = items["vid1"] as? NSArray
{
self.videoDescription1 = myfav1[2] as! String
self.videoTitle1 = myfav1[1] as! String
self.videoIMG1 = myfav1[3] as! String
print(self.videoDescription1)
print(self.videoIMG1)
print(self.videoTitle1)
self.vid1array.append(self.videoTitle1)
print(self.videoTitle1)
}
}
}
}
I'm printing a successful save and each element of the saved array. When I try to add one of those elements to vid1array the app crashes I get "Fatal error: unexpectedly found nil while unwrapping an Optional value". Please help.
* UPDATE *
When I print vid! I get this...
vid1 = (
"YiiqHq_kNnU",
"INFINITE \"Back\" Official MV",
"If you like this video, plz click \"LIKE\" and \"SUBSCRIBE\". INFINITE \"Back\" Official MV INFINITE Official Website : http://www.ifnt7.com INFINITE Official YouTube ...",
"https://i.ytimg.com/vi/YiiqHq_kNnU/default.jpg"
);
It's not nil but I don't see array brackets either, could that be it? I'm printing out the elements I just want to save them to my array so I can use them in my tableview later and the array is coming up nil.
* FOLLOW UP QUESTION *
Would it be advised to place this in viewWillAppear vs. I currently have it in viewDidLoad?

Update values on parse

Is there a way to update a number on parse not just by replacing it or creating a new object id then deleting the old one but by actually incrementing the value from the stored value on parse and the new value being added to it. Ie.
On parse: Correct = 5 ; On app: Correct = 10 ; new value on parse: 15
This is my code but it just creates a new object id with the new values every time...
func saveScoresOnParse() {
scores["Right"] = rightAnswers
scores["Wrong"] = wrongAnswers
scores["Skipped"] = skippedQuestions
scores["User"] = PFUser.currentUser()
scores.saveInBackground()
}
On parse:
I want the values to be added up and as well as only have one objectid for each one.
let scoreQuery = PFQuery(className: "Scores")
scoreQuery.whereKey("user", equalTo: PFUser.currentUser()!)
scoreQuery.getFirstObjectInBackgroundWithBlock { (object, error) -> Void in
if error == nil {
if let scores = object {
scores["Right"] = rightAnswers
scores["Wrong"] = wrongAnswers
scores["Skipped"] = skippedQuestions
scores.saveInBackground()
}
}
}

NSPredicate & Parse Have it return results matching current user, but also username strings, from an array

Basically, I am working on a chat app. In order to retrieve "room" objects from parse, I am using a query like this:
func loadData(){
rooms = [PFObject]()
users = [PFUser]()
self.tableView.reloadData()
let pred = NSPredicate(format: "user1 = %# OR user2 = %#", PFUser.currentUser()!, PFUser.currentUser()!)
let roomQuery = PFQuery(className: "Room", predicate: pred)
roomQuery.orderByDescending("lastUpdate")
roomQuery.includeKey("user1")
roomQuery.includeKey("user2")
roomQuery.findObjectsInBackgroundWithBlock { (results, error) -> Void in
if error == nil {
self.rooms = results as! [PFObject]
for room in self.rooms {
let user1 = room.objectForKey("user1") as! PFUser
let user2 = room["user2"] as! PFUser
if user1.objectId != PFUser.currentUser()!.objectId {
self.users.append(user1)
}
if user2.objectId != PFUser.currentUser()!.objectId {
self.users.append(user2)
}
}
self.tableView.reloadData()
}
}
}
Now, this is working fine when it comes to find any rooms that the current user is a member of. What I want to do is that somewhere in the app, I also have an array of usernames(String type).
So the question is: how do I use the above NSPredicate, in such a way that the results/PFObject returned are also assured to match any of the usernames in the array for the usernames of user1 or user2.
(Basically, the idea is that, that array represents your contacts and if someone is no longer one you should no longer see that room)
Please pretty please? :<
You should create an array of the allowed users, including the current user and placeholder objects for all your other user ids (see objectWithoutDataWithClassName:objectId:). then use that array in your query with IN instead of testing for direct equality (=).

Resources