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

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

Related

PFFile and JSON?

In my chatting application, I'm using Parse for a user table, getting ID's, images, etc. I recently added this functionality, and I have encountered a problem. When I send a message, I create an NSDictionary with information about the message such as time, message, sender, sender objectId, etc. But, when I try to add the PFFile (image file) associated with the user, I get an error saying that PFFile cannot be converted to JSON (PubNub message format). How can I add PFFile as part of the NSDictionary used in the message to be compatible with JSON, or there might be another way.
I'm not familiar with asynchronous tasks, but in my code, I have a method - (NSDictionary *)parseMessageToDisplay:(NSDictionary *)message {} where the input would be message received from PubNub, and it would return a format better united to be displayed in a UITableView. If I added the ID of the file or user to my dictionary, how could I get my image in UIImage or NSData, and return it from my method in an NSDictionary. Sorry if this post seems long, just trying to provide a lot of information.
In order to use parse.com, PFFile in particular, you'll probably want that NSDictionary to be a PFObject instead. A PFFile reference can be saved as an attribute of a PFObject -- in fact that's the only way it can be saved.
Thanks to #danh for this suggestion, but you really saved me. Apparently Parse creates a URL for all PFFiles and I can just send that URL (NSString *) with my NSDictionary to PubNub, and then in my - (NSDictionary *)parseMessageToDisplay:(NSDictionary *)message method just use [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURLString]]; and get data from that. YAY! No long running confusing asynchronous tasks to made my day terrible!

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.

Need help understanding a conditional crash when accessing my NSDictionary

I am keeping my data in a property called practiceRecords (an NSArray of dictionaries).
I check to see if the data already exists in the documents folder.
If yes, I load the data into self.practiceRecords.
If not, I build the array of dictionaries (using literal syntax), keeping this data in the self.practiceRecords property, and then write the data out to the documents folder.
(I am NOT reloading the data after writing it out)
As far as I am able to tell, there are no problems occurring during this process.
Then I have a step where I modify my data as follows ...
-(void)incNumberOfTriesFor:(NSString *)stringOfIndex {
if (self.practiceRecords)
{
int index = [stringOfIndex intValue];
int numberOfTries = [(NSNumber *)(self.practiceRecords[index][#"tries"]) intValue] + 1;
//CRASHING on this next line.
self.practiceRecords[index][#"tries"] = #(numberOfTries);
//message to helper method
[self writePracticeRecords];
}
}
So the first time through (when the array is built and written out) I get a crash at the indicated line.
The error is:
-[__NSDictionaryI setObject:forKeyedSubscript:]: unrecognized selector sent to instance
I quit the app, check the documents folder and see the data file written out with no issues.
I re-run the app, and then get no crash and the data file still looks great.
This is repeatable.
If the data file exists, no crash.
If the data first needs to be created, then a crash.
(In all cases, I manually look inside the resulting data file and see exactly what I expect to see - no issues there)
I'm not sure where to even begin squashing this bug, and would really like to understand the details of why this is happening.
Thanks very much for any help!
Just to recap the correct comments above:
-[__NSDictionaryI setObject:forKeyedSubscript:]: unrecognized selector sent to instance
NSDictionary does not implement any of the set... methods because it is immutable. You state that you're creating with literals syntax when the data is not found on disk. The literal syntax creates immutable containers
Instead, try...
// try to initialize from disk, but if not
// we can still use literal (immutable) syntax, but in a mutable container
self.practiceRecords = [NSMutableDictionary
dictionaryWithDictionary:#{ #"key" : #"value" }];

iOS RestKit artificial identification attributes

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.

Resources