I've familiarized myself with the NSXMLParser from the iPhone SDK but I find the event-driven nature of it awkward for my purposes. I just want to extract some element values but this concept of having to handle the startElement, foundCharacters, and endElement seems like more work than it really should be. Am I just looking at this the wrong way or is there a simpler tree/DOM-based way of working with XML in the iPhone SDK?
If the advice is to just work with NSXMLParser, are there certain design patterns I can use to keep my code from having 5 levels of nested ifs in the startElement method?
If you're on the iPhone, using tree-based parsing can be a prohibitive memory hog. Trust me, I've been there, and I've tried many different approaches over the last five months of development of my main iPhone application. Tree-based parsing works fine until you download someone's comment stream containing 400 very long comments, clocking in at about 600KB of raw data. Quite aside from the size of the resultant XML tree, the memory allocated internally while creating that tree can be enormous.
I wound up creating a variant of NSXMLParser which pulls data in from a supplied NSInputStream rather than using a single chunk of data, and which passes only 1KB at a time into libxml for handling (NSXMLParser uses libxml too, but passes 100% of the data in one go).
The source code is available on github (look in the StreamingXMLParser folder). You'll also find a delegate superclass in there; for most parsing needs you can subclass AQXMLParserDelegate and implement -start[Element]WithAttributes: (NSDictionary *) attrs and -end[Element] in your subclass. These methods will be called for you as start & end tags are discovered, and inside the end tag you can use self.characters to access the content characters or CDATA of the element.
For more on the relative memory footprints of the different parsers (albeit on the Mac, not the iPhone) see my original blog post here and the followup on NSXMLDocument here.
Consider the following code snippet, that uses libxml2, Matt Gallagher's libxml2 wrappers and Ben Copsey's ASIHTTPRequest to parse an XML document.
The nodes instance of type NSArray* will contain NSDictionary* objects that you can parse recursively to get the data you want.
Or, if you know the scheme of your XML document, you can write an XPath query to get you to a nodeContent or nodeAttribute value directly.
ASIHTTPRequest *request = [ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:#"http://stackoverflow.com/"];
[request start];
NSError *error = [request error];
if (!error) {
NSData *response = [request responseData];
NSLog(#"Root node: %#", [[self query:#"//" withResponse:response] description]);
}
else
#throw [NSException exceptionWithName:#"kHTTPRequestFailed" reason:#"Request failed!" userInfo:nil];
[request release];
...
- (id) query:(NSString *)xpathQuery withResponse:(NSData *)respData {
NSArray *nodes = PerformXMLXPathQuery(respData, xpathQuery);
if (nodes != nil)
return nodes;
return nil;
}
Repurposing the code from Seismic XML provides a very good API that creates NSObject subclasses from XML.
If the advice is to just work with NSXMLParser, are there certain design patterns I can use to keep my code from having 5 levels of nested ifs in the startElement method?
I depends on what you are trying to do. You could put your element names in a dictionary and take action based on the relevant object in a dictionary - this is effectively what SeismicXML does.
Related
Good evening guys,
My question is more of an engineering/design pattern approach than specifically technical.
I am developing an app that requires lots of interaction with a remote API returning JSON objects. The retrieval, parsing and utilisation of the data is not a problem and is working very smoothly. I am wanting to get some direction on the best design approach for this sort of scenario.
I will explain what I have so far (in pseudo code and declarations) and see if you can help:
A HTTP Fetcher class implementing the necessary NSURLConnection delegate methods. I initialise the class with the callback method selector like so for returning to the calling class on completion
#implementation HTTPFetcher{
- (id)initWithUrlRequest:(NSURLRequest *)aRequest receiver:(id)aReceiver action:(SEL)aReceiverAction
{
//set the member variables etc..
}
//all NSURLConnection delegate methods
- (void)connectionDidFinishLoading...
{
[receiver performSelector:action withObject:self];
}
}
I then have a Singleton HTTPController class for calling the HTTPFetcher:
- (void)postWithRequestString:(NSString *)aRequestString
{
[urlRequest setHTTPBody:[aRequestString dataUsingEncoding:NSUTF8StringEncoding]];
fetcher = [[HTTPFetcher alloc]initWithUrlRequest:urlRequest receiver:self action:#selector(receivedDataFromService:)];
[fetcher start];
}
- (void)receivedDataFromService:(HTTPFetcher *)aFetcher{
//handle the received data and split the parent object into an NSMutableDictionary
}
Now this approach works fantastically well for the app I have especially given the separate entities that I have to model (I will basically have a Singleton HTTPController for each entity).
My issue is where to handle the custom parsing of the JSON. Currently, I am doing the parsing the in ViewController where the data is required but this is too close to the source and needs to be abstracted out further but I am unsure how.
Should I include the methods to facilitate the parsing within the Singleton classes or should I create further controllers for parsing actions?
I look forward to hearing from you
Thanks
I would recommend you build on an existing JSON parsing library, in particular John Engelhart's JSONKit, considering it's arguably the highest performance JSON parsing library out there for iOS. Saves you implementing custom JSON parsing at all, but especially saves you implementing code which turns out to be too slow for your needs and then you will need to iteratively refine it until it gets fast enough for you to use.
For HTTP requests, I know you've implemented the behaviour already, but you might want to investigate ASIHTTPRequest or AFNetworking as general purpose networking libraries which have a reputation for being quite robust. Note AFNetworking uses the above JSONKit library for JSON parsing.
The way ASIHTTPRequest (the library I use in my projects) works is by using a delegate object implementing the protocol ASIHTTPRequestDelegate, which you assign after creating a request with a URL. There's a global network queue which is just an NSOperationQueue, and that handles asynchronous or multiple concurrent active requests.
You can setDelegate: for the object to start checking whether your delegate has implemented any of the methods at different points, such as didReceiveData: or requestDidFinish: by default, but you can also set a custom selector path to check by using the methods for individual operations (setDidFinishSelector:#selector(downloadComplete:)).
What you could do when, for example, the didReceiveData: callback happens, is pass the newly received data into a buffer stored in a wrapper class for an ASIJSONRequest (or use AFNetworking, which already encapsulates this). When the buffer is such that there is a complete JSON object in there which can be parsed correctly, then you call out to JSONKit to do the grunt work and then maybe send another callback yourself to an ASIJSONRequestDelegate for didReceiveData:, but now the data is in a format which is readable by the rest of your application.
Another method of using ASIHTTPRequest is with blocks. There is support for setting a completion block for a request, a block that is called when data is received, etc. For this design pattern you don't even need a wrapper class, just set the code block up to do the parsing itself and return any new data parsed to its desired destination.
One possibility would be for the View or view controller to ask a Model object for any state that it needs (including stuff from a remote server). The Model object would be told when there was any new data from the server, and it could then call any required data munging routines required to update its internal state (converting plists or json into a more canonical dictionary format, for instance).
The current app I'm developing for the iPad involves handling many network requests and persisting the processed results in core data.
The scenario is follows - the application needs to download images for objects I'm displaying in a grid view, which can show a total of 30 objects. Each object can consist of up to 15 png images (also in a grid). Due to the way the server is implemented (meaning I didn't implement it and can't change it easily), each image must be requested separately, so I need to make up to 15 requests per object versus just 1 request to download all 15 images.
For each object, I'm currently using an ASINetworkQueue to queue up the 15 image requests. Once the queue finishes, I create a thumbnail snapshot of the object with its images to display in the grid, then persist all the png files to core data.
I'm currently running everything on the main thread except the network requests which are handled by ASI asynchronously, but since there are so many requests, the app UI is essentially locked until all the requests are processed and results saved to core data.
One solution I came across was doing the core data operations and writes in a separate thread or using grand central dispatch. Another is to only download the images for the visible objects, and download the rest when the user scrolls down.
I'm looking for other suggestions to help keep the main ui responsive, or better ways to structure the network and core data operations. Thanks.
First of all, avoid storing large blobs in Core Data, saving the thumbnails is fine (although you should optimize your model for it), but you should store the full image once it's reconstructed in the Documents folder.
You should definitely use a queue, either NSOperationQueue or ASI network queue. I do something similar in my app which has multiple dependencies. So, for each of the 30 objects you need to have a block (or work function) get called when the 15 images have downloaded. You ideally want to do this work off the main thread. Put all these requirements together, and I'd say you need at least two queues, one for network requests and one for worker blocks, and you should use NSBlockOperations which makes the whole thing much easier. So, the code would be something like this...
// Loop through the objects
for (NSArray *objectParts in objectsToDownload) {
// Create our Object
Object *obj = [Object insertIntoManagedObjectContext:self.moc];
// This is the block which will do the post processing for the object
NSBlockOperation *processBlock = [NSBlockOperation blockOperationWithBlock:^{
// Do post processing here, be very careful with multi-threading CoreData
// it's likely you'll need some class to dispatch you MOCs which have all
// all the observers set up.
// We're gonna assume that all the sub-images have been stored in a instance
// variable:
[obj performPostProcessing];
}];
// Given the list of 15 images which form each object
for (NSURL *part in objectParts) {
// Create the ASI request for this part
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:part];
// Configure the request
[request setDelegate:self];
[request setDidFailSelector:#selector(partRequestDidFail:)];
[request setDidFinishSelector:#selector(partRequestDidFinish:)];
// Store the object in the UserInfo dictionary
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:obj, #"Object", nil];
[request setUserInfo:userInfo];
// Add it as a dependency
[processBlock addDependency:request];
// Add it to our network queue
[networkQueue addOperation:request];
}
// Add the processBlock to our worker queue
[workerQueue addOperation:processBlock];
}
Then you'll also need to write the delegate methods, the didFinish one would look something like this...
- (void)partRequestDidFinish:(ASIHTTPRequest *)request {
// Remember this is one the main thread, so any heavy lifting should be
// put inside a block operation, and queued, which will complicate the
// dependencies somewhat, but is possible.
// Get the result data
NSData *data = [request responseData];
// Get the object that it belongs to from the user info dic
Object *obj = [[request userInfo] objectForKey:#"Object"];
// Keep track of the partial data in the object
[obj storePartialDataForPostProcessing:data];
}
And all of that would go into your class which connects to your server and creates your objects, so it's not a view controller or anything, just a regular NSObject subclass. It will need to have two queues, a managed object context (and more than likely a method which returns another MOC for you to use in threads, something like this:
// Fends a MOC suitable for use in the NSBlockOperations
- (NSManagedObjectContext *)moc {
// Get a blank managed object context
NSManagedObjectContext *aContext = [[UIApplication sharedApplication] managedObjectContext;
[aContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(mergeChangesFromMOC:) name:NSManagedObjectContextDidSaveNotification object:aContext];
return aContext;
}
- (void)mergeChangesFromMOC:(NSNotification *)aNotification {
#try {
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:aNotification];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:[aNotification object]];
}
#catch (NSException * e) {
NSLog(#"Stopping on exception: %#", [e description]);
}
#finally {}
}
You'll also need to hook in some way of monitoring progress, re-queuing failed downloads, cancelling, and saving the MOC at the end. Re-queuing failed downloads is quite tricky. Anyway, hope that helps.
So, just to clarify, in your delegate method, you'd store the downloaded image in a temporary instance variable on your Object. Then when all 15 dependencies finish, you can access that instance variable and do your work.
To kick things off, 1 Queue should be enough for all the image requests.
What you might want to do is keep references to the request so you can cancel them if the object is no longer needed.
For the locking, there's a few things to consider with images:
They are compressed, so they need to be inflated before being UIImages, that is very heavy on the CPU.
If you ever want to write to the filesystem, that process is locking. Do it in another Queue to avoid locking.
It's never a good idea to store Blob into CoreData, store the file path as a string in Core Data and fetch it from disk using a queue
Just using 3 different NSOperationQueue will make your app much more responsive:
1 for ASIHTTPRequests (Don't create a new one, use the default with startAsynchronous)
1 for image writing to disk
1 for image fetching from disk
Since you will display image to view, why don't you SDwebImage:
SDImageCache manages an asynchronous download queue, ties the downloader with the image cache store, maintains a memory cache and an optional disk cache. Disk cache write operations are performed asynchronous so it doesn't add unnecessary latency to the UI.
[imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]]
https://github.com/rs/SDWebImage
How can I create a new file containing just it's own delegate so that I can make an ASIHTTPRequest with its own asynchronous ending, and something easy enough where I just need to set [request setDelegate:self]; to something like [request setDelegate:AlternateDelegate]; and just add an include at the begining of the document to reference the AlternateDelegate delegate
I know this question is old, but in case anyone comes across it:
#Hankweb seemes to be talking about using a request as its own delegate. There are certainly situations where this works. For example, I'm working on a project that uses ASIHTTPRequest to fetch JSON from a remote source and import it into a Core Data store.
This operation (literally, as ASIHTTPRequest is a subclass of NSOperation) is almost entirely self-contained; I have a custom request on a background thread using a streaming JSON parser to import objects into a NSManagedObjectContext, which, when saved, triggers a notification that I catch internally and pass to the main thread's context using performSelectorOnMainThread:waitUntilDone:.
I'm using ASIHTTPRequest's block support to accomplish this; in my custom initWithURL: method, I set up the relevant callbacks (dataReceivedBlock, completionBlock, failureBlock, etc.). The traditional delegation pattern (using the ASIHTTPRequestDelegate protocol) should also work, though.
One gotcha: you should make sure the request doesn't retain itself too many times, or else you'll end up with a memory leak. This can be easy to miss when using multiple threads, and especially when using blocks. Instead of:
- (id)initWithURL:(NSURL *aURL) {
//...
[self setCompletionBlock:^{
[self doSomething];
}];
//...
return self;
}
Use the __weak attribute (or __block if you're not using ARC) when referencing self within the blocks:
- (id)initWithURL:(NSURL *aURL) {
//...
__weak id blockSelf = self;
[self setCompletionBlock:^{
[blockSelf doSomething];
}];
//...
return self;
}
If you don't know why this is important, make sure to read Apple's guide to blocks in Objective-C, and the ASIHTTPRequest block API documentation.
A delegate for ASIHTTPRequest is just a standard objective C object. Just create a new class, include it's header, create/get the object and set the delegate to be that object.
Have you tried this and run into a problem? If so what is the problem?
I am intending to create 2 requests using NSURLConnection. When the server responds and calls connectionDidFinishLoading it passes in the connection as the parameter, but how do I identify which connection is passed in?
Save both NSURLConnection objects as member variables of whatever delegate object you passed to connectionWithRequest:delegate:. Then you can just compare each of those to the NSURLConnection passed to connectionDidFinishLoading:, and respond appropriately:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (connection == firstConnection) {
// do something
}
else if (connection == secondConnection) {
// do something else
}
}
Another slightly more object-oriented option would be to create two different delegate objects, each of which knows how to deal with each each type of connection. Then just pass the appropriate delegate when you create each connection. That way you don't need to check to see which connection you have, because each delegate will only receive connectionDidFinishLoading: for its own connection.
I prefer different delegates for each connection, too. Although it's a bit of overhead. Fortunately, you can simplify things by using blocks. It's a new feature that doesn't exist in standard SDK yet, but there is 3rd-party framework called PLBlocks that you can use already. Here is an article on how to use them, it also contains example for NSURLConnection.
This is the client code making HTTP request with block callback:
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com"]];
[NSURLConnection sendAsynchronousRequest:req onCompletionDo: ^(NSData *data, NSURLResponse *res, NSError *err) {
NSLog(#"data: %ld bytes. res: %#, error: %#", (long)[data length], res, err);
[cell.activity stopAnimating];
}];
I used to create a custom wrapper around NSURLConnection, too, but I've now switched over to ASIHTTPRequest. This is a fantastic library providing much more flexibility and features than NSURLConnection. Have a look and give it a try, it's really worth it.
What I do in my projects is create a wrapper class for the connection. This way, you can keep a new instance for each connection you need, and maintain these classes in another manager class.
Something like [AsynchronousConnection initWithURL:delegate:selector:]
Then you can be ensure the right thing is called when the NSURLConnection is done/failed.
Please do not refer https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html
GO to the NSURLConnection.h file and you will find the following.
When created, an NSURLConnection performs a deep-copy of the
NSURLRequest. This copy is available through the -originalRequest
method. As the connection performs the load, this request may change
as a result of protocol canonicalization or due to following
redirects. -currentRequest can be used to retrieve this value.
Ultimately [connection currentRequest].URL absoluteURL might help.
Regards,
PRASANNA.
In my iPhone app I want to download multiple files which are on IIS with authentication. On a button click i want to start the downloading process.
I know how to download a file with authentication.
NSURLRequest* request =
[NSURLRequest requestWithURL:mMovieURL
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
movieConnection =
[[NSURLConnection alloc] initWithRequest:request delegate:self ];
and i have couple of delegate methods with the above code.
But how to do it with mutliple downlaods going at the same time.
Thanks,
I'm not familiar with MultipleDownload, but in case it doesn't meet your needs, the issue I take it is that you have a single object that is the delegate to many NSURLConnections, and you want to know how to keep them straight.
The delegate methods all return the NSURLConnection itself as their first parameter. So you can keep track of which data goes where by testing which NSURLConnection is calling you back. One way to do this is with an NSDictionary that maps the connection to its NSMutableData object. Now the trick is that you can't make an NSURLConnection be the key in a dictionary because it doesn't conform to NSCopying (and you wouldn't want it to). One way to work around this is to use the address of the connection such as:
NSString *key = [NSString stringWithFormat:#"%p", connection];
This will return a unique key for any object (the hex representation of its address). Some people use description for this purpose, but I don't like that because it's not a well defined interface. There's no promise that it be unique. In systems where I do this a lot, I implement the above -stringWithFormat: in a method called -uniqueIdentifier and make it a category on NSObject so anything can be tracked in a dictionary.
I often find it's easier just to create a small wrapper object so that each object controls its own NSURLConnection, much as I'm sure MultipleDownload is doing, but still this technique is useful in a variety of cases, whether you're managing multiple tableviews, or anything else that has a delegate.
EDIT: Replaced %x I had above with %p as noted by Peter. He's right, and I wasn't thinking correctly. Double-checking my code, I actually have been using %p, having run into this error before....
I've done this before when I wanted to download 10 XML files at the same time (it was much faster than queuing them to download one after the other). I used the libraries found here:
http://github.com/leonho/iphone-libs/tree/master
They were easy to implement and there's some example code on the front page to get you started.
self.urls = [NSMutableArray arrayWithObjects:
#"http://maps.google.com/maps/geo?output=json&q=Lai+Chi+Kok,Hong+Kong",
#"http://maps.google.com/maps/geo?output=json&q=Central,Hong+Kong",
#"http://maps.google.com/maps/geo?output=json&q=Wan+Chai,Hong+Kong",
nil];
self.downloads = [[MultipleDownload alloc] initWithUrls: urls];
self.downloads.delegate = self;
Good luck.
I think the simplest way to do this is to use NSOperation - and a NSOperationQueue.
This will mean that you can specifiy if each operation should happen sequentially or in parallel. You can even limit the number of parallel operations - so that there are a max of 5 (say) running at one time time and then other operations queue up behind.
This really is a great way of letting the OS handle multiple activities - and works well with the lazy loading type philosophy of the iPhone OS.
You can then get each operation to make a call back as it finishes - or even make progress callbacks on the main thread.
I have changed my code to all work this way now and found it to be much more robust and user firendly.
I'm not familiar with
MultipleDownload, but in case it
doesn't meet your needs, the issue I
take it is that you have a single
object that is the delegate to many
NSURLConnections, and you want to know
how to keep them straight.
The delegate methods all return the
NSURLConnection itself as their first
parameter. So you can keep track of
which data goes where by testing which
NSURLConnection is calling you back.
One way to do this is with an
NSDictionary that maps the connection
to its NSMutableData object. Now the
trick is that you can't make an
NSURLConnection be the key in a
dictionary because it doesn't conform
to NSCopying (and you wouldn't want it
to). One way to work around this is to
use the address of the connection such
as:
NSString *key = [NSString
stringWithFormat:#"%p", connection];
A better way would be to use NSValue with the valueWithNonretainedObject constructor. That way you can access the key object from the NSDictionary if you have to.
NSURLConnection is asynchronous and init exits immediately. Just run it multiple times.
NSArray *connections = [[NSArray alloc] initWithObjects:
[[NSURLConnection alloc] initWithRequest:request1 delegate:self ],
[[NSURLConnection alloc] initWithRequest:request2 delegate:self ],
[[NSURLConnection alloc] initWithRequest:request3 delegate:self ],
[[NSURLConnection alloc] initWithRequest:request4 delegate:self ],
nil];