iOS RestKit artificial identification attributes - ios

linked RestKit issue #1604
If my API gives me no id attribute, but i still want to cache the objects via Core Data, what should i use to identify my object.
For example i have
response = {
translation = {
text = "longlongtext";
dictionary = "general";
lang = "en";
};
otherdata = {
author = "May";
date = "434134";
};
}
So i would be glad to use hashed (md5) translation text as an id string.
Notice that my future requests which happen without network connection should be able to identify this cached entity and give it as a result.
I cant declare mapping to fill responseID property from [translation.text md5hash] to use as responseMapping.identificationAttributes = #[ responseID ]; because mappings doesnt have such feature.

As proposed by #segiddins in the github issue discussion:
... in your managed object subclass, hook into one of the core data callbacks to generate a compound key that is saved as part of the model and just use that key as your identification attribute.
The approach may look like this:
#property (nonatomic, copy) NSString *identifier;
- (void)willSave
{
[super willSave];
NSString *computedIdentifier = [[NSString stringWithFormat:#"%#%#", self.text, self.langCode] md5hash];
[self setPrimitiveValue:computedIdentifier forKey:#"identifier"];
}

I also wanted to do a hash of the JSON fields like you, but as you know it's not possible. I ended up doing the following to achieve (I believe) the same end result, which is for JSON objects returned without a unique ID, a unique identification attribute is generated by RestKit:
entityMapping.identificationAttributes = #[ #"text",#"dictionary",#"lang",#"author",#"date" ];

You should keep this kind of functionality outside of RestKit if you have no identifiers being provided by the server.
I would generate a custom identifier for each request you make (a GUID), I'd save that identifier into each of the result objects in the RestKit success completion block. I'd also save the request details and the identifier into user defaults.
Now, when the user makes a request and they are offline you can analyse user defaults to determine if it's a repeat request and find the identifier with which to query the results from the data store.

Just to clarify about offline requests after discussion.
In the end, such feature (offline requests) does not exist inside RestKit. The way you can achieve it is complicated, but possible. Steps are:
you use CoreData with RestKit (RKEntityMapping, managed objects etc)
Your provide good identification attributes for entities. It can be URL of request from #metadata.
on both success and failure callbacks from getObjectsAtPath you query CoreData with fetch request and return the result just the same way as if it was loaded directly and taken from mappingResult.firstObject and mark it as cached if it is old result loaded on failure.

Related

How do I "box" a PFObject inside an iOS push notification?

I want to send a PFObject directly over a push notification. I send the Parse object directly inside the push (e.g. with a custom key "arg") but I couldn't figure out how to construct a real PFObject from the received data. Received data is (obviously) an NSDictionary, with all the keys (object ID, created at, ACLs etc) available. How do I convert it to a PFObject instance?
I need a real way to construct a PFObject with the available data, so don't come with obvious solutions like "send the object ID and then fetch that object at client with Parse's methods." etc. I already know that obvious solution, but it's time/bandwidth/quota inefficient as it requires a new query, while I can have everything I need in that query anyway.
I'm looking for an automatic way, if any. I am targeting iOS 8 so maximum push payload size is also not an issue (2KB is more than enough for my case).
UPDATE: I've tried [PFObject objectWithClassName:#"MyClassName" dictionary:receivedDictionaryObject]; but no avail. It just does not work, the fields are nil even though the dictionary has all the data directly from Parse itself.
I think you can use something like this
+ (PFObject *)objectFromDictionary:(NSDictionary *)dictionaryFromPush{
PFObject *theObject = [[PFObject alloc] initWithClassName:#"MyClassName"];
for( NSString *keys in [dictionaryFromPush allKeys] )
{
[theObject setObject:[dictionaryFromPush objectForKey:keys] forKey:keys];
}
return theObject;
}
This is an untested code but im pretty sure will give you and idea of my point, to get the NSDcitionary from the Push and sent it to this method to be able to convert it to a PFObject
Hope this help

How to pass Core Data objectID and use it with Continuity

Just trying to update some Core Data apps with Continuity and have run into a bit of an issue with using the selected objects ID in the userInfo dictionary to display the correct data on the continuing device.
My first thought was to use the ObjectID, however on the receiving device this would never find a corresponding object in the Core Data store.
As it turns out the URL representation of the objectID contains the UUID of the store itself, and because the two stores UUID's are different this is obviously going to fail.
So I guess I could replace the Core Data store's UUID in the URL with the continuing devices UUID and use this, and no doubt it would work.
The Url seems to be of the following format
Does anyone know what the correct way would be to pass a reference to an object between two devices with core data stores that are synchronised via iCloud?
I'll answer this one myself and see if there are any better answers...
I pass the url of the objectID (from objectID.URIRepresentation) using Continuity API and on the receiving device create a new URL using the following:
url is the url passed in the NSUserActivity.userInfo dictionary
let storeUUID = self.identifierForStore()
// Switch the host component to be the local storeUUID
let newURL = NSURL(scheme: url.scheme!, host: storeUUID, path: url.path!)
func identifierForStore()->NSString? {
if let store = self.persistentStoreCoordinator?.persistentStores[0] as? NSPersistentStore {
return store.identifier
} else {
return nil
}
}
This seems to work just fine - hope it helps someone

Merge two objects of same type

I have two objects:
deviceConfigInfo and deviceStatusInfo
Both contain an array of devices (so theres a third device object actually).
For each device returned in deviceConfigInfo there are these properties:
uuid
name
somethingElse
lookAnotherOne
and for deviceStatusInfo
uuid
name
somethingElse
someStatusInfo
someMoreStuff
(If you hadn't guessed, I just made up some random properties)
So back to that third object I mentioned, device, I created it with all the properties combined. Now, my question is, say the deviceStatusInfo gets updated, how can I update the device object without losing the "old" data that isn't overwritten (in this case, the lookAnotherOne property).
Does it have to be a manual process of getting the device with the matching uuid and then updating each of the properties for deviceStatusInfo or is there a quicker way of doing this? Imagine there were loads of properties.
Hopefully this makes sense. If it helps, I am using Mantle to create the objects/models.
I noticed that Mantle has the following function which I was able to use:
mergeValueForKey:fromModel:
So in my device model, I added two functions:
mergeConfigInfoKeysFromModel:
mergeStatusInfoKeysFromModel:
These functions have access to an array that contains NSString values representing the properties/keys. There is one array for the configInfo and another for statusInfo properties/keys.
I then loop through the keys and use valueForKey to check it has an actual value. If it does, I then call the mergeValueForKey:fromModel:.
Example Code:
- (void)mergeConfigInfoKeysFromModel:(MTLModel *)model
{
NSArray *configInfoKeys = #[#"uuid", #"name", #"somethingElse", #"lookAnotherOne"];
for (NSString *key in configInfoKeys) {
if ([model valueForKey:key]) {
[self mergeValueForKey:key fromModel:model];
}
}
}
All I have to do now, is call the appropriate merge function on the device object when I get an update, passing over the updated device object. Just as below:
[self.device mergeConfigInfoKeysFromModel:deviceUpdate];

How do I get a server timestamp from Firebase's iOS API?

I have an iOS app that uses Firebase and currently has a few dictionaries with keys that are NSDate objects. The obvious issue with this is that NSDate draws from the device's system time, which is not universal.
With that, what's the best way to get a server timestamp (similar to Firebase.ServerValue.TIMESTAMP for the Web API) using Firebase's iOS API so that I can sort my dictionary keys chronologically?
I'm also aware of the chronological nature of IDs generated by childByAutoID, but I can't figure out the proper way to sort these in code. While they may be returned in chronological order, any time something like allKeys is called on them, the order goes out the window.
Any help with this issue would be greatly appreciated!
Update: In Firebase 3.0 + Swift, you can use
FIRServerValue.timestamp(). In Objective-C this is [FIRServerValue timestamp].
In Swift, you can now use FirebaseServerValue.timestamp() with Firebase 2.0.3+ (before 3.0).
The equivalent for Firebase.ServerValue.TIMESTAMP in iOS is kFirebaseServerValueTimestamp. Right now, this only works for Objective-C and not Swift.
In Swift, you can create your own global timestamp with
let kFirebaseServerValueTimestamp = [".sv":"timestamp"]
and then you'll be able to use kFirebaseServerValueTimestamp in the same way.
But you can only use this as the value or priority of a node. You won't be able to set it as the key name (although, I don't believe you could in the Web API either).
In general, calling allKeys on a dictionary does not guarantee order. But if you're using childByAutoID at a node, you can get back the right order by ordering the NSArray returned by allKeys lexicographically. Something like this would work:
[ref observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
NSDictionary *value = snapshot.value;
NSLog(#"Unsorted allKeys: %#", value.allKeys);
NSArray *sortedAllKeys = [value.allKeys sortedArrayUsingSelector:#selector(compare:)];
NSLog(#"Sorted allKeys: %#", sortedArray);
}];
This is similar to sorting an NSArray alphabetically, but when sorting the auto-generated IDs, you do not want localized or case insensitive sort, so you use compare: instead of localizedCaseInsensitiveCompare:
Caveat: Seems like the timestamp is added AFTER your object is persisted in Firebase. This means that if you have a .Value event listener set up on the location your object is persisted to, it will be triggered TWICE. Once for the initial object being stored in the location, and again for the timestamp being added. Struggled with this issue for days :(
Helpful information for anyone else who can't figure out why their event listeners are triggering twice/multiple times!
As of Firebase 4.0 you can use ServerValue.timestamp()
for example:
let ref = Database.database().reference().child("userExample")
let values = ["fullName": "Joe Bloggs", "timestamp": ServerValue.timestamp()] as [String : Any]
ref.updateChildValues(values) { (err, ref) in
if let err = err {
print("failed to upload user data", err)
return
}
}
You can get Time Stamp using FIRServerValue.timestamp().
But, Because of FIRServerValue.timestamp() listener is called two times. Listener will be called two times.

Exporting large amounts of data from coredata to json

I'm trying to export some data from core data to JSON. While the record count isn't particularly large (around 5000-15000 records), my data model is complex and there is a large amount of data in each record, so when I export this I exceed the allowable memory and iOS kills my app.
The steps i currently take are:
1. I have a method that extracts all the data from cordata and stores it an `NSDictionary`
2. I then write it to a file using an `NSOutputStream` and `NSJSONSerialization`
3. I then zip up the file and send it via email
I'm pretty sure that steps 2 and 3 are fine from a max memory perspective as I stream the data. But the problem is that it gets killed in step 1 because I'm effectively pulling all the data out of CD and putting it in memory so I can pass it through NSOutputStream to NSJSONSerialization.
Anyone know how to not have to pull everything into memory, but still write to a single tree JSON file?
Update - More Detail
My data structure (simplified for clarify) looks like this.
Given its not just a flat set of records but a hierarchal structure of objects with relationships i cant figure out how to pull the data out of core data in batches and fed tot he json streamer rather than all in memory to construct the json. my step one above is actually a collection of recursive methods that pull the data out of the core data entities and construct the 'NSDictionary'.
Folder {
Folder {
Word {
details type 1
details type 2
}
Word {
details type 1
details type 2
}
}
Folder {
Word {
details type 1
details type 2
}
Word {
details type 1
details type 2
}
}
Word {
details type 1
details type 2
}
}
[UPDATED TO IMPLEMENT LOW MEMORY SERIAL OUTPUT OF NESTED FOLDER HIERARCHY AS NESTED JSON OBJECT FILE]
Now you have provided more detail it's clear the original problem statement lacked sufficient detail for anyone to be able to provide an answer for you. Your issue is actually an age-old problem of how to traverse hierarchies in a memory efficient way combined with the fact the iOS JSON Library is quite light and doesn't easily support streamed writing of deep hierarchies).
The best approach is to use a technique known as the visitor pattern. For each of your NSManagedObject types shown above, implement a protocol called visitor, e.g. just the interface line for each object should look something like this:
#interface Folder : NSManagedObject <Visitable>
#interface Word : NSManagedObject <Visitable>
The visitor protocol should define a method call for all objects that comply with the protocol.
#protocol Visitable <NSObject>
- (void)acceptVisitor:(id<Visitor>)visitor;
#end
You are going to define a visitor object, which itself implements a visitor protocol.
#protocol Visitor <NSObject>
- (void)visitFolder:(Folder*)folder;
- (void)visitWord:(Word*)word;
#end
#interface JSONVisitor : NSObject <Visitor>
#property (nonatomic, strong) NSURL *streamURL;
- (void)startVisiting:(id<Visitable>)visitableObject;
#end
#implementation JSONVisitor
#property (nonatomic, strong) NSOutputStream *outputStream;
- (void)startVisiting:(id<Visitable>)visitableObject
{
if ([visitableObject respondsToSelector:#selector(acceptVisitor:)]
{
if (_outputStream == nil)
{
// more code required set up your output stream
// specifically as a JSON output stream.
// add code to either set the stream URL here,
// or set it when the visitor object is instantiated.
_outputStream = [NSOutputStream outputStreamWithURL:_streamURL append:YES];
}
[_outputStream open];
// Note 1a Bypass Apple JSON API which doesn't support
// writing of partial objects (doing so is very easy anyway).
// Write opening root object fragment text string to stream
// such as:
// {
// "$schema" : "http://myschema.com/draft-01/schema#Folder1",
// "name" : "Folder export",
// "created" : "2013-07-16T19:20:30.45+01:00",
// "Folders" : [
[visitableObject acceptVisitor:self];
// Note 1b write closing JSON root object
// e.g.
// ]
// }
[_outputStream close];
}
}
- (void)visitFolder:(Folder*)folder
{
// Note 2a Bypass Apple JSON API which doesn't appear to support
// writing of partial objects (Writing JSON is very easy anyway).
// This next step would be best done with a proper templating system,
// but for simplicity of illustration I'm suggesting writing out raw
// JSON object text fragments.
// Write opening JSON Folder object fragment text string to stream
// e.g.
// "Folder" : {
if ([folder.folders count] > 1) {
// Write opening folder array fragment to stream e.g.
// "Folders" : [
// loop through folder member NSManagedObjects here
// (note defensive checks for nulls not included).
NSUInteger count = 0;
for (Folder *nestedFolder in folder.folders)
{
if (count > 0) // print comma to output stream
[nestedFolder acceptVisitor:self];
count++;
}
// write closing folders array to stream
// ]
}
if ([folder.words count] > 1) {
// Write opening words array fragment to stream e.g.
// "Words" : [
// loop through Word member NSManagedObjects here
// (note defensive checks for nulls not included).
NSUInteger count = 0;
for (Word *nestedWord in folder.words)
{
if (count > 0) // print comma to output stream
[nestedFolder acceptVisitor:self];
count++;
}
// write closing Words array to stream
// ]
}
// Print closing Folder object brace to stream (should only be followed
// a comma if there are more members in the folder this object is contained by)
// e.g.
// },
// Note 2b Next object determination code here.
}
- (void)visitWord:(Word*)word
{
// Write to JSON stream
[NSJSONSerialization writeJSONObject:word toStream:_outputStream options: NSJSONWritingPrettyPrinted error:nil];
}
#end
This object is able to "visit" each object in your hierarchy and do some work work with it (in your case write it to a JSON stream). Note you don't need to extract to a dictionary first. You just work directly with the Core Data objects, making them visitable. Core Data contains it's own memory management, with faulting, so you don't have to worry about excessive memory usage.
This is the process. You instantiate the visitor object and then call it's start visiting method passing in the root Folder object of your hierarchy above. In that method, the visitor object "knocks on the door" of the first object to be visited by calling - (void)acceptVisitor:(id<Visitor>)visitor on the object to be visited. The root Folder then "welcomes the visitor in" by calling a method back on the visitor object matching it's own object type, e.g.:
- (void)acceptVisitor:(id<Visitor>)visitor
{
if ([visitor respondsToSelector:#selector(visitFolder:)]) {
[visitor visitFolder:self];
}
}
This in turn calls the visitFolder: method on the visitor object which opens the stream writes the object as JSON and closes the stream. This is the important thing. This pattern may appear complex at first, but I guarantee, if you are working with hierarchies, once you have implemented it you will find it powerful and easy to manage.
To support low memory serial output of a deep hierarchy, I'm suggesting you write your own JSON Folder object to the output stream. Since JSON is so simple, this is much easier than it might at first appear. The alternative is to look for a JSON Library which supports low memory serialised writing of nested objects (I haven't used JSON much so don't know if such exists and is easy to use on iOS). The visitor pattern ensures you need have no more than one NSManagedObject instantiated to work on for each level of the hierarchy (though of course more will inevitably need to be instantiated as you implement hierarchy traversal logic) so this is light on memory usage.
I have given examples of the text string that needs to be written to the output stream. Best practice would dictate using a templating system for this rather than directly writing statically allocated strings. But personally I wouldn't worry about adopting the quick and dirty approach if your deadline is tight.
I've assumed your folder objects contain a folders property providing a set of additional folders. I have also assumed your Folders NSManagedObject class contains a words property containing a set of Words NSManagedObjects. Remember if you stay working in Core Data it will look after ensuring you keep a low memory footprint.
At the end of the visitFolder: method, you can use the following logic.
Check if the Folder's contains any folders and visit each in turn if it does.
If it contains no more folders, check if it contains any Words, and visit each in turn if it does.
Note the above code is the simplest construct for minimising the memory footprint. You may want to optimise it for performance by e.g. only doing an auto-release when a certain batch size is exceeded. However given the problem you have described, it will be best to implement the most memory efficient method first.
If you have polymorphic hierarchies - your on your own :) - get a book out and do some study -managing them is a grad degree in itself.
Clearly this code is untested!
Check the NSFetchRequest documentation. You will see two properties:
- (NSUInteger)fetchOffset;
– fetchBatchSize;
With use of these two properties you can restrict the number of returned NSManagedObjects to a given batch size.
Open a stream you can write too. Set up a loop to execute a fetch request. But set a batch size (x) and then update the fetch offset of the fetch request at the end of the loop code for the next iteration of the loop.
myFetchRequestObject.fetchOffset += x;
Process the batch of data objects writing the JSON data to your open stream before starting the next iteration of the loop.
When either no more objects are returned or the number of objects returned by the fetch are less than the batch size, exit your loop.
Close your stream.
problem was that i had Enable Zombie Objects in the project schema turned on.
For some reason this also carried through to the release build too.
turning it off fixed all my problems.
I ended up also using TheBasicMinds design pattern because its a cool design pattern...

Resources