I have an iOS application which downloads and parses a Twitter JSON feed and then presents that feed in a UITableView. This all works fine but I have one question:
When the user taps a UITableView cell, the app will look into the array "tweets_links" and see if that particular tweet has an attached URL, if it does then the web view will appear.
Because not all tweets have website URLs, I have added a simple try catch statement (like in C++) which can tell me if there is an exception when trying to access that part of the array.
My question is: is this is good or bad approach to doing this??
Here is my code:
int storyIndex = indexPath.row;
int url_test = 1;
NSString *url;
#try {
url = [[tweets_links[storyIndex] valueForKey:#"url"] objectAtIndex:0];
}
#catch (NSException *problem) {
// There is NO URL to access for this Tweet. Therefore we get the out of bounds error.
// We will NOT take the user to the web browser page.
// Uncomment the line below if you wish to see the out of bounds exception.
// NSLog(#"%#", problem);
url_test = 0;
}
if (url_test == 1) {
WebBrowser *screen = [[WebBrowser alloc] initWithNibName:nil bundle:nil];
self.seconddata = screen;
seconddata.web_url = url;
screen.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:screen animated:YES completion:nil];
}
else if (url_test == 0) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Info" message:#"There is no URL attatched to this Tweet." delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[alertView show];
[tweetTableView deselectRowAtIndexPath:indexPath animated:YES];
}
Is there a much better way of trying to achieve what I am doing???
Thanks, Dan.
Using try and catch is Objective-C isn't encouraged there are other ways checking and handling errors
// firstObject will return the first object in the array or nil if the array is empty.
url = [[tweets_links[storyIndex][#"url"]] firstObject];
if (!url) {
// handle the case for no url
} else {
// do something with url
}
Since sending a message to nil is safe in Objective-C and returns nil it's safe to chain calls. e.g. If the dictionary didn't have an object for that key, then it would return nil and sending firstObject to nil returns nil.
Using either if the below approaches will be fine because TRY CATCH is used to catch programming errors
and use
objectForKey:
instead of
valueForKey:
if ([tweets_links[storyIndex] objectForKey:#"url"] != nil)
OR
if ([url isKindOfClass:[NSString class]])
{
// Code handling the URL
}
else
{
// Code handling there is no URL
}
I don't know a ton about the Twitter feed, but you can probably check for a nil value returned from objectForKey: like so
if ([tweets_links[storyIndex] objectForKey:#"url"] != nil) { /* process the URL */ }
Your code assumes that the value is always an array of at least size = 1, it would be safer to inspect the #"url" key's value before assuming it's an array.
Using exceptions in Objective-C is throwned upon. Exceptions are reserved for programming errors. You don't catch them, you fix the code. With a JSON document, you never have any guarantees what you received, so just be careful.
NSString* url = nil;
NSArray* linksArray = nil;
NSDictionary* linkDict = nil;
NSArray* urlArray = nil;
if ([tweet_links isKindOfClass:[NSArray class]])
linksArray = tweet_links;
if (storyIndex >= 0 && storyIndex < linksArray.count)
linkDict = linksArray [storyIndex];
urlArray = linkDict [#"url"];
if ([urlArray isKindOfClass:[NSArray class]] && urlArray.count > 0)
url = urlArray [0];
if ([url isKindOfClass:[NSString class]])
{
// Code handling the URL
}
else
{
// Code handling there is no URL
}
Note that sending messages to a nil object always returns 0 / NO / nil as appropriate.
And please get into the habit of naming variables properly. You wrote "int url_test = 1;". What does url_test mean? I read the variable name, I have no idea what it means. I need to understand all the code. Making it "int" means it could be 0, 1, 2, 20000 or whatever. If you write instead "BOOL urlValid = YES;" that is clear: It means that you have a valid URL.
Since url value is a NSString value, you could use length to check both if it's nil and if not, if it has any value (not empty string). You can check then if this NSString is a valid url.
- (BOOL) validateUrl: (NSString *) candidate {
NSString *urlRegEx = #"(http|https)://((\\w)*|([0-9]*)|([-|_])*)+([\\.|/]((\\w)*|([0-9]*)|([-|_])*))+";
NSPredicate *urlTest = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", urlRegEx];
return [urlTest evaluateWithObject:candidate];
}
....
NSString *url = [[tweets_links[storyIndex][#"url"]] firstObject];
if ([url length] && [self validateUrl: url]) {
// Has a valid URL
}
Related
so I have a question how to use & implement nextPageToken Using GTLRYoutube in iOS Objective c which the example result is here :
( https://developers.google.com/youtube/v3/docs/playlistItems/list ) and set the maxResults to "10" and the "nextPageToken will generate it".
btw my flow to get the :
ChannelID
and getting PlaylistID -> to get PlaylistItems
and VideoID one by one
and the code like this :
1.
- (void)fetchChannelResource {
GTLRYouTubeQuery_ChannelsList *query =
[GTLRYouTubeQuery_ChannelsList queryWithPart:#"snippet,contentDetails"];
query.mine = true;
[self.service executeQuery:query
delegate:self
didFinishSelector:#selector(displayResultWithTicket:finishedWithObject:error:)];
}
and execute query :
- (void)displayResultWithTicket:(GTLRServiceTicket *)ticket
finishedWithObject:(GTLRYouTube_ChannelListResponse *)channels
error:(NSError *)error {
if (error == nil) {
NSMutableString *output = [[NSMutableString alloc] init];
if (channels.items.count > 0) {
self.youtubeChannelDataArray = channels.items;
[output appendString:#"Channel information:\n"];
for (GTLRYouTube_Channel *channel in channels) {
NSString *title = channel.snippet.title;
NSString *description = channel.snippet.description;
NSNumber *viewCount = channel.statistics.viewCount;
[output appendFormat:#"Title: %#\nDescription: %#\nViewCount: %#\n", title, description, viewCount];
NSString *uploadID = channel.contentDetails.relatedPlaylists.uploads;
self.youtubeUploadID = uploadID;
}
//CALL fetchPlaylistItemResource
[self fetchPlaylistItemResource];
} else {
[output appendString:#"Channel not found."];
}
// self.output.text = output;
} else {
// [self showAlert:#"Error" message:error.localizedDescription];
}
}
all the data saved in NSArray and NSMutableDictionary.
and in other viewController, i need to display them all.. but the problem is how to implement with nextPageToken which is will display 5 items resultPerPage ??
because when I use this "GTLRYoutube", looks like we don't need to make a new model, just use the model from GTLRYoutube, besides that also didn't make mapping and access the youtube API in the Datamanager.m
anyone have a suggestion or advise ???
thanks before!
When parsing API responses, sometimes I can not rely on strings being embedded in quotation marks. ID's are a good example of this, where some API's will send the numerical ID as a string while some will send it as a number.
What is a good practice when parsing such a value? If I simply parse it to an NSString like so:
NSString *myID = (NSString *)message["myID"];
I can end up with an NSString object that somehow contains (long)123.
And using stringValue would cause issues when the value is actually already sent as a string (since NSString does not have a stringValue function).
A way that works, but is somewhat ugly, is this:
id myID = (NSString *)message["myID"];
if ([myID respondsToSelector:#selector(stringValue)])
{
myID = [myID stringValue];
}
You could do something like:
id myID = message["myID"];
if ([myID isKindOfClass:[NSString class]]) { ... }
else { ... }
As long as this logic is encapsulated inside data parser and is opaque for your api users (i.e. they will always get a string) any approach is fine, e.g.:
- (NSString*)parseID:(NSDictionary*)message {
id rawID = message["myID"];
if ([rawID isKindOfClass:[NSString class]]){
return rawID;
} else if ([rawID isKindOfClass:[NSNumber class]]) {
return [(NSNumber*)rawID stringValue];
} else {
// We might still want to handle this case.
NSAssert(false, #"Unexpected id type");
return nil;
}
}
Alternative is to define stringValue in extension, so any possible objet will respond to selector:
#implementation NSString(JSONStringParsing)
- (NSString *)stringValue {
return [self copy];
}
#end
Why not just use description?
NSArray *objects = #[
#NSIntegerMin,
#NSIntegerMax,
#"123456789"
];
for (id object in objects) {
NSString *stringObject = [object description];
NSLog(#"%# -> %# | %#", [object className], [stringObject className], stringObject);
}
I am getting a Json from server by making a network request in my app.I am getting <null> value for some keys in Json object.My app gets crashed if this type of response is received.Please tell me how can i validate>?
I have tried this but it does not work all time.
if(!(user_post.username==(id)[NSNull null]) )
{
user_post.username=[dict_user_info objectForKey:#"name"];
if(user_post.username!=nil)
{
ser_post.username=[dict_user_info objectForKey:#"name"];
}
else
{
user_post.username=#"Username";
}
}
Consider testing the value for null so your program won't crash. Like this:
if([dict_user_info objectForKey:#"name"] != [NSNull null])
{
ser_post.username=[dict_user_info objectForKey:#"name"];
}
Create a Category of NSDictionary and add following method in it, which replaces null value with empty string for each key in dictionary.
- (NSDictionary *)dictionaryByReplacingNullsWithStrings
{
const NSMutableDictionary *replaced = [self mutableCopy];
const id nul = [NSNull null];
const NSString *blank = #"";
for(NSString *key in self) {
const id object = [self objectForKey:key];
if(object == nul || object == NULL) {
//pointer comparison is way faster than -isKindOfClass:
//since [NSNull null] is a singleton, they'll all point to the same
//location in memory.
[replaced setObject:blank
forKey:key];
}
}
return [replaced copy];
}
Usage :
[yourJSONDictionary dictionaryByReplacingNullsWithStrings];
Read more about Category in iOS Tutorial 1 and Tutorial 2
yourJsonObject = [myDic valueforkey#"key"];
if(yourJsonObject != [NSNull null])
{
//not null
}
** you can also check whether object exist or not
if(yourJsonObject)
{
//exist
}
I think you've confused your logic. I am trying to stay true to your code, but let me know if the following is not what you intended:
if (dict_user_info[#"name"] != nil && [dict_user_info[#"name"] isKindOfClass:[NSNull class]] == NO) {
user_post.username = dict_user_info[#"name"];
if (user_post.username != nil) {
ser_post.username = user_post.username;
} else {
user_post.username = #"Username";
}
}
These are a couple of methods I wrote for my projects, try them :
/*!
* #brief Makes sure the object is not NSNull or NSCFNumber, if YES, converts them to NSString
* #discussion Sometimes JSON responses can contain NSNull objects, which does not play well with Obj-C. So when you access a value from a JSON and expect it to be an NSString, pass it through this method just to make sure thats the case.
* #param str The object that is supposed to be a string
* #return The object cleaned of unacceptable values
*/
+ (NSString *)cleanedJsonString:(id)str
{
NSString *formattedstr;
formattedstr = (str == [NSNull null]) ? #"" : str;
if ([str isKindOfClass:[NSNumber class]]) {
NSNumber *num = (NSNumber*) str;
formattedstr = [NSString stringWithFormat:#"%#",num];
}
return formattedstr;
}
/*!
* #brief Makes Sure the object is not NSNull
* #param obj Sometimes JSON responses can contain NSNull objects, which does not play well with Obj-C. So when you access a value from a JSON ( NSArray, NSDictionary or NSString), pass it through this method just to make sure thats the case.
* #return The object cleaned of unacceptable values
*/
+ (id)cleanedObject:(id)obj
{
return (obj == [NSNull null]) ? nil : obj;
}
/*!
* #brief A JSON cleaning function for NSArray Objects.
* #discussion Sometimes JSON responses can contain NSNull objects, which does not play well with Obj-C. So when you access a value from a JSON and expect it to be an NSArray, pass it through this method just to make sure thats the case. This method first checks if the object itself is NSNull. If not, then it traverses the array objects and cleans them too.
* #param arr The Objects thats supposed to be an NSArray
* #return The NSNull Cleaned object
*/
+ (NSArray *)cleanedJsonArray:(id)arr
{
if (arr == [NSNull null]) {
return [[NSArray alloc] init];
}
else
{
NSMutableArray *arrM = [(NSArray*)arr mutableCopy];
int i=0;
for (id __strong orb in arrM)
{
if (orb == [NSNull null])
{
[arrM removeObjectAtIndex:i];;
}
i++;
}
return arrM;
}
}
Just pass a JSON string, array or object to the appropriate method and the method will clean it for you.
Do yourself a favour and write a method that handles this and put it into an extension. Like
- (NSString*)jsonStringForKey:(NSString*)key
{
id result = self [key];
if (result == nil || result == [NSNull null]) return nil;
if ([result isKindOfClass:[NSString class]]) return result;
NSLog (#"Key %#: Expected string, got %#", key, result);
return nil;
}
You might even add some code that accepts NSNumber* results and turns them into strings, if that is what your server returns (some poster here had the problem that his server returned dress sizes as numbers like 40 or strings like "40-42" which makes something like this useful).
And then your code becomes one readable line
user_post.username = [dict_user_info jsonStringForKey:#"name"] ?: #"username";
I actually use several slightly different methods depending on whether I expect null, expect no value, expect an empty string or not, which gives me warnings when my assumptions are wrong (but always returns something that doesn't break).
try this:
if(!(user_post.username == (NSString *)[NSNull null]) )
I'm loading an JSON but i want to check of the "URL": "", in the json is empty sometimes the ID is empty how can i check?
if(URL == HOW TO CHECK IF EMPTY?)
{
}
else
{
}
Error:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 0 beyond bounds for empty array'
*** First throw call stack:
Hmm, try
if ([URL isEqualToString:#"The URL?"]) {
If the URL object is a string, you can use either,
if([string length] == 0) { //empty }
or
if([string isEqualToString:#""]) { // empty }
If the URL object is an NSURL, you can use:
if([[url absoluteString] isEqualToString:#""]) { //empty }
if (URL == [NSNull null]) {
//...
} else {
//...
}
Or
if (URL == nil) {
//...
} else {
//...
}
Or Check with length of URL
When working with JSON data I tend to be very careful. Let's say I have a JSON deserialized into a NSDictionary. With that, I need to pull a string associated with the key "URL" out of the dictionary and turn it a NSURL. In addition, I'm not 100% confident in the JSON or the string value.
I would do something like this:
NSURL *URL = nil;
id URLObject = [JSON valueForKey:#"URL"];
if ([URLObject isKindOfClass:[NSString class]] && [URLObject length] > 0) {
URLObject = [URLObject stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
URLObject = [URLObject stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
URL = [NSURL URLWithString:URLObject];
}
After this, URL will have either a nil or a valid URL. -isKindOfClass: weeds out the value being an NSDictionary, NSArray, NSNumber, or NSNull. -length > 0 filters out empty string (which, as you know, can mess up an NSURL). The extra paranoia of decoding then re-encoding the URL escapes handles partially encoded URLs.
if (url.absoluteString.length==0)
{
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Error" message:#"Please enter a url" delegate:nil cancelButtonTitle:nil otherButtonTitles:#"Ok", nil];
[alert show];
}
Depending on how it is stored, you may need to verify if it is null (URL == nil) or if the string is empty. Assuming your URL is stored in an NSString, you would go for something like:
BOOL empty = URL == nil || [URL length] == 0;
Try this. It worked for me.
NSURL *url;
if ([url path])
{
// url is not empty
}
else
{
// url is empty
}
For swift 3 and swift 4 use this
var UrlUploadVideo = NSURL()
if UrlUploadVideo.absoluteString == "" {
// your code
}
guard let url = URL(string: urlStr) else { return }
Part of this assignment includes printing out on the display the current equation that is present to be solved, for that I use the following methods:
+ (NSString *)descriptionOfTopOfStack:(NSMutableArray *)stack {
NSMutableString *programFragment = [NSMutableString stringWithString:#""];
id topOfStack = [stack lastObject];
if (topOfStack) [stack removeLastObject];
if ([topOfStack isKindOfClass:[NSNumber class]]) {
[programFragment appendFormat:#"%g", [topOfStack doubleValue]];
} else if ([topOfStack isKindOfClass:[NSString class]]) {
NSString *operation = topOfStack;
if ([self isDoubleOperandOperation:operation]) {
[programFragment appendFormat:#"(%# %# %#)", [self descriptionOfTopOfStack:stack], operation, [self descriptionOfTopOfStack:stack]];
} else if ([self isSingleOperandOperation:operation]) {
[programFragment appendFormat:#"%#( %# )", operation, [self descriptionOfTopOfStack:stack]];
} else if ([ self isNoOperandOperation:operation]) {
[programFragment appendFormat:#"%#", operation];
} else if ([self isVariable:operation]) {
[programFragment appendFormat:#"%#", operation];
}
}
return programFragment;
}
+ (NSString *)descriptionOfProgram:(id)program {
NSMutableArray *stack;
if ([program isKindOfClass:[NSArray class]]) {
stack = [program mutableCopy];
}
return [self descriptionOfTopOfStack:stack];
}
My program computes the results and everything just fine, the only problem is that when I enter a variable, digit or single operand operation the display only shows said last entry, because it doesn't continue to iterate over the rest of the values present in the array, because no other recursive calls are made, any idea how I can make the program execute throughout the entire stack and not have it break the output?
I am not quite sure what you mean. The recursion should stop at a variable, digit or single operand operation. Although for a sin(operand) operation it should continue with the operand.
Did you take into account that your stack might be not completely defined?
Say you enter: 3 Enter 5 + 6 Enter 7 * 9 sqrt
this should translate to: 3+5, 6, sqrt(7*9)
So you have three elements still on your stack, but your approach stopped at sqrt(7*9).
You need to add a check at the to see if there is anything left on the stack, and continue if necessary (and add the comma's).
OK, another hint then (to be added at the end):
if ([stack count]) { // did I finish the entire stack?
[programFragment appendFormat:#"%#, %#", [self describeStack:stack], programFragment];
}
Interestingly you have used a NSMutableString, I did it with a NSString and used the class method stringWithFormat. So each time my result is a new string. I do not know if either approach is better.
aleene already answered, but just to clarify. I added the [stack count] check in the method that calls the recursive function.
+ (NSString *)descriptionOfProgram:(id)program {
NSMutableArray *stack;
NSString *strDesc = #"";
if ([program isKindOfClass:[NSArray class]]) {
// Make a consumable, mutable copy:
stack = [program mutableCopy];
}
while (stack.count) {
strDesc = [strDesc stringByAppendingString:[self descriptionOfTopOfStack:stack]];
if (stack.count) {
// More statements still on stack. We will loop again, but first, append comma separator:
strDesc = [strDesc stringByAppendingString:#", "];
}
}
return strDesc;
}