Lightweight migration of a NSPersistentDocument - objective-c

I'm trying to do a lightweight migration of a SQLite store in Core Data. Working on Lion 10.7.3 with Xcode 4.3.1.
In my NSPersistentDocument subclass (AccountDocument), I've overridden the method used to configure the persistent store coordinator so that it gets the proper options for the migration:
- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)url ofType:(NSString *)fileType modelConfiguration:(NSString *)configuration storeOptions:(NSDictionary *)storeOptions error:(NSError **)error
{
NSMutableDictionary *newStoreOptions;
if (storeOptions == nil) {
newStoreOptions = [NSMutableDictionary dictionary];
}
else {
newStoreOptions = [storeOptions mutableCopy];
}
[newStoreOptions setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[newStoreOptions setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
BOOL result = [super configurePersistentStoreCoordinatorForURL:url ofType:fileType modelConfiguration:configuration storeOptions:newStoreOptions error:error];
return result;
}
(Thanks to Malcolm Crawford for that tip: http://homepage.mac.com/mmalc/CocoaExamples/controllers.html)
When I run the app, it fails in NSPersistentDocument's implementation of -managedObjectModel:
* thread #1: tid = 0x2703, 0x00007fff931d9350 libobjc.A.dylib`objc_msgSend_vtable13 + 16, stop reason = EXC_BAD_ACCESS (code=13, address=0x0)
frame #0: 0x00007fff931d9350 libobjc.A.dylib`objc_msgSend_vtable13 + 16
frame #1: 0x00007fff8935e975 CoreData`-[NSKnownKeysDictionary1 _setValues:retain:] + 197
frame #2: 0x00007fff8935f288 CoreData`_newReadModelFromBytes + 648
frame #3: 0x00007fff8935b93e CoreData`+[NSManagedObjectModel(_NSManagedObjectModelPrivateMethods) _newModelFromOptimizedEncoding:error:] + 9310
frame #4: 0x00007fff89359451 CoreData`-[NSManagedObjectModel(_NSManagedObjectModelPrivateMethods) initWithContentsOfOptimizedURL:] + 305
frame #5: 0x00007fff89358d7b CoreData`-[NSManagedObjectModel initWithContentsOfURL:] + 443
frame #6: 0x00007fff893e9519 CoreData`+[NSManagedObjectModel mergedModelFromBundles:] + 377
frame #7: 0x00007fff8ded7037 AppKit`-[NSPersistentDocument managedObjectModel] + 301
frame #8: 0x00007fff8ded70b3 AppKit`-[NSPersistentDocument managedObjectContext] + 75
frame #9: 0x00007fff8ded6e3f AppKit`-[NSPersistentDocument _persistentStoreCoordinator] + 18
frame #10: 0x00007fff8ded6b5d AppKit`-[NSPersistentDocument configurePersistentStoreCoordinatorForURL:ofType:modelConfiguration:storeOptions:error:] + 51
frame #11: 0x0000000100003193 BeanCounter`-[AccountDocument configurePersistentStoreCoordinatorForURL:ofType:modelConfiguration:storeOptions:error:] + 419 at AccountDocument.m:298
From what I can tell from the documentation, the default implementation looks something like this:
- (id)managedObjectModel
{
NSManagedObjectModel *result = [NSManagedObjectModel mergedModelFromBundles:nil];
return result;
}
So to debug the problem a little more, I overrode that method with this:
- (id)managedObjectModel
{
NSBundle *bundle = [NSBundle mainBundle];
NSURL *url = [bundle URLForResource:#"AccountDocument2" withExtension:#"momd"];
NSManagedObjectModel *result = [[[NSManagedObjectModel alloc] initWithContentsOfURL:url] autorelease];
return result;
}
(Thanks to Jeff LaMarche for the idea: http://iphonedevelopment.blogspot.com/2009/09/core-data-migration-problems.html)
The bundle and url both point to the places I expect (and I've followed Marcus Zarra's advice to clean the project so there aren't any stray .mom or .momd bundles in the application package: Using mergedModelFromBundles: and versioning (CoreData)). Yet the app continues to crash while loading the model from the url.
I've checked that the AccountDocument2.xcdatamodeld is a package that has two models for versioning: AccountDocument 2.xcdatamodel and (the original) AccountDocument.xcdatamodel. The "Versioned Core Data Model" popup menu in the file properties is set to "AccountDocument 2".
The only difference between the two models is that one Entity has an additional (and optional) Attribute. My understanding is that qualifies the model for a lightweight migration.
Obviously, I'm doing something wrong here, but I have no idea what. Any help would be most appreciated…
Update:
Per Martin's suggestion (and a check of the NSPersistentDocument documentation) I tried using this code for the accessor:
- (id)managedObjectModel
{
static id sharedManagedObjectModel = nil;
if (sharedManagedObjectModel == nil) {
NSBundle *bundle = [NSBundle mainBundle];
NSURL *url = [bundle URLForResource:#"AccountDocument2" withExtension:#"momd"];
sharedManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:url];
}
return sharedManagedObjectModel;
}
Still crashing…
Update
After some suggestions on Twitter, I upgraded to Xcode 4.3.2, but the problems persist.
RAGE UPDATE
I just created the versioned model package (AccountDocument2.xcdatamodeld) using Xcode 4.2 on Snow Leopard. After building and running the app, everything works as expected.
I then took the AccountDocument2.xcdatamodeld file package back over to Lion and Xcode 4.3.2. When I build and run the app, it continues to crash while loading the .momd resource. Yes kids, that means Xcode 4.3.x and the Data Model Compiler (MOMC) are to blame. I don't see a workaround other than doing all my builds on Snow Leopard.
I'm not one to bash Xcode 4, but when we find ourselves in a situation where the toolchain can't produce an opaque file (.mom and .momd) from an opaque specification (.xcdatamodel and .xcdatamodeld) it's pretty hard to be upbeat about the state of Mac and iOS tools. It's ridiculous that a core component of these platforms breaks to the degree where I can't build and run my app on the latest version of the SDK and developer tools.
It's Come To This Update
More proof that this is a serious bug with the Data Model Compiler (MOMC) in Xcode 4.3.2: if I copy the .momd bundle from the Resource folder created by Xcode 4.2 into my project and add them to the build as a Copy Files build phase, the application works fine.
I also did some tests where I removed validation rules and default values for the Attributes of the various Entities (based on Marcus' suggestion below.) No change, the compiler still creates an invalid .momd. I also tried creating a versioned model where NOTHING was changed: the compiled .momd continued to crash. So whatever you have in your current models (and data they represent) is the source of the problem.
Also of note: this bug is not isolated to NSPersistentDocument (as I originally thought when I started this question.) I can cause an app to crash by just using [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL].
For now, I'll be editing/versioning my models using Xcode 4.2 on Snow Leopard and moving the compiled resources over to Xcode 4.3.2 on Lion. If you use Core Data in any way, I suggest you do the same until this bug is addressed. Trust me, you'll spend days trying to figure out what the hell is going on if you don't.
Now to submit a Radar…
Radar Update
I just submitted this Radar:
http://www.openradar.me/11184500
The Oh Crap It Must Be Lion Update
I just downloaded and installed the Xcode 4.2 for Lion tools from http://developer.apple.com/downloads. The sample application used in the Radar still crashes.
(Note: you can't install Xcode 4.2.1 because the certificate used to sign the DeveloperTools.pkg has expired. Only Xcode 4.2 will work.)
If you're under NDA you'll also find that the beta tools aren't helpful either.
Hope you have a copy of Snow Leopard with Xcode 4.2 sitting around: http://furbo.org/2012/03/28/vmware-for-developers/
The WTF Do Fetch Requests Have To Do With Versioned Entities and Attributes Update
Via Evadne Wu on Twitter:
https://twitter.com/#!/evadne/status/187625192342818818
And how she did it:
https://twitter.com/#!/evadne/status/187629091518816258
(.mom files are binary plists.)
The root of the problem is a single Fetch Request. How that figures into a migration of data from one model to another is for an engineer at Apple to figure out.

The compiled .momd resources can be loaded after an "existingPartner" fetch request is changed from:
name == $name
to:
name == $NAME
It's counterintuitive that a part of the object model that does not affect the persistence of data breaks versioning and lightweight migration. It's clear from the documentation that this should not be the case:
Core Data’s perspective on versioning is that it is only interested in features of the model that affect persistence.
Use the power of the CHOCKLOCK to fix your Fetch Requests or delete them completely and rely on NSPredicates created in code.

I think you need to store the managed object model in an instance variable. You are returning an autoreleased object, which is what is probably causing the bad access.

Based on your theory that it is an issue with the MOMC, Do you have any validation rules in the mom?
I have seen reports where validation rules do not survive the 4.x MOMC.

This might be somewhat related to an issue I had with using Fetch Requests when iOS5 first came out in beta. It was causing a build warning and would crash the app on launch. I wasn't really using the fetch request so I removed it and everything worked fine:
Core Data warning: "Version hash information not available for all models"

Related

Write SDK version to binary when compiling from command line (macOS)

I am trying to detect whether the system is in dark mode.
I have already tried reading AppleInterfaceStyle from the user defaults i.e.
NSString *interfaceStyle = [[NSUserDefaults standardUserDefaults] stringForKey:#"AppleInterfaceStyle"];
BOOL isDark = [#"dark" caseInsensitiveCompare:interfaceStyle] == NSOrderedSame;
which works most of the time but has issues in Auto mode on Catalina.
Now from what I have read is that the more robust approach is to check the effectiveAppearance of NSApplication which looks like this:
NSApplication *app = [NSApplication sharedApplication];
NSAppearance *appearance = app.effectiveAppearance;
NSAppearanceName appearanceName = [appearance bestMatchFromAppearancesWithNames:#[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];
BOOL isDark = [appearanceName isEqualToString:NSAppearanceNameDarkAqua];
The problem with this approach is that the application I am writing this for manually sets its appearance property, which prevents the effectiveAppearance from using the system appearance.
I tried settings app.appearance = nil before checking the effectiveAppearance but it didn't help.
Now there also is [NSAppearance currentAppearance] which uses the appearance of the current thread. I'm not quite sure what this value resolves to if the thread hasn't set the value explicitly.
My big problem here is that I have no access to a machine running macOS to check my code, so I would highly appreciate if someone knows what to do here.
Edit: It looks like the issue is that the library isn’t compile against the correct version of the SDK. Or at least that version isn’t written to the library information.
From the documentation:
If you build your app against an earlier SDK but still want to support Dark Mode, include the NSRequiresAquaSystemAppearance key (with a value of NO) in your app's Info.plist file. Do so only if your app's appearance looks correct when running in macOS 10.14 and later with Dark Mode enabled.
I am already specifying the version through -mmacosx-version-min=10.14. From what I have found this issue is basically the same that I have, but I don’t quite understand what the solution is from the commit.
I guess it has something to do with the -isysroot and -platform_version. But I didn’t find any good reference for what they do and how they work.
My updated question would be:
How do -isysroot and -platform_version work and how do I use them to enable SDK specific functionality with my binaries?
The solution is quite simple. When manually compiling from the command line -mmacosx-version-min=10.14 needs to get passed to the compiler and the linker.

Parse login hang since Facebook 4.0.x with [PFFacebookUtils initializeFacebookWithApplicationLaunchOptions:launchOptions] (semaphore_wait_slow trap)

Since updating Facebook to v4.0.x and the latest Parse libraries, my app is hanging, seemingly when trying to log in the user.
My stack trace looks like this:
I had a very similar problem previously, answered here: Parse crash when calling [PFFacebookUtils initializeFacebook] - semaphore_wait_trap
However that solution no longer works, since it seems [PFUser currentUser] has been replaced with [PFUser(Private) _getCurrentUserWithOptions:] and [BFTask(Private) waitForResult:withMainThreadWarning:] where it gets stuck.
In my app, I've subclassed PFUser to a class called MPLUser, and overridden the user method. Not sure if this might be something to do with the issue?
+ (MPLUser *)user
{
return (MPLUser *)[PFUser user];
}
Once this starts occurring, it becomes impossible to launch the app. However, I usually manage to launch the app a few times before the lock starts happening. It usually happens after a crash...
I'm using pod 'ParseFacebookUtilsV4' and have updates all libraries to latest versions.
UPDATE:
Here's more stack trace from another thread, that is seemingly trying to log on:
I initialise Parse and Facebook in the following order. If I reverse the calls, it crashes:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self initDefaults];
[self initialiseApplicationSpecifics];
[self setupParseWithOptions:launchOptions];
[self enableCrashReporting];
[self setupIAPs];
//etc...
}
- (void)initialiseApplicationSpecifics
{
[Flurry setCrashReportingEnabled:YES];
[self registerParseSubclasses];
[ParseCrashReporting enable];
[Parse enableLocalDatastore];
#ifdef MPL
[Parse setApplicationId:#"xxxyyy"
clientKey:#"xxxyyy"];
[Flurry startSession:#"xxxyyy"];
#elif MGM
[Parse setApplicationId:#"yyyxxx"
clientKey:#"yyyxxx"];
[Flurry startSession:#"yyyxxx"];
#endif
}
- (void)setupParseWithOptions:(NSDictionary *)launchOptions
{
[PFFacebookUtils initializeFacebookWithApplicationLaunchOptions:launchOptions];
[PFTwitterUtils initializeWithConsumerKey:#"aaaabbbb"
consumerSecret:#"bbbbaaaa"];
[PFAnalytics trackAppOpenedWithLaunchOptions:launchOptions];
}
Seems to be fixed with parse 1.7.2
According to v1.7.2 — April 27, 2015
New: Local Data Sharing for Extensions and WatchKit.
Improved nullability annotations for ParseFacebookUtils.
Fixed: logOutInBackground with block callback not called on main thread.
Fixed: Potential compilation error with using imports for PFSubclassing.h.
Fixed: Not persistent currentUser if saving automatic user via saveEventually.
Fixed: Rare deadlock scenario with using ParseFacebookUtils and currentUser.
Fixed: Rare issue with pinning multiple objects in a row to the same pin.
Fixed: Rare scenario when user could be not linked with Facebook.
Improved performance and reliability of Local Datastore.
Performance improvements.
Other small bug fixes.
I'm having the same problem with Parse 1.7.1 & FBSDK 4.0.1 and I reported the bug to Parse but with no luck so far. It has something to do with the local datastore.
https://developers.facebook.com/bugs/779176035499837
Please provide further info there.
I checked with my team working on the iOS SDK and was informed the latest SDK should resolve this. Can you try updating?

Is there any way to differentiate between iTunes Match and DRMed tracks on iOS 5?

For a given MPMediaItem representing a track in an iOS5 user's music library, how can we determine if a track is:
an iTunes Match track that has not been downloaded from iCloud
vs.
a DRMed track
?
In both cases the NSURL returned by MPMediaItemPropertyAssetURL is nil. Therefore instantiating an AVAsset to check the exportable flag is not a viable solution.
It's my understanding that it depends on the version of iOS you use. I think prior to maybe 4.3, an asset returning nil meant simply that the item was DRMed and you didn't have access to it. However, in current versions (5), nil means it's iCloud only. Maybe you have tracks that you think are just DRMed but are in fact iCloud stored songs. On the current app I'm working on, I originally didn't account for iCloud tracks at all (as I was targeting the app for prior versions of iOS) and so I was getting crashes depending on who's device I used. To solve the issue and test for iCloud/DRM I use:
AVURLAsset* asset;
NSURL* realAssetUrl = [item valueForProperty:MPMediaItemPropertyAssetURL];
if(!realAssetUrl){
//track is iCloud
}
asset = [[AVURLAsset alloc]initWithURL:realAssetUrl options:nil];
if(asset == nil || asset.hasProtectedContent){
//asset is DRMed such that it cannot be played back.
//most apps can stop here but I need to be able to export the song
}
if (!asset.exportable || !asset.readable){
//the asset cannot be exported and thus cannot be cached to a file
//the current app directory and cannot be transferred over network
//if asset passed earlier check, can still be used for local playback
}
[asset release];
That seems to work fine for me, but you also imply you were headed down the same path, already, so I'm not sure how much help that'll be to you. However, good luck with your project and I hope you find the answer you're looking for!

Why is checking [NSMetadataQuery class] crashing on iOS 4.2.1?

I'm attempting to use this approach, described by Marco Arment, for checking if a class exists before using it. With the correct settings, classes are automatically weak-linked when it's appropriate. As Marco describes, "you can safely subclass or have pointers to whatever you want (as long as you’re careful not to instantiate them when they’re not available)".
My app runs fine on iOS 5. I've followed the conditions mentioned at the link:
Base SDK is Latest iOS (iOS 5.1)
Deployment Target is iOS 4.0
Compiler for C/C++/Objective-C is Apple LLVM compiler 3.1 (also tried LLVM GCC 4.2)
Any time I reference NSMetadataQuery I'm making sure the class exists first:
if ([NSMetadataQuery class] != nil) …
Despite all this my app crashes immediately on launch if I try to run it on an iPod touch with iOS 4.2.1:
dyld: Symbol not found: _OBJC_CLASS_$_NSMetadataQuery
I've tried commenting out all the code any my app runs fine. As soon as I add back in a single reference to NSMetadataQuery, it crashes. I've even tried this:
if ([NSMetadataQuery class] != nil) NSLog(#"OK");
Simply including that line, and no other reference to NSMetadataQuery, crashes the app. Even more strange, checking for other iOS 5 classes doesn't cause any problems:
if ([UIDictationPhrase class] != nil) NSLog(#"OK");
That works fine, as expected.
I have been able to work around the problem using the uglier NSClassFromString() to make sure the class exists, but I'd love to know why the other approach isn't working.
I don't have an explanation to this but I ran into the same problem as you. No matter what I/you do, NSMetadataQuery just won't be weak linked...
Refer to this answer, which is really the best one in another question:
https://stackoverflow.com/a/8426591/129202
In short, other auto weak linking seems to work, it's just NSMetadataQuery* that you have to remove from source and replace with id. Instantiate the class with NSClassFromString() etc. No hiccups on other classes like UIDocument however so you can safely use those in the normal sweat free way.
NSMetadataQuery is available in iOS 5.0 and above, so any versions below that has no clue as to what it is. By merely using it in your code, the class name will be added to a symbol table and looked-up when the app launches.

Add an image (or other resources) to a Mac or iOS Framework

Let's say I have an existing iOS or Mac Framework that we'll call Test.framework. This framework has compiled and linked successfully (a trick was done to lipo both i386 and armv6/7 archs for the iOS part) and is working perfectly fine.
Now I want to add an image to it let's call it 'test.png'. I have copied it into the Resources folder of my framework so that I have the following structure:
ls Test.framework
- Headers -> Versions/Current/Headers
- Resources -> Versions/Current/Resources
- Test -> Versions/Current/Test
- Versions
ls Test.framework/Resources
- Info.plist
- test.png
Yet when I create a project using my framework I cannot access that image.
I've tried:
[UIImage imageNamed:#"test.png"]
[UIImage imageNamed:#"Test.framework/test.png"]
[UIImage imageNamed:#"Test.framework/Resources/test.png"]
But nothing worked out that always gave me back a nil object.
Any ideas ?
EDIT
After much further investigation it seems what I am trying to accomplish can't be done on iOS. The reason is that the final application bundle (the .app) doesn't copy the private frameworks where applications compiled for Mac OS will.
This is further detailed in the iOS Bundle Structures documentation.
Thanks to Rob Keniger and xuzhe for their appreciated help. I will credit xuzhe for the answer as it is actually the most appropriate answer to my original problem (even though Rob comment made me dig quite deeper into the issue)
The "imageNamed:" method is only for images in your App bundle. You should use
[[UIImage alloc] initWithContentsOfFile:path];
instead.
About the path, since I am not sure if the "Test.Framework" is in you App bundle, I am not able to give you a sample code. But if it dose, the code below should work:
NSString* path = [Nsstring stringWithFormat:#"%#/Test.framework/Resources/test.png", [[NSBundle mainBundle] bundlePath]];
You can get the bundle for a particular class using [NSBundle bundleForClass:[SomeClass class]]. All you need to do is pass in a class that's defined in the framework and you'll have a reference to the framework's bundle.
You can then ask the bundle for the path to the image using the pathForResource:ofType: method of NSBundle, and then use the initWithContentsOfFile: method of NSImage to create your image.
Note that you should never hard-code paths. Ever. There are many different functions and methods for obtaining paths to resources, you never need to hard-code them.