How to use committedValuesForKeys to save and reconstitute NSManagedObjects? - swift

Nota Bene
Please advise in comments if more information is required.
Background
Using only Apple APIs I am interested in persisting data from an NSManagedObject or an object graph to file so that I can save it in CloudKit as an asset and share between users via the public database. Several questions on SO tackle the question of persisting NSManagedObjects. None are giving answers appropriate to my use-case.
Theory
I noted the following entry in Apple's documentation. Since this has been available since iOS 3.0, I wondered whether or not others have used it and if the resulting dictionary could be a suitable solution for persisting the data that I want to send over the wire. My thinking is simply that a dictionary can be archived or written to file and uploaded as a CKAsset. The downloaded asset could then be unarchived into a dictionary that should also allow one to reconstruct a new NSManagedObject for a new Core Data Context.
Debug Code
So, thinking that of course this is possible, I jumped right in and started working with the dictionary. This code was to try to help me understand the nature of the values one could expect in such a dictionary and how one might use those values to recreate a new object.
func debugEncodeObject () {
if let aManagedObject = self.videoStory {
if let uncodedObject = aManagedObject.committedValuesForKeys(nil) as? NSDictionary {
for (key, value) in uncodedObject {
print("key:\(key)\nvalue:\(value)\ndynamictype:\(value.dynamicType)\ndescription:\(value.description)")
}
// let tmpPath = NSTemporaryDirectory().NS.stringByAppendingPathComponent("MNG-\(NSUUID().UUIDString)")
// print(tmpPath)
//
// if uncodedObject.writeToURL(NSURL(fileURLWithPath: tmpPath), atomically: true) {
// print(uncodedObject)
// MNGCloudManager.sharedInstance.insertSharedStoryRecord(uncodedObject)
//
// }
}
}
}
Complications
So looking at the results of my inspection code I can see that there are several non-standard entries in my dictionary. A couple of the more interesting results show some complications that raise the stakes a bit. For example viewing the value of an NSOrderedSet stored in the dictionary shows the in-memory(pointer?) representation of the object including a representation of sub-objects. I do also see a separate representation of the sub-objects so I think I should be able to rebuild new objects with the existing data.
key:videos
value:Relationship 'videos' on managed object (0x1467b9750) <ths.VideoStory: 0x1467b9750> (entity: VideoStory; id: 0xd000000000380002 <x-coredata://C26A39CE-7016-48EE-9228-9B6B39E94BFB/VideoStory/p14> ; data: {
assetURL = nil;
authoredBy = nil;
collageImage = <ffd8ffe0 00104a46 49460001 01000000 00000000 ffe10080 45786966 00004d4d 002a0000 00080005 01120003 00000001 0001>;
comments = Story;
externalGUID = nil;
externalURL = nil;
lastModified = nil;
location = nil;
metadata = nil;
ratingsArray = nil;
sharable = 0;
sharedWith = (
);
summary = "A saga to tell. ";
tagCount = 0;
tagsArray = nil;
timeStamp = "2015-12-24 19:29:46 +0000";
title = Story;
tokens = (
);
videos = (
"0xd0000000006c0004 <x-coredata://C26A39CE-7016-48EE-9228-9B6B39E94BFB/Video/p27>",
"0xd0000000004c0004 <x-coredata://C26A39CE-7016-48EE-9228-9B6B39E94BFB/Video/p19>"
);
}) with objects {(
<ths.Video: 0x1467ef540> (entity: Video; id: 0xd0000000006c0004 <x-coredata://C26A39CE-7016-48EE-9228-9B6B39E94BFB/Video/p27> ; data: {
author = nil;
comment = nil;
location = nil;
tagsArray = nil;
thumbnail = <ffd8ffe0 00104a46 49460001 01000048 00480000 ffe1004c 45786966 00004d4d 002a0000 00080002 01120003 00000001 0001>;
timeStamp = "2016-01-25 17:21:28 +0000";
videoFile = "0xd0000000006c0006 <x-coredata://C26A39CE-7016-48EE-9228-9B6B39E94BFB/VideoFile/p27>";
videoStories = "<relationship fault: 0x1479305c0 'videoStories'>";
}),
<ths.Video: 0x1467f63a0> (entity: Video; id: 0xd0000000004c0004 <x-coredata://C26A39CE-7016-48EE-9228-9B6B39E94BFB/Video/p19> ; data: <fault>)
)}
dynamictype:_NSFaultingMutableOrderedSet
description:Relationship 'videos' on managed object (0x1467b9750) <ths.VideoStory: 0x1467b9750> (entity: VideoStory; id: 0xd000000000380002 <x-coredata://C26A39CE-7016-48EE-9228-9B6B39E94BFB/VideoStory/p14> ; data: {
assetURL = nil;
authoredBy = nil;
collageImage = <ffd8ffe0 00104a46 49460001 01000000 00000000 ffe10080 45786966 00004d4d 002a0000 00080005 01120003 00000001 0001>;
comments = Story;
externalGUID = nil;
externalURL = nil;
lastModified = nil;
location = nil;
metadata = nil;
ratingsArray = nil;
sharable = 0;
sharedWith = (
);
summary = "A saga to tell. ";
tagCount = 0;
tagsArray = nil;
timeStamp = "2015-12-24 19:29:46 +0000";
title = Story;
tokens = (
);
videos = (
"0xd0000000006c0004 <x-coredata://C26A39CE-7016-48EE-9228-9B6B39E94BFB/Video/p27>",
"0xd0000000004c0004 <x-coredata://C26A39CE-7016-48EE-9228-9B6B39E94BFB/Video/p19>"
);
}) with objects {(
<ths.Video: 0x1467ef540> (entity: Video; id: 0xd0000000006c0004 <x-coredata://C26A39CE-7016-48EE-9228-9B6B39E94BFB/Video/p27> ; data: {
author = nil;
comment = nil;
location = nil;
tagsArray = nil;
thumbnail = <ffd8ffe0 00104a46 49460001 01000048 00480000 ffe1004c 45786966 00004d4d 002a0000 00080002 01120003 00000001 0001>;
timeStamp = "2016-01-25 17:21:28 +0000";
videoFile = "0xd0000000006c0006 <x-coredata://C26A39CE-7016-48EE-9228-9B6B39E94BFB/VideoFile/p27>";
videoStories = "<relationship fault: 0x1479305c0 'videoStories'>";
}),
<ths.Video: 0x1467f63a0> (entity: Video; id: 0xd0000000004c0004 <x-coredata://C26A39CE-7016-48EE-9228-9B6B39E94BFB/Video/p19> ; data: <fault>)
)}
Questions
Before reinventing the wheel, am I missing something obvious about the nature and intended usage of this dictionary? Like a built-in API that will do what I want (use the dictionary to build and insert a matching new managed object graph into a different context on a different phone)? Also, are the values in this dictionary NSValues? If so, I would probably need to store them as objects in a new dictionary. Apple provided this information to easily spinoff NSManagedObjects to an NSDictionary. Am I missing a function to do the reverse?
Other Thoughts
I do understand that NSManagedObjects were originally designed to exist only within a context. However the API method has been sitting there since iOS 3.0, so one supposes that it has some useful purpose. I need to create the most conservative memory usage operation possible. Could anyone with experience in this area share concerns with this approach or suggest an alternative?

Use KVC.
For attributes of NSManagedObject, use:
NSArray *keys = [[[object entity] attributesByName] allKeys];
NSDictionary *dictionary = [object dictionaryWithValuesForKeys:keys];
The resulting dictionary will have its values in standard types as defined in your model. To restore values from such dictionary into instantiated NSManagedObject:
[object setValuesForKeysWithDictionary:dictionary];
For relationships there is relationshipsByName. By calling dictionaryWithValuesForKeys: you will get dictionary with its values of NSManagedObject type for to-one, and, depending of your model, of ordered or unordered collections for to-many relationships, each containing NSManagedObjects. You have to iterate those and get its properties likewise.

Related

How to access a certain value in an NSArray?

I have an array called someArray. I would like to access the name value of the NSArray. I'm trying to access it using the following, but with out any luck. How do I do it properly?
cell.textLabel.text = [[someArray objectAtIndex:indexPath.row] objectForKey:#"name"];
some array {
haserror = 0;
headers = {
code = 0;
haserror = 0;
nodeid = "fe1.aaaaaa.2.undefined";
time = 16;
};
results = (
{
coords = (
"44.916667",
"8.616667"
);
id = 2;
key = alessandria;
name = Alessandria;
state = Piemonte;
zip = 1512;
},
{
coords = (
"43.616944",
"13.516667"
);
id = 3;
key = ancona;
name = Ancona;
state = Marche;
zip = 601;
},
}
As far as i see from you data model, the key name is a node under the key results. You can use this data model as a dictionary map, the code snippet below must give you what you need..
NSDictionary *myObject = [[someArray objectAtIndex:indexPath.row] objectForKey:#"results"];
cell.textLabel.text = [myObject objectForKey:"name"];
IMPORTANT NOTE: If you have some lopps or some other mechanisms for receiving data, there may be more efficent ways for your sıolution, so please give some more additional info about what you are exactly tryin to do
As others have noted, someArray is a dictionary while results is a key pointing to an array inside of your dictionary. If you want an array of all of the name fields in your results array, you could use valueForKeyPath: on the someArray variable, like this:
NSArray *names = [someArray valueForKeyPath:#"results.name"];
The names variable should now contain "Alessandria", and "Ancona" from the data set your show in your example code.

Accessing info from NSDictionary based on print out

I'm using NSLog(#"%#", [filter attributes]); to print out the following from a dictionary:
CIAttributeFilterDisplayName = "Color Controls";
CIAttributeFilterName = CIColorControls;
inputBrightness = {
CIAttributeClass = NSNumber;
CIAttributeDefault = 0;
CIAttributeIdentity = 0;
CIAttributeSliderMax = 1;
CIAttributeSliderMin = "-1";
CIAttributeType = CIAttributeTypeScalar;
};
I'm a little confused about NSDictionarys and how the information is organized. If I needed to access the attributes for inputBrightness, what would be the syntax to retrieve this form the dictionary?
If you want to retrieve inputBrightness from dictionary filter, you can try this:
NSDictionary *inputBrightnessDict = filter[#"inputBrightness"]; //or [filter valueForKey:#"inputBrightness"];
This will return another dictionary with key value pairs CIAttributeClass:NSNumber, CIAttributeDefault:0 etc..
You can confirm that filter[#"inputBrightness"] is a dictionary by looking at the NSLog statement. Key value pairs enclosed in { and } represents a dictionary where as ( and ) represents an array.
Inorder to retrieve any value from inputBrightnessDict you can fetch it as, inputBrightnessDict[#"CIAttributeType"];
[filter objectForKey:inputBrightness];
Hope this helps..
Dictionary work with the concept of object and keys. You can retrieve an object using an key. Key-object come as a pair.

Json Parsing issue iOS : missing "

I got a big issue when trying to parse json data in xcode. I have actually tried with two different parser and it still returns me a wrong json. Could anyone help in that ?
The string to parse (called jsonResp) is equal to :
{
"error":false,
"errorMessage":null,
"debugMessage":null,
"count":1,
"list":"links",
"data":[
{
"date":"Jeudi \u00e0 00:00:00",
"type":"friend",
"picture":"http://graph.facebook.com/22222222/picture? type=square",
"name":"Etouda Gaudo",
"ink_id":"1",
"chat_id":"1",
"count":"1",
"last_message":"CoUcou"
}
]
}
the string to parse is equal to :
NSData *jsonData = [jsonResp dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *dictionary = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:&error];
NSLog(#"dictionary %#", dictionary);
and then I got the following result for the NSLog of dictionary :
dictionary {
count = 1;
data = (
{
"chat_id" = 1;
count = 1;
date = "Jeudi \U00e0 00:00:00";
"ink_id" = 1;
"last_message" = CoUcou;
name = "Test name";
picture = "http://graph.facebook.com/22222222/picture?type=square";
type = friend;
}
);
debugMessage = "<null>";
error = 0;
errorMessage = "<null>";
list = links;
}
I can't figure out why the " are missing...
Does anyone have a solution.
Thanks in advance.
NSLog is just a print representation for developers to view, it is the result of the description method being called on a class instance. Quotes are only added where the item might be ambitious without them such as a string with an embedded space. To verify that the JSON was parsed correctly validate it with code.
You are deserializing the JSON into an NSDictionary, which doesn't have to have quotes around it's property names, unlike JSON. Your parser is working correctly, but the NSLog of an NSDictionary won't show up exactly the same as the original JSON would.

Retrieving the key name on AVMetadataItem for an AVAsset in iOS

I am trying to identify the different metadata items on a video on the iPad. So far I was able to successfully use the AVAsset libraries to find the file, and generate an array of AVMetadataItems using metadataForFormat:. Only iTunes and Quicktime User data formats were found in the file. The issue is now that I have that information, I have no way of identifying what is what. I intended to load a dictionary with the information, indexed by the metadata key, but using the key property of AVMetadataItem appears not to work correctly as if returns a number (debugger says its an NSCFNumber). Here is some sample code of what I am doing:
ALAssetRepresentation *representation = [[valAsset defaultRepresentation] retain];
NSURL *url = [representation url];
AVURLAsset *aAsset = [[AVURLAsset URLAssetWithURL:url options:nil] retain];
metaDataDict = [[NSMutableDictionary dictionary] retain];
NSArray *fmtmetadata = [aAsset metadataForFormat:#"com.apple.itunes"];
for (AVMetadataItem* meta in fmtmetadata)
{
[metaDataDict setObject:[meta stringValue]
forKey:[meta key]];
NSLog(#"metadata: key = %#", [meta key]);
}
This yields the following output in the debugger console:
metadata: key = -1452383891
metadata: key = -1452841618
metadata: key = 1684370275
metadata: key = 1818518899
metadata: key = 1937009003
metadata: key = -1453101708
Incidentally, changing the NSLog line to read:
NSLog(#"metadata: %#", meta);
gives us output like:
metadata: keySpace=itsk, key=desc, commonKey=(null), locale=(null), value=This is the Description of the Video, time={INVALID}, duration={INVALID}, extras={
dataType = 1;
}
Any help is greatly appreciated!
Looks like these keys are encoded ID3 tags:
1684370275 = 0x64657363 = {'d', 'e', 's', 'c'}
1818518899 = 0x6C646573 = {'l', 'd', 'e', 's'}
1937009003 = 0x7374696B = {'s', 't', 'i', 'k'}
etc.
You can use following extensions:
extension AVMetadataItem {
var keyString: String {
if key is NSString { return key as! String}
else if key is NSNumber {
var keyValue = (key as? NSNumber)?.uint32Value ?? 0
keyValue = CFSwapInt32BigToHost(keyValue)
let array = withUnsafeBytes(of: &keyValue) { Array($0) }
let string = String(decoding: array, as: UTF8.self)
return String(string.map {$0 == "�" ? "#" : $0 })
} else {
return "Unknown"
}
}
}
It can return strings like this:
keyString
Description
#alb
Album
aART
Album Artist
#ART
Artist
#gen
Gener
#nam
Title
#cmt
Comment
To use:
let albumMetaData: [AVMetadataItem] = AVMetadataItem.metadataItems(from: asset.metadata, filteredByIdentifier: .iTunesMetadataAlbum)
print(albumMetaData.first?.keyString) // #alb
Credit goes to:
Learning AV Foundation: A Hands-on Guide to Mastering the AVFoundation Framework (McCune, Bob)

Obj-C / iPhone: NSArray question

I have an array that looks like this when printed via NSLog:
{
response = "Valid";
updates = (
{
string = "test";
integer = 3493;
},
{
string = "test2";
integer = 55454;
}
);
start-index = 0;
My question is how I can loop through through the "updates" array so that I may print the values for each "string" respectively.
Should be an easy "for" loop or something?
Dave
Assuming you NSLogged data has a type of NSDictionary with name data.
NSArray *updates = [data objectForKey:#"updates"];
for (NSDictionary *update in updates) {
NSLog(#"Update: %# - %#", [update objectForKey:#"string"], [update objectForKey:#"integer"]);
}