NSKeyedUnarchiver cannot decode object of class NSKnownKeysDictionary1 - objective-c

I'm getting the following exception when trying to unarchive when using Swift:
Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (NSKnownKeysDictionary1) for key (NS.objects); the class may be defined in source code or a library that is not linked'
The context: I'm creating a "Share Links" extension. In my main app (written in Objective C) I write out an array of dictionaries with the information about the links using MMWormhole.
NSFetchRequest* bookmarkFetch = [NSFetchRequest fetchRequestWithEntityName:#"XX"];
bookmarkFetch.propertiesToFetch = #[
#"name", #"text", #"date", #"url"
];
bookmarkFetch.resultType = NSDictionaryResultType;
NSArray* bookmarks = [moc executeFetchRequest:bookmarkFetch error:NULL];
[wormhole passMessageObject:bookmarks identifier:#"XXX"];
The values in the array are NSStrings and an NSDate.
In the bowels of MMWormhole you get:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:messageObject];
messageObject is just the bookmarks array without any intermediate processing.
In the extension I have:
let wormhole = MMWormhole(applicationGroupIdentifier: "group.XX", optionalDirectory:nil)
let bookmarks = wormhole.messageWithIdentifier("XXX") as? Array<Dictionary<String,AnyObject>>
messageWithIdentifier: ends up calling this ultimately:
id messageObject = [NSKeyedUnarchiver unarchiveObjectWithData:data];
The array is written out to the app group folder correctly -- I can read it using another extension, one written in Objective C.
This exception appears when I run in the Simulator. The code appears to work correctly when run on a 32-bit device (iPhone 5 and iPad 3). I don't have a 64-bit device to test on currently.
I imagine I'm missing an import or a framework, but which one(s)?

This is just a side note:
You can set class names for both the NSKeyedArchiver & NSKeyedUnarchiver.
I had this problem without dealing with CoreData at all. The unarchiver did not find my own class anymore.
Setting the className for my class works as shown below:
For the archiver:
NSKeyedArchiver.setClassName("MyClass", for: MyClass.self)
let data = NSKeyedArchiver.archivedData(withRootObject: root)
And the unarchiver:
NSKeyedUnarchiver.setClass(MyClass.self, forClassName: "MyClass")
let root = NSKeyedUnarchiver.unarchiveObject(with: data)

I asked this on the Apple Developer forums and (for once) got a good answer from Apple Developer Relations. I'll quote the important bits:
NSKnownKeysDictionary1 is a Core Data voodoo that I do not understand.
... Clearly something is going wrong with its serialisation and
deserialisation. Do you have Core Data up and running on both ends of
the wormhole? Regardless, it might make more sense to do a deep copy
of your bookmarks array (and anything else you get back from Core
Data) so that you’re sending standard dictionaries across the ‘wire’
rather than Core Data stuff.
So my solution is either to add the Core Data framework to the extension or to do the deep copy. (I've temporarily done the former. The proper solution is probably the latter.)

Related

How to convert FlutterStandardTypedData to NSArray?

I have an app written in dart which uses a channel to call a method in Objective-C. The method needs data in form of an NSArray to act. I am passing this data as Uint8List from dart but in Objective-C, it shows that it's a FlutterStandardTypedData. Is there any way to convert that to an NSArray? Thanks in advance.
You will likely have to copy it out of the FlutterStandardTypedData using something like... (Note that in this example the typed data is doubles - you presumably have bytes)
if let args = call.arguments as? Dictionary<String, Any>,
let au = args["data"] as? FlutterStandardTypedData {
au.data.withUnsafeBytes{(pointer: UnsafeRawBufferPointer) in
let doubles = pointer.bindMemory(to: Double.self)
for index in 0..<Int(au.elementCount) {
// copy to the NSArray
}
}
result(nil)
} else {
result(FlutterError.init(code: "errorProcess", message: "data or format error", details: nil))
}
I'm having issues w/ FlutterStandardTypedData as well. From what i'm reading this should be serialized "auto-magically" on the iOS side as it's a supported data type. I can see on my end, for instance, that the length of the byte buffer is the value i'm expecting. My issue is I cannot seem to access a single value of the byte data in the same method i can see the length value. "Unrecognized selector for FlutterStandardTypedData" whenever i try to access it like an array...I cannot find good documentation either. If i use a simple data type, like string, things work out just fine. I'm only getting burned by the more complex data type(s)...
I figured it out, in the plugin you have to use FlutterStandardTypedData as the data type for the array, and then you can see the .data elements on the native iOS side. Without this data type, coming in to the iOS side i could "see" the data but would get memory exceptions every time i would try to access the data(objects/elements) in the list. Xcode was previously converting my NSArray to a FlutterStandardTypedData in the IDE (even though i specified NSArray NOT FlutterStandard...) auto-magically, but again, erroring out whenever i would try to work w/ the object. Simply changing from NSArray in plugin to FlutterStandardTypedData solved my problem, just took a while to figure out b/c the documentation wasn't very clear (that i came across).

How to take parameters from plist?

I am quite new at Objective-C programming, I was asked to develop a framework that could be implemented in IOS apps. This framework has three methods (that take a model object as an argument) that perform API comsumption and return a message (that takes from response). The problem is that I was asked to store the module parameters in plist, and I don´t have a good clue what this means. I been reading about plist and I know they can store serialized objects. But I really don´t understand what it means to be storing all parameters on this file.
A plist is essentially a dictionary (or NSDictionary) -- with keys and values -- written to a specific file format that iOS expects.
To write a plist file is easy when you do it from Xcode. In Xcode 10.3 you can go to "File" -> "New" --> "File..." and select "Property List" from the types of files you see:
I created a file (as an example) named "SomeFile.plist" and then added a couple keys & values to it:
Now after you get this file included in your new project, you need to read the keys & values back in. Here is a related question that shows you different ways to read the plist / dictionary, such as:
NSString *path = [[NSBundle mainBundle] pathForResource: #"YourPLIST" ofType: #"plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile: path];
NSString *name = [dict stringForKey: #"RaphaelName"];

Preferred way of storing data in OS X/Cocoa? [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
iOS store just a little bit of data
New OS X dev here. I have a modicum of user data I need to store (just paths to recently opened files, really). What is the preferred way of storing these in Cocoa land? I've heard of Core Data before but, as a Windows dev who has encountered tons of APIs from MS like this, does anyone actually use this?
I could just write everything to my own file, of course, but I'd prefer to do things The Right Way(TM).
Any suggestions would be great!
If your application is document based, the list of recently opened files is automatically stored for you. If you need to store them yourself, then I would suggest using NSUserDefaults. It is the most common way to store lightweight information such as preferences and recently used items.
Yes, people do use core data, but it is usually used for more complex data, such as a document with different parts.
See my answer to this thread for five suggestions for storing data. Although that thread covers iOS and therefore Cocoa Touch instead of Cocoa, the answers are all pretty much the same.
Note that the first answer, NSUserDefaults, is meant for saving data like app preferences. That might be most appropriate if the application will always want to load the same set of data; if the data is more like a document, where you might have different sets of data stored in different files, you should use one of the other methods. Writing a property list would probably be simplest in this case:
// store some words in an array and write to a file at pathToFile
NSMutableArray *array = [NSMutableArray array];
[array addObjects: #"foo", #"bar", #"baz", nil];
[array writeToFile:pathToFile];
// (later) read contents of the file at pathToFile into a new array
NSArray *words = [NSArray arrayWithContentsOfFile:pathToFile];
As for Core Data, yes, many people use it. It's a very nice way to manage persistent objects. However, it sounds like it's way more than you need for just storing a bunch of paths.
As ughoavgfhw mentioned, the NSDocument architecture already takes care of keeping a list of recent documents. (If you look through your Preferences folder, the *.LSSharedFileList.plist preference files hold this data).
If you take a look at those files in Property List Editor or Xcode 4, you'll see the preferred way to store a reference to a file in a persistent manner is to use Alias (or "Bookmark") data. If you're coming from a Windows/*nix background, alias data can keep track of an item even if it's renamed or moved.
If you need to store a list of recent files by yourself, and can require OS X 10.6+, you can use NSUserDefaults, along with the bookmark data functionality found in NSURL.
In your method that opens files, you could do something like this:
NSString * const MDRecentDocumentsKey = #"MDRecentDocuments";
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames {
// assume single item
NSURL *URL = [NSURL fileURLWithPath:[filenames objectAtIndex:0]];
NSMutableArray *recentAppBookmarks =
[[[[NSUserDefaults standardUserDefaults] objectForKey:MDRecentDocumentsKey]
mutableCopy] autorelease];
// assume 20 item limit
if ([recentAppBookmarks count] + 1 > 20) {
[recentAppBookmarks removeLastObject];
}
NSData *data = [ bookmarkDataWithOptions:0 includingResourceValuesForKeys:nil
relativeToURL:nil error:NULL];
[recentAppBookmarks insertObject:data atIndex:0];
[[NSUserDefaults standardUserDefaults] setObject:recentAppBookmarks
forKey:MDRecentDocumentsKey];
}
To get the list of recent files at app launch, you could do something like this:
- (void)awakeFromNib {
recentAppURLs = [[NSMutableArray alloc] init];
NSArray *recentAppBookmarks =
[[NSUserDefaults standardUserDefaults] objectForKey:MDRecentDocumentsKey];
for (NSData *bookmarkData in recentAppBookmarks) {
NSURL *resolvedURL = [NSURL URLByResolvingBookmarkData:bookmarkData
options:NSURLBookmarkResolutionWithoutUI|NSURLBookmarkResolutionWithoutMounting
relativeToURL:nil bookmarkDataIsStale:NULL error:NULL];
if (resolvedURL) [recentAppURLs addObject:resolvedURL];
}
}
Otherwise, if you need compatibility with OS X 10.5 and earlier, I posted some categories on NSString in this answer.

Core Data migration problem: "Persistent store migration failed, missing source managed object model."

The Background
A Cocoa Non Document Core Data
project with two Managed Object
Models.
Model 1 stays the same. Model
2 has changed, so I want to migrate
the store.
I've created a new version
by Design > Data Model > Add Model
Version in Xcode.
The difference between versions is a single relationship that's been changed from to a one to many.
I've made my
changes to the model, then saved.
I've made a new Mapping Model that
has the old model as a source and new
model as a destination.
I've ensured
all Mapping Models and Data Models
and are being compiled and all are
copied to the Resource folder of my
app bundle.
I've switched on migrations by
passing in a dictionary with the
NSMigratePersistentStoresAutomaticallyOption
key as [NSNumber
numberWithBool:YES] when adding the
Persistent Store.
Rather than merging
all models in the bundle, I've specified the two
models I want to use (model 1 and the
new version of model 2) and merged
them using modelByMergingModels:
The Problem
No matter what I do to migrate, I get the error message:
"Persistent store migration failed,
missing source managed object model."
What I've Tried
I clean after every single build.
I've tried various combinations of
having only the model I'm migrating
to in Resources, being compiled, or
both.
Since the error message
implies it can't find the source
model for my migration, I've tried
having every version of the model in
both the Resources folder and being
compiled.
I've made sure I'm not
making a really basic error by
switching back to the original
version of my data model. The app
runs fine.
I've deleted the Mapping
Model and the new version of the
model, cleaned, then recreated both.
I've tried making a different change
in the new model - deleting an entity
instead.
I'm at my wits end.
I can't help but think I've made a huge mistake somewhere that I'm not seeing. Any ideas?
Two possibilities:
Your source model in your app does not match the actual store on disk.
Your mapping model does not match your source model.
Turn on Core Data debugging and you should be able to see the hashes that Core Data is looking for when it is doing the migration. Compare these hashes to what is in your store on disk and see if they match up. Likewise the debugging should let you see the hashes in the mapping model to help you match everything up.
If it is just your mapping model that is misaligned, you can tell it to update from source from the design menu in Xcode. If you are missing the actual source model for your store file on disk then you can look in your version control system or try using an automatic migration to get that file to migrate to the model that you believe is the source.
Update 1
The location for changing the source and destination models has moved to the bottom of the editor window:
Rather than merging all models in the
bundle, I've specified the two models
I want to use (model 1 and the new
version of model 2) and merged them
using modelByMergingModels:
This doesn't seem right. Why merge the models? You want to use model 2, migrating your store from model 1.
From the NSManagedObjectModel class reference
modelByMergingModels:
Creates a single
model from an array of existing
models.
You don't need to do anything special/specific with your source model (model 1).. just so long as it's in your bundle, the automatic lightweight migration process will discover and use it.
I would suggest abandoning the mapping model you created in Xcode, as I've seen terrible performance in comparison with the automatic-lightweight migrations. Your mileage may vary, my changes between models are different to yours, but i wouldn't be surprised. Try some timing with and without your own mapping model in the bundle.
/* Inferred mapping */
NSError *error;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,nil];
NSPersistentStore *migratedStore = [persistentStoreCoordinator addPersistentStoreWithType:nil
configuration:nil
URL:self.storeURL
options:options
error:&error];
migrationWasSuccessful = (migratedStore != nil);
You can verify in your code that your source model is available, by attempting to load it and verify that it is not nil:
NSString *modelDirectoryPath = [[NSBundle mainBundle] pathForResource:#"YourModelName" ofType:#"momd"];
if (modelDirectoryPath == nil) return nil;
NSString *modelPath = [modelDirectoryPath stringByAppendingPathComponent:#"YourModelName"];
NSURL *modelFileURL = [NSURL fileURLWithPath:modelPath];
NSManagedObjectModel *modelOne = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelFileURL];
if (modelOne == nil) {
NSLog(#"Woops, Xcode lost my source model");
}
else {
[modelOne release];
}
This assumes in your project you have a resource "YourModelName.xcdatamodeld" and "YourModelName.xcdatamodel" within it.
Also, you can check if that model is compatible with your existing, pre-migration persistent store:
NSError *error;
NSDictionary *storeMeta = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:nil URL:self.storeURL error:&error];
if (storeMeta == nil) {
// Unable to read store meta
return NO;
}
BOOL isCompatible = [modelOne isConfiguration:nil compatibleWithStoreMetadata:storeMeta];
That code assumes you have a method -storeURL to specify where the persistent store is loaded from.
When I got this error, I had updated my core data models but not cleared the app instance from my test phone. This meant the models saved to core data on the phone did not match what I was trying to use in the code.
I removed the app from the phone and re-built/ran successfully.
While attempting to upgrade an existing app's Core Data model (and migrate legacy data), I just ran across a scenario where a third-party Framework was writing data into an app's database. I was getting this error, "Can't find model for source store." Because the third party model was not loaded when I was attempting the migration, the migration was failing.
I wrote this method (below) during troubleshooting this issue. It may be useful to those who are facing these types of issues.
- (BOOL) checkModelCompatibilityOfStoreWithURL: (NSURL *) myStoreURL
forModelNamed: (NSString *) myModelName
withBasePath: (NSString *) myBasePath;
{
NSError * error = nil;
NSDictionary *storeMeta = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:nil URL:myStoreURL error:&error];
if (!storeMeta) {
NSLog(#"Unable to load store metadata from URL: %#; Error = %#", myStoreURL, error);
return NO;
}
NSString * modelPath = [myBasePath stringByAppendingPathComponent: myModelName];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: modelPath]) {
// uh oh
NSLog(#"Can't find model.");
return NO;
}
NSURL * modelURL = [NSURL fileURLWithPath: modelPath];
NSManagedObjectModel * model = [[[NSManagedObjectModel alloc] initWithContentsOfURL: modelURL] autorelease];
BOOL result = [model isConfiguration: nil compatibleWithStoreMetadata: storeMeta];
NSLog(#"Tested model, %#, is%# compatible with Database", modelPath, result ? #"" : #" ~not~");
return result;
}
This code snippet will obtain a store's metadata.
NSError *error = nil;
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSDictionary *storeMeta = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:nil URL:storeUrl error:&error];
NSLog(#"%#", [storeMeta objectForKey: #"NSStoreModelVersionHashes"]);
The VersionInfo.plist (stored in the compiled app's bundle) contains the hashes that are associated with the various entities in your models (base64 encoding). Similarly, a BLOB column in the datastore (Z_METADATA.Z_PLIST) contains a binary-encoded property list that has the hashes (also base64 encoded) for each entity that is associated with the data.
The -entitiesByName method on NSManagedObjectModel is useful for dumping the entities and hashes that exist within a specific model.
I had a similar problem. I have used +modelByMergeingModels:, but I did not use Mapping Model. However merging models does not work with lightweight data migration.
From the Apple Docs:
To perform automatic lightweight migration, Core Data needs to be able to find the source and destination managed object models itself at runtime.
If you use +modelByMergeingModels: than that is used for the destination model. However Core Data will not be able to find source model. Source model has been created using +modelByMergeingModels: in older version of the application and Core Data does try to merge models to find out the source model.
What I ended up doing is that I have (manually) created a new merged .xcdatamodeld by editing the XML files of the models, added it into the project, removed the separate .xcdatamodelds from Compile Sources and instead of using +modelByMergeingModels: use NSManagedObjectModel's -initWithContentsOfURL: with the URL of the new merged model. I'll probably create a script that will automatically merge the models in the future.
If you have this issue for a Mac Catalyst application then you need to delete the stored data which can be found in ~/Library/Containers/name-of-app

Cocoa QTKit and recording movies

I'm new with the whole QTKit and I was looking for some feedback on the following code that I am attempting to use to display the camera's image and record movies.
- (void)initializeMovie {
NSLog(#"Hi!");
QTCaptureSession* mainSession = [[QTCaptureSession alloc] init];
QTCaptureDevice* deviceVideo = [QTCaptureDevice defaultInputDeviceWithMediaType:#"QTMediaTypeVideo"];
QTCaptureDevice* deviceAudio = [QTCaptureDevice defaultInputDeviceWithMediaType:#"QTMediaTypeSound"];
NSError* error;
[deviceVideo open:&error];
[deviceAudio open:&error];
QTCaptureDeviceInput* video = [QTCaptureDeviceInput deviceInputWithDevice:deviceVideo];
QTCaptureDeviceInput* audio = [QTCaptureDeviceInput deviceInputWithDevice:deviceAudio];
[mainSession addInput:video error:&error];
[mainSession addInput:audio error:&error];
QTCaptureMovieFileOutput* output = [[QTCaptureMovieFileOutput alloc] init];
[output recordToOutputFileURL:[NSURL URLWithString:#"Users/chasemeadors/Desktop/capture1.mov"]];
[mainSession addOutput:output error:&error];
[movieView setCaptureSession:mainSession];
[mainSession startRunning];
}
Also, I'm not sure about the whole error parameter that the methods keep calling for, I saw the "&error" symbol in an example but I don't know what it means.
I'm also getting an error "cannot initialize a device that is not open" when I explicitly open the devices.
If anyone could help me sort this out, it'd be a great help, thanks.
QTCaptureDevice* deviceVideo = [QTCaptureDevice defaultInputDeviceWithMediaType:#"QTMediaTypeVideo"];
QTCaptureDevice* deviceAudio = [QTCaptureDevice defaultInputDeviceWithMediaType:#"QTMediaTypeSound"];
Pass the actual constants here, not string literals containing their names. There's no guarantee that QTMediaTypeVideo is defined to #"QTMediaTypeVideo"; it could be #"Ollie ollie oxen free", and even if it is what you expect now, it could change at any time.
[output recordToOutputFileURL:[NSURL URLWithString:#"Users/chasemeadors/Desktop/capture1.mov"]];
Don't assume that the current working directory is /. Always use absolute paths. (I know this is test code; in real code, of course, you would have run an NSSavePanel and gotten the path from there.)
Also, I'm not sure about the whole error parameter that the methods keep calling for, I saw the "&error" symbol in an example but I don't know what it means.
The & means you're taking the address of a variable, which in this case is error. You're passing this address (a.k.a. pointer) to the error: argument of one of QTKit's methods. The method will, if it encounters an error, create an NSError object and store it at that address—i.e., in your variable. This is called “return-by-reference” (the “reference” being the pointer you provided).
I'm also getting an error "cannot initialize a device that is not open" when I explicitly open the devices.
Which method returns the error? Are you talking about an NSError, or just a Console message? If the latter, check your NSError variable and see what the problem method left behind.
This, incidentally, is why you should bail out if any of the QTKit methods returns an error: one of the subsequent messages may clobber it with a new error if you don't.
Also, you may want to look at the MyRecorder sample code. It's a fully functional video recorder based on the QTKit Capture API. The code is reasonably simple and should be easy to understand.