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"];
Related
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.
I'm wondering under which circumstances this code breaks in the second assert. In other words, when can -[UIImage CGImage] return nil? The documentation isn't very telling here.
- (void)setImage:(UIImage *)anImage {
assert(anImage);
CGImageRef cgimage = anImage.CGImage;
assert(cgimage);
}
I'm pretty sure, the UIImage is correct since it's fetched from the app bundle. So far I haven't been able to reproduce the case but I do see some user crash reports.
One more possibility that I've come across in the docs:
If the UIImage object was initialized using a CIImage object, the value of the property is NULL.
Another possibility is that you have multiple threads accessing the same UIImage object at the same time; this was happening in my code with the same symptom.
That might explain your sporadic crash reports, too, since the access patterns would depend on timing that changes from run to run.
To the best of my knowledge, if the first assert passes(showing that anImage is not nil) it means that it could not load the image. Check to make sure that the image is being copied into your bundle.
A case where anImage would be non-nil but cgimage would be nil could be contrived as follows:
UIImage* myImage = [[UIImage alloc] initWithCGImage:nil];
As previous answers have indicated there are other ways one could find themselves in such a scenario.
I am using a Core Data NSManagedObject (in an iOS app) with an attribute of type "transformable" to store a chunk of data. The data is encoded/decoded using the built-in NSKeyedUnarchiveFromData value transformer. The problem is that I'm having trouble getting the NSManagedObject to update properly after the binary data has changed. Say, for example, the code looks like:
id temp = [myManagedObject myTransformableAttribute];
//.. do something with temp
[myManagedObject setMyTransformableAttribute:temp];
NSError *error;
if(![[myManagedObject managedObjectContext] save:&error]) {
//present error
}
It seems to me that "re-setting" the transformable attribute onto the managed object, and then saving the managed object, should cause the data to be re-encoded via the NSKeyedUnarchiveFromData value transformer. But the encoder never gets called, and the updated data doesn't get saved.
If instead of re-setting the original data back onto the managed object, I create a copy of the modified data and set that onto the managed object, then the changes are recognized. Is it possible that core data is using a cached version of the data? Or is there something else I'm doing wrong here? Thanks...
Seems you need to override Class initialize method in your NSManagedObject entity subclass also for transformer to work (known Core Data bug).
Following code is from Apple's location code sample, it is tested and works:
http://developer.apple.com/library/ios/#samplecode/Locations/Introduction/Intro.html
+ (void)initialize {
if (self == [Event class]) {
UIImageToDataTransformer *transformer = [[UIImageToDataTransformer alloc] init];
[NSValueTransformer setValueTransformer:transformer forName:#"UIImageToDataTransformer"];
}
}
I just ran into this same problem and apparently it seems to be a known bug:
http://lists.apple.com/archives/Cocoa-dev/2009/Dec/msg00979.html
I am saving an NSMutableArray in a Transformable property in my Core Data store. I can create the entity properly with data in the NSMutableArray and then load it out of the property, and even make changes. When I go around my app and re-access it, my changes are saved. However when I reload the app, the changes have not saved in the core data store. Other changes to the entity - e.g. changing its title, which is saved as an NSString - are saved even when I quit and re-open the app.
I read in another StackOverflow question that Transformable attributes are not automatically notified of changes, and that I'd have to call "the appropriate setter before saving". However even using the actual setter functions - I have also tried calling didChangeValueForKey - the property is not saved properly. Any good ideas?
You must, as you note, "re-set" a transformable property:
id temp = [myManagedObject myTransformableAttribute];
//.. do something with temp
[myManagedObject setMyTransformableAttribute:temp];
There is no way that Core Data could appropriately monitor an arbitrary transformable object so that it could 'do the right thing' automatically.
Furthermore, you must be certain that you actually save the managed object context after you modify the transformable attribute:
NSError *error;
if(![[myManagedObject managedObjectContext] save:&error]) {
//present error
}
During a single run of the program, unsaved changes will appear visible because the managed object context keeps modified instances in memory. Without saving the context, however, those changes will not be persisted to disk.
I've noticed that you not only need to re-set the property. But you also need another instance. That is why it wasn't working with NSMutableArray
Transformable *copy = [managedObject.transformableProperty copy];
// do stuff
managedObject.transformableProperty = copy;
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