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.
I need a little help converting this
MIDIDeviceRef midiDevice = MIDIGetDevice(i);
NSDictionary *midiProperties;
MIDIObjectGetProperties(midiDevice, (CFPropertyListRef *)&midiProperties, YES);
NSLog(#"Midi properties: %d \n %#", i, midiProperties);
to swift. I have this but I am getting hung up on casting the CFPropertList.
var midiDevice = MIDIGetDevice(index)
let midiProperties = NSDictionary()
MIDIObjectGetProperties(midiDevice, CFPropertyListRef(midiProperties), 1);
println("Midi properties: \(index) \n \(midiProperties)");
Any help would be great.
Thanks
This is the signature for MIDIObjectGetProperties in Swift:
func MIDIObjectGetProperties(obj: MIDIObjectRef, outProperties: UnsafeMutablePointer<Unmanaged<CFPropertyList>?>, deep: Boolean) -> OSStatus
So you need to pass in an UnsafeMutablePointer to a Unmanaged<CFPropertyList>?:
var midiDevice = MIDIGetDevice(0)
var unmanagedProperties: Unmanaged<CFPropertyList>?
MIDIObjectGetProperties(midiDevice, &unmanagedProperties, 1)
Now you have your properties, but they're in an unmanaged variable -- you can use the takeUnretainedValue() method to get them out, and then cast the resulting CFPropertyList to an NSDictionary:
if let midiProperties: CFPropertyList = unmanagedProperties?.takeUnretainedValue() {
let midiDictionary = midiProperties as NSDictionary
println("Midi properties: \(index) \n \(midiDictionary)");
} else {
println("Couldn't load properties for \(index)")
}
Results:
Midi properties: 0
{
"apple.midirtp.errors" = <>;
driver = "com.apple.AppleMIDIRTPDriver";
entities = (
);
image = "/Library/Audio/MIDI Drivers/AppleMIDIRTPDriver.plugin/Contents/Resources/RTPDriverIcon.tiff";
manufacturer = "";
model = "";
name = Network;
offline = 0;
scheduleAheadMuSec = 50000;
uniqueID = 442847711;
}
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.
I have the following JSON that validates as valid JSON..
{"flights":[{"flight":{"flightno":"BAW699","timestamp":"2011-03-22 07:06:02","route":"LOWW-EGLL"}},{"flight":{"flightno":"BAW706","timestamp":"2011-03-21 19:08:02","route":"EGLL-LOWW"}},{"flight":{"flightno":"BAW5MG","timestamp":"2011-03-21 16:33:02","route":"LEMG-EGLL"}},{"flight":{"flightno":"BAW3AG","timestamp":"2011-03-21 11:25:02","route":"EGLL-LEMG"}},{"flight":{"flightno":"BAW5EB","timestamp":"2011-03-20 19:18:02","route":"EGLL-LPPT"}},{"flight":{"flightno":"BA0489","timestamp":"2011-03-20 09:40:02","route":"LEBL-EGLL"}},{"flight":{"flightno":"BA0475","timestamp":"2011-03-19 16:51:02","route":"LEBL-EGLL"}},{"flight":{"flightno":"BAW489","timestamp":"2011-03-18 08:08:02","route":"LEBL-EGLL"}},{"flight":{"flightno":"BA0799","timestamp":"2011-03-17 18:37:02","route":"EGLL>EFHK"}},{"flight":{"flightno":"BAW799","timestamp":"2011-03-17 16:31:01","route":"EFHK-EGLL"}},{"flight":{"flightno":"BAW794","timestamp":"2011-03-17 11:55:02","route":"EGLL-EFHK"}},{"flight":{"flightno":"GEUUZ","timestamp":"2011-03-17 09:43:02","route":""}},{"flight":{"flightno":"BAW487","timestamp":"2011-03-16 19:32:01","route":"LEBL-EGLL"}},{"flight":{"flightno":"BAW486","timestamp":"2011-03-16 16:57:02","route":"EGLL-LEBL"}},{"flight":{"flightno":"BAW459","timestamp":"2011-03-16 14:10:01","route":"LEMD-EGLL"}},{"flight":{"flightno":"BAW41LM","timestamp":"2011-03-16 10:09:02","route":"EGLL-LEMD"}},{"flight":{"flightno":"BA0795","timestamp":"2011-03-16 08:27:02","route":""}},{"flight":{"flightno":"BAW795","timestamp":"2011-03-16 07:01:02","route":"EFHK-EGLL"}},{"flight":{"flightno":"BAW798","timestamp":"2011-03-15 18:23:02","route":"EGLL-EFHK"}},{"flight":{"flightno":"BAW701","timestamp":"2011-03-15 14:24:01","route":"LOWW-EGLL"}},{"flight":{"flightno":"BAW700","timestamp":"2011-03-15 11:04:01","route":"EGLL-LOWW"}},{"flight":{"flightno":"BAW68BL","timestamp":"2011-03-15 08:14:01","route":"LEBL-EGLL"}},{"flight":{"flightno":"BAW82BL","timestamp":"2011-03-14 18:40:03","route":"EGLL-LEBL"}},{"flight":{"flightno":"BAW849","timestamp":"2011-03-14 08:15:02","route":"EPWA-EGLL"}},{"flight":{"flightno":"BAW40CB","timestamp":"2011-03-13 19:30:03","route":"EGLL-LEBL"}},{"flight":{"flightno":"BAW475","timestamp":"2011-03-13 15:30:02","route":"LEBL-EGLL"}},{"flight":{"flightno":"z.NO-FLIGHTNO","timestamp":"2011-03-13 13:00:03","route":""}},{"flight":{"flightno":"BAW474","timestamp":"2011-03-13 12:00:03","route":"EGLL-LEBL"}},{"flight":{"flightno":"BAW4WP","timestamp":"2011-03-13 08:45:02","route":"LPPT-EGLL"}}]}
I then try and parse this using JSONKit but can only get as far down as "flights" I cannot seem to access the "flight" object.
When I do the following
-(void)parseJSON:(NSString *)jsonData{
NSDictionary *deserializedData = [jsonData objectFromJSONString];
for (id key in deserializedData) {
NSLog(#"key: %#, value: %#", key, [deserializedData objectForKey:key]);
}
}
Only the following is put out to the log....
key: flights, value: (
{
flight = {
flightno = BAW906N;
route = "EGLL-EDDF";
timestamp = "2011-03-24 08:38:02";
};
},
{
flight = {
flightno = BAW365;
route = "LFLL-EGLL";
timestamp = "2011-03-24 06:17:01";
};
},
{
flight = {
etc....
I want to be able to get down to each flight and at the moment am stuck!
Get an array of all flights like this:
NSArray *flights = [deserializedData valueForKeyPath:#"flights.flight"];
thats because you have an array of dictionairies in your dictionairy object, acces them like this:
for (NSDictionairy *flight in [deserializedData objectForKey:#"flights"]){
NSLog(#"flight object with route.: %#", [flight objectForKey:#"route"]);
}
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"]);
}