I have a networking method that provides a friendly interface to my API. Something like:
getWeatherForCities:(NSArray *)cityCodes
startDate:(NSDate *)startDate
endDate:(NSDate *)endDate
useCelcius:(BOOL)useCelcius
maxResults:(NSNumber *)maxResults
This is called multiple times in my app, sometimes concurrently with different parameters. There is also a completion and failure block but they aren't needed here.
I would like to add an option that tells the method only to execute the completion block if the data is different to the last time it was requested with the same parameters. This way some consumers can say that they want to know everything, and others can ask only for data if it is new.
It seems like I need some way to store a representation of all the parameters, alongside the last received response for those parameters. I would love to do this in an NSDictionary, but am open to other ideas. Is there some way to convert the parameters into a unique key? Or some better solution?
I am currently using the [dictionary description] as the key.
Related
I'm making an iOS app using swift and am using a cloud backend for the first time, namely Parse.com. I am curious what I can pass as parameters to cloud code functions and then receive as a return value. I want to send an array of PFObjects and also return an array of them, but I get the error Caught "NSInternalInconsistencyException" with reason "PFObjects are not allowed here." I am thinking of instead sending an array of dictionaries of the values I need to send and then return the same way. Can I do it this way, or is there another, better way to do this?
When I implement the getLoggedInUserOnSuccess:onFailure method (or the loginWithUsername: password: onSuccess:^(NSDictionary *results)...method in xcode, the results array does not become available until after all of my code has finished running. (If I NSLog the results, they will show up correctly.)
There is one other question that mentions this in Stackoverflow:
How to get the sm_owner User object using StackMob as backend
However, the Stackmob Evangelist in the answer here does not suggest that there is any requirement for a completion block or something of this nature. (And in fact, in his own code, it appears to work without such a block or any sort of "waiting.") This was my first hunch as to what might be going wrong.
(Without posting a ton of code, I am trying to use this function to get the sm_owner which then serves as the predicate in the FetchedResultsController's getter, to ensure the user only sees their own creations and not those of other users, when in one view; in another fetch, they might be able to see the creations of users they follow.)
Has anyone else tried to use one of these methods with a results dictionary returned to write the predicate on a FetchedResultsController or similar and been able to make it work?
None of the Stackmob tutorials appear to limit data returned from a database based on its creator as far as I can tell.
If you set your schema's read permissions to "Allow to sm_owner", then you don't need to place a predicate on the fetch. Doing a generic fetch will return only those objects owned by the current logged in user.
As of iOS 5 and OSX 10.7 and higher it is really easy to parse JSON with NSJSONSerialization, which will return either an NSDictionary or NSArray (or mutable variants, if specified) when parsing JSON. Values are parsed as common Cocoa types such as NSString and NSNumber however I would be interested to know how careful I need to be when taking the data from the NSDictionary or NSArray and parsing it into data objects in my app. My key concerns are whether the key's value a) is not nil and b) isn't of an unexpected type.
For example, assume I had the following JSON object:
{
"version":1,
"title":"Some interesting title",
"info":"Some detail here"
}
Currently, this would be parsed as an NSDictionary:
#{
#"version": #1,
#"title":#"Some interesting title",
#"info": #"Some detail here"
}
My problem is how careful I should be when checking the data types of what I'm getting back. In theory, if I'm using a good API I should always get a numeric value for the version key, but what if for some reason it is changed server side to the following:
{ "version:"1", ... }
Or even worse:
{ "version:"one", ... }
If I attempt the following code, I will get hit an exception and my app would crash:
NSNumber * myNumber = dictionary[#"version"];
if ([myNumber isEqualToNumber:#1])
{
...
}
The code wouldn't execute because a) dictionary[#"version"] would be an NSString and b) isEqualToNumber: is only available on NSNumber (unrecognized selector exception, app would crash).
Equally, problems could arise if the JSON for "info" was changed to the following:
{
"info":{
"code":200,
"message":"Some detail here"
}
}
If my app expects an NSString for the key info it will again crash, because an NSDictionary will have been found instead.
On the large part, most JSON from an API or file should be sound and supported by the current version of the app and one would hope that all JSON is versioned and correctly encoded server side. In some cases, if the JSON has been corrupted or modified, the app could crash, which I want to avoid.
Potential solutions:
Check every single key/value pair for isKindOfClass: or respondsToSelector: and only continue if true
Check the key exists and produce an error if nil
Wrap up everything in a try/catch block, however I would rather what can be used is used and an error is produced if something is wrong with the data. This could end up with a lot of #try/#catch statements inside one another
Each of these solutions is rather bulky and adds a lot to my code which I would prefer to avoid, if possible (and when working with 'good' JSON it is perfectly possible). If there is an alternative solution that will handle the process of parsing JSON, checking keys' type and values before putting it in a custom object I would love to know.
You should generally be running against a stable API. The kind of changes you're worried about should be accompanied by a version number change in any reasonable system which would insulate your app from the change until an appropriate upgrade time. So, you should generally know the data type to expect.
In some cases the API will specify that a dictionary or an array may be received depending on the multiplicity, something like that. In this case you should check the class and act accordingly.
You should definitely check for nil and NSNull and handle those gracefully.
Corrupted JSON should be handled by the parser and an appropriate error returned to you.
Also, you could use a framework like RestKit to do the mapping to your custom objects for you. It does a lot of data type checking as standard and removes basically all of your mapping code into a simple configuration. It also handles all of the network comms (via AFNetworking).
You need to make sure your code is safe against attacks from hackers. When you request JSON from a server, you must expect that the data doesn't come from your server but from somewhere else, and that someone else might have designed the data returned to cause maximum damage. Now just crashing if you receive a string instead of a number is quite secure.
You must expect that your request to the server is instead fulfilled by some brain damaged hardware that tries to be "helpful" for example when an internet connection fails. Instead of JSON you might receive a "helpful" website that is supposed to tell a user how to reset their router. A user trying to use someone's free WiFi may have connections return weird result. That's usually no problem with JSON because the parsing will fail (so failed parsing is something you should expect and handle), more of a problem if you expect html.
You must expect that a public API that you are using has bugs or unexpected behaviour and you should behave well when that happens. Add debugging code that will at least log anything unexpected while you are developing. Write your code so that it works with any behaviour that the API shows.
If you are using your own API, you should also log anything unexpected, and then tell the server people if they do anything they shouldn't.
Similar to the iPhone Facebook app search function, I am implementing search as you type functionality into my application although I have a problem when decoding the data into JSON format.
Basically what happens is because some searches take longer than others, they return at different intervals and this causes some small visual issues when the data is presenting on the screen.
I have set an NSLOG after each decode using NSJSONSerialization for the keyword 'industry'
2013-04-09 23:38:18.941 Project Name [42836:1d03] http://fooWebAddress/json/?method=search&limit=10&q=indus
2013-04-09 23:38:19.776 Project Name [42836:3e07] http://fooWebAddress/json/?method=search&limit=10&q=indu
2013-04-09 23:38:20.352 Project Name [42836:8803] http://fooWebAddress/json/?method=search&limit=10&q=indust
2013-04-09 23:38:21.814 Project Name [42836:4e03] http://fooWebAddress/json/?method=search&limit=10&q=industr
2013-04-09 23:38:23.434 Project Name [42836:8803] http://fooWebAddress/json/?method=search&limit=10&q=ind
2013-04-09 23:38:24.070 Project Name [42836:7503] http://fooWebAddress/json/?method=search&limit=10&q=industry
As you can see it is all out of order.
Does anyone have any way of stopping NSJSONSerialization for the previous connection.
Or possibly any other way to go about this problem?
Steps up to NSJSONSerialization...
NSURLRequest (initwithURL)
NSOperationQueue
NSURLConnection (asynchronous)
NSJSONSerialization
Thanks in advance.
When the user starts typing more text, you could cancel your previous connections and ignore any further delegate callbacks you receive from them. Then make the new request for the current text.
You can do this by maintaining some sort of lastRequest or lastOperation reference. When the user starts typing, call [self.lastRequestOrOperation cancel] and ignore any further notifications from that request with a check like if (request != self.lastRequest) { return; } in whatever callbacks you have.
However this has the problem that if the user keeps typing for a while you are constantly cancelling requests and they may not see any results until they have stopped typing.
A better solution would be to add sequencing so that each request is associated with an increasing sequence ID. You then only parse the result and update the UI when the sequence of the response is higher than the last one you received. If you receive any out-of-band responses from earlier, you just ignore them.
This is a much more complex issue than just being able to cancel the NSJSONSerialization. My suggestion is to use NSFetchedResultsController to populate your table view that shows the search results. Use the search term as one of the predicate variable in the NSFetchRequest attached to NSFetchedResultsController. And then, when you parse the results using NSJSONSerialization, store the results with the search term associated with that request. As soon as the search term changed (which you can detect when the user types more characters), re-create the NSFetchedResultsController and reload your table view. In addition, you can also try to cancel the call to parse the previous results if you launched it using performSelector:withObject:afterDelay. Beware that this cannot be always relied upon as the call may have been initiated by the time you are trying to cancel.
Kinda basic, but you could always maintain an nsdictionary of sub-classed NSURLRequests (sub-classed to provide a tag).
Start request - add request to dicationary with tag = array.count - 1, with key matching tag
Connection returns - is the request the most recent request, if so, parse json
Parse JSON - is the request the most recent request, if so, show results, if not, only display if there are no previous results displayed
Request handling - remove key from dictionary
most recent request = does the dictionary contain an object with a higher key value
Currently what you are doing is, you type each character and calling web-service. Why to call web-service for each letter you type. If user is type continuously, then it will increase the load, so call the web-service only when user stops for a particular interval of time. and then pass that string to call web-service or what ever method you are calling.
[NSObject cancelPerformSelectorsWithTarget:self]; // This will cancel your all req which is going to make when user typing without stopping
[self performSelector:#selector(sendSearchRequest) withObject:searchText afterDelay:0.1f]; // This will pass the string to call a web-service method, on which user hold for some time.
Let's assume that I have a program that parses a RSS feed.
I have a method that runs in a thread which keeps checking for updates. If updates is found a NSNotification is created. Is this a stupid implementation?
And is it possible to pass custom parameters within a NSNotification, such as the elementId that was updated.
Sounds like a good plan to me. Yes, you can pass user data.
See NSNotification's notificationWithName:object:userInfo:. The userInfo is an NSDictionary so you can pass whatever you like around.