this is my first iPhone application and I'm using JSON framework to decode JSON sent from a server.
I insert the data in a NSMutableArray from an AppDelegate file.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
responseData = [[NSMutableData data] retain];
museums = [[NSMutableArray alloc] init];
viewController = [[RootViewController alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://my_json_link"]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
[window addSubview:navigationController.view];
[window makeKeyAndVisible];
return YES;
}
- (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 {
NSLog(#"Connection failed: %#", [error description]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSError *error;
SBJSON *json = [[SBJSON new] autorelease];
NSDictionary *data = (NSDictionary *)[json objectWithString:responseString error:&error];
[responseString release];
if (data == nil)
NSLog(#"JSON parsing failed: %#", [error localizedDescription]);
else {
for (NSDictionary *item in data) {
// Read the data
NSString *aName = [item objectForKey:#"name"];
NSString *aDescription = [item objectForKey:#"description"];
// Create a new museum object with the data from json
Museum *museum = [[Museum alloc] initWithName:aName description:aDescription];
// Add the museum object to the Array
[museums addObject:museum];
[museum release];
}
}
viewController.museums = museums;
}
The museums array is not empty inside connectionDidFinishLoading function, but I can't see it when I try to print it in RootViewController.
I tried to set the array in the line
viewController.museums = museums;
but I didn't understand what is wrong.
I can fix the problem only if I move these lines:
[window addSubview:viewController.view];
[window makeKeyAndVisible];
from the first function to connectionDidFinishLoading function. But in this case doesn't work the other view when I click one record of the table.
Thanks for any help.
viewController = [[RootViewController alloc] init];
First, I need you to make sure that the viewController you create in didFinishLaunching... is actually the correct viewController. Have you actually wired up that instance to be what you think it is or do you two instance os RootViewController?
Second if you are actually setting museums on the right instance of RootViewController you need to make sure that your timing is correct. This means that are you setting museums BEFORE you trying to print it out in viewController
--Edit--
OK since we established that things are happening in the wrong order you should try and reload the table. The UITableView has a method called reloadData that will take care of this for you and you need to call this everytime you change the data source after the table has been created.
So in RootViewController add a method called reload which in turn calls reloadData on your UITableView and modify your code:
viewController.museums = museums;
[viewController reload];
You could add to your view controller, temporarily just for debugging:
-(void) setMuseums:(NSMutableArray*)m {
self->_museums = [m retain];
}
and then add a breakpoint in there. Make sure it's getting hit, or maybe there's something later coming along and setting it to nil.
The Museums property is declared as #property (nonatomic, retain) NSMutableArray *museums; right?
Try to put a NSLog inside the for loop and check if it is executed.
try using
viewController.museums = [museums retain];
Related
My app is calling the rqst_run method below in didViewLoad method but I've an error. Debugger reports the following error:
[CFDictionary count]: message sent to deallocated instance
and debug marker is placed on this line (in tableView numberOfRowsInSection method below):
if([self.listing_items count] > 0)
I don't know where this variable get released
Declared in header file (interface section):
NSMutableString *rqst_error;
NSMutableData *rqst_data;
NSMutableDictionary *listing_items;
and I defined this method in implementation:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if([self.listing_items count] > 0)
{
if([self.listing_items objectForKey:#"items"])
{
return [[self.listing_items objectForKey:#"items"] count];
}
}
}
- (void)rqst_run
{
rqst_data = [[NSMutableData data] retain];
NSMutableURLRequest *http_request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.feedserver.com/request/"]];
[http_request setHTTPMethod:#"POST"];
NSString *post_data = [[NSString alloc] initwithFormat:#"param1=%#¶m2=%#¶m3=%#",rqst_param1,rqst_param2,rqst_param3];
[http_request setHTTPBody:[post_data dataUsingEncoding:NSUTF8StringEncoding]];
rqst_finished = NO;
[post_data release];
NSURLConnection *http_connection = [[NSURLConnection alloc] initWithRequest:http_request];
[http_request release];
if(http_connection)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
if([rqst_data length]>0)
{
NSString *rqst_data_str = [[NSString alloc] rqst_data encoding:NSUTF8StringEncoding];
SBJsonParser *json_parser = [[SBJsonParse alloc] init];
id feed = [json_parser objectWithString:rqst_data_str error:nil];
listing_items = (NSMutableDictionary *)feed;
[json_parser release];
[rqst_data_str release];
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Feed" message:#"No data returned" delegate:self cancemButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[alert show];
[alert release];
}
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Connection Problem" message:#"Connection to server failed" delegate:self cancemButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[alert show];
[alert release];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[rqst_data setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[rqst_data appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[rqst_data release];
[connection release];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[rqst_data release];
[connection release];
rqst_finished = YES;
}
NSMutableDictionary must be properly initialized before use. In your code you assign feed to listing_items and then release it. It haven't been retained so listing_items is also removed.
Try to init dictionary like this:
listing_items = [[NSMutableDictionary alloc] initWithDictionary:feed];
and all should work fine.
Instead of this
listing_items = (NSMutableDictionary *)feed;
Use this
self.listing_items = [NSMutableDictionary dictionaryWithDictionary:feed];
It's pretty clear that:
* You use the instance variable instead of the property to assign the value to listing_items
* You put an autoreleased value in this ivar.
listing_items = (NSMutableDictionary *)feed; is clearly your error because feed is an autorleased variable (and then will be deallocated at the end of the current runloop by definition.
Either declare a #property(retain) for listing_items and use it each time you want to assign (autoreleased) value to it (so that the property will manage the retain/release on assignation)
Or retain the stored value manually (but this is painful as you need not to forget to release the previous value before assigning a new one to listing_items each time... which is what the setter method does when called either directly or thru the property assignment)
i think you need to initialize your NSMutableDictionary. Right now its just a pointer pointing to feed. when feed gets released, it just points to nil.
in the viewDidLoad :
listing_items = [[NSMutableDictionary alloc] init];
or you need to retain the data:
listing_items = [(NSMutableDictionary *)feed retain];
SBJsonParser *json_parser = [[SBJsonParse alloc] init];
id feed = [json_parser objectWithString:rqst_data_str error:nil];
listing_items = (NSMutableDictionary *)feed;
[json_parser release];
When you release the json_parser, the dictionary it holds is released too.
So, as others said, you need to retain the dictionary obtained from json_parser.
I've tried everything I can think of and it still responds NO and Apple's doesn't have any hints.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"finished!");
NSString *tempString = [NSString stringWithFormat:#"file://%#%#", NSTemporaryDirectory(), [[NSProcessInfo processInfo] globallyUniqueString]];
NSURL *tempURL = [NSURL URLWithString:tempString];
[receivedData writeToURL:tempURL atomically:YES];
NSLog(#"tempURL is written!");
UIDocumentInteractionController *interactionController = [UIDocumentInteractionController interactionControllerWithURL:tempURL];
interactionController.delegate = self;
[interactionController retain];
NSLog(#"--- %i", [interactionController presentPreviewAnimated:YES]);
NSLog(#"presented preview");
[connection release];
[receivedData release];
}
- (UIViewController*)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller
{
NSLog(#"asdf");
UIViewController *viewController = [[[UIViewController alloc] init] autorelease];
[self.navigationController pushViewController:viewController animated:YES];
return viewController;
}
1) are you confident the document you're opening is kosher? If not, then returning NO would be the thing to expect
2) I'm puzzled by your delegate method. The present method is happy to do the pushing onto the navigation controller all by itself. Do you fair any better if you use this code instead? (See the docs for rationale.)
- (UIViewController *) documentInteractionControllerViewControllerForPreview: (UIDocumentInteractionController *) controller
{
return [self navigationController];
}
I had a similar issue with presentPreviewAnimated which returned NO. In my case I was missing an extension of the file. When I renamed my file from document to document.xlsx it was opened successfully.
I am trying to add Loading data option in viewDidLoad() function before [self.tableview reloaddata]. I am not sure how to add it and is there a way to make the user know that there is data getting loaded.
I am parsing JSON file and the data gets loaded on 3G pretty slow, so its the better way to allow user to know that data is being loaded with loading option. Here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
// Add the view controller's view to the window and display.
responseData = [[NSMutableData data] retain];
self.twitterArray = [NSMutableArray array];
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:#"http://search.twitter.com/search.json?q=mobtuts&rpp=5"]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
[super viewWillAppear:animated];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSDictionary *results = [responseString JSONValue];
self.twitterArray = [results objectForKey:#"results"];
[self.tableView reloadData]; // How to add loading view before this statement
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [self.twitterArray count];
}
I'm not quite sure what you mean by "loading view" but i guess you mean an activity indicator or something else what should be presented while you are loading data.
make an ivar UIActivityIndicatorView *myLoadingView;
Init it in viewDidLoad
myLoadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
myLoadingView.hidesWhenStopped = YES;
[myLoadingView stopAnimating];
[self.view addSubView:myLoadingView];
show the view before you start your connection [myLoadingView startAnimating];
hide it again when the download was finished by stoping it in the delegate method connectionDidFinishLoading: after [self.tableView reloadData]; [myLoadingView stopAnimating];
release it in viewDidUnload [myLoadingView release];
Feel free to ask if you have questions or if i had misunderstood you.
I need to load data from my API without waiting 20s each time I launch my application.
So I use:
NSURL *myUrlCourses = [[NSURL alloc] initWithString:url];
NSMutableURLRequest *request = [NSURLRequest requestWithURL: myUrlCourses];
NSURLConnection *connexion = [[NSURLConnection alloc] initWithRequest:request delegate:self];
for my 10 first request in while loop which permit to get data in background.
But, when I get data from this request with:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{}
The result don't match.
So, I think I have to use thread or something like that to get the right data for each request but I don't really know how!?
Could you help me to solve this problem?
Thanks
Store all of your connections in variables with describing names and then compare the pointer values.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if(connection == _theFacebookConnection)
{
//Handle Facebook code
}
else if(connection == _theTwitterConnection)
{
//Handle Twitter code
}
}
I use a CFMutableDictionaryRef to save another mutable dictionary for each connection. This inner dictionary can hold as much data as you want.
like this:
#interface Foo {
CFMutableDictionaryRef connections;
}
#implementation Foo
- (id)init {
self = [super init];
if (self) {
connections = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
return self;
}
- (BOOL)addURLRequest:(NSURLRequest *)request successSelector:(SEL)successSelector errorSelector:(SEL)errorSelector {
NSMutableDictionary *connectionInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSMutableData data], #"receivedData",
[NSValue valueWithPointer:successSelector], #"successSelector",
[NSValue valueWithPointer:errorSelector], #"errorSelector",
request, #"request",
nil];
NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO] autorelease];
CFDictionaryAddValue(connections, connection, connectionInfo);
[connection start];
return YES;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSMutableDictionary *connectionInfo = (NSMutableDictionary *)CFDictionaryGetValue(connections, connection);
[[connectionInfo objectForKey:#"receivedData"] appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSMutableDictionary *connectionInfo = (NSMutableDictionary *)CFDictionaryGetValue(connections, connection);
NSData *data = [connectionInfo objectForKey:#"receivedData"];
LogInfo(#"Finished Connection %#", connection);
SEL selector = [[connectionInfo objectForKey:#"successSelector"] pointerValue];
if ([self respondsToSelector:selector]) {
[self performSelector:selector withObject:data];
}
CFDictionaryRemoveValue(connections, connection);
}
I use ASIHTTPRequest for this sort of thing because I can use [request setUserInfo:(NSDictionary *)] to specify additional data that travels around with the request responses.
Then when I receive each response, I can look at that requests UserInfo dictionary and process the data accordingly.
What's nice about this is you can put as much or as little data into the UserInfo Dictionary as you require.
From my ApplicationDelegate, I'm doing an NSURLConnection fetch over the network (it's wrapped in a class, as you'll see below). This one seems to work correctly: I get all the data in didReceiveData and I get the completion call connectionDidFinishLoading. At the end of connectionDidFinishLoading, I instantiate one or more of a slightly different kind of wrapper class, but they're essentially the same thing. The problem is that the second NSURLConnection's delegate is never having it's methods called.
I've looked at many different answers, but all to no avail. I'm not spawning any new threads and all the [NSThread isMainThread] checks I've littered throughout the code return true.
I'm stumped. Can anyone help me out? Here's the relevant code:
App Delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
ConnectionWrapper* w = [[ConnectionWrapper alloc] initWithParams:self
url:[NSURL URLWithString:<url>]];
[w beginFetch];
return YES;
}
...
-(void)fetchCompleted:(NSURL*)url directory:(NSString*)directory
{
NSLog(#"fetch completed");
}
-(void)fetchFailed:(NSURL*)url
{
NSLog(#"fetch failed");
}
...
ConnectionWrapper:
-(id)initWithParams:(id<ConnectionWrapperDelegate>)d url:(NSURL*)url
{
delegate = d;
connURL = url;
return [self init];
}
-(void)beginFetch
{
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:connURL];
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[conn release];
[request release];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"append");
[responseData appendData:data];
}
- (void) connectionDidFinishLoading: (NSURLConnection*) connection
{
... parsing ....
DifferentConnectionWrapper* w = [[DifferentConnectionWrapper alloc] initWithParams:self
url:[NSURL URLWithString:<different url>]];
[w beginFetch];
}
-(void)fetchCompleted:(NSURL*)URL
{
NSLog(#"completed: %#", URL);
}
-(void)fetchFailed:(NSURL*)URL
{
NSLog(#"failed");
}
DifferentConnectionWrapper:
-(id)initWithParams:(id)d url:(NSURL*)url
{
delegate = d;
connURL = url;
return [self init];
}
-(void)beginFetch
{
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:connURL];
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[conn release];
[request release];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"append");
[responseData appendData:data];
}
- (void) connectionDidFinishLoading: (NSURLConnection*) connection
{
... parsing ....
DifferentConnectionWrapper* w = [[DifferentConnectionWrapper alloc] initWithParams:self
url:[NSURL URLWithString:<different url>]];
[w beginFetch];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"got response");
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"got data");
[responseData appendData:data];
}
- (void) connectionDidFinishLoading: (NSURLConnection*) connection
{
NSLog(#"image saver completed: %#", connURL);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"error");
}
ConnectionWrapper and DifferentConnectionWrapper have similar functions, but there's other logic that I've omitted here for brevity.
thanks for the help. I appreciate it.
A couple of things: I don't see a didFailWithError: in your first wrapper class, and (a little off topic) are you leaking memory with your DifferentConnectionWrapper *w ?
Anyway, what I would try is: see if you can invoke DifferentConnectionWrapper directly from the appDelegate instead of the ConnectionWrapper.
And I would try to decouple the two calls in any event. When the first one finishes, and calls appDelegate, can't you launch your DifferentConnectionWrapper from there?
I realize this doesn't explain your problem, but you might get it working (and which of THOSE two things is more important, is an entirely different debate.)
I believe the problem is that you’re releasing the URL connection in -beginFetch:
-(void)beginFetch
{
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:connURL];
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[conn release];
[request release];
}
The URL connection object should be kept alive and released when the connection has finished loading:
- (void) connectionDidFinishLoading: (NSURLConnection*) connection
{
... parsing ....
// *** Release the connection and whatever data you’ve kept related to
// this particular connection
[connection release];
[responseData release];
// *** or [responseData setLenght:0]; depending on how you’re
// managing responseData
DifferentConnectionWrapper* w = [[DifferentConnectionWrapper alloc] initWithParams:self
url:[NSURL URLWithString:<different url>]];
[w beginFetch];
}
or when there’s been an error:
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
// *** Release the connection and whatever data you’ve kept related to
// this particular connection
[connection release];
[responseData release];
// *** or [responseData setLenght:0]; depending on how you’re
// managing responseData
// inform the user
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
Edit: Your initialiser is a tad weird:
-(id)initWithParams:(id<ConnectionWrapperDelegate>)d url:(NSURL*)url
{
delegate = d;
connURL = url;
return [self init];
}
There’s no way to know what happens unless we see the code for -init and, at any rate, this should be the designated initialiser, so it shouldn’t be sending -init to self anyway. Furthermore, you should be retaining the url object that’s being passed to the initialiser.
The following makes more sense:
-(id)initWithParams:(id<ConnectionWrapperDelegate>)d url:(NSURL*)url
{
self = [super init];
if (self) {
delegate = d;
connURL = [url retain];
}
return self;
}
Don’t forget to release the url object in -dealloc or when you’re assigning another value to connURL.
OK. It turns out that this bug was caused by something I missed. I was going into a hard spin right after that second request, which would probably screw up just about anything. Once I fixed that problem, everything worked just fine.
Thanks for the help.