NSURLConnection and NSXMLParser for multiple objects?

Discussion in 'iOS Programming' started by MACloop, Mar 2, 2010.

  1. MACloop macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #1
    Hi,
    I have tried to implement a NSURLConnection and NSXMLParser where multiple files will be used. I have found an approach where I subclasses the NSURLConnection and gives every connection an id. In the delegate methods do I look the connection id up in oder to keep the connetions apart. Now is the next step to implement the parser and I need different parsers aswell. Is it ok to use the same approach here? Give every parser an id and look in the delegate method which parser who is calling/using the method?

    I have doen all this in the AppDelegate in order to look the data up on program start - is that ok?

    I hope someone will ge me a hint on this?
    Thanks in advance!
    MACloop
     
  2. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #2
    Why are you giving them IDs? Why not simply keep track of the objects against the path? Use a NSDictionary or similar to map the path (as a NSString say) to the NSURLConnection...
     
  3. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #3
    Thanks for your answer! I did not use NSDictionary till now and have to look some into the documentation. One question:
    Why is your approach better(I am sure it is because you always come with very good advice!)? I found it very simple to override the NSURLConnection like I did, but perhaps there is a problem with doing that?

    MACloop
     
  4. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #4
    You are writing more code. Every line of code you write might have a bug in it. If you write less code less potential bugs!

    Subclassing will use fractionally more memory than not. Adding a field will use slightly more memory per object.

    If you are not using a dictionary how are you making use of your ID field that you've added right now? Could you, perhaps, post the code from a delegate method that shows how you are using it?
     
  5. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #5
    Of course, here is some code.I have build my code on the sample code from apple (SeismicXML) in order to try to understand the procedure and to have a proper beginning...


    Code:
    #pragma mark NSURLConnection delegate methods
    
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    	MultiURLConnection *conn = (MultiURLConnection *)connection;
    	if([conn.connectionID isEqualToString:@"theID"]) {
    		self.burgerdienstData = [NSMutableData data];
    	}
    	/*else if([conn.connectionID isEqualToString:@"SOMETHINGE ELSE"]) {
    		...
    	}*/
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    	MultiURLConnection *conn = (MultiURLConnection *)connection;
    	if([conn.connectionID isEqualToString:@"theID"]) {
    		[burgerdienstData appendData:data];
    	}
    	/*else if([conn.connectionID isEqualToString:@"SOMETHINGE ELSE"]) {
    		...
    	}*/
    }
    
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;   
        if ([error code] == kCFURLErrorNotConnectedToInternet) {
            // if we can identify the error, we can present a more precise message to the user.
            NSDictionary *userInfo = [NSDictionary dictionaryWithObject:NSLocalizedString(@"No Connection Error", @"Error message displayed when not connected to the Internet.") forKey:NSLocalizedDescriptionKey];
            NSError *noConnectionError = [NSError errorWithDomain:NSCocoaErrorDomain code:kCFURLErrorNotConnectedToInternet userInfo:userInfo];
            [self handleError:noConnectionError];
        } else {
            // otherwise handle the error generically
            [self handleError:error];
        }
        self.theFeedConnection = nil;
    }
    
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    	MultiURLConnection *conn = (MultiURLConnection *)connection;
    	
    	if([conn.connectionID isEqualToString:@"theID"]) {
    		self.theFeedConnection = nil;
    		[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;   
    		
    		[NSThread detachNewThreadSelector:@selector(parseData:) toTarget:self withObject:data];
    		
    		self.data = nil;
    	}
    	/*else if([conn.connectionID isEqualToString:@"SOMETHING ELSE"]) {
    		...
    	}*/
        
    }
    
    
    the subclass for NSURLConnection:
    Code:
    #import "MultiURLConnection.h"
    @implementation MultiURLConnection
    @synthesize connectionID;
    
    - (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate connectionID:(NSString*)connectionID {
    	self = [super initWithRequest:request delegate:delegate];
    	
    	if (self) {
    	 self.connectionID = connectionID;
    	 }
    	return self;
    }
    
    - (void)dealloc {
    	[connectionID release];
    	[super dealloc];
    }
    
    @end
    
     
  6. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #6
    So you are hand-coding "magic values" for each ID you expect to see? That seems like a very bad design decision to me! Personally I'd look at using an NSDictionary that mapped the NSURLConnection to the NSData. Or if you need to track more stuff (callbacks to the original caller when the data is loaded?) multiple NSDictionaries or one with a custom value that stores all the data you are interested in...
     
  7. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #7
    You are absolutely right! The magic values are bad design! I think you have convinced me and I will try your approach. Can I use the same pattern on the parser? I intend to define all this in the AppDelegate - what do you say about that?

    MACloop
     
  8. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #8
    Yes you can do the same with the parser. Personally I'd encapsulate all the getting and parsing an separate object but that's up to you.
     
  9. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #9
    Thanks alot for you help! I will try to implement it now! :)
    MACloop
     
  10. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #10
    hmmm... I have read abit about NSDictionary and I am not sure how to implement it in combination with the NSURLConnection... Every connection will have a key, that is understood. Those keys are the url-path as a string. So in every delegate method do I have to look which key the current key is in oder to save the current data into the correct NSMutableData object...?

    So, I guess I have to start with creating a Dictionary with those connetions (it is only four by now). The keys could be defines as contants reused later on in the code. Then I go on and start each and everyone of those connections and the delegate methods will be called at the same time...is it possible...? When the connection is done the data is saved into 4 different NSMutableData which all may e parsed in the same way, differentiated by a key....?

    Am I right about the flow here? Is this the way to structure it? It seems abit like magic to me...

    I hope you can give me some hints - because I think this model is really interesting and I now understand that it certanly is important to use NSDictionary in order to make the code reliable and clean!

    Thanks in advance!
    MACloop
     
  11. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #11
    So, this is my beginning :)
    It works pretty good actually - but I did not yet figure out how to select the proper connection object in the delegate methods... Do I have to define NSDictionary as global in order to reach the key value in the delegate method and compare it to the constant value? Perhaps I did not understand all the goodies with the dictionary...? The connection self has no key and I cannot call something like connection.key to differentiate the connections. I paste the delegate code below the dictionary definition in order to make it easier to follow my thoughts.
    Any ideas?
    Thanks in advance!
    MACloop

    Code:
    	#pragma mark NSDictionary constants
    	static NSString * const kTheURL = @"http://theURL";
    	static NSString * const kAURL = @"http://aURL";
    	static NSString * const kAnotherURL = @"http://AnotherURL";
    	
    	//creating dictionary and save NSURLConnection objects with proper keys for identification
    	NSArray *keys = [NSArray arrayWithObjects:kTheURL, kAURL, kAnotherURL, nil];
    	NSArray *objects = [NSArray arrayWithObjects:the, a, another nil]; //where and how to define those?
    	NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
    	
    	
    	for (id key in dictionary) {
    		NSURLRequest *theURLRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:key]];
    		self.feedConnection = [[[NSURLConnection alloc] initWithRequest:theURLRequest delegate:self] autorelease];
    	}
    
    Delegate example:
    Code:
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    	if(WHAT_TO_CALL_HERE??? isEqualToString:ktheURL])
    	self.Data = [NSMutableData data];
    }
     
  12. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #12
    Create the NSDictionary as a property of whatever object this is all happening in. Personally I'd do something like this (sort of: in real life I'd probably create a custom object type to store the data, target and callback selector in and have one connectionToXXX dictionary).

    Code:
    @interface DataAccess : NSObject
    {
    NSMutableDictionary *urlToConnection;
    NSMutableDictionary *connectionToData;
    NSMutableDictionary *connectionToCallbackTarget;
    NSMutableDictionary *connectionToCallbackSelector;
    }
    
    @property (retain) NSMutableDictionary *urlToConnection;
    @property (retain) NSMutableDictionary *connectionToData;
    etc
    
    - (DataAccess *) sharedDataAccess;
    - (void) startLoadingURL:(NSURL *) url withCallbackTarget:(id) target andSelector:(SEL) selector;
    @end
    
    I'd have an implementation something like;
    Code:
    // Synthesize properties
    // Singlton pattern stuff to create the instance and return it when sharedDataAccess is called: this is in one of Apple's basic Cocoa/Objective-C pattern documents
    
    - (void) startLoadingURL:(NSURL *) url withCallbackTarget:(id) target andSelector:(SEL) selector
    {
    if ([urlToConnection objectForKey:URL] != nil)
    {
    // Rerequested to get a URL that is currently in-flight.  Depending on your app you either cancel that connection and remove it from all the dictionaries or you allow a more complex model where this target and selector get added to arrays in those dictionaries...
    }
    NSURLConnection *myConnection = // Create the connection
    // Store the connection keyed by URL in urlToConnection
    // Create an empty NSData object and store that keyed by connection in connectionToData (unless you have the above case and do not cancel, rather allow merging of the two requests...
    // Store the target and selector too...
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
    // Use the connection as the key to retrieve the data object from connectionToData and append the data
    }
    
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
    // Lookup the NSData object, target and selector using the connection as key.
    // Call [object selector:data] to send the data to the original caller.  If you allow merging of multiple requests for the same URL loop over all the callers...
    // Remove the values for the connection from the three connection keyed dictionaries
    // Remove the connection/URL from the url->connection dictionary
    }
    
    This is a very basic outline: you probably want to alter this somewhat. For example create a custom object to track the data you require in one connection keyed dictionary. Also instead of target/selector you'd be better with a protocol so you could tell the called if the connection failed/redirected etc.
     
  13. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #13
    Thank you very very very much!!! :)
    This is a great exmplanation! I will have lunch break now and give it a try after that! I really appreciate you helping me!
    MACloop
     
  14. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #14
    Hello again,
    I have tried out to implement it like you wrote but have not yet been sucessful. The app is crashing on startup. I do not get any error message but I assume that I sis not really understand and correctly implement you advice. Here is what I have done so far - do you have any comments on that/hints?
    Thanks in advance!
    MACloop

    In the AppDelegate.h
    Code:
    NSMutableDictionary *urlToConnection;
    NSMutableDictionary *connectionToData;
    NSMutableDictionary *connectionToCallbackTarget;
    NSMutableDictionary *connectionToCallbackSelector;
    NSMutableData *theData;
    id target;
    SEL selector;
    
    @property (retain) NSMutableDictionary *urlToConnection;
    @property (retain) NSMutableDictionary *connectionToData;
    @property (retain) NSMutableDictionary *connectionToCallbackTarget;
    @property (retain) NSMutableDictionary *connectionToCallbackSelector;
    @property (retain) NSMutableData *theData;
    @property (assign) id target;
    @property SEL selector;
    
    - (AppDelegate *) sharedDataAccess;[COLOR="Red"] //I am not sure how to use this[/COLOR]
    - (void) startLoadingURL:(NSURL *) url withCallbackTarget:(id) target andSelector:(SEL) selector;
    
    In the AppDelegate.m:
    Code:
    // Synthesize properties
    @synthesize urlToConnection,connectionToData,connectionToCallbackTarget,connectionToCallbackSelector,theData,target,selector;
    // Singlton pattern stuff to create the instance and return it when sharedDataAccess is called: this is in one of Apple's basic Cocoa/Objective-C pattern documents
    [COLOR="Red"]//I know what singleton is, bur I am not sure what you mean here...[/COLOR]
    
    ...
    
    //in the applicationDidFinish method:
    [self startLoadingURL:[NSURL URLWithString:kURL] withCallbackTarget:self andSelector:@selector(parseData:)];
    
    - (void) startLoadingURL:(NSURL *) url withCallbackTarget:(id) t andSelector:(SEL) s{
    	if ([urlToConnection objectForKey:url] != nil){
    [COLOR="Red"]//I do not think that I get this...[/COLOR]
    		// Rerequested to get a URL that is currently in-flight.  
    		//Depending on your app you either cancel that connection and remove it from all the dictionaries
    		//OR you allow a more complex model where this target and selector get added to arrays in those dictionaries...
    	}
    	NSURLRequest *theRequest = [NSURLRequest requestWithURL:url];
    	NSURLConnection *myConnection = [[NSURLConnection alloc]initWithRequest:theRequest delegate:self]; // Create the connection
    	[theRequest release];
    	
    	// Store the connection keyed by URL in urlToConnection
    	[urlToConnection setObject:myConnection forKey:url];
    	
    	// Create an empty NSData object and store that keyed by connection in 
    	//connectionToData (unless you have the above case and do not cancel, rather allow merging of the two requests...
    	NSData *data = nil;
    	[connectionToData setObject:data forKey:myConnection];
    	
    	// Store the target 
    	[connectionToCallbackTarget setObject:t forKey:myConnection];
    	
    	//and selector too...
    [COLOR="Red"]//I get a warning here:passing argument 1 of setObject:forKey from incompatible pointer type[/COLOR]
    	[connectionToCallbackSelector setObject:s forKey:myConnection];
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSMutableData *)data{
    	// Use the connection as the key to retrieve the data object from connectionToData 
    	theData = [connectionToData objectForKey:connection];
    	
    	//and append the data
    	[theData appendData:data];
    }
    
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
    	//Lookup the NSData object, target and selector using the connection as key.
    	NSMutableData *d = [connectionToData objectForKey:connection];
    	
    	target = [connectionToCallbackTarget objectForKey:connection];
    
    [COLOR="Red"]//Getting incompatible pointer typer here too[/COLOR]
    	selector = [connectionToCallbackSelector objectForKey:connection];
    	
    	[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;   
    
    	[NSThread detachNewThreadSelector:@selector(selector) toTarget:target withObject:d];
    	
    	[COLOR="Red"]//I do not understand this...[/COLOR]
    	//Call [object selector:data] to send the data to the original caller.  
    	//If you allow merging of multiple requests for the same URL loop over all the callers...
    	
    	// Remove the values for the connection from the three connection keyed dictionaries
    	[connectionToData removeObjectForKey:connection];
    	[connectionToCallbackTarget removeObjectForKey:connection];
    	[connectionToCallbackSelector removeObjectForKey:connection];
    	
    	// Remove the connection/URL from the url->connection dictionary
    	[urlToConnection removeObjectForKey:connection];
    }
    
    ...	
    
    
     
  15. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #15
    Ups...the crash was caused by a totally different error. The questions above are still the same... but there is no crash anymore.
    MACloop
     
  16. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #16
    I always assume you have read and understood all the core documentation. In the case of the singleton object it is covered in iPhone Cocoa Core Competencies. You should know all of this. In this case they link to the excellent Cocoa Fundamentals Guide. Again you should have read, understood and remembered (the existence of, if not the detail) of the contents of that document.

    Regarding the invalid pointers when working with the selectors: do what I suggested would be better. Declare a protocol that the calling object implements. Then when you have to do something you can be sure that the object implements that method and call it as normal. This also gets your round not understanding "//Call [object selector:data] to send the data to the original caller." (hint: look at performSelector:withObject:)
     
  17. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #17
    Thanks again :)
    I will read the document. I was searching for something to read, but there is so much and sometimes I think it is really hard to know which one is the one to look into. I will look at the protocoll aswell! Sorry for asking so much - but I really want to learn about this but I get a bit confused sometimes.... I am really glad that you are helping me!
    Thanks
    MACloop
     
  18. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #18
    Good Morning!
    So I spend the evening yesterday trying to understand what I have missed on all this. My approach now is the following:
    - I have created a class DataAccess which is a singleton class. This class is responsible to the URLConnection and for now(I indend to make a seperate parser class) also parsing. To make this class singleton I have the folloging definition in the .h file:
    Code:
    + (DataAccess *) sharedDataAccess;
    and this in the .m file:
    Code:
    + (DataAccess *)sharedDataAccess  {
    	static DataAccess *sharedDataAccess;
    	@synchronized(self) {
    		if(!sharedDataAccess) {
    			sharedDataAccess = [[DataAccess alloc] init];
    		}
    	}
    	return sharedDataAccess;
    }
    
    The rest of this class is like the code parts above in this thread. The warnings about the selector are still there and I suppose this may be because I do not really understand this part... :-(

    From my AppDelegate I intend to fill some arrays to be used in the views of the app. So I have to call the function
    Code:
    - (void) startLoadingURL:(NSURL *) url withCallbackTarget:(id) t andSelector:(SEL) s{
    in order to do this.
    The DataAccess object has the class method sharedDataAccess and to call this I do not have create an instance of a DataAccess class. It may be called with
    Code:
    [DataAccess sharedDataAccess]; 
    BUT - the question here is: what is in this sharedDataAccess? As I can see it, the data has to be saved into this object, right?
    So, (if we skip the parser for now) this means I have to get the data into this variable in the method
    Code:
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    In this method I get the data, selector and the target for a specific connection. I suppose the selector here is the method to use when "doing something" with this data. The data is the data - but what about the target? That must be the reciever, as self if the parser happend to be defined in the same class, or am I wrong here?
    So, with
    Code:
    [self performSelector:(SEL)selector withObject:(id)data];
    a call for a specific selector for a specific connection should be called and the current data will be send into this method (selector). The target is the class it self in this case...when the parser is defined in the DataAccess class.

    Is this all wrong or is at least the thoughts about the structure of this right???

    My questions now are:
    - how to get rid of the errors regarding the selector. What on earth am I missing here? I think I understand the meaning with a selector but there must be something in the chain above that I am doing wrong.
    - how to call this DataAccess from my AppDelegate class? If I call
    Code:
    [DataAccess sharedDataAccess]; 
    this is returning an empty object, right? So Should I, in this method, call the startLoadingURL method or should I do this from outside (ie from my AppDelegate)?
    - How do I, in my DataAccess class fill the sharedDataAccess object with data to be used in for instance an array later on in my app?

    I am sorry for me asking so many questions, but I really want to get this right and I do really want to understand it. I have read about the issuses in one of my iPhone books (iPhone SDK3 Programming, Maher Ali) and that did not help me futher. I am sure that if I could understand the structure of this all, I could implement it properly.

    I am very very greatful if you would like to go on heling me!
    Thanks in advance!
    MACloop
     
  19. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #19
    The object returned by this

    Code:
    [DataAccess sharedDataAccess];
    
    is the same object no matter how many times you call this. As to what it is? It's just an object. Think of it in the same way as using:

    Code:
    [NSUserDefaults standardUserDefaults];
    
    You're use of performSelector is way off: you don't want to perform that on self: that will send the message to the shared instance of DataAccess. You want to call that on the object that made the request for data.

    As to getting rid of the warnings about selectors: declare a protocol for the objects that will request data, ensure that they all implement that protocol and use it.
     
  20. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #20
    Ok, thanks alot - again :)
    I'll give it a try with the protocol and your advice about performSelector. But the rest of my structure and plan is ok?

    Thanks alot!
    MACloop
     
  21. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #21
    To be honest I'm not sure. It all seems very confused. Largely I am expecting to see something like this (as a minimum: if it was me I'd have more delegate methods):

    Code:
    @interface MyAppDelegate <UIApplicationDelegate, DataAccessDelegate>
    {
    }
    - (void) data:(NSData *) wasLoadedForURL:(NSURL *) url; // This is just a suggested name for the delegate callback
    @end
    
    @implementation MyAppDelegate 
    - (void)applicationDidFinishLaunching:(UIApplication *)application
    {
    // Other stuff
    [[DataAccess sharedDataAccess] startLoadingURL:myURL withDelegate:self];
    }
    
    - (void) data:(NSData *) wasLoadedForURL:(NSURL *) url
    {
    // If we requested more than one URL check which URL it is
    // Then call whatever is required to parse/understand the data
    }
    @end
    
     
  22. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #22
    well, confused is exactly how I feel - but as I mentioned - I intend to learn it and thats is the reason why I have all those questions. I am sure there will be more ;-) Thanks for the advice of structure for the AppDelegate!
    MACloop
     
  23. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #23
    hmmm... why is the method startLoadingURL not called with selector and target any more?

    I have defined the protocol DataAccessDelegate in the same file as the class DataAccess and I use both of those in the AppDelegate. In the class DataAccess did I define the delegate like
    Code:
    @property (assign)id<DataAccessDelegate> delegate;
    I have never created a protocol like this before and may have done something wrong here.

    I read the file here

    I call the DataAccess from the AppDelegate like:
    Code:
    [[DataAccess sharedDataAccess] startLoadingURL:[NSURL URLWithString:@"http://www......"] withDelegate:self];
    and it does not work, because this method definition does not excist. Should I set this method to required in the protocol and override it in the class using the delegate? I suppose not, because I would like to use the connection-stuff in the DataAccess...

    Any advices?
    MACloop
     
  24. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #24
    I expect you to be reading this and making changes based on what you are doing: the creation of the delegate was, as I said multiple times, to allow the removal of the selector and target.
     
  25. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #25
    Ok, I read the document again and try it out.
    Thanks alot for you help!
    MACloop
     

Share This Page