Use NSDictionary 'initWithContentsOfURL' instead Reachability.h - iphone

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.

Related

What are keys and how do I use them?

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.

Storing data from ObjectiveFlickr in a Singleton on iPhone

The goal is to have a singleton data controller class called FetchData.h/.m that pulls data using ObjectiveFlickr ( https://github.com/lukhnos/objectiveflickr ).
FetchData.m grabs the data with this:
OFFlickrAPIContext *context = [[OFFlickrAPIContext alloc] initWithAPIKey:YOUR_KEY sharedSecret:YOUR_SHARED_SECRET];
OFFlickrAPIRequest *request = [[OFFlickrAPIRequest alloc] initWithAPIContext:context];
// set the delegate, here we assume it's the controller that's creating the request object
[request setDelegate:self];
[request callAPIMethodWithGET:#"flickr.photos.getRecent" arguments:[NSDictionary dictionaryWithObjectsAndKeys:#"1", #"per_page", nil]]
and then implement the following delegate:
- (void)flickrAPIRequest:(OFFlickrAPIRequest *)inRequest didCompleteWithResponse:(NSDictionary *)inResponseDictionary;
Currently I have this code to save the NSDictionary as a property list to a file as an alternative to a singleton:
- (void)flickrAPIRequest:(OFFlickrAPIRequest *)inRequest didCompleteWithResponse: (NSDictionary *)inResponseDictionary{
if([inResponseDictionary writeToFile:#"inResponseDictionary.xml" atomically:TRUE])
{
NSLog(#"%#", [[NSFileManager defaultManager] fileExistsAtPath:#"inResponseDictionary.xml"]);
}
}
When I read this file back, I get Null. The file is read back as such:
NSDictionary *inResponseDictionary = [NSDictionary dictionaryWithContentsOfFile:#"inResponseDictionary.xml"];
NSDictionary *photoDict = [[inResponseDictionary valueForKeyPath:#"photos.photo"] objectAtIndex:0];
NSLog(#"%#", [photoDict count]);
Is there a better way to store this data from ObjectiveFlickr so that it can be accessed by other classes and view controllers? Or is there a better way to implement this in the View Controller.
What is in the returned NSDictionary? Are you sure they are all valid plist objects? The photo data might need to be modified (say, base 64 encoded into an array) before your write will work.
The docs for NSDictionary writeToFile: say
This method recursively validates that all the contained objects are property list objects (instances of NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary) before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
As for the singleton aspect - will you be making more than one of these calls at a time? Is there a need to persist the data? If no & no, just keep the dictionary in memory. If you have multiple calls happening at once, you'll need another layer of abstraction (some indexing) to put each call's results in it's own unique location. And that's not happening with your current implementation.

Cocoa - `stringWithContentsOfURL/dataWithContentsOfURL` causes ERROR?

The following code for some reason poradically works. I have checked the URL so many times it's not funny (It returns plain text that I would like to parse). The code was 100% functional then it just stopped working and started giving me a EXC_BAD_ACCESS error.
There is nothing in the debugging output to post other than a line saying the output is switching to the process twice. (Except sometimes something about a double release.)
So far (as much as I can remember) I have tried:
Reinstalling the app - it only has problems on the 'Default' run (not the first Run/initiate Run.)
Running the URL in the browser (chrome, firefox, IE...)
Putting the call in a #try / #catch block
Using retain
Using a temp NSAutoreleasePool
Splitting up / separating the elements of the call (along with loggin Everything - once it hits the error, nothing gets logged)
Using the dataWithContentsOfURL functions with the above
NSAutoreleasePool *tmpPool = [[NSAutoreleasePool alloc] init];
NSString *url_string = [self getNormalVersionDownloadURL];
NSLog(#"urlString: -%#-", url_string);
NSError *er;
NSURL *the_URL = [[NSURL URLWithString:url_string] retain];
NSString *version_String = [NSString stringWithContentsOfURL:the_URL encoding:NSASCIIStringEncoding error:&er];
NSLog(#"verions_string: -%#-", version_String);
if ([version_String length] < 16)
return;
[tmpPool release];
(NSAutoreleasePool and autorelease added due to http://discussions.apple.com/thread.jspa?threadID=1667544)
(Cashed page - http://webcache.googleusercontent.com/search?q=cache:8D7zlQdG9PMJ:discussions.apple.com/thread.jspa%3FthreadID%3D1667544+http://discussions.apple.com/thread.jspa%3FthreadID%3D1667544&cd=1&hl=en&ct=clnk&gl=us&source=www.google.com)
discussions.apple.com is currently down so I cannot read the discussion thread. At any rate:
NSString *url_string = [[self getNormalVersionDownloadURL] autorelease];
Does -getNormalVersionDownloadURL return an owned or a non-owned object? You only send -autorelease if the method returns an owned object.
NSError **er;
This should be NSError *er instead, or it should be initialised with the address of a variable of type NSError *. Since the latter is uncommon and unnecessary, the following assumes NSError *er.
NSURL *the_URL = [[NSURL URLWithString:url_string] autorelease];
+URLWithString: returns an NSURL object that you don’t own, hence you don’t (auto)release it.
version_String = [[NSString stringWithContentsOfURL:the_URL
encoding:NSASCIIStringEncoding error:er] autorelease]; //ERROR occurs here
Two problems:: +stringWithContentsOfURL: returns an NSString object that you don’t own, hence you don’t (auto)release it. Furthermore, the third parameter should be &er instead of er.
URLWithString and stringWithContentsOfURL are convenience methods and then already put the variable in autorelease I don't think you need to add autorelease while creating the_URL and version_String
try to remove autorelease ...

Can I store AVAudioPlayer references in an NSMutableArray and play them at will?

I have a library of 16 short sound clips I need to be able to play in quick succession. I realized that creating and preparing AVAudioPlayer objects in real time was too much to ask of the iPhone.
So instead, during my app's initialization, I am pre-creating a series of AVAudioPlayers so that each one is basically pre-loaded with one of my 16 sounds, so they're ready to be played at any time.
The problem is, to keep this clean, I would like to store the references for these 16 AVAudioPlayers in an NSMutableArray, so I can easily get at them just by knowing their array location. However, the way I'm doing it is crashing the simulator w/no error messages in the log:
Here is how I'm currently setting up the array of AVAudioPlayer references:
// (soundPlayers is an NSMutableArray instance var)
soundPlayers = [NSMutableArray arrayWithCapacity:(NSUInteger)16];
for ( int i = 0; i < 16; i++ ) {
NSString *soundName = [NSString stringWithFormat:#"sound-%d", i];
NSString *soundPath = [[NSBundle mainBundle] pathForResource:soundName ofType:#"mp3"];
NSURL *soundFile = [[NSURL alloc] initFileURLWithPath:soundPath];
AVAudioPlayer *p = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFile error:nil];
[soundFile release];
[p prepareToPlay];
[soundPlayers addObject:(id)p];
[p release];
}
Then later I try to load, say, sound #8 and play it back:
// (soundPlayer is an AVAudioPlayer instance var)
self.soundPlayer = [soundPlayers objectAtIndex:(NSUInteger)8];
[soundPlayer play];
Any ideas? Also does anyone know if any of the slick debugging tools that come with XCode would be useful for this type of problem?
I'm just guessing here, but there might be a problem with the capacity of the array.
Try: soundPlayers = [[NSMutableArray alloc] init]; instead.
This isn't likely to be a cause of the crash, but try NSLogging self.soundPlayer before calling -play on it to make sure that its not nil. And another minor little thing: you don't need to typecast p when adding it to the array. Just call [soundPlayers addObject:p] without (id).
I'm not sure of the exact cause of your crash, just going through the code and pointing out what doesn't look right. Hope this helps!
It is most likely the case that your soundPlayers mutable array is being released at some point since you aren't retaining it. I would ensure your property for the array is set to retain and do what Macatomy previously stated:
self.soundPlayers = [[NSMutableArray alloc] init];
Or even initWithCapacity:
It looks OK to me except perhaps you shouldn't call prepareToPlay before you put them into the array.
It's probably better to do this when you actually are going to play them. The description of that method says it preloads buffers and acquires the audio hardware for playback. Doing it 16 times at once may be too much.
EDIT - you should also add error checking, your missing results that might tell you something is wrong, initWithContentsOfURL:outError: returns an error message. Take advantage of that:
NSError* error = nil;
AVAudioPlayer *p = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFile error:&error];
if (error) {
NSLog(#"ERROR: %#, URL=%#", error.localizedDescription, soundFile);
}

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.)