I have this code to store all contacts image in dictionary. But, in some cases when it is interrupted, the image for contacts just disappear.
dispatch_async(dispatch_get_main_queue(), ^{
if (ABPersonHasImageData(_personObj)) {
// UIImage *image = [UIImage imageWithData:(__bridge NSData*) ABPersonCopyImageDataWithFormat(_personObj, kABPersonImageFormatThumbnail)];
NSData *data = (__bridge NSData *) ABPersonCopyImageDataWithFormat(_personObj, kABPersonImageFormatThumbnail);
UIImage *image = [UIImage imageWithData:data scale:1];
int recordId = ABRecordGetRecordID(_personObj);
[contactImagesDi setValue:image forKey:[NSNumber numberWithInt:recordId]];
}
});
A single ABPerson is not threadsafe. You cannot pass an ABPerson to a background queue using dispatch_async(). If you want to do background processing, you must generate a new ABAddressBook on each thread and use ABPerson records fetched from that address book on that thread.
If you need to logically pass an ABPerson between threads, you need to fetch its ID with ABRecordGetRecordID(). You can pass that and reconstruct a new ABPerson record on the other thread (with its own address book) using ABAddressBookGetPersonWithRecordID().
#try/#catch is very rare in ObjC, and you should have a very good reason for doing it. Under ARC, you will generally leak memory. Exceptions are meant to indicate that the program is in trouble and should crash shortly.
You are leaking data. You should be using CFBridgingRelease() here, not __bridge. You need to balance the Copy.
Your modification of contactImagesDi is very dangerous, assuming this is a dictionary. NSMutableDictionary is not threadsafe. If it is an object that you are using KVC on, then it could be thread-safe, but only if you have taken some pains to ensure that. Typically the better solution is to use dispatch_async to put that kind of update back onto the main thread.
Related
iOS 5 introduced a new way to quickly fetch data on a background thread by initializing the MOC using NSPrivateQueueConcurrencyType and then doing the fetch in performBlock:
One of the rules of thumb of Core Data has been that you can not share a managed object between threads/queues. Is it still the case with performBlock:? Is the following:
[context performBlock:^{
// fetch request code
NSArray *results = [context executeFetchRequest:request error:nil];
dispatch_async(dispatch_get_main_queue(), ^(void) {
Class *firstObject = [results objectAtIndex:0];
// do something with firstObject
});
}];
still unacceptable since I'm sharing my results array/objects between the bg queue and the main queue? Do I still need to use the managed object IDs to do that?
When you use NSPrivateQueueConcurrencyType you need to do anything that touches that context or any object belonging to that context inside the -performBlock: method.
Your code above is illegal since you're passing those objects back to the main queue. The new API helps you in solving this, though: You create one context that's associated with the main queue, i.e. with NSMainQueueConcurrencyType:
// Assume we have these two context (They need to be set up. Assume they are.)
NSManagedObjectContext *mainMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType] autorelease];
NSManagedObjectContext *backgroundMOC = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
// Now this can safely be called from ANY thread:
[backgroundMOC performBlock:^{
NSArray *results = [backgroundMOC executeFetchRequest:request error:nil];
for (NSManagedObject *mo in results) {
NSManagedObjectID *moid = [mo objectID];
[mainMOC performBlock:^{
NSManagedObject *mainMO = [mainMOC objectWithID:moid];
// Do stuff with 'mainMO'. Be careful NOT to use 'mo'.
}];
}
}];
This gets less confusing if you move the inner [mainMOC performBlock:] call into its own method. You may also want to pass an array of object IDs back to the main thread's context in stead of executing a block for each object ID. It depends on your needs.
As Daniel Eggert explains, this is definitely still the case. The exception is for NSMainQueueConcurrencyType, where you can also use the managed object context and objects safely on the main thread (as well as from other threads via the performBlock mechanism). The usefulness of this cannot be understated!
iOS 5 also introduced the concept of parent contexts, which also hugely simplify background operations and remove the need to worry about using notifications to propogate changes between threads.
The WWDC 2012 video "Session 214 - Core Data Best Practices" goes into a lot more detail on both subjects and is very comprehensive. The video is essential viewing for anyone using Core Data.
The current app I'm developing for the iPad involves handling many network requests and persisting the processed results in core data.
The scenario is follows - the application needs to download images for objects I'm displaying in a grid view, which can show a total of 30 objects. Each object can consist of up to 15 png images (also in a grid). Due to the way the server is implemented (meaning I didn't implement it and can't change it easily), each image must be requested separately, so I need to make up to 15 requests per object versus just 1 request to download all 15 images.
For each object, I'm currently using an ASINetworkQueue to queue up the 15 image requests. Once the queue finishes, I create a thumbnail snapshot of the object with its images to display in the grid, then persist all the png files to core data.
I'm currently running everything on the main thread except the network requests which are handled by ASI asynchronously, but since there are so many requests, the app UI is essentially locked until all the requests are processed and results saved to core data.
One solution I came across was doing the core data operations and writes in a separate thread or using grand central dispatch. Another is to only download the images for the visible objects, and download the rest when the user scrolls down.
I'm looking for other suggestions to help keep the main ui responsive, or better ways to structure the network and core data operations. Thanks.
First of all, avoid storing large blobs in Core Data, saving the thumbnails is fine (although you should optimize your model for it), but you should store the full image once it's reconstructed in the Documents folder.
You should definitely use a queue, either NSOperationQueue or ASI network queue. I do something similar in my app which has multiple dependencies. So, for each of the 30 objects you need to have a block (or work function) get called when the 15 images have downloaded. You ideally want to do this work off the main thread. Put all these requirements together, and I'd say you need at least two queues, one for network requests and one for worker blocks, and you should use NSBlockOperations which makes the whole thing much easier. So, the code would be something like this...
// Loop through the objects
for (NSArray *objectParts in objectsToDownload) {
// Create our Object
Object *obj = [Object insertIntoManagedObjectContext:self.moc];
// This is the block which will do the post processing for the object
NSBlockOperation *processBlock = [NSBlockOperation blockOperationWithBlock:^{
// Do post processing here, be very careful with multi-threading CoreData
// it's likely you'll need some class to dispatch you MOCs which have all
// all the observers set up.
// We're gonna assume that all the sub-images have been stored in a instance
// variable:
[obj performPostProcessing];
}];
// Given the list of 15 images which form each object
for (NSURL *part in objectParts) {
// Create the ASI request for this part
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:part];
// Configure the request
[request setDelegate:self];
[request setDidFailSelector:#selector(partRequestDidFail:)];
[request setDidFinishSelector:#selector(partRequestDidFinish:)];
// Store the object in the UserInfo dictionary
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:obj, #"Object", nil];
[request setUserInfo:userInfo];
// Add it as a dependency
[processBlock addDependency:request];
// Add it to our network queue
[networkQueue addOperation:request];
}
// Add the processBlock to our worker queue
[workerQueue addOperation:processBlock];
}
Then you'll also need to write the delegate methods, the didFinish one would look something like this...
- (void)partRequestDidFinish:(ASIHTTPRequest *)request {
// Remember this is one the main thread, so any heavy lifting should be
// put inside a block operation, and queued, which will complicate the
// dependencies somewhat, but is possible.
// Get the result data
NSData *data = [request responseData];
// Get the object that it belongs to from the user info dic
Object *obj = [[request userInfo] objectForKey:#"Object"];
// Keep track of the partial data in the object
[obj storePartialDataForPostProcessing:data];
}
And all of that would go into your class which connects to your server and creates your objects, so it's not a view controller or anything, just a regular NSObject subclass. It will need to have two queues, a managed object context (and more than likely a method which returns another MOC for you to use in threads, something like this:
// Fends a MOC suitable for use in the NSBlockOperations
- (NSManagedObjectContext *)moc {
// Get a blank managed object context
NSManagedObjectContext *aContext = [[UIApplication sharedApplication] managedObjectContext;
[aContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(mergeChangesFromMOC:) name:NSManagedObjectContextDidSaveNotification object:aContext];
return aContext;
}
- (void)mergeChangesFromMOC:(NSNotification *)aNotification {
#try {
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:aNotification];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:[aNotification object]];
}
#catch (NSException * e) {
NSLog(#"Stopping on exception: %#", [e description]);
}
#finally {}
}
You'll also need to hook in some way of monitoring progress, re-queuing failed downloads, cancelling, and saving the MOC at the end. Re-queuing failed downloads is quite tricky. Anyway, hope that helps.
So, just to clarify, in your delegate method, you'd store the downloaded image in a temporary instance variable on your Object. Then when all 15 dependencies finish, you can access that instance variable and do your work.
To kick things off, 1 Queue should be enough for all the image requests.
What you might want to do is keep references to the request so you can cancel them if the object is no longer needed.
For the locking, there's a few things to consider with images:
They are compressed, so they need to be inflated before being UIImages, that is very heavy on the CPU.
If you ever want to write to the filesystem, that process is locking. Do it in another Queue to avoid locking.
It's never a good idea to store Blob into CoreData, store the file path as a string in Core Data and fetch it from disk using a queue
Just using 3 different NSOperationQueue will make your app much more responsive:
1 for ASIHTTPRequests (Don't create a new one, use the default with startAsynchronous)
1 for image writing to disk
1 for image fetching from disk
Since you will display image to view, why don't you SDwebImage:
SDImageCache manages an asynchronous download queue, ties the downloader with the image cache store, maintains a memory cache and an optional disk cache. Disk cache write operations are performed asynchronous so it doesn't add unnecessary latency to the UI.
[imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]]
https://github.com/rs/SDWebImage
I have an app with multiple UIView subclasses that acts as pages for a UIScrollView. UIViews are moved back and forth to provide a seamless experience to the user. Since the content of the views is rather slow to draw, it's rendered on a single shared CGBitmapContext guarded by locks by NSOperation subclasses - executed one at once in an NSOperationQueue - wrapped up in an UIImage and then used by the main thread to update the content of the views.
-(void)main {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
if([self isCancelled]) {
return;
}
if(nil == data) {
return;
}
// Buffer is the shared instance of a CG Bitmap Context wrapper class
// data is a dictionary
CGImageRef img = [buffer imageCreateWithData:data];
UIImage * image = [[UIImage alloc]initWithCGImage:img];
CGImageRelease(img);
if([self isCancelled]) {
[image release];
return;
}
NSDictionary * result = [[NSDictionary alloc]initWithObjectsAndKeys:image,#"image",id,#"id",nil];
// target is the instance of the UIView subclass that will use
// the image
[target performSelectorOnMainThread:#selector(updateContentWithData:) withObject:result waitUntilDone:NO];
[result release];
[image release];
[pool release];
}
The updateContentWithData: of the UIView subclass performed on the main thread is just as simple
-(void)updateContentWithData:(NSDictionary *)someData {
NSDictionary * data = [someData retain];
if([[data valueForKey:#"id"]isEqualToString:[self pendingRequestId]]) {
UIImage * image = [data valueForKey:#"image"];
[self setCurrentImage:image];
[self setNeedsDisplay];
}
// If the image has not been retained, it should be released together
// with the dictionary retaining it
[data release];
}
The drawLayer:inContext: method of the subclass will just get the CGImage from the UIImage and use it to update the backing layer or part of it. No retain or release is involved in the process.
The problem is that after a while I run out of memory. The number of the UIViews is static. CGImageRef and UIImage are created, retained and released correctly (or so it seems to me). Instruments does not show any leaks, just the free memory available dip constantly, rise a few times, and then dip even lower until the application is terminated. The app cycles through about 2-300 of the aforementioned pages before that, but I would expect to have the memory usage reach a more or less stable level of used memory after a bunch of pages have been already skimmed at fast speed or, since the images are up to 3MB in size, deplete way earlier.
Any suggestion will be greatly appreciated.
I realize this is an old posting, but in case it helps anybody else .... This looks like a case of memory fragmentation. We have an app that behaves the same way. The amount of memory actually allocated by the app never reaches dangerous levels, but if you look at the amount of resident memory for the app (using VM Tracker snapshots in the Allocations Instrument, or the Activity Monitor Instrument), it climbs inexorably over time until a not-very-large transient spike kills the app.
The app in question is a multi-threaded app that makes tons of transient allocations in a large range of sizes, the timing of which can't be predicted or controlled. Such an app has to be paranoid about releasing unneeded memory allocations, not because they take up too much memory per se, but because they can create holes that prevent larger images from fitting into the allocated blocks. Even smaller allocations that tend to be overlooked are important in fragmentation (granted that the low-level allocator does group allocations by size, which is helpful to an extent). Memory zones are theoretically helpful for addressing fragmentation but pretty hard to make effective, at least in my experience. Also, use custom auto-release pools, or better yet, alloc/init as much as you can, and release as early as possible. The fact that the underlying frameworks are always making their own allocations for caching purposes probably doesn't help.
I have a reasonably complex Core Data app for the the iPhone. For the most part, it's working well, but I'm running into a sporadic problem that manifests itself within Core Data.
I'm fetching data from a remote service, said service returns data. I parse the data and modify various managed objects. Then I save the managed object context and I get an error. In the course of printing the error I get the following message:
*** -[UIImage length]: unrecognized selector sent to instance 0x8cd7aa0
I can isolate the problem down to a single setter in my one of my managed objects. I save the managed object context before using the setting and I save the managed object context right after. Failure happens right after.
This is all being done in the main thread. I have more than one managed object context, but only one persistent store.
Any pointers for debugging this sort of Core Data problem are appreciated.
This particular problem was caused by a method with the word "get" in it that corresponded to a field name in the managed object. It, in turn, masked the real problem which is database related.
I had the same problem, so I crafted a workaround. Subclass the NSManagedObject and manually transform the UIImage by overriding the accessor methods for the image property.
In the setter method, transform the UIImage object into an instance of NSData, and then set the managed object's image property.
In the getter method, transform image property's data, and return an instance of UIImage.
Let's get to work:
In the data model, when you click the attribute, "image", delete the Value Transformer Name's text. It defaults to NSKeyedUnarchiveFromData, which is what you now want.
Next, subclass the entity by clicking on it in the data model, click New File. When you have the entity selected, you should see a new class in Cocoa Touch Classes titled "Managed Object Class." Click Next, leave the file's location as is by clicking Next again, and then put a checkmark next to all the entities you want to subclass.
In the implementation file of your subclassed NSManagedObject, override the image property's accessor methods by including the following two:
- (void)setImage:(UIImage *)image {
[self willChangeValueForKey:#"image"];
NSData *data = UIImagePNGRepresentation(image);
[self setPrimitiveValue:data forKey:#"image"];
[self didChangeValueForKey:#"image"];
}
- (UIImage *)image {
[self willAccessValueForKey:#"image"];
UIImage *image = [UIImage imageWithData:[self primitiveValueForKey:#"image"]];
[self didAccessValueForKey:#"image"];
return image;
}
Then, whenever you set the image, use:
object.image = [UIImage imageNamed:#"icon.png"];
rather than:
[object setValue:[UIImage imageNamed:#"icon.png"] forKey:#"image"];
Whenever you get the image, use:
UIImage *myImage = object.image;
rather than:
UIImage *myImage = [object valueForKey:#"image"];
Does UIImage ever removes images from its cache? Can I keep a pointer to an image I got from imageNamed: and use it as long as I like or must I always call imageNamed:?
The UIImage object that is returned from imageNamed: is treated like all other objects as far a memory management goes. If you want to keep the reference to the object between method calls, you should retain it and release it when you are done to decrement the reference count.
UIImage * cachedImage;
-(void) getTheImage {
UIImage * cachedImage = [[UImage imageNamed:#"MyImage.png"] retain];
//Do something with the image...
}
//In some other method or dealloc
[cachedImage release];
Also, note that the UIImage class reference says:
In low-memory situations, image data
may be purged from a UIImage object to
free up memory on the system. This
purging behavior affects only the
image data stored internally by the
UIImage object and not the object
itself. When you attempt to draw an
image whose data has been purged, the
image object automatically reloads the
data from its original file. This
extra load step, however, may incur a
small performance penalty.
UIImage caches the data itself. You must not hold a pointer and just pass that around. That can be unsafe since when there is a memory warning and there was no strong ref to that object then UIImage will purge cached data. Call [UIImage imageNamed:] every time. It is fast and returns the ref to the image from memory. If the image is no longer in memory it will reload it and pass that ref