Unrecognized selector occurring after Lightweight Core Data migration - sqlite

I'm getting an unrecognized selector on a newly added field to my Core Data (SQLite) db on iOS. I added a new model version as directed using Xcode's Editor menu, and then verified that new version is the current one. I made sure also that the .h and .m files for the modified table were updated, though I did this by hand (how to have these generated for you?). Nothing unusual though, just a String type field.
The problem seems to be that the lightweight migration never takes place by the time code is run that tries to reference the database object. Tring to access newFieldName gives:
-[MyEntity newFieldName]: unrecognized selector sent to instance 0x852b510
2014-03-27 13:26:21.734 ASSIST for iPad[41682:c07] *** Terminating app due to
uncaught exception 'NSInvalidArgumentException', reason: '-[MyEntity newFieldName]:
unrecognized selector sent to instance 0x852b510'
The line of code that generates the above error is the only line in the for loop below:
DataStoreCoreData *dStore = [[DataStoreCoreData alloc] initWithDataItemDescription:did];
for (MyEntity *myEnt in [dStore objects])
NSString *name = [myEnt newFieldName];
As mentioned, when I examine the SQLite db it has no new field in it, which makes sense given the error. So I also stepped through the execution of the code that is supposed to do the migration and it seems to work fine. Success. It looks like the following:
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"ASSIST.sqlite"]];
// handle db upgrade
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error])
NSLog(#"\r\n Fail. [error localizedDescription]: %#", [error localizedDescription]);
else
NSLog(#"\r\n Success");
This is the 6th db version upgrade. All have been lightweight migrations with no unusual problems. Should not the above code force the SQLite db to reflect the new schema? How do I get it to do so, or is there another problem here?

I finally discovered the answer. The code I provided with the question is only part of the code required to be modified when making minimal changes to the database (lightweight migration). You also need to specify the new version when you create the object model. Notice "MyNewVersion" in the code below. You are supposed to update this parameter to reflect the new version you created and then selected as the current Model Version:
NSString *path = [[NSBundle mainBundle] pathForResource:#"MyNewVersion" ofType:#"mom" inDirectory:#"ASSIST.momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];

Related

Copying plist to App Group Container - iOS 8

I am adding an App Group to my app for sharing a single plist between the app and the watch. I used to copy a plist from the bundle to Documents for when the app first started up. But with the watch I am now trying to convert it to save to the container but it always seems to be empty. The targets have app group enabled, and I am using the right name in my code. What could be going wrong?
Old Way
// COPY PLIST TO DOCUMENTS
NSFileManager *fileManger=[NSFileManager defaultManager];
NSError *error;
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath=[pathsArray objectAtIndex:0];
NSString *destinationPath= [doumentDirectoryPath stringByAppendingPathComponent:#"badger.com.vacations.plist"];
NSLog(#"plist path %#",destinationPath);
if (![fileManger fileExistsAtPath:destinationPath]){
NSString *sourcePath=[[[NSBundle mainBundle] resourcePath]stringByAppendingPathComponent:#"badger.com.vacations.plist"];
[fileManger copyItemAtPath:sourcePath toPath:destinationPath error:&error];
}
New way - not working
// COPY PLIST TO CONTAINER
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.com.xxxxx.xxx.container"];
containerURL = [containerURL URLByAppendingPathComponent:#"name.com.data.plist"];
NSString *destinationPath= containerURL.path;
NSLog(#"destinationPath %#", containerURL);
NSString *sourcePath=[[[NSBundle mainBundle] resourcePath]stringByAppendingPathComponent:#"name.com.data.plist"];
[fileManger copyItemAtPath:sourcePath toPath:containerURL.path error:&error];
You can´t share a plist as a file (or I simply don´t know about this feature), instead you just generate a new NSUserDefaults instance, which is shareable between targets.
Have a look here:
https://devforums.apple.com/message/977151#977151
or the Apple documentation
https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html
In the Apple Member Center, under 'Identifiers > App Groups' register a new App Group (e.g. group.de.myApp.sharedGroup)
In the Apple Member Center, under 'Identifiers > App IDs' refresh your App Ids for the targets that need sharing to use the App Groups feature
Regenerate all needed Provisioning Profiles and get them into Xcode
Back to Xcode: Under 'Capabilities' in each of your targets that need to share data, set 'App Groups' to on and add the previously registered App group.
Talk to the shareable NSUserDefaults container like this:
Store stuff:
NSUserDefaults *groupDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.de.myApp.sharedGroup"];
[groupDefaults setInteger:1337 forKey:#"testEntry"];
[groupDefaults synchronize];
Read stuff:
NSUserDefaults *groupDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.de.myApp.sharedGroup"];
NSInteger testEntry = [groupDefaults integerForKey:#"testEntry"];
NSLog(#"testEntry: %ld", (long)testEntry);

Why does OCMock cause nsnotificaiton center to have EXC_BAD_ACCESS when using mockObserver

I setup mock observers like this:
id quartileObserverMock = [OCMockObject observerMock];
[[NSNotificationCenter defaultCenter] addMockObserver:quartileObserverMock
name:kVPAdPlayerDidReachQuartileNotification
object:self.adPlayer];
[[quartileObserverMock expect]
notificationWithName:kVPAdPlayerDidReachQuartileNotification
object:self.adPlayer
userInfo:#{#"quartile" : #(VPAdPlayerFirstQuartile), #"trackingEvent" : VPCreativeTrackingEventFirstQuartile}];
my unit tests run; but I get spurious EXC_BAD_ACCESS errors when the notificaiton is posted.
i.e.
[[NSNotificationCenter defaultCenter]
postNotificationName:kVPAdPlayerDidReachQuartileNotification
object:self.adPlayer
userInfo:#{#"quartile" : #(quartile), #"trackingEvent" : trackingEvent}];
When I comment out the observermock code my tests run fine every single time.
When I put the code back in, I get spurious crashes on postNotiicaitonName:object:userInfo, maybe once every 2.5 times.
Anyone got any ideas?
Refer the following sample code, this might help you. It worked for me
- (void)test__postNotificationwithName__withUserInfo
{
id observerMock = [OCMockObject observerMock];
[[NSNotificationCenter defaultCenter] addMockObserver:observerMock name:#"NewNotification" object:nil];
NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:#"2",#"2", nil];
[[observerMock expect] notificationWithName:#"NewNotification" object:[OCMArg any] userInfo:userInfo];
NotificationClass *sut = [[NotificationClass alloc] init];
[sut postNotificationwithName:#"NewNotification" userInfo:userInfo];
[[NSNotificationCenter defaultCenter] removeObserver:observerMock];
[observerMock verify];
}
and my posting notification method
- (void)postNotificationwithName:(NSString *)notifName userInfo:(NSDictionary *)userInfo
{
[[NSNotificationCenter defaultCenter] postNotificationName:notifName object:self userInfo:userInfo];
}
Please note:
observerMock always raise an exception when an unexpected notification is received.
Remove the mocked observer from the NSNotificationCenter at the end of the test case.
There are a couple of reasons why this could be happening (I know, because I just solved both of them them in my own code today)
1) A mock observer from a previous test was not removed
2) An non-mock instance object from a previous test is observing for the same notification, but that object has since become obsolete. In my case, an instance object from my setUp method was listening to the notification, but when it was dealloced, it did not remove itself from the NSNotificationCenter's observers list.
In both cases the solution is to use
[[NSNotificationCenter defaultCenter] removeObserver:name:object:]
Depending on the scope: 1) in the dealloc of all classes that observe NSNotificationCenter, 2) in the tearDown method, or 3) at the end of the test case (as #PrasadDevadiga mentioned)

bundle sqlite database with app and stringByAppendingPathComponent error

There are probably many other questions I don't even know to ask yet since I'm new to app programming.
I initially created the database from within the app, copied it to my working folder (which is probably not where it should ultimately reside), then appended my records (about 1,000 of them) from a text file.
The first two questions that come to mind are:
- what folder should the database be in?
- how does it get deployed with the app?
I found quite few examples using the following lines in persistentStoreCoordinator function:
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"myDatabase.sqlite"];
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
But the first line gives me the pre-compile error: "Receiver type 'NSURL' for instance message does not declare a method with selector 'stringByAppendingPathComponent:'. Why is it not working for me?
And is this in fact the best way to bundle my database with the rest of the app?
Thanks!
Easiest solution is to use NSUrl instead of NSString. SO user #trapper already provided a solution in the below link.
importing sqlite to coredata
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Database.sqlite"];
// If the database doesn't exist copy in the default one
if (![storeURL checkResourceIsReachableAndReturnError:NULL])
{
NSURL *defaultStoreURL = [[NSBundle mainBundle] URLForResource:#"Database" withExtension:#"sqlite"];
if ([defaultStoreURL checkResourceIsReachableAndReturnError:NULL])
{
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager copyItemAtURL:defaultStoreURL toURL:storeURL error:NULL];
}
}

AVAudioPlayer EXC_BAD_ACCESS

I am trying to pause the AVAudioPlayer if it is currently playing. When I debug my code and check AVAudioPlayer, I see it is allocated. When I try to access a method/property on it (i.e. myAudioPlayer isPlaying), I get an EXC_BAD_ACCESS error. This happens if the AVAudioPlayer does not have a sound loaded. Is there a way I can do a check to see if it has loaded a sound? I tried accessing myAudioPlayer.data, but I still get the same error.
For me it was because I wasn't calling the one of the designated initialisers. I was instantiating it with AVAudioPlayer() instead of the designated initialisers which are public init(contentsOfURL url: NSURL) throws and public init(data: NSData) throws
As of iOS 13, make sure you are removing the initialization on AVAudioPlayer before asigning it with AVAudioPlayer(contentsOf: URL(...))
i.e. change
var audioPlayer = AudioPlayer() to var audioPlayer: AVAudioPlayer!
I guess you have to use prepareToPlay method to find whether it has loaded or not.
First you have to add AVFoundation Framework.Then,import AVFounadtion framework in .h file.
.h:
AVAudioPlayer *audioPlayer;
.m:
NSURL *url1 = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/audio1.mp3", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url1 error:&error];
//audioPlayer.numberOfLoops = 0;
[audioPlayer play];
Try this code it may help you..

sqlite database resets on each compile and run

I have built up a basic database application in which user can insert records onto a table and app displays them on a TableView.
Everything is working as it is supposed to be. For example, the new records do display even if we kill the app from app switcher and relaunch it from the springboard.
BUT every time I build and run using Xcode, the database just goes to default records! The new records are just not there.
Is it normal?... but what if I want to test my app for new records? Any fix?
BTW, JFYI, below is the code I use to make editable DB.
-(NSString *)createEditableDatabase{
// Check if DB already exists
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *writableDB = [documentsDir stringByAppendingPathComponent:#"Database.db"];
success = [fileManager fileExistsAtPath:writableDB];
//The editable DB already exists
if (success) {
return writableDB;
}
//The editable DB does not exist
//Copy the default DB into App's Doc Dir.
NSString *defaultPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Database.db"];
success = [fileManager copyItemAtPath:defaultPath toPath:writableDB error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable DB file: '%#'", [error localizedDescription]);
}
return writableDB;
}
While digging deeper, I noticed that the database modify date in finder was not updating when I inserted a record. So, I found out that I was still using old path to perform DB operations (not the Documents one). :) Now everything working fine. Anyways thanks Nick

Resources