What are keys and how do I use them? - iphone

I'm working on an iCloud compatible app and I am researching how to detect whether a file is being uploaded/downloaded or has completed that. I found that this can be detected with NSURL "keys," such as NSURLUbiquitousItemIsDownloadingKey or NSURLUbiquitousItemIsUploadingKey. I'm still working on learning about programming, so what are these keys? How can I use them to detect the status of the files (I want the app to know when a file is done uploading to iCloud or done downloading (whichever side the device is on)).
I read that I can use resourceValuesForKeys:error: to query the state of these keys, so would I put this into an IF statement and see if the result is expected, such as "yes" or "no"? Thanks for your help.
if ([destination resourceValuesForKeys:[NSArray arrayWithObject:NSURLUbiquitousItemIsUploadingKey] error:NULL]) {
//is uploading??
}

Your proposed code looks almost workable, but for one thing: resourceValuesForKeys:error: returns a dictionary whose keys are the same as the constants you pass in and whose values are as specified in the documentation for those keys. In the case of NSURLUbiquitousItemIsUploadingKey, the value is an NSNumber instance wrapping a BOOL value.
So... assuming destination is an NSURL pointing to an item in your ubiquity container:
NSError *error;
NSArray *keys = [NSArray arrayWithObject:NSURLUbiquitousItemIsUploadingKey];
NSDictionary *values = [destination resourceValuesForKeys:keys error:&error];
if (values == nil)
NSLog(#"error: %#", error);
else if ([[values objectForKey:NSURLUbiquitousItemIsUploadingKey] boolValue])
NSLog(#"uploading");
else
NSLog(#"not uploading");
If you're only querying one key, you can use getResourceValue:forKey:error: to be a little more concise.

Related

Yahoo! weather in an iphone app

im developing an iphone app using yahoo weather service ( i have a key ).
i have 2 question :
can i use it in my app for commercial use ( like posting my app in appstore for free or no )
why the xml and json result are different :
http://weather.yahooapis.com/forecastrss?w=29330057&u=c
and
http://weather.yahooapis.com/forecastjson?w=29330057&u=c
there is any thing to do to much ( the first have the wanted location )?
thank you.
I suspect this is an issue with XML namespaces. Depending on the framework used and the actual full XML you'd have to access the elements by their namespace. You might want to switch to another, DOM-based framework (not using NSXMLParser), for example GDataXMLNode by Google. In a DOM-based framework you can access the individual nodes in a tree-like structure instead of building one on your own.
There are plenty of examples for this on the net, for example Building an RSS reader or How to read and write XML documents with GDataXML. But to give a quick example how this might look:
NSError *error = nil;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:data options:0 error:&error];
if (doc == nil) { return nil; }
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
NSArray *lists = [doc nodesForXPath:#"/result/list" error:nil];
if ([lists count] > 0)
{
for (GDataXMLNode *list in lists) {
int listid = [self integerInNode:list forXPath:#"listid"];
NSString *listname = [self stringInNode:list forXPath:#"name"];
[result setValue:[NSNumber numberWithInt:listid] forKey:listname];
}
}
[doc release];
return [result autorelease];
Yes, Yahoo! let you use their APIs under a fair-use policy, even commercially. Don’t be an ass and give them enough props though, e.g. their icon or logo with a link to their website.
I don’t think that it’s important to know why there are differences in both output formats. Use what is better / easier for you. Personally I prefer using JSON and Apple’s NSJSONSerialization class.

Use NSDictionary 'initWithContentsOfURL' instead Reachability.h

I would like to better understand the functioning of of initWithContentsOfURL of NSDictionary.
This function manages by itself the failure of a connection?
From the initWithContentsOfURL of NSDictionary reference:
- (id)initWithContentsOfURL:(NSURL *)aURL
Return Value
An initialized dictionary-which Might be different than
the original-that contains the receiver at aURL dictionary, or nil if
there is an error or if the contents of the resource are an invalid
representation of a dictionary.
Ok, but does not specify whether the url passed is valid or not.
But since i'm sure my plist is well-formatted, i could use the method in question to see if the connection is available or not, instead of using the Reachability.h. It is of course just to understand if a data connection is available, not to understand what kind of connection is active(e.g. WiFi, etc).
I say this because if i do a simple test like this in airplane mode, [dict count]; always returns 0.
NSURL * plist = [NSURL URLWithString: # "http://www.example.com/example.plist"];
NSDictionary * dict = [[[NSDictionary alloc] initWithContentsOfURL:plist] autorelease];
if ([dict count] == 0) {
//no connection
}
TIA.
Yes you can do that. Keep in mind that [[NSDictionary alloc] initWithContentsOfURL:plist] is a synchronous blocking call. If you block the main thread too long then you'll get 0x8BADF00D, and the watch dog will kill your process.

Question about deep copying example

In the Beginning iPhone 4 book, the author has this code to create a category for creating a deep copy of an NSDictionary that has an NSArray of names for each letter of the alphabet to show an example of an indexed table with a search bar.
#import "NSDictionary-MutableDeepCopy.h"
#implementation NSDictionary (MutableDeepCopy)
- (NSMutableDictionary *) mutableDeepCopy {
NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
NSArray *keys = [self allKeys];
for (id key in keys) {
id oneValue = [self valueForKey:key];
id oneCopy = nil;
if ([oneValue respondsToSelector:#selector(mutableDeepCopy)]) oneCopy = [oneValue mutableDeepCopy];
else if ([oneValue respondsToSelector:#selector(mutableCopy)]) oneCopy = [oneValue mutableCopy];
if (oneCopy == nil)
oneCopy = [oneValue copy];
[returnDict setValue:oneCopy forKey:key];
[oneCopy release];
}
return returnDict;
}
#end
Can someone explain the for loop logic? I'm not sure what he's trying to do in seeing which value responds to which selector, and why it would be added to the dictionary. Thanks.
So, the for loop simply iterates through all the keys in the dictionary. Beforehand, we create a new dictionary called returnDict - this will be what we return.
For each key in the dictionary we want to copy, we...
Get the object stored for that key ([self valueForKey:key]), and save it into a variable called oneValue.
If oneValue implements our mutableDeepCopy method (ie, it's an NSDictionary) go call it, and assign the return value into a variable called oneCopy.
Else, we see if oneCopy implements the mutableCopy method. If it does, we put the output into the oneCopy variable.
At this point, we check to see if following steps (2) and (3) the oneCopy variable has had anything assigned to it (if (oneCopy == nil)). If it doesn't (ie, it's equal to nil) we can assume the object doesn't implement either mutableDeepCopy or mutableCopy, so we instead call a plain old copy and assign its value to oneCopy.
Add oneCopy into our returnDict dictionary using the original key.
That's the for loop - at the end of it all, we go and return the copied dictionary.
The logic in the for-loop is convoluted because the author is trying to get as mutable and as deep a copy of the entire array as possible. The code tries three different ways to satisfy this, in order of preference:
Use mutableDeepCopy if possible (if the object understands that message).
Otherwise, use mutableCopy if possible.
If all else fails, just use copy.
If the object is just plain not copiable, your code goes boom when it sends the object -copy, since no test is made for whether the object responds to -copy. This is appropriate, since trying to deep copy an array containing items that cannot be copied is definitely programmer error.

Parse JSON collection from Rails in Objective-C on iPhone

I'm using TouchJSON to parse the output of a JSON Rails API, but am having difficulties. The overall goal is to loop through the response, parse the JSON, create a Round instance for each JSON object, and stick those Round objects into an NSArray so I can load this into a UITableView. So if there's a more straight-forward way to do that than what I'm about to show (which currently is NOT working, btw) please let me know.
The Rails API is returning a collection that looks something like this:
[
{
"round": { "course_title": "Title A", "result": "+8" }
},
{
"round": { "course_title": "Title B", "result": "+4" }
},
...
]
I'm also using ASIHTTPRequest and I can successfully get the response using:
NSString *responseString = [request responseString];
But from there, I cannot seem to get anywhere. Here's more-or-less what TouchJSON suggests:
NSString *jsonString = [request responseString]; // [{"round":{...}}, ..., {"round:{...}}]
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF32BigEndianStringEncoding];
NSDictionary *dictionary = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:nil];
// then I do this...
NSLog(#"JSON: %#", dictionary); // JSON: null
I thought from there I would be able to loop through the dictionary and create the object mappings using my Round class. But maybe that's the wrong approach altogether.
My thoughts are that the JSON being returned from Rails is an array of JSON objects, so maybe that's why the JSON parser doesn't recognize it as valid JSON? From this, I have two questions:
1) Should TouchJSON be able to accept an array of JSON objects like what my API is returning?
2) Is it possible to cast the responseString to an NSArray so I can loop through each "round" and parse the JSON that way? If I remove the first and last characters from the response string (i.e. "[" and "]") the JSON parser will only grab the first "round" in the collection.
3) Am I going about this whole process correctly?
Any tips/advice would be much appreciated.
TouchJSON presents three main ways to go from JSON to an Obj-C object. They are all present in the header for CJSONDeserializer which you're already using:
- (id)deserialize:(NSData *)inData error:(NSError **)outError;
- (id)deserializeAsDictionary:(NSData *)inData error:(NSError **)outError;
- (id)deserializeAsArray:(NSData *)inData error:(NSError **)outError;
The first one will return return whatever, either a dictionary, array, string or whatever the root type of the JSON is.
The other two expect a dictionary or an array and will complain (i.e. return nil and give you an NSError) if they don't get the right data.
The deserializeAsDictionary:error: method of CJSONDeserializer relies on the scanJSONDictionary:error: method of CJSONScanner. This method expects the "dictionary" to be an object literal. Therefore, your data must start with a {. Since your data is an array, you would want to use the deserializeAsArray:error: method of CJSONDeserializer.
Read the documentation carefully, your code is incorrect. It should look like this:
NSData *jsonData = [request responseData]
NSArray *rounds = [[CJSONDeserializer deserializer] deserialize:jsonData error:nil];
// then I do this...
NSLog(#"JSON: %#", rounds);
You could also have used:
NSArray *rounds = [[CJSONDeserializer deserializer] deserializeAsArray:jsonData error:nil];
However your absolute BIGGEST mistake was passing nil for error. You could have avoided going to stackoverflow at ALL if you had passed something in for NSError and then checked that.
With the right tools, this is WAY simpler than you're making it. I do this sort of thing all the time.
Use Stig's JSON framework, and import the NSString category that provides the JSONValue method.
Then inside your ASIHTTPRequest response handler code, go thusly:
NSMutableArray *roundlist = [NSMutableArray array];
NSArray *results = [[request responseString] JSONValue];
for (NSDictionary *item in results) {
Round *myRound = [item objectForKey:#"round"];
//don't actually do the above. Do whatever you do to instantiate a 'Round'.
[roundlist addObject:myRound];
}
[self.tableView reloadData];
EDIT: Geezo. Objection noted re valueForKey: vs objectForKey:. I updated my code sample, and I think we all learned something here.
I also didn't mean any offense with the phrase "with the right tools". OP was looking to simplify his code, and the RIGHT TOOL for that is the library with the simplest interface. I have nothing against TouchJSON per se, but JSON Framework has the simpler interface.

Why am I having trouble with a deep copy in Objective C?

I'm assuming my understanding of how to perform a deep copy isn't just there yet. The same with some sub-optimal memory handling that I'm performing down below. This code below probably depicts a shallow copy, and I believe that's where my problem might be. I have some cookie-cutter code for an example that looks like the following:
NSArray *user = [[xmlParser createArrayWithDictionaries:dataAsXML
withXPath:kUserXPath] retain];
if([user count] > 0) {
self.name = [[user valueForKey:#"name"] copy];
}
// Crash happens if I leave the next line un-commented.
// But then we have a memory leak.
[user release];
[xmlParser release];
Unfortunately when I comment out [user release], the code works, but we have an obvious memory leak. The method createArrayWithDictionaries:withXPath: was refactored last night when the SO community helped me understand better memory management. Here's what it looks like:
- (NSArray *)createArrayWithDictionaries:(NSString *)xmlDocument
withXPath:(NSString *)XPathStr {
NSError *theError = nil;
NSMutableArray *dictionaries = [NSMutableArray array];
CXMLDocument *theXMLDocument = [CXMLDocument alloc];
theXMLDocument = [theXMLDocument initWithXMLString:xmlDocument
options:0
error:&theError];
NSArray *nodes = [theXMLDocument nodesForXPath:XPathStr error:&theError];
for (CXMLElement *xmlElement in nodes) {
NSArray *attributes = [xmlElement attributes];
NSMutableDictionary *attributeDictionary;
attributeDictionary = [NSMutableDictionary dictionary];
for (CXMLNode *attribute in attributes) {
[attributeDictionary setObject:[attribute stringValue]
forKey:[attribute name]];
}
[dictionaries addObject:attributeDictionary];
}
[theXMLDocument release];
return dictionaries;
}
I'm guessing there's a couple of issues that might be going on here:
Auto release on my dictionaries array is happening, thus my app crashing.
I'm not performing a deep copy, only a shallow copy. Thus when the user array is released, self.name is done for.
With NSZombieEnabled, I see the following:
*** -[CFString respondsToSelector:]:
message sent to deallocated instance 0x1ae9a0
Also, the final call where the backtrace shows this is crashing contains the following code in a separate module from the other two methods:
User *u = self.user;
NSString *uri = [NSString stringWithFormat:#"%#/user/%#/%#",
[self groupName], u.userId, kLocationsUri];
Between all the auto releasing/copies/retain happening between the client code and createArrayWithDictionaries:withXPath, I'm a bit confused as to the real problem here. Thanks again for helping me understand.
OK, you don't need to retain the return value from createArrayWithDictionaries: since you're not keeping it around. The return value is autoreleased. I'd strongly recommend reading up on how autoreleasing works. You only retain things that you intend to keep around in your object.
Also, user is an NSArray. If you call [user valueForKey:#"name"], you'll get another NSArray of values representing the values of the name key for each of the objects in users. Furthermore, how is the name property on your object defined? If you declared it as copy or retain (I believe retain is the default if you don't specify it yourself), you don't need to copy or retain the value. Indeed, the accessor should always be responsible for doing the memory management, not the caller. If you wrote your own accessor (i.e. you didn't use the #synthesize keyword), you need to make sure you do the memory management there.
I'm guessing what you meant to write was something more like this:
NSArray *user = [xmlParser createArrayWithDictionaries:dataAsXML withXPath:kUserXPath];
if ([user count] > 0)
self.name = [[user objectAtIndex:0] objectForKey:#"name"];
[xmlParser release];
I think your troubles are stemming from a misunderstanding of how memory management works in Objective-C.
Hope this helps.
Auto release on my dictionaries array is happening, thus my app crashing.
If the caller intends to keep the array around somewhere, it needs to retain it. Otherwise, it will crash when it tries to access the (now-deceased) object.
If the caller is going to store it in a property, it must use the self.dictionaries = […] syntax, not dictionaries = […]. The former is a property access, which calls the setter method; the latter is a direct instance variable assignment.
Coming back to your actual question, that of a deep copy: You need to get the sub-elements of every element and put them in each element's dictionary.
Basically, you need a recursive method (or a queue, but that's harder—file under premature optimization until you've proven you need it) that takes an element and returns a dictionary, and then you need to call this method on each of your element's child elements, and collect the results into an array and put that into the dictionary you're creating.
I would recommend making this recursive method an instance method of the element. Something like:
- (NSDictionary *) dictionaryRepresentation {
NSMutableDictionary *attributeDictionary = [NSMutableDictionary dictionary];
for (CXMLNode *attribute in attributes) {
[attributeDictionary setObject:[attribute stringValue] forKey:[attribute name]];
}
NSArray *childElements = [self childElements];
return [NSDictionary dictionaryWithObjectsAndKeys:
attributeDictionary, #"attributes",
[childElements valueForKey:#"dictionaryRepresentation"], #"childElements",
nil];
}
Then you replace the loop in createArrayWithDictionaries:withXPath: with a similar valueForKey: message. I'll leave you to fill it in.
valueForKey: is Key-Value Coding's principal method. In both places, we're making use of NSArray's handy implementation of it.
(If the use of valueForKey: still doesn't make sense to you, you should read the KVC Programming Guide. KVC is vitally important in modern Cocoa, so you do need to read this sooner or later.)