Variations of this question have been asked here and here, but it appears that the question hasn't received a clear answer.
The problem that I face is that the MPMediaLibrary framework keeps a reference to each MPMediaItem (music, video, podcast, ...) as an usigned long long (uint64_t), but I can't seem to find a way to store this value using Core Data. Using Integer 64 as the data type doesn't seem to do the trick and I don't see an alternative.
Since there's no support for unsigned long long in Core Data you might need to literally "do the trick" yourself.
One of the ideas is to store the value as ...binary data, and define custom accessors that return the data as uint64_t:
// header
#interface Event : NSManagedObject
#property (nonatomic, retain) NSData * timestamp;
- (void)setTimestampWithUInt64:(uint64_t)timestamp;
- (uint64_t)timestampUInt64;
#end
// implementation
#implementation Event
#dynamic timestamp;
- (void)setTimestampWithUInt64:(uint64_t)timestamp
{
self.timestamp = [NSData dataWithBytes:×tamp length:sizeof(timestamp)];
}
- (uint64_t)timestampUInt64
{
uint64_t timestamp;
[self.timestamp getBytes:×tamp length:sizeof(timestamp)];
return timestamp;
}
#end
It seems to do the job. The code below:
Event *event = [NSEntityDescription insertNewObjectForEntityForName:#"Event"
inManagedObjectContext:self.managedObjectContext];
uint64_t timestamp = 119143881477165;
NSLog(#"timestamp: %llu", timestamp);
[event setTimestampWithUInt64:timestamp];
[self.managedObjectContext save:nil];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Event"];
Event *retrievedEvent = [[self.managedObjectContext executeFetchRequest:request
error:nil] lastObject];
NSLog(#"timestamp: %llu", [retrievedEvent timestampUInt64]);
Outputs:
2012-03-03 15:49:13.792 ulonglong[9672:207] timestamp: 119143881477165
2012-03-03 15:49:13.806 ulonglong[9672:207] timestamp: 119143881477165
A hack like this of course adds a level of indirection, and it may affect performance when timestamp is heavily used.
While the context in this case is super late, I'm sure I'm not the only one that will stumble upon it. In the case of the MPMediaLibrary, storing the ID as a NSString instead:
ie:
[NSString stringWithFormat:#"%#", [currentMediaItem valueForProperty:MPMediaEntityPropertyPersistentID]];
Related
Just a little question...
When performing CoreData requests (in objective-C), do I have to make them on the context queue explicitly, or are they assumed to do that by themselves?
In other words can I just write:
NSArray *results = [context executeFetchRequest:request error:nil];
Or do I have to write (if I want to avoid issues):
__block NSArray *results = nil;
[context performBlockAndWait:^{
results = [context executeFetchRequest:request error:nil];
}];
Thanks!
You can never assume that Core Data will use the correct queue. You always need to tell it via one of the “perform” methods. The only exception is if you have a main-queue context and you know your code is running on the main queue. You can skip “perform” in that one and only case.
You mention using a convenience method to do a fetch, using “perform” inside the method. That’s fine, but don’t forget that you also need to use “perform” any time you use any of the fetched objects. For example, you can’t use property values from fetched objects unless you also use “perform” (again with the one exception I mentioned).
So just to be sure Tom:
All my CoreData objects (subclasses of NSManagedObject) have their CoreData properties as private (with a prefix "cd_"). I never expose them and use wrapper properties instead:
- (nullable NSString *) email {
return self.cd_email;
}
I've created a method on NSManagedObject:
- (id) safeValueForProperty: (SEL)property {
__block id value = nil;
[self.managedObjectContext performBlockAndWait:^{
value = [self valueForKey:NSStringFromSelector(property)];
}];
return value;
}
So now I can use (from any thread in theory):
- (nullable NSString *) email {
return [self safeValueForProperty:#selector(cd_email)];
}
(and of course the same for setters).
Am I doing right?
I am trying to save a one-to-many relation in core data. There is a user decision involved to determine whether the new child list object needs to be attached to a new parent object. In the other case an existing database entry is used as a parent object. Under certain circumstances after saving, the app crashes.
FINAL EDIT: Sorry if you mind me keeping all of the edits, I still will. The process of enlightenment was quite convoluted. After all I started out thinking it was a data conflict... Thanks again to Tom, who helped point me in the right direction: I am still using a relation for sorting and grouping core data entities with an NSFetchedResultsController. I have written a valid compare: method for my entity class now and so far from what I can see it is working. I am about to write an answer for my question. I will still be very grateful for any information or warnings from you concerning this!
EDIT 3: The save procedure and the user alert seem to be incidental to the problem. I have zoomed in on the NSFetchedResultsController now and on the fact that I am using a relation ('examination') as sectionNameKeyPath. I will now try to write a compare: method in a category to my Examination entity class. If that does not work either, I will have to write a comparable value into my Image entity class in addition to the relation and use that for sections. Are y'all agreed?
EDIT 1: The crash only occurs after the user has been asked whether she wants a new examination and has answered YES. The same method is also entered when there was no user prompt (when the creation of a new examination has been decided by facts (no examination existing = YES, existing examination not timed-out = NO). In these cases the error does NOT occur. It must be that the view finishes loading while the alert view is open and then the collection view and its NSFetchedResultsController join the fun.
EDIT 2: Thanks to Tom, here is the call stack. I did not think it was relevant, but the view controller displays images in a collection view with sections of images per examination descending. So both the section key and the sort descriptor of the NSFetchedResultsController are using the examination after the MOCs change notification is sent. It is not the save that crashes my app: it is the NSSortDescriptor (or, to be fair, my way to use all of this).
The code for the NSFetchedResultsController:
#pragma mark - NSFetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController
{
if (m_fetchedResultsController != nil) {
return m_fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:NSStringFromClass([Image class]) inManagedObjectContext:[[HLSModelManager currentModelManager] managedObjectContext]];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key, identical sort to section key path must be first criterion
NSSortDescriptor *examinationSortDescriptor = [[NSSortDescriptor alloc] initWithKey:kexaminationSortDescriptor ascending:NO];
NSSortDescriptor *editDateSortDescriptor = [[NSSortDescriptor alloc] initWithKey:keditDateSortDescriptor ascending:NO];
NSArray *sortDescriptors =[[NSArray alloc] initWithObjects:examinationSortDescriptor, editDateSortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[[HLSModelManager currentModelManager] managedObjectContext] sectionNameKeyPath:kSectionNameKeyPath cacheName:NSStringFromClass([Image class])];
aFetchedResultsController.delegate = self;
m_fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
HLSLoggerFatal(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return m_fetchedResultsController;
}
#pragma mark - NSFetchedResultsControllerDelegate - optional
/* Asks the delegate to return the corresponding section index entry for a given section name. Does not enable NSFetchedResultsController change tracking.
If this method isn't implemented by the delegate, the default implementation returns the capitalized first letter of the section name (seee NSFetchedResultsController sectionIndexTitleForSectionName:)
Only needed if a section index is used.
*/
- (NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName
{
return sectionName;
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
// In the simplest, most efficient, case, reload the table view.
[[self collectionView] reloadData];
}
/* THE OTHER DELEGATE METHODS ARE ONLY FOR UITableView! */
The code for saving examination (existing or new) and the new image:
-(BOOL)saveNewImage
{
BOOL done = NO;
// remove observer for notification after alert
[[NSNotificationCenter defaultCenter] removeObserver:self name:kExaminationTimedoutAlertDone object:nil];
Examination * currentExamination = [self getCurrentExamination];
if ([self userWantsNewExamination] == YES)
{ // if an examination was found
if (currentExamination != nil)
{ // if the found examination is not closed yet
if ([currentExamination endDate] == nil)
{ // close examination & save!
[currentExamination closeExamination];
NSError *savingError = nil;
[HLSModelManager saveCurrentModelContext:(&savingError)];
if (savingError != nil)
{
HLSLoggerFatal(#"Failed to save old, closed examination: %#, %#", savingError, [savingError userInfo]);
return NO;
}
}
}
currentExamination = nil;
}
// the examination to be saved, either new or old
Examination * theExamination = nil;
// now, whether user wants new examination or no current examination was found - new examination will be created
if (currentExamination == nil)
{
// create new examination
theExamination = [Examination insert];
if (theExamination == nil)
{
HLSLoggerFatal(#"Failed to create new examination object.");
currentExamination = nil;
return NO;
}
// set new examinations data
[theExamination setStartDate: [NSDate date]];
}
else
{
theExamination = currentExamination;
}
if (theExamination == nil)
{ // no image without examination!
HLSLoggerFatal(#"No valid examination object.");
return NO;
}
Image *newImage = [Image insert];
if (newImage != nil)
{
// get users last name from app delegate
AppDelegate * myAppDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
// set image data
[newImage setEditUser: [[myAppDelegate user] lastName]];
[newImage setEditDate: [NSDate date]];
[newImage setExamination: theExamination];
[newImage setImage: [self stillImage]];
[newImage createImageThumbnail];
// update edit data
[theExamination setEditUser: [[myAppDelegate user] lastName]];
[theExamination setEditDate: [NSDate date]];
// unnecessary! CoreData does it automatically! [theExamination addImagesObject:newImage];
//! Important: save all changes in one go!
NSError *savingError = nil;
[HLSModelManager saveCurrentModelContext:(&savingError)];
if (savingError != nil)
{
HLSLoggerFatal(#"Failed to save new image + the examination: %#, %#", savingError, [savingError userInfo]);
}
else
{
// reload data into table view
[[self collectionView] reloadData];
return YES;
}
}
else
{
HLSLoggerFatal(#"Failed to create new image object.");
return NO;
}
return done;
}
The error:
2013-05-22 17:03:48.803 MyApp[11410:907] -[Examination compare:]: unrecognized selector sent to instance 0x1e5e73b0
2013-05-22 17:03:48.809 MyApp[11410:907] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[Examination compare:]: unrecognized selector sent to instance 0x1e5e73b0 with userInfo (null)
2013-05-22 17:03:48.828 MyApp[11410:907] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Examination compare:]: unrecognized selector sent to instance 0x1e5e73b0'
And here are the entity class files, too:
//
// Examination.h
// MyApp
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class Image;
#interface Examination : NSManagedObject
#property (nonatomic, retain) NSDate * editDate;
#property (nonatomic, retain) NSString * editUser;
#property (nonatomic, retain) NSDate * endDate;
#property (nonatomic, retain) NSDate * startDate;
#property (nonatomic, retain) NSSet *images;
#end
#interface Examination (CoreDataGeneratedAccessors)
- (void)addImagesObject:(Image *)value;
- (void)removeImagesObject:(Image *)value;
- (void)addImages:(NSSet *)values;
- (void)removeImages:(NSSet *)values;
#end
//
// Examination.m
// MyApp
//
#import "Examination.h"
#import "Image.h"
#implementation Examination
#dynamic editDate;
#dynamic editUser;
#dynamic endDate;
#dynamic startDate;
#dynamic images;
#end
This error had nothing to do with the saving of data to the MOC.
Because the saving of the new image data is triggered in the prepareForSegue of the previous view controller and the user alert gives the next view controller the time to finish loading, also creating the NSFetchedResultsController and its connection to its delegate, the exception was raised in the temporary context of the save to the MOC and only after the user alert.
The NSFetchedResultsController had started listening for changes of the MOC only in this case. It seems that if it gets alerted to an MOC change it will fetch only the changes and only then it needs to compare the new data with the existing data. Further information on this would be very welcome!
Then, because I had set a sort descriptor (and also the sectionNameKeyPath) to a relation and not provided the means to sort the entity objects in my core data entity class, the NSFetchedResultsController could not continue. Looking back it seems all so easy and natural, I really become suspicious of the simplicity of my solution...
I find it interesting that it could fetch the initial data in one go, when no change interfered. After all it was using the same NSSortDescriptor. Any ideas?
This is my solution:
//
// MyCategoryExamination.m
// MyApp
//
#import "MyCategoryExamination.h"
#implementation Examination (MyCategoryExamination)
- (NSComparisonResult)compare:(Examination *)anotherExamination;
{
return [[self startDate] compare:[anotherExamination startDate]];
}
#end
Please tell me if there is something wrong with this.
UITextField *textField;
UISwitch *someSwitch;
NSManagedObject *editedObject;
NSString *editedFieldKey;
NSString *editedFieldName;
NSString *z=[NSString stringWithFormat:#"%#",[editedObject valueForKey:editedFieldKey]];
NSString *y=[NSString stringWithFormat:#"b"];
if(z==y){
[someSwitch setOn:YES animated:YES];
}
else{[someSwitch setOn:NO animated:YES];}
-(IBAction) toggleButtonPressed{
NSString *a=[NSString stringWithFormat:#"a"];
NSString *b=[NSString stringWithFormat:#"b"];
if(someSwitch.on){
[editedObject setValue:b forKey:editedFieldKey];
}
else{
[editedObject setValue:a forKey:editedFieldKey];
}
}
-(IBAction)save {
NSUndoManager * undoManager = [[editedObject managedObjectContext] undoManager];
[undoManager setActionName:[NSString stringWithFormat:#"%#", editedFieldName]];
if (editingName){
[editedObject setValue:textField.text forKey:editedFieldKey];
}else{
[self toggleButtonPressed];
}
[self.navigationController popViewControllerAnimated:YES];
}
I can't get a UISwitch to work in the context of Core Data and detail view controllers. When you create a BOOL in core data, then have the corresponding class made in xcode, it makes an NSNumber. This is fine but instead of making "0" and "1"'s when data is saved and recalled, it comes up with very large integers (7-8 digits). What I did above was to store the information as a binary string, using "a" or "b" for the string for storage. This hasn't worked well, mostly because I can't get the UISwitch to load with the previously stored value (on or off). I am sure this has been dealt with before, but I can't find much documentation online. If there are any ideas or suggestions, relative to the above code, let me know. Thanks.
2011-12-06 15:49:41.042 sampNav[820:207] 101783056
2011-12-06 15:49:41.043 sampNav[820:207] 80887600
- (IBAction)save {
NSUndoManager * undoManager = [[editedObject managedObjectContext] undoManager];
[undoManager setActionName:[NSString stringWithFormat:#"%#", editedFieldName]];
if (editingName){
[editedObject setValue:textField.text forKey:editedFieldKey];
}else{
[self toggleButtonPressed];
}
[self.navigationController popViewControllerAnimated:YES];
}
-(IBAction) toggleButtonPressed{
if (someSwitch.on==YES)
{
[editedObject setValue:[NSNumber numberWithInt:1] forKey:editedFieldKey];
NSLog(#"%d",[editedObject valueForKey:editedFieldKey]);
}
else [editedObject setValue:[NSNumber numberWithInt:0] forKey:editedFieldKey];
NSLog(#"%d",[editedObject valueForKey:editedFieldKey]);
}
The problem is when these values are stored as NSNumber objects the values are large integers. The NSLog is given at the very top. Can anyone explain this?
your code as it stands:
if(z==y){
Is comparing pointers not the strings, you need:
if([z isEqualToString:y]){
To get the string comparison to work so that the switch has the correct initial state...
But you really want to use the core data BOOL version and get the value of the NSNumber that is stored in the NSManagedObject. I suspect you are not converting the NSNumber pointer to object into a boolValue.
if([[editedObject valueForKey:editedFieldKey] boolValue]) {
This is the follow up to my question earlier about the Xcode 4 static analyzer. It is not specifically a problem since I have the code now working as it needs to, but I am just wondering how things are working behind the scenes. Consider the following code:
- (IBAction)cameraButtonPressed:(id)sender
{
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] == NO)
{
return;
}
UIImagePickerController *cameraUI = [[UIImagePickerController alloc] init];
cameraUI.sourceType = UIImagePickerControllerSourceTypeCamera;
cameraUI.allowsEditing = NO;
cameraUI.delegate = self;
[self presentModalViewController:cameraUI animated:YES];
NSString *theString = [[NSString alloc] initWithString:#"cameraButtonPressed done"];
NSLog(#"%#", theString);
}
To me, the way this code looks, there are two objects (cameraUI and theString) that need to be released. However, the analyze function correctly identifies that only theString needs to be released at the end of the method, even though both objects are returned from alloc-init, which in my experience has always meant that you release when you are done.
The question I have here is, how does the static code analyzer know not to flag cameraUI as an issue?
I would call this a bug with the static analyzer. The UIImagePickerController instance assigned to cameraUI should be released or autoreleased in a non-garbage-collected environment (like iOS).
I have the following problem making me crazy.
My app has to play a tick sound every second for a specified number of times (e.g. 5) after the user has pressed a button.
I used this:
for (int w=1; w<=5; w++) {
[NSThread detachNewThreadSelector:#selector(tic) toTarget:self withObject:nil];
[NSThread sleepForTimeInterval:1.0];
}
where:
- (void)tic {
NSAutoreleasePool *ticPool = [[NSAutoreleasePool alloc] init];
player_tic = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource:#"tick" ofType:#"aif"]] error:nil];
[player_tic setDelegate:self];
[player_tic play];
[ticPool drain];
}
and:
- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer *)player_tic successfully:(BOOL)flag {
NSLog(#"Audio finished playing.");
[player_tic release];
}
All seems to work. My problem is that the object are released together at the end.
I need that after every played sound the related 'player_tic' has to be released immediately.
Other Info:
In .m:
#synthesize player_tic;
In .h:
#interface myController : UIViewController {
...
AVAudioPlayer *player_tic;
}
#property (nonatomic, retain) AVAudioPlayer *player_tic;
On top of all, I have this warning in compilation:
local declaration of 'player_tic' hides instance variable
Please help me!
Thank you very much in advance.
--Carlo
I found System Sound Services more responsive than AVAudioPlayer.
It also solves your release problem, which is caused by the retain property of your controller. Read the reference manual on memory management for further information on retain/release.
I think you want to use self.player_tic when setting that variable, and i believe you also want to autorelease that instance. I'm not sure that setting up a separate NSAutoreleasePool is necessary either.
self.player_tic = [[[AVAudioPlayer alloc] initWithContentsOfURL:... error:nil] autorelease]