Basically I am trying to display 3 users, I am querying from the _User class the following: username, profilePicture & Name.
After that I would like to query the last photo they posted from the Posts class.
Here is how I have setup my code :
let userQuery = PFQuery(className: "_User")
userQuery.limit = 3
userQuery.addDescendingOrder("createdAt")
userQuery.findObjectsInBackgroundWithBlock ({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
self.profilePicArray.removeAll(keepCapacity: false)
self.usernameArray.removeAll(keepCapacity: false)
self.fullnameArray.removeAll(keepCapacity: false)
self.uuidArray.removeAll(keepCapacity: false)
for object in objects! {
self.profilePicArray.append(object.valueForKey("profilePicture") as! PFFile)
self.usernameArray.append(object.valueForKey("username") as! String)
self.fullnameArray.append(object.valueForKey("firstname") as! String)
self.uuidArray.append(object.valueForKey("uuid") as! String)
}
let imageQuery = PFQuery(className: "Posts")
imageQuery.whereKey("username", containedIn: self.usernameArray)
imageQuery.findObjectsInBackgroundWithBlock({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
self.lastPicArray.removeAll(keepCapacity: false)
for object in objects! {
self.lastPicArray.append(object.valueForKey("image") as! PFFile)
}
self.collectionView.reloadData()
} else {
print(error!.localizedDescription)
}
})
} else {
print(error!.localizedDescription)
}
})
}
But when I run it, it isn't showing the correct image for the user showing...I cannot get my head round it, it's been driving me mad for several hours now!!
The background thread isn't a problem for fetching your images, but your image query is not set to return images in any specific order, nor does it seem to limit how many photos will come back for each user (unless you have it set up so that the Post object can only hold one post per user). You could handle this in a few different ways, but the easiest way might be to sort your object array in some predictable order, using the username as the sort key, and then when you get your images back you can sort them in the same order based on their username property before putting them into the array that drives the collectionView.
One other note - you'll want to put your .reloadData() call back on the main thread or it'll happen at an unpredictable time.
It might cause by data transferring in different time cost since you use findObjectsInBackgroundWithBlock. Threads running in background are hard to control. So the order of images in imageQuery objects is different with usernameArray. Or findObjectsInBackgroundWithBlock doesn't guarantee that data will be fetched in same order with keys set. Not 100% sure about this.
But try putting the image fetching procedure inside the first for object in objects! loop. It might be able to solve your problem.
For sorting answer from creeperspeak, you should be able to get username back from imageQuery class just like object.valueForKey("username") in your first loop. Then match them with self.usernameArray.
Related
I am working on an app displaying places (downloaded from firebase) based on user location.
I have currently 5k entries and they are displayed in about 10seconds.
I plan to have 80k entries and I don't want users to wait that long.
What I did :
I created a Place class, I do 'observe'(.value) on my firebase ref and on each child I put each element in an attribute of the Place class.
Then the place:Place = Place(attributes) id added to an array:Place until all places have been downloaded.
self.ref.queryOrderedByKey().observe(.value, with: {(snapshot) in
if snapshot.childrenCount > 0 {
for place in snapshot.children.allObjects as! [DataSnapshot] {
When all places are in the array I compare places locations with the user location and sort the array to display them by distance in a tableview.
What I tried:
I also tried to use GeoFire but it is slower.
How the db looks like (80k elements) :
{
"users": {
"DFkjdhfgYG": {
"id":"DFkjdhfgYG"
,"key2":"value"
,"key3":"value"
,"key4":"value"
,"key5":"value"
,"key6":"value"
,"key7":"value"
,"key8":"value"
,"key9":"value"
,"key10":"value"
,"key11":"value"
,"key12":value
,"key13":value
,"key14":"value"
,"key15":"value"
,"key16":"value"
,"key17":"value"
,"key18":"value"
,"key19":"value"
,"key20":"value"
,"key21":value
,"key22":value
,"key23":value
,"key24":value
,"key25":value
,"key26":"value"
,"key27":value
,"key28":value
,"key29":"value"
},
"BVvfdTRZ": {
"id":"BVvfdTRZ"
,"key2":"value"
,"key3":"value"
,"key4":"value"
,"key5":"value"
,"key6":"value"
,"key7":"value"
,"key8":"value"
,"key9":"value"
,"key10":"value"
,"key11":"value"
,"key12":value
,"key13":value
,"key14":"value"
,"key15":"value"
,"key16":"value"
,"key17":"value"
,"key18":"value"
,"key19":"value"
,"key20":"value"
,"key21":value
,"key22":value
,"key23":value
,"key24":value
,"key25":value
,"key26":"value"
,"key27":value
,"key28":value
,"key29":"value"
}
}
}
Now I don't know what to do and I absolutely need to user Firebase.
Can you help me to improve the way I download firebase db elements, or to show me another way to do it, to make the whole process faster ?
Thanks !
You're using a for loop in a function that is being called the same number of times as there are children in your database path, making the for loop completely useless and overkill, which can add extra time to the whole process.
Another thing that you can do is have this be called on a different thread and making it the highest priority over the rest of your code. Here's how to do both of those:
func handleFirebase() {
DispatchQueue.global(qos: .userInteractive).async {
self.ref.queryOrderedByKey().observe(.value, with: { (snapshot) in
guard let value = snapshot.value as? String else { return }
let key = snapshot.key
print("KEY: \(key), VALUE: \(value)")
}, withCancel: nil)
}
}
I'm working on local parse with swift 3.0
I'm doing querys to get results from server. but if there's no connection it wont show last results we got because losing connection.
so what i want to do is to save query results to view it if there is no connection
this is the query:
var query = PFUser.query()
query = PFQuery(className: "_User")
// query?.fromLocalDatastore()
query!.whereKey("objectId", equalTo: PFUser.current()!.objectId!)
query!.findObjectsInBackground {
(objects , error) -> Void in
if error == nil {
for object in objects! {
self.usernamelbl.text = object["username"] as! String
if let userp = PFUser.current()?["photo"] as? PFFile {
userp.getDataInBackground {
(imageData, error) -> Void in
if error == nil {
self.profilepic.image = UIImage(data: imageData!)!
}
}
}
}
Now how can i save the results and view them offline also if app closed?
Any help will be appreciated
It's possible to set the caching policy of specific PFQuery calls. To save a copy to disk, and rely on that before making another network hit, you set the kPFCachePolicyCacheElseNetwork policy.
However according to this Parse question, there is apparently a pretty strict limit on the size these caches are allowed to be. I'm not sure if those still apply in the open source version of Parse, but if you want to save more information to disk, it might be appropriate to use a more dedicated data persistence framework, like Core Data, SQLite, or Realm (Full disclosure: I work for Realm. :) )
For the purposes of image files, I'd recommend you manually manage the caching of those on disk, instead of storing it in Parse's cache (Due to the size constraints). There are some great image caching libraries out there (Like PINCache) that make it very easy.
I'm parsing data from a JSON file that has approximately 20000 objects. I've been running the time profiler to figure out where my bottlenecks are and speed up the parse and I've managed to reduce the parse time by 45%, however according to the time profiler 78% of my time is being taken by the context.save() and much of the heavy portions throughout the parse are sourcing from where I call NSEntityDescription.insertNewObjectForEntityForName.
Does anyone have any idea if theres any way to speed this up? I'm currently batching my saves every 5000 objects. I tried groupings of 100,1000,2000,5000,10000 and I found that 5000 was the most optimal on the device I'm running. I've read through the Core Data Programming Guide but have found most of the advice it gives is to optimizing fetching on large numbers of data and not parsing or inserting.
The answer could very well be, Core Data has its limitations, but I wanted to know if anyone has found ways to further optimize inserting thousands of objects.
UPDATE
As requested some sample code on how I handle parsing
class func parseCategories(data: NSDictionary, context: NSManagedObjectContext, completion: ((success: Bool) -> Void)) {
let totalCategories = data.allValues.count
var categoriesParsed = 0
for (index, category) in data.allValues.enumerate() {
let privateContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
privateContext.persistentStoreCoordinator = (UIApplication.sharedApplication().delegate as! AppDelegate).persistentStoreCoordinator!
privateContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
//Do the parsing for this iteration on a separate background thread
privateContext.performBlock({ () -> Void in
guard let categoryData = category.valueForKey("category") as? NSArray else{
print("Fatal Error: could not parse the category data into an NSArray. This should never happen")
completion(success: false)
return
}
let newCategory: Categories?
do {
let newCategory = NSEntityDescription.insertNewObjectForEntityForName("Categories", inManagedObjectContext: privateContext) as! Categories
newCategory.name = category.valueForKey("name") as? String ?? ""
newCategory.sortOrder = category.valueForKey("sortOrder") as? NSNumber ?? -1
SubCategory.parseSubcategories(category.valueForKey("subcategories") as! NSArray, parentCategory: newCategory, context: privateContext)
} catch {
print("Could not create the Category object as expected \(error)")
completion(success: false)
}
do {
print("Num Objects Inserted: \(privateContext.insertedObjects.count)") //Num is between 3-5k
try privateContext.save()
} catch {
completion(success: false)
return
}
categoriesParsed+=1
if categoriesParsed == totalCategories{
completion(success: true)
}
})
}
}
In the above code, I look through the top level data objects which I call a "Category", I spin off background threads for each object to parse concurrently. There are only 3 of this top level object, so it doesn't get too thread heavy.
Each Category has SubCategories, and several other levels of child objects which yield several thousand objects each getting inserted.
My core data stack is configured with one sqlite database the standard way that is configured when you create an app with CoreData
One reason is that you're saving the managed object context in each single iteration, which is expensive and not needed. Save it after the last item has been inserted.
I am using some Facebook IDs in my app, and I have an array of serveral ID's, the array can be 10 numbers but can also be 500 numbers..
Right now the numbers are displayed in a tableview, and I want all the results there too, so they need to be in an array.
let profileUrl = NSURL(string:"http://www.facebook.com/" + newArray[0])!
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl) {
(data, response, error) -> Void in
// Will happen when task completes
if let urlContent = data {
let webContent = NSString(data: urlContent, encoding: NSUTF8StringEncoding)
dispatch_async(dispatch_get_main_queue(),
{ () -> Void in
let websiteArray = webContent!.componentsSeparatedByString("pageTitle\">")
//print(websiteArray[1])
let secondArray = websiteArray[1].componentsSeparatedByString("</title>")
print(secondArray[0])
})
}
}
this code takes the first number of the array, goes to facebook.com/[the actual number], and then downloads the data and splits the data into pieces, so that the data that I want it in the secondArray[0]. I want to do this for every number of the array, take the result data and put it back into an array. I have no idea how to do this because you don't know how much numbers there are gonna be etc, does someone has a good solution for this?
Any help would be appreciated, really!
Thanks
You have several problems here, and you should take them one at at a time to build up to your solution.
First, forget the table for the moment. Don't worry at all about how you're going to display these results. Just focus on getting the results in a simple form, and then you'll go back and convert that simple form into something easy to display, and then you'll display it.
So first, we want this in a simple form. That's a little bit complicated because it's all asynchronous. But that's not too hard to fix.
func fetchTitle(identifier: String, completion: (title: String) -> Void) {
let profileUrl = NSURL(string:"http://www.facebook.com/" + identifier)!
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl) {
(data, response, error) -> Void in
if let urlContent = data {
let webContent = NSString(data: urlContent, encoding: NSUTF8StringEncoding)
let websiteArray = webContent!.componentsSeparatedByString("pageTitle\">")
let secondArray = websiteArray[1].componentsSeparatedByString("</title>")
let title = secondArray[0]
completion(title: title)
}
}
task.resume()
}
Now this is still pretty bad code because it doesn't handle errors at all, but it's a starting point, and the most important parts are here. A function that takes a string, and when it's done fetching things, calls some completion handler.
(Regarding error handling, note how many places this code would crash if it were returned surprising data. Maybe the data you get isn't a proper string. Maybe it's not formatted like you think it is. Every time you use ! or subscript an array, you run the risk of crashing. Try to minimize those.)
So you might then wrap it up in something like:
var titles = [String]()
let identifiers = ["1","2","3"]
let queue = dispatch_queue_create("titles", DISPATCH_QUEUE_SERIAL)
dispatch_apply(identifiers.count, queue) { index in
let identifier = identifiers[index]
fetchTitle(identifier) { title in
dispatch_async(queue) {
titles.append(title)
}
}
}
This is just code to get you on the right track and start studying the right things. It certainly would need work to be production quality (particularly to handle errors).
Once you have something that returns your titles correctly, you should be able to write a program that does nothing but take a list of identifiers and prints out the list of titles. Then you can add code to integrate that list into your tableview. Keep the parts separate. The titles are the Model. The table is the View. Read up on the Model-View-Controller paradigm, and you'll be in good shape.
To repeat code for whole array put your code in a loop and run that loop from 0 to array.count-1
You don't need to know how many items there will be an array. You can just get the count at run time array.count here array is your array.
I hope this is what you wanted to know, your question doesn't make much sense though.
I have this code to retrieve images from parse data.
How do I query it by creation date? I tried using findObjectsInBackgroundWithBlock but it wont work with a PFFile. What do i do?
Also, as a side question, sometimes when i place the images in a UIImageView they are upside down or sideways. Why?
if let objects = objects as? [PFObject] {
for object in objects {
if let userPicture = object.valueForKey("Image") as? PFFile {
userPicture.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
if (error == nil)
{
let image = UIImage(data:imageData!)
self.ImageArray.insert(image!, atIndex: 0)
}
else {
self.alert("Error: \(error!) \(error!.userInfo!)", Message: "Make sure you have a secure internet connection")
}
dispatch_async(dispatch_get_main_queue())
{
self.collectionView.reloadData()
println("Finished Pictures")
}
})
}
}}
You have a set of objects which all reference an image file, but probably a set of other things too, and the image can likely be changed. So, the object creation date isn't the same as the image creation date and the image file doesn't know (or at least doesn't expose) it's creation date. You're also currently always adding the images to the start of the array so the position will be set by how big (and therefore how long it takes to download) each image is. Also, trying to download lots of images at the same time could just mean you get lots of timeouts.
So, really you should have a column on your object which holds the date at which the image was updated, and sort the objects by that date. As you download the images you place them into the image array in the same index as the owning object in its array (pad the array out with NSNull so you know what's going on).