I'm just getting started with Objective C and Restkit
I created a sample application and added the RKRequestDelegate in MyAppDelegate file
#interface MyAppDelegate : NSObject <UIApplicationDelegate, RKRequestDelegate> {…
and added
RKClient* client = [RKClient clientWithBaseURL:#"http://localhost:3000"];
NSLog(#"I am your RKClient singleton : %#", [RKClient sharedClient]);
[client get:#"/titles.json" delegate:self];
to MyAppDelegate.m in the
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method
I also added a method to MyAppDelegate.m
- (void) request: (RKRequest *) request didLoadResponse: (RKResponse *) response {
if ([request isGET]) {
NSLog (#"Retrieved : %#", [response bodyAsString]);
}
}
so far so good everything is working and I see the results from my Rails app in the output!!!
As those things don't belong into MyAppDelegate.m I'm moving that stuff into my models. In my Titles.h I added
#interface Titles : NSManagedObject <RKRequestDelegate> {
and in Titles.m I added
+ (void) update {
[[RKClient sharedClient] get:#"/titles.json" delegate:self];
}
and
- (void) request: (RKRequest *) request didLoadResponse: (RKResponse *) response {
if ([request isGET]) {
NSLog (#"Retrieved : %#", [response bodyAsString]);
}
}
In my MyAppDelegate.m I replaced :
RKClient* client = [RKClient clientWithBaseURL:#"http://localhost:3000"];
NSLog(#"I am your RKClient singleton : %#", [RKClient sharedClient]);
[client get:#"/titles.json" delegate:self];
with
RKClient* client = [RKClient clientWithBaseURL:#"http://localhost:3000"];
NSLog(#"I am your RKClient singleton : %#", [RKClient sharedClient]);
[Titles update];
when I run now I don't get any output.
I put several breakpoints, one in the - (void)didFinishLoad:(RKResponse*)response in the RKRequest file
and there the if test for :
if ([_delegate respondsToSelector:#selector(request:didLoadResponse:)]) {
[_delegate request:self didLoadResponse:finalResponse];
}
fails while it succeeds in my first attempt (when everything is in MyAppDelegate)
I checked the variable _delate in de debugger and it says: _delegate = MyAppDelegate in my first attempt and _delegate = Titles in my second attempt (both like it should)
Why does that respondsToSelector fail ? (the delegate is correct and the method exists in Titles)
Your problem is that your trying to set a class as the delegate:
+ (void) update {
[[RKClient sharedClient] get:#"/titles.json" delegate:self];
}
self here is the class Titles.
The callback is (as expected), an instance method:
- (void) request: (RKRequest *) request didLoadResponse: (RKResponse *) response {
if ([request isGET]) {
NSLog (#"Retrieved : %#", [response bodyAsString]);
}
}
You should have some kind of "DataModel" model class (perhaps "SongList" or whatever makes sense). This is often a singleton, so you have a +sharedModel instance. That instance is what is the delegate for RKClient.
Related
Hi I am using AFNetworking in my IOS app and I am unable to maintain session. I am using an AFNetworking client and using it in all requests to the server.
I have gone through the following questions: How to manage sessions with AFNetworking , but I don't plan to manipulate the cookies or the session. I intend to implement a session throughout the life cycle of the app.
My AFNetworking client's .m is as follows
#implementation MyApiClient
+(MyApiClient *)sharedClient {
static MyApiClient *_sharedClient = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedClient = [[MyApiClient alloc] initWithBaseURL:[GlobalParams sharedInstance].baseUrl];
});
return _sharedClient;
}
-(id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:#"text/html"]];
self.parameterEncoding = AFFormURLParameterEncoding;
return self;
}
#end
and I make the following requests to the server on the call of "- (void)searchBarSearchButtonClicked:(UISearchBar *)search" -->>
NSString *path = [NSString stringWithFormat:#"mobile"];
NSURLRequest *request = [[MyApiClient sharedClient] requestWithMethod:#"GET" path:path parameters:#{#"q":search.text}];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request,
NSHTTPURLResponse *response, id json) {
// code for successful return goes here
NSLog(#"search feed %#", json);
search_feed_json = [json objectForKey:#"searchFeed"];
if (search_feed_json.count > 0) {
//<#statements-if-true#>
show_feed_json = search_feed_json;
//reload table view to load the current non-empty activity feed into the table
[self.tableView reloadData];
} else {
//<#statements-if-false#>
NSLog(#"Response: %#", #"no feed");
}
} failure :^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
// code for failed request goes here
NSLog(#"nops : %#", error);
}];
[operation start];
Can anyone please guide me or point out where I am wrong ?
Any help would be appreciated.
Thanks in advance.
Edit ::
I found my answer in the following post : https://stackoverflow.com/a/14405805/1935921
I initialised the methods listed in the answer in my GlobalParams class, which contains all the Global parameters and methods.
I call the "saveCookies" method when the app does the login and the server sends the Cookies.
Then these cookies are loaded every time I make any subsequent request by using the method "loadCookies". The Code looks as follows :
GlobalParams.h
#import <Foundation/Foundation.h>
#interface GlobalParams : NSObject
// Your property settings for your variables go here
// here's one example:
#property (nonatomic, assign) NSURL *baseUrl;
// This is the method to access this Singleton class
+ (GlobalParams *)sharedInstance;
- (void)saveCookies;
- (void)loadCookies;
#end
GlobalParams.m
#import "GlobalParams.h"
#implementation GlobalParams
#synthesize myUserData,baseUrl;
+ (GlobalParams *)sharedInstance
{
// the instance of this class is stored here
static GlobalParams *myInstance = nil;
// check to see if an instance already exists
if (nil == dronnaInstance) {
myInstance = [[[self class] alloc] init];
myInstance.baseUrl = [NSURL URLWithString:#"http://www.test.dronna.com/"];
}
// return the instance of this class
return myInstance;
}
- (void)saveCookies{
NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject: [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject: cookiesData forKey: #"sessionCookies"];
[defaults synchronize];
}
- (void)loadCookies{
NSArray *cookies = [NSKeyedUnarchiver unarchiveObjectWithData: [[NSUserDefaults standardUserDefaults] objectForKey: #"sessionCookies"]];
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in cookies){
[cookieStorage setCookie: cookie];
}
}
#end
then I call " [[GlobalParams sharedInstance] saveCookies]; " or " [[GlobalParams sharedInstance] loadCookies]; "(depending on read/write required in cookies) in all my server calls after defining the NSMutableRequest.
I hope this helps somebody.
I found my answer in the following post : https://stackoverflow.com/a/14405805/1935921
I initialised the methods listed in the answer in my GlobalParams class, which contains all the Global parameters and methods. I call the "saveCookies" method when the app does the login and the server sends the Cookies. Then these cookies are loaded every time I make any subsequent request by using the method "loadCookies". The Code looks as follows :
GlobalParams.h
#import <Foundation/Foundation.h>
#interface GlobalParams : NSObject
// Your property settings for your variables go here
// here's one example:
#property (nonatomic, assign) NSURL *baseUrl;
// This is the method to access this Singleton class
+ (GlobalParams *)sharedInstance;
//saving cookies
- (void)saveCookies;
//loading cookies
- (void)loadCookies;
#end
GlobalParams.m
#import "GlobalParams.h"
#implementation GlobalParams
#synthesize myUserData,baseUrl;
+ (GlobalParams *)sharedInstance
{
// the instance of this class is stored here
static GlobalParams *myInstance = nil;
// check to see if an instance already exists
if (nil == dronnaInstance) {
myInstance = [[[self class] alloc] init];
myInstance.baseUrl = [NSURL URLWithString:#"http://www.test.dronna.com/"];
}
// return the instance of this class
return myInstance;
}
- (void)saveCookies{
NSData *cookiesData = [NSKeyedArchiver archivedDataWithRootObject: [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject: cookiesData forKey: #"sessionCookies"];
[defaults synchronize];
}
- (void)loadCookies{
NSArray *cookies = [NSKeyedUnarchiver unarchiveObjectWithData: [[NSUserDefaults standardUserDefaults] objectForKey: #"sessionCookies"]];
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in cookies){
[cookieStorage setCookie: cookie];
}
}
I have a ViewController declared as:
#interface DownloadViewController : UIViewController
<UITableViewDataSource, UITableViewDelegate>
and I want to use NSURLConnection to download files. NSURLConnection simply "doesn't start", the delegate methods don't work (for example connection:didReceiveResponse is never called) . I noticed in some sample code that the class was subclassing NSObject instead of UIViewController.
How do I combine it? I want to use ViewController methods but then I can't use NSURLConnection.
It's not so easy to find a fully explained example how to download file with NSURLConnection. Everyone only concentrates on the easy methods like didReceiveResponse.
Using a UIViewController instead of an NSObject should not be your problem here !
I'm using a NSURLConnection in an UIViewController with no issue !
Here is a part of my code (not sure it will compile as it is) :
//
// MyViewController.h
//
#import <Foundation/Foundation.h>
#interface MyViewController : UIViewController {
#protected
NSMutableURLRequest* req;
NSMutableData* _responseData;
NSURLConnection* nzbConnection;
}
- (void)loadFileAtURL:(NSURL *)url;
#end
-
//
// MyViewController.m
//
#import "MyViewController.h"
#implementation MyViewController
- (void)loadView {
// create your view here
}
- (void) dealloc {
[_responseData release];
[super dealloc];
}
#pragma mark -
- (void)loadFileAtURL:(NSURL *)url {
// allocate data buffer
_responseData = [[NSMutableData alloc] init];
// create URLRequest
req = [[NSMutableURLRequest alloc] init];
[req setURL:_urlToHandle];
nzbConnection = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];
[req release];
req = nil;
}
#pragma mark -
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Append data in the reception buffer
if (connection == nzbConnection)
[_responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (connection == nzbConnection) {
[nzbConnection release];
nzbConnection = nil;
// Print received data
NSLog(#"%#",_responseData);
[_responseData release];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// Something went wrong ...
if (connection == nzbConnection) {
[nzbConnection release];
[_responseData release];
}
}
#end
If you plan to download large files, consider storing the received packets in a file instead of storing it in memory !
If you're having problems, you could consider using the well regarded ASIHTTPRequest library to manage your download. It takes care of everything for you.
For example, just 2 lines will do it.
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDownloadDestinationPath:fullPathOfWhereToStoreFile];
Use "NSURLConnection asynchronously" search for the term and you'll find source. Or just NSURLConnection.
For example:
NSURLConnection NSURLRequest proxy for asynchronous web service calls
Using NSURLConnection from apple with example code
Objective-C Programming Tutorial – Creating A Twitter Client Part 1
I'm in the process of trying to move code from a UITableViewController class to a "helper" class.
The code utilizes NSURLConnection to grab and parse JSON and then populate an NSMutableArray.
What I'd like to do is call a method in my helper class that returns a NSMutableArray. What I don't understand is how to return the array from the connectionDidFinishLoading delegate class of NSURLConnection (where the array is actually built) as though it was from the originally called method that started the connection. In other words, how does the method that calls NSURLConnection get control back so it can return a value from the whole operation?
Here are the relevant methods from the helper class. How do I get the getMovies method to return the listOfMovies that is built in the connectionDidFinishLoading delegate class?
-(NSMutableArray)getMovies:(NSURL*)url {
responseData = [[NSMutableData data] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//NSURLRequest* request = [NSURLRequest requestWithURL: url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval: 30.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
//TODO error handling for connection
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//---initialize the array---
listOfMovies = [[NSMutableArray alloc] init];
tmdbMovies = [[NSArray alloc] init];
posters = [[NSArray alloc] init];
thumbs = [[NSDictionary alloc] init];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
SBJsonParser *json = [[SBJsonParser new] autorelease];
tmdbMovies = [json objectWithString:responseString];
// loop through all the top level elements in JSON
for (id movie in tmdbMovies) {
// 0 - Name
// 1 - Meta
// 2 - Url
if ((NSNull *)[movie objectForKey:#"name"] != [NSNull null]) {
if (![[movie objectForKey:#"name"] isEqualToString:#""]) {
name = [movie objectForKey:#"name"];
}
}
if ((NSNull *)[movie objectForKey:#"info"] != [NSNull null]) {
if (![[movie objectForKey:#"info"] isEqualToString:#""]) {
meta = [movie objectForKey:#"info"];
}
}
if ((NSNull *)[movie objectForKey:#"thumb"] != [NSNull null]) {
if (![[movie objectForKey:#"thumb"] isEqualToString:#""]) {
thumbUrl = [movie objectForKey:#"thumb"];
}
}
NSLog(#"Name: %#", name);
NSLog(#"Info: %#", meta);
NSLog(#"Thumb: %#", thumbUrl);
NSMutableArray *movieData = [[NSMutableArray alloc] initWithObjects:name,meta,thumbUrl,nil];
// add movieData array to listOfJMovies array
[listOfMovies addObject:movieData];
[movieData release];
}
//FIXME: Connection warning
if (connection!=nil) {
[connection release];
}
[responseData release];
[responseString release];
}
What you really need to do here is create a #protocol that creates a delegate for your helper class. Then change -(NSMutableArray)getMovies:(NSURL*)url to -(void)getMovies:(NSURL*)url
The class that is calling your helper method needs to implement your helper method's delegate.
Then - (void)connectionDidFinishLoading:(NSURLConnection *)connection calls the delegate method(s). It's best to have a one for success and one for failure.
=Update Begin=
You will need to also define an id delegate in your helper file which the calling class sets to self after init but before calling -(void)getMovies:(NSURL*)url. That way the helper file knows where to call back to.
getMovies *movieListCall = [[getMovies alloc] init];
movieListCall.delegate = self;
[movieListCall getMovies:<your NSURL goes here>];
You will see some additional lines for the inclusion of a delegate in both the getMovies.h and getMovies.m files.
=Update End=
in your getMovies.h file add:
#protocol getMoviesDelegate
#required
- (void)getMoviesSucceeded:(NSMutableArray *)movieArray;
- (void)getMoviesFailed:(NSString *)failedMessage;
#end
#interface getMovies : NSOBject {
id delegate;
}
#property (nonatomic, assign) id delegate;
in your getMovies.m file add:
#synthesize delegate;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
//TODO error handling for connection
if ([delegate respondsToSelector:#selector(getMoviesFailed:)]) {
[delegate getMoviesFailed:[error localizedDescription]];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//finishes with
if ([delegate respondsToSelector:#selector(getMoviesSucceeded:)]) {
[delegate getMoviesSucceeded:listOfMovies];
}
}
update your calling class .h file to use getMoviesDelegate:
#interface MoviesView : UIViewController <getMoviesDelegate>{
.
.
.
}
add the getMoviesDelegate methods to your calling class .m file
- (void)getMoviesSucceeded:(NSMutableArray *)movieArray {
//deal with movieArray here
}
- (void)getMoviesFailed:(NSString *)failedMessage {
//deal with failure here
}
This is not tested but hopefully gives you a road map to work with.
Protocols are nice because you can make both required and optional delegate methods and it helps in refining your helper methods to become very reusable across projects. The compiler will also warn you if you have implemented a protocol but not implemented the protocol's required delegate methods. If you follow this path be sure to use conformsToProtocol: and respondsToSelector:
Fundamentally, what's happening is that you're starting an asynchronous network load (asynchronous is the right way to do this, almost assuredly), and then you need some way to resume whatever operation you were doing before the load began. You have a few options:
Create your own delegate protocol. Your UITableViewController would then set itself as the helper's delegate, and the helper would call helperDidLoad or whatever you named that method. There's more information on writing delegates in the Cocoa Programming Guide.
Use blocks and continuation passing style. This is a bit more advanced but I like it. In your UITableViewController you'd write something like this:
[helper doSomething:^ (id loaded) {
[modelObject refresh:loaded]; // or whatever you need to do
}];
And then in your helper you'd write:
- (void)doSomething:(void ^ (id))continuation {
_continuation = continuation;
//kick off network load
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
_continuation(_data);
}
Use notifications. Read the NSNotificationCenter docs.
Use KVO. The KVO programming guide has a lot of good info on Key-Value Observing.
How to i get the getMovies method to return the listOfMovies that is built in the connectionDidFinishLoading delegate class?
I'm going to argue that you should not do that.
Network requests should be made asynchronously. If your getMovies were to make a synchronous request and return only when it had data you would block that entire thread while you waiting for a network connection to finish. This is a bad idea in general and a terrible idea if your main thread is calling getMovies. Blocking the main thread will prevent you from responding to touches or updating the UI, your app will appear frozen, and the OS will terminate it if your users don't quit in frustration first.
Instead have the helper class notify the caller when data is available (or when it failed to retrieve data) through a delegate call back, notification, KVO, or whatever mechanism you prefer.
Here are the steps, pseudocode like style:
[helperInstance setDelegate:self]; // where self is your UITableViewController class
in your helper class, in the connectionDidFinishLoading do something like this:
[delegate finishedLoadingData:JSONData];
Also you can define a protocol for your delegate, and the declare the delegate like this in your helper class:
#property (nonatomic, assign) id<YourProtocol> delegate;
Hope this helps,
Moszi
I've read posts about this, and it seems pretty straight-forward. I'm pretty new to Obj-C and iPhone dev in general, so I could easily be overlooking something. I can't seem to return the NSMutableArray with the Article objects. I don't get any errors, but when I try to NSLog() some stuff I'm getting EXEC_BAD_ACCESS errors (I'm assuming a memory access issue?). I have an ArticlesParser class that does the parsing... Here's what it looks like:
// ArticlesParser.h
#import <Foundation/Foundation.h>
#import "Article.h"
#class Article;
#interface ArticlesParser : NSObject <NSXMLParserDelegate> {
NSMutableString *currentCharaters;
Article *currentArticle;
NSMutableArray *articlesCollection;
NSMutableData *xmlData;
NSURLConnection *connectionInProgress;
BOOL connectionHasCompleted;
}
#property (nonatomic, assign) BOOL connectionHasCompleted;
- (void)parseUrl:(NSString *)url;
- (void)beginParsing:(NSURL *)xmlUrl;
- (NSMutableArray *)arrayOfArticles;
#end
Here's the implementation...
// ArticlesParser.m
#import "ArticlesParser.h"
#implementation ArticlesParser
#synthesize connectionHasCompleted;
#pragma mark -
#pragma mark Parsing methods
- (void)parseUrl:(NSString *)url
{
[self setConnectionHasCompleted:NO];
NSURL *xmlUrl = [NSURL URLWithString:url];
[self beginParsing:xmlUrl];
}
- (void)beginParsing:(NSURL *)xmlUrl
{
[articlesCollection removeAllObjects];
articlesCollection = [[NSMutableArray alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:xmlUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
// clear existing connection if there is one
if (connectionInProgress) {
[connectionInProgress cancel];
[connectionInProgress release];
}
[xmlData release];
xmlData = [[NSMutableData alloc] init];
// asynchronous connection
connectionInProgress = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
}
- (NSMutableArray *)arrayOfArticles
{
// NOT RETURNING ANYTHING
return articlesCollection;
}
#pragma mark -
#pragma mark NSXMLParserDelegate methods
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[xmlData appendData:data];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqual:#"article"]) {
currentArticle = [[Article alloc] init];
return;
}
if ([elementName isEqual:#"title"]) {
currentCharaters = [[NSMutableString alloc] init];
return;
}
if ([elementName isEqual:#"last_updated"]) {
currentCharaters = [[NSMutableString alloc] init];
return;
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
[currentCharaters appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqual:#"article"]) {
[articlesCollection addObject:currentArticle];
[currentArticle release], currentArticle = nil;
return;
}
if ([elementName isEqual:#"title"]) {
[currentArticle setTitle:currentCharaters];
[currentCharaters release], currentCharaters = nil;
return;
}
if ([elementName isEqual:#"last_updated"]) {
[currentArticle setLastModified:currentCharaters];
[currentCharaters release], currentCharaters = nil;
return;
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
[parser setDelegate:self];
[parser parse];
[parser release];
[self setConnectionHasCompleted:YES];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[currentArticle release];
currentArticle = nil;
[currentCharaters release];
currentCharaters = nil;
[articlesCollection release];
articlesCollection = nil;
[connectionInProgress release];
connectionInProgress = nil;
[xmlData release];
xmlData = nil;
NSLog(#"connection failed: %#", [error localizedDescription]);
}
#end
I know that the actual parsing works because I did have this directly in my view controller and everything worked fine. But now I want to access basically the same thing from another controller, only the URL is different (returns the same formatted XML though).
Here's how I'm trying to make use of this class in my controller:
// instance method called within an articles controller
// that is to load the results in a table view
- (void)loadArticles
{
// (leaving off the URL because it's not important)
NSString *urlToRequest = [NSString stringWithFormat:#"...", [self letterToList]];
ArticlesParser *aParser = [[ArticlesParser alloc] init];
// initiate the parsing
[aParser parseUrl:urlToRequest];
// load up the articles ivar so the tableview can
// make use of it to load its cells
articles = [aParser arrayOfArticles];
}
Is there something obvious that I'm missing? Is this even a good way to share the NSXMLParser code?
I'm pulling my hair out over this one... thanks in advance!
What is it you're trying to NSLog that generates the EXEC_BAD_ACCESS error? Looking at your code your call to arrayOfArticles should return an NSMutableArray with no elements, so e.g. something like this would understandably give an EXEC_BAD_ACCESS:
NSLog(#"%#", [[articles objectAtIndex:0] description]); // index out of bounds
By having your XML parser class also responsible for fetching the data it's going to parse (using NSURLConnection) you've made it asynchronous, which means it's no longer suitable to be used like this:
ArticlesParser *ap = [[[ArticlesParser alloc] init] autorelease];
[ap parseURL:#"http://example.com/foo"];
NSArray *anArray = [ap arrayOfArticles];
anArray is now an empty array, and will only be populated at some indeterminate point in the future, if at all - and you can't detect when that time comes without polling the array. Urgh! :)
There are a couple of ways you might get around this. One approach is to have your XML Parser class declare delegate methods, offering callbacks for when the XML data has been fully fetched and parsed and when error conditions occur (in much the same way that the delegate methods in NSURLConnection work). Another approach is to have your XML Parser class be a simple (synchronous) XML parser, and move the asynchronous data-fetching code to outside your class.
There are a few things I see a problem with off the top of my head.
First, you need to either copy or retain the return from arrayOfArticles if you're going to hang on to it and use it later.
articles = [[aParser arrayOfArticles] copy];
You then of course need to make sure you release it later whenever it's appropriate.
Second, as it's written loadArticles actually leaks the ArticleParser it creates so you need to call [aParser release] at the end of the method.
The fact that you have to release the parser, which created the array, is what makes it necessary to retain/copy the return value. As soon as the ArticlesParser is deallocated it'll release it's internal articlesCollection and deallocate it if that was the last reference. Since your arrayOfArticles method hands out that reference to others, they need to copy the array or retain the reference to keep it alive after the ArticlesParser that created it dies.
Finally, you're downloading the data asynchronously, but you're calling arrayOfArticles immediately after calling parseUrl:. This is never going to result in you getting anything useful because no data has been downloaded or parsed yet. You need your ArticlesParser to provide some way to notify interested parties when it is done parsing the downloaded data and THEN they can call arrayOfArticles to get the data.
EDIT:
One way to deal with the notification would be to create a delegate protocol, add a delegate property to ArticlesParser, have the controller set itself as the value of that property, and have the parser call the delegate's method when it's done.
For example:
// ArticlesParser.h
#import <Foundation/Foundation.h>
#import "Article.h"
#class Article;
#class ArticlesParser;
#protocol ArticlesParserDelegate <NSObject> {
- (void)parserDidFinish:(ArticlesParser*)parser;
- (void)parser:(ArticlesParser*)parser didFailWithError:(NSError*)error;
#end
#interface ArticlesParser : NSObject <NSXMLParserDelegate> {
id<ArticlesParserDelegate> delegate;
// ... the rest the same ...
}
#property (nonatomic, assign) id<ArticlesParserDelegate> delegate;
// ... the rest the same ...
#end
// ArticlesParser.m
// ... the same as you have, but with this stuff added ...
#synthesize delegate;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// ... same as you have, add this at end...
[delegate parserDidFinish:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// ... same as you have, add this at end...
[delegate parser:self didFailWithError:error];
}
I am trying to launch the App Store without launching Safari with all the redirects and I am getting an error about "Request for member 'iTunesURL' in something not a structure or union."
I am new to a lot of this so thank you for being patient with me. I think it has something to do with me calling "self.iTunesURL" since it doesn't think iTunesURL is a part of the current class, but I could be very wrong.
Thank you in advance for your help while I am (slowly) learning all of this.
SampleAppDelegate.h
-(void)launchStore:(NSURL *)iTunesURL;
-(void)connectionDidFinishLoading:(NSURLConnection *)connection;
SampleAppDelegate.m
// Process a LinkShare/TradeDoubler/DGM URL to something iPhone can handle
- (void)launchStore:(NSURL *)iTunesURL {
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:iTunesURL] delegate:self startImmediately:YES];
[conn release];
}
// Save the most recent URL in case multiple redirects occur
// "iTunesURL" is an NSURL property in your class declaration
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response {
self.iTunesURL = [response URL];
return request;
}
// No more redirects; use the last URL saved
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[[UIApplication sharedApplication] openURL:self.iTunesURL];
}
MyViewController.h
#import "SampleAppDelegate.h"
and i have NSURL *iTunesURL; within the #interface curley braces.
#property (nonatomic, retain) NSURL *iTunesURL;
- (IBAction) proButtonPressed: (id)sender; // press to launch App Store
MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
#synthesize iTunesURL;
- (IBAction) proButtonPressed: (id) sender {
NSURL *iTunesLink = [NSURL URLWithString:#"actual http URL goes here"];
SampleAppDelegate *appDelegate = (SampleAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate launchStore:iTunesLink];
}
iTunesURL is a property of the ViewController class and you can only use the self reference within the methods of that class. Importing the ViewController.h class doesn't give the SampleAppDelegate class the ability to call the properties of ViewController class unless it is a subclass of ViewController.
You need to create a new another property within SampleAppDelegate and assign the value of ViewController.iTunesURL to that property.