I have a problem.I have saved images in the core data.I have a TableView and in every cell of that table I have an image,which loads from the core data.
Here are codes.
In this part of my code,I get data for images for every cell
if let img = CoreDataFunctions.loadImageFromCache(imgURL){
cell.PersonImage?.image = UIImage(data:img)
}
It is CoreDataFunctions.loadImageFromCache method
class func loadImageFromCache(selfiePath:String?) -> NSData? {
let managedObjectContext: NSManagedObjectContext = AppDelegate().managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Images")
fetchRequest.predicate = NSPredicate(format: "selfie == %#", selfiePath!)
if let fetchResults = try! managedObjectContext.executeFetchRequest(fetchRequest) as? [Images] {
if fetchResults.count == 1 {
return fetchResults[0].imageData
}
}
return nil
}
When I finish the loading of the images, the Memory Usage becomes too high and stays on that position.Can anyone tell me the reason ???
1) Use a NSFetchedResultsController to display the images in a table view. You will then have a significantly lower memory footprint. You can also eliminate your expensive fetch method.
2) Check "External Storage" for your data attribute in the model editor to make sure the images are not actually stored in the database. Alternatively, devise your own storage/naming scheme and store the images in the applications documents directory.
Related
i am storing data in core data. I want to remove data if its exceed count 10. I am not sure how to write predicate for this
for ex. if i have 20 record data from 11 to 20 should remove
my remove code is like
func removeOldData(_ removeAfterCount: Int) {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "dataTrack")
fetchRequest.returnsObjectsAsFaults = false
let predicaate = ?? how to write predicate for this case
fetchRequest.predicate = predicaate
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try context.execute(deleteRequest)
try context.save()
print("Deleted Old Core data objects from Entity ")
} catch let error {
print("Detele Old data in error :", error)
}
}
It depends on indexes you used in this class. It isn't always sorted by created time.
So if you use offset property of predicate, it could be correct or not.
To flow offset approach, you should use created time property to sort it to ensure the correct result.
But why you don't prevent from insertion?, you could avoid insert if number of record is exceeded 10. You could implement func validateForInsert() to avoid insertion.
One more thing, in your source code, the fetchRequest.returnsObjectsAsFaults = false should be set to true, you don't need to fetch unFault of object to delete it.
I want to retrieve the image that is stored in the storage of an user and place it next to his name in a custom UITableViewCell. The problem now is that the tableview will load when the images aren't done downloading (I think?), causing the application to crash because the image array is nil. So what is the correct way to load the tableview? I think, for the user experience, it is important that the tableviewcell image should be shown even if the images aren't done downloading, and present them a default image that is saved in the assists. I thought about making an array with UIImages that links to the default asset of loading an image and changing the image to the profile picture when it is done downloading. But I really have no clue how to do that. This is what I got so far about downloading the image:
let storage = FIRStorage.storage()
let storageRef = storage.reference(forURL: "link.appspot.com")
channelRef?.observeSingleEvent(of: .value, with: { (snapshot) in
if let snapDict = snapshot.value as? [String:AnyObject]{
for each in snapDict{
let UIDs = each.value["userID"] as? String
if let allUIDS = UIDs{
let profilePicRef = storageRef.child((allUIDS)+"/profile_picture.png")
profilePicRef.data(withMaxSize: 1 * 500 * 500) { data, error in
if let error = error {
}
if (data != nil)
{
self.playerImages.append(UIImage (data: data!)!)
}
}
}
let userNames = each.value["username"] as? String
if let users = userNames{
self.players.append(users)
}
}
}
self.tableView.reloadData()
})
This is in the cellForRow
cell.playersImage.image = playerImages[indexPath.row] as UIImage
My rules, haven't changed it from the default rules:
service firebase.storage {
match /b/omega-towers-f5beb.appspot.com/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
Thank you.
Regarding user experience, you are correct. It is standard to have some sort of default image when loading an image from a URL. A great library to use for image caching and using default assets in its' place is AlamofireImage
Vandan Patel's answer is correct in saying you need to ensure your array is not nil when loading the tableview. You will be given a completion block to handle any extra work you would like to do with your image, using the AlamofireImage library.
This is all assuming you are getting a correct image URL back for your Firebase users.
You should call tableView.reloadData() when the images are done downloading. One important thing, initialize your playerImages as playerImages = [UIImage]() instead of playerImages: [UIImage]!. if it's empty, it wouldn't show your array is nil.
Update:
if let players = playerImages {
//code
}
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 CloudKit as a server backend for my iOS application. I'm using it to house some relatively static data along with a handful of images(CKAsset). I ran into a problem when the time came for me to actually fetch those assets from the public database. They load at an excruciatingly slow speed.
My use case is to load an image into every cell inside of a collection view. The images are only 200kb in size, but the fetch process took an average of 2.2 seconds for the download to complete and set the image in a cell. For comparison, I took URLs of similar sized stock images and loaded them in using NSURLSession. It took a mere 0.18 - 0.25 seconds for each image to load.
I have tried multiple different ways of downloading the images from CK: direct fetch of the record, query, and operation query. All of them have similar results. I am also dispatching back to the main queue within the completion block prior to setting the image for the cell.
My database is setup to have a primary object with several fields of data. I then setup a backwards reference style system for the photos, where each photo just has a reference to a primary object. That way I can load the photos on demand without bogging down the main data.
It looks something like this:
Primary Object:
title: String, startDate: Date
Photo Object:
owner: String(reference to primary object), image: Asset
Here is an example request that I tried to directly fetch one of the photos:
let publicDb = CKContainer.defaultContainer().publicCloudDatabase
let configRecordId = CKRecordID(recordName: "e783f542-ec0f-46j4-9e99-b3e3ez505adf")
publicDb.fetchRecordWithID(configRecordId) { (record, error) -> Void in
dispatch_async(dispatch_get_main_queue()) {
guard let photoRecord = record else { return }
guard let asset = photoRecord["image"] as? CKAsset else { return }
guard let photo = NSData(contentsOfURL: asset.fileURL) else { return }
let image = UIImage(data: photo)!
cell.cardImageView.image = image
}
}
I can't seem to figure out why these image downloads are taking so long, but it's really quite the showstopper if I can't get them to load in a reasonable about of time.
Update: I tried the fetch operation with a smaller image, 23kb. The fetch was faster, anywhere from 0.3 - 1.1 seconds. That's better, but still doesn't meet the expectation that I had for what CloudKit should be able to provide.
I am using CKQueryOperation. I found that once I added the following line to my code that downloading CKAssets sped up by about a factor of 5-10x.
queryOperation.qualityOfService = .UserInteractive
Here is my full code:
func getReportPhotos(report:Report, completionHandler: (report:Report?, error:NSError?) -> ()) {
let photo : Photo = report.photos![0] as! Photo
let predicate : NSPredicate = NSPredicate(format: "recordID = %#", CKRecordID(recordName: photo.identifier!))
let query : CKQuery = CKQuery(recordType: "Photo", predicate: predicate)
let queryOperation : CKQueryOperation = CKQueryOperation()
queryOperation.query = query
queryOperation.resultsLimit = numberOfReportsPerQuery
queryOperation.qualityOfService = .UserInteractive
queryOperation.recordFetchedBlock = { record in
photo.date = record.objectForKey("date") as? NSDate
photo.fileType = record.objectForKey("fileType") as? String
let asset : CKAsset? = record.objectForKey("image") as? CKAsset
if asset != nil {
let photoData : NSData? = NSData(contentsOfURL:asset!.fileURL)
let photo : Photo = report.photos![0] as! Photo
photo.image = UIImage(data:photoData!)
}
}
queryOperation.queryCompletionBlock = { queryCursor, error in
dispatch_async(dispatch_get_main_queue(), {
completionHandler(report: report, error: error)
})
}
publicDatabase?.addOperation(queryOperation)
}
There seems to be something slowing down your main thread which introduces a delay in executing the capture block of your dispatch_async call. Is it possible that your code calls this record fetching function multiple times in parallel ? This would cause the NSData(contentsOfURL: asset.fileURL) processing to hog the main thread and introduce cumulative delays.
In any case, if only as a good practice, loading the image with NSData should be performed in the background and not on the main thread.
I ran into some trouble with Core Data in Swift.
I am trying to remove a relationship from an object without deleting it, since it must still exist in other relationships.
Basically I have albums with photos in them and these photos have thumbnails.
One photo can exist in many albums and an album can have many photos.
This was working or appeared to be working before I implemented the thumbnails, although it might never have worked and the problem only showed now. At the moment it doesn't remove the relationship.
The reason I added thumbnails is because full res images were to big. At the same time I also changed my fetch requests from core data to only fetch the bare minimum and store the needed data in a Tuple. After that I reset the moc and return as much as possible to nil.
Not super fast, but very light.
So is it bad to have more than one fetch request in the same function?
Thanks and sorry if this is a stupid question, maybe I just need some sleep to see what is really going on!
My method :
make a mutable set of the albums (smallest number of objects to fetch) related to a photo
remove the current album from that set
save the altered set as the relationship with other albums.
My code :
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let moc:NSManagedObjectContext = appDel.managedObjectContext!
let asPredicate = NSPredicate(format: "albumID = %#", albumTuple.iD)
let asFetchRequest = NSFetchRequest(entityName: "Album")
asFetchRequest.predicate = asPredicate
let asResults = moc.executeFetchRequest(asFetchRequest, error:nil)!
currentAlbum = asResults.last as Album
var foundPhotos : NSMutableSet = currentAlbum.photos as NSMutableSet
var foundThumbs : NSMutableSet = currentAlbum.thumbPhotos as NSMutableSet
let tPredicate = NSPredicate(format: "thumbID = %#", thumbs[counter].iD)
let tFetchRequest = NSFetchRequest(entityName: "Thumbnail")
tFetchRequest.predicate = tPredicate
let tResults = moc.executeFetchRequest(tFetchRequest, error:nil)!
var thumbToDelete = tResults.last as Thumbnail
foundThumbs.removeObject(thumbToDelete as Thumbnail)
currentAlbum.thumbPhotos = foundThumbs
let pPredicate = NSPredicate(format: "photoID = %#", thumbs[counter].iD)
let pFetchRequest = NSFetchRequest(entityName: "Photo")
pFetchRequest.predicate = pPredicate
let pResults = moc.executeFetchRequest(pFetchRequest, error:nil)!
var photoToDelete = pResults.last as Photo
foundPhotos.removeObject(photoToDelete as Photo)
currentAlbum.photos = foundPhotos
moc.save(nil)
update:
i tried with and without multiple predicates and requests in the same function. Makes no difference. I tried breaking the relationship by removing an object at either end of the relationship. moc save gives no errors. The count for the object I delete goes down 1 after removing an object. If I then refresh the moc and reset it, the count goes up again by one.
So for some reason the moc doesn't recognise my managed objects and doesnt break the relationship.
Am I casting something wrong? Is there a constant that should be a variable?
A typo? Or is it a problem with Core Data itself? Can I scare the moc into accepting changes??
func deletePhoto () {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let moc:NSManagedObjectContext = appDel.managedObjectContext!
let asPredicate = NSPredicate(format: "albumID = %#", albumTuple.iD)
let asFetchRequest = NSFetchRequest(entityName: "Album")
asFetchRequest.predicate = asPredicate
let asResults = moc.executeFetchRequest(asFetchRequest, error:nil)!
currentAlbum = asResults.last as Album
println("number of photos at start")
println(currentAlbum.photos.count)
let pPredicate = NSPredicate(format: "photoID = %#", thumbs[counter].iD)
let pFetchRequest = NSFetchRequest(entityName: "Photo")
pFetchRequest.predicate = pPredicate
let pResults = moc.executeFetchRequest(pFetchRequest, error:nil)!
var photoToDelete : Photo = pResults.last as Photo
currentAlbum.photos.removeObject(photoToDelete as Photo)
var error: NSError? = nil
if moc.hasChanges && !moc.save(&error) {
println("error")
abort()
}
println("number of photos left")
println(currentAlbum.photos.count)
}
Found the problem.
My NSManaged Object subclasses had the to-many relationships with an NSMutableSet type instead of an NSSet. Don't do this.
I don't know why this worked with only one many-to-many relationship and stopped working when there where two. It shouldn't have worked at all.