NSArray with multiple nested requests - iphone

I have an app that uses a segmentedControl. First item is an "All" item, where the rest is created from an array based on result from webservice. When "All" is selected I want to request all the request.
How can I go about this,
NSArray *urls = [NSArray arrayWithObjects:#"http://service/group/1/",
#"http://service/group/2/", nil];
I want to collect all result from the calls into a collection and display it in a UITableView when the "All" item is selected and probably in viewDidLoad.
For the other segments only one of the request is issued and callback with an array that then is used in:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
I have tried to look at this example for making the request from the array MultipleDownloads
Thanks,
The method in my viewController to initiate the multiple download:
- (void)requestChildrenInBackground {
queue = [[NSOperationQueue alloc] init];
//Todo remove hard coded and get from previous request respons
NSArray *urls = [NSArray arrayWithObjects: #"http://service/1/children",
#"http://service/2/children",
#"http://service/3/children", nil];
NSLog(#"%#", urls);
for (NSString * url in urls)
{
GetSchedule *operation =
[GetSchedule urlDownloaderWithUrlString:url];
[queue addOperation:operation];
}
}
This is how the multiple request gets handled:
#import "GetSchedule.h"
#import "JSON.h"
#import "Authentication.h"
#import "AttendanceReportViewController.h"
#interface GetSchedule ()
- (void)finish;
#end
#implementation GetSchedule
#synthesize appDelegate;
#synthesize username;
#synthesize password;
#synthesize authenticationString;
#synthesize encodedLoginData;
#synthesize schedulesArray;
#synthesize url = _url;
#synthesize statusCode = _statusCode;
#synthesize data = _data;
#synthesize error = _error;
#synthesize isExecuting = _isExecuting;
#synthesize isFinished = _isFinished;
+ (id)urlDownloaderWithUrlString:(NSString *)urlString {
NSURL * url = [NSURL URLWithString:urlString];
GetSchedule *operation = [[self alloc] initWithUrl:url];
return [operation autorelease];
}
- (id)initWithUrl:(NSURL *)url {
self = [super init];
if (self == nil)
return nil;
_url = [url copy];
_isExecuting = NO;
_isFinished = NO;
return self;
}
- (void)dealloc
{
[username release];
[password release];
[encodedLoginData release];
[_url release];
[_connection release];
[_data release];
[_error release];
[super dealloc];
}
- (BOOL)isConcurrent
{
return YES;
}
- (void)start
{
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(start) withObject:nil waitUntilDone:NO];
return;
}
self.username = appDelegate.username;
self.password = appDelegate.password;
Authentication *auth = [[Authentication alloc] init];
authenticationString = (NSMutableString*)[#"" stringByAppendingFormat:#"%#:%#", username, password];
self.encodedLoginData = [auth encodedAuthentication:authenticationString];
[auth release];
NSLog(#"operation for <%#> started.", _url);
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
// Setup up the request with the url
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:_url];
[request setHTTPMethod:#"GET"];
[request setValue:[NSString stringWithFormat:#"Basic %#", encodedLoginData] forHTTPHeaderField:#"Authorization"];
_connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if (_connection == nil)
[self finish];
else {
_data = [[NSMutableData alloc] init];
}
}
- (void)finish
{
NSLog(#"operation for <%#> finished. "
#"status code: %d, error: %#, data size: %u",
_url, _statusCode, _error, [_data length]);
[_connection release];
_connection = nil;
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
#pragma mark -
#pragma mark NSURLConnection delegate
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
//[_data release];
//_data = [[NSMutableData alloc] init];
[_data setLength:0];
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
_statusCode = [httpResponse statusCode];
}
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
{
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Parse the responseData of json objects retrieved from the service
SBJSON *parser = [[SBJSON alloc] init];
NSString *jsonString = [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding];
NSDictionary *jsonData = [parser objectWithString:jsonString error:nil];
NSMutableArray *array = [jsonData objectForKey:#"Children"];
schedulesArray = [NSMutableArray array];
[schedulesArray addObject:array];
// Callback to AttendanceReportViewController that the responseData finished loading
[attendanceReportViewController loadSchedule];
[self finish];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
_error = [error copy];
[self finish];
}
#end
When all data is received I want to call back to my ViewController and get an array with all data from all request made.

Create a downloader
Create a downloader class using NSURLConnection.
Keep a member variable called downloaderObjectId.
Write a delegate method for this object. This method will pass the downloaded data and downloaderObjectId back to the delegate.
In the delegate.
Create multiple downloader objects(As per your ncessity) with unique value for the downloaderObjectId.
Store these objects in a NSMutableDictionary
Key for each object will be downloaderObjectId. so that when the delegate method is called after download you take the exact object back from the NSMutableDictionary using this key.
Main point.
Each time delegate is called. You should remove the object from dictionary(The object who is done with the download and called his delgate. You can identify this object by the key downloaderObjectId he holds. )
Then check the count of dictionary. If it is zero you can make sure that your downloads are completed. So you can call your viewcontroller.

Related

Calls to NSURLConnectionDataDelegate methods to download images only works occasionally

I currently have two UIImageViews in my storyboard, one of which downloads my own Facebook profile picture, and the other a friend's profile picture.
However, my issue is that only 60% of the time this works as expected, while the other 40% of the times my own profile picture appears where my friend's picture should show in the bottom, while the top box remains empty. I am unsure whether this is the result of how I call the NSURLConnectionDataDelegate methods as it downloads or completes view, or the nature of my requests called to Facebook.
I've pasted the condensed version of my two requests to Facebook in viewDidLoad, one which gets my own profile pic, and the other to get my friend's pic:
// ----- Gets my own profile picture, using requestForMe -----
FBRequest *request = [FBRequest requestForMe];
[request startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
//handle response
if(!error){
//Some methods not included for breveity, includes facebookID used below
NSURL *pictureURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=large&return_ssl_resources=1", facebookID]];
self.imageData = [[NSMutableData alloc] init];
switcher = 1;
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:pictureURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:2.0f];
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
if (!urlConnection){
NSLog(#"Failed to download picture");
}
}
}];
// ----- Gets a profile picture of my friend, using requestForMyFriends -----
FBRequest *requestForFriends = [FBRequest requestForMyFriends];
[requestForFriends startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if(!error){
//Other methods not included, including facebookFriendID
NSURL *friendPictureURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=large&return_ssl_resources=1", facebookFriendID]];
self.supportImageData = [[NSMutableData alloc] init];
switcher = 2;
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:friendPictureURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:2.0f];
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
if (!urlConnection){
NSLog(#"Failed to download picture");
}
}
}];
Both requests make calls to the NSURLConnectionDataDelegate methods, and I use the switcher to decide when to load which picture:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// As chuncks of the image are received, we build our data file
if (switcher == 1) [self.imageData appendData:data];
if (switcher == 2)[self.supportImageData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//When the entire image is finished downloading
if (switcher == 1) {
UIImage *image = [UIImage imageWithData:self.imageData]; //puts the completed picture into the UI
self.titleImageView.image = image;
[self.titleImageView setClipsToBounds:YES];
}
if (switcher == 2) {
UIImage *supportImage = [UIImage imageWithData:self.supportImageData];
self.supportImageView.image = supportImage;
[self.titleImageView setClipsToBounds:YES];
}
}
You have two asynchronous processes, both of which can result in having your NSURLConnectionDataDelegate methods in self being called. But if they happen at the same time, they're going to step on top of each other (you're presumably using a single NSMutableData variable to reference the data being downloaded).
Either create dedicated class that you can instantiate once for each of your two NSURLConnection requests (a NSOperation based approach, like AFNetworking, is ideal), or use sendAsynchronousRequest instead. But don't use a single object as the delegate for two concurrent NSURLConnection requests at the same time.
If you wanted to see a minimalist download operation, it might look like:
// NetworkOperation.h
#import <Foundation/Foundation.h>
typedef void(^DownloadCompletion)(NSData *data, NSError *error);
#interface NetworkOperation : NSOperation
- (id)initWithURL:(NSURL *)url completion:(DownloadCompletion)completionBlock;
#property (nonatomic, copy) NSURL *url;
#property (nonatomic, copy) DownloadCompletion downloadCompletionBlock;
#end
and
// NetworkOperation.m
#import "NetworkOperation.h"
#interface NetworkOperation () <NSURLConnectionDataDelegate>
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, strong) NSMutableData *data;
#end
#implementation NetworkOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
- (id)initWithURL:(NSURL *)url completion:(DownloadCompletion)downloadCompletionBlock
{
self = [super init];
if (self) {
self.url = url;
self.downloadCompletionBlock = downloadCompletionBlock;
_executing = NO;
_finished = NO;
}
return self;
}
#pragma mark - NSOperation related stuff
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
NSURLRequest *request = [NSURLRequest requestWithURL:self.url];
NSAssert(request, #"%s: requestWithURL failed for URL '%#'", __FUNCTION__, [self.url absoluteString]);
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[connection start];
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
- (BOOL)isConcurrent
{
return YES;
}
#pragma mark NSURLConnectionDataDelegate methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.data = [NSMutableData data];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if ([self isCancelled]) {
[connection cancel];
self.executing = NO;
self.finished = YES;
return;
}
[self.data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (self.downloadCompletionBlock) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.downloadCompletionBlock(self.data, nil);
self.downloadCompletionBlock = nil;
}];
}
self.executing = NO;
self.finished = YES;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
if (self.downloadCompletionBlock) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.downloadCompletionBlock(nil, error);
self.downloadCompletionBlock = nil;
}];
}
self.executing = NO;
self.finished = YES;
}
#end
And then, when you wanted to use it, it might look like:
NSOperationQueue *networkQueue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
// ----- Gets my own profile picture, using requestForMe -----
FBRequest *request = [FBRequest requestForMe];
[request startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
//handle response
if(!error) {
//Some methods not included for breveity, includes facebookID used below
NSURL *pictureURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=large&return_ssl_resources=1", facebookID]];
[networkQueue addOperation:[[NetworkOperation alloc] requestWithURL:pictureURL completion:^(NSData *data, NSError *error) {
if (!error) {
self.meImageView.image = [UIImage imageWithData:data];
}
}]];
}
}];
// ----- Gets a profile picture of my friend, using requestForMyFriends -----
FBRequest *requestForFriends = [FBRequest requestForMyFriends];
[requestForFriends startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if(!error){
//Other methods not included, including facebookFriendID
NSURL *friendPictureURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=large&return_ssl_resources=1", facebookFriendID]];
[networkQueue addOperation:[[NetworkOperation alloc] requestWithURL:friendPictureURL completion:^(NSData *data, NSError *error) {
if (!error) {
self.friendImageView.image = [UIImage imageWithData:data];
}
}]];
}
}];

iOS 5 NSURLConnection with NSOperationQueue - Providing UI Feedback

I need to make multiple NSURLConnections to a JSON Web Service. I would like each WS call to keep in UI informed, probably with a UIActivityIndicatorView and label. So far I've created a NSURLConnection helper class to handle the connection and placed the URL delegates in the View. This works great for updating the UI with a single WS call.
For multiple calls, I'm trying to use an NSOperationQueue. I'd like to setMaxConcurrentOperationCount to one on the queue so that each Operation executes one at a time. Here's the relevant code on my View Controller:
ViewController.m
#import "URLOperationHelper.h"
#implementation ViewController
- (IBAction)showPopup:(id)sender
{
// Dictonary holds POST values
NSMutableDictionary *reqDic = [NSMutableDictionary dictionary];
// Populate POST key/value pairs
[reqDic setObject:#"pw" forKey:#"Password"];
[reqDic setObject:#"ur" forKey:#"UserName"];
operationQueue = [[NSOperationQueue alloc] init];
[operationQueue setMaxConcurrentOperationCount:1];
[operationQueue cancelAllOperations];
[operationQueue setSuspended:YES];
URLOperationHelper *wsCall1 = [[URLOperationHelper alloc] initWithURL:#"urlString1" postParameters:reqDic urlDelegate:self];
URLOperationHelper *wsCall2 = [[URLOperationHelper alloc] initWithURL:#"urlString2" postParameters:reqDic urlDelegate:self];
[operationQueue addOperation:wsCall1];
[operationQueue addOperation:wsCall2];
}
// Did the URL Connection receive a response
-(void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"Did receive response: %#", response);
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
int code = [httpResponse statusCode];
// Handle status code here
webData = [[NSMutableData alloc]init];
}
// Did the URL Connection receive data
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"Did receive data: %#", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
assert(webData != nil);
[webData appendData:data];
}
// Did the connection fail with an error?
-(void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"%#", error);
}
// Executes after a successful connection and data download
-(void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Connection finished");
}
#end
And here is my URLOperationHelper.m
#implementation URLHelper
- (id)initWithURL:(NSString *)urlPath
postParameters:(NSMutableDictionary *)postParameters
urlParentDelegate:(id) pDelegate
{
if(self = [super init])
{
connectionURL = urlPath;
postParams = postParameters;
parentDelegate = pDelegate;
}
return self;
}
- (void)done
{
// Cancel the connection if present
if(urlConnection)
{
[urlConnection cancel];
urlConnection = nil;
}
// Alert
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
executing = NO;
finished = YES;
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
}
- (void)cancel
{
// Possibly add an NSError Property
[self done];
}
- (void)start
{
// Make sure this operation starts on the main thread
if(![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(start) withObject:nil waitUntilDone:NO];
return;
}
// Make sure that the operation executes
if(finished || [self isCancelled])
{
[self done];
return;
}
[self willChangeValueForKey:#"isExecuting"];
executing = YES;
[self main];
[self willChangeValueForKey:#"isExecuting"];
}
- (void)main
{
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:postParams options:NSJSONWritingPrettyPrinted error:&error];
// Convert dictionary to JSON
NSString *requestJSON = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"JSONRequest: %#", requestJSON);
// Declare Webservice URL, request, and return data
url = [[NSURL alloc] initWithString:connectionURL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
NSData *requestData = [NSData dataWithBytes:[requestJSON UTF8String] length:[requestJSON length]];
// Build the request
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%d", [requestData length]] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:requestData];
// Connect to Webservice
// Responses are handled in the delegates below
urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:parentDelegate startImmediately:YES];
}
- (BOOL)isConcurrent
{
return YES;
}
- (BOOL)isExecuting
{
return executing;
}
-(BOOL)isFinished
{
return finished;
}
#end
The problem that I'm having is the Start method for the URLOperation is never called. The OperationQueue is created and the Operations are called, but nothing happens after that, execution or thread wise.
Also, is this a correct line of thinking to provide UI feedback using NSOperationQueues like this? I.E. calling the NSURLDelegates from the Operation?
If you set setSuspended to YES before adding the Operations then your Operations will be queued into a suspended queue.. i suggest not to suspend the queue at
furthermore, your operation never ends anyway. You need to assign the operation itself as the delegate and implement all necessary delegate methods. In these methods you can forward the messages to your parentDelegate and decide when you are finished and call your done method when appropriate (i suggest connection:didFailWithError: and connectionDidFinishLoading:)
There is a good tutorial here: http://blog.9mmedia.com/?p=549
You are also not completely implementing key-value-coding compilant properties correct. Whenever you call willChangeValueForKey: you also need to call didChangeValueForKey afterwards:
- (void)start
{
...
[self willChangeValueForKey:#"isExecuting"];
executing = YES;
[self didChangeValueForKey:#"isExecuting"];
[self main];
}
and:
- (void)done
{
...
// Alert
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
executing = NO;
finished = YES;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
}
See this Q/A for KVC: when to use "willChangeValueForKey" and "didChangeValueForKey"?

Trouble using a custom NSObject class to simplify repetitive code in iPhone app

I am trying to store a REST connection model in a single object so that I don't keep having to use it over and over again. Here is the model I created:
//RestModel.h
#import "ASIHTTPRequest.h"
#interface RestModel : NSObject{
NSString* _baseUrl;
NSString* _modelUrl;
}
#property (nonatomic, retain) NSString* modelUrl;
- (id)initWithModel:(NSString*)model;
- (NSDictionary*)getById:(NSInteger*)ident;
- (NSDictionary*)getAll;
#end
//RestModel.m
#import "RestModel.h"
#implementation RestModel
#synthesize modelUrl = _modelUrl;
- (id)init
{
self = [super init];
if (self) {
_baseUrl = #"http://myRESTurl.com";
}
return self;
}
- (id)initWithModel:(NSString*)model
{
self = [super init];
if (self) {
_baseUrl = #"http://myRESTurl.com";
self.modelUrl = [NSString stringWithFormat:#"%#/%#/", _baseUrl, model];
}
return self;
}
- (NSDictionary*)HTTPRequest:(NSURL*)url
{
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
NSError *error = [request error];
if(!error){
NSData *responseData = [request responseData];
NSString *errorDesc = nil;
NSPropertyListFormat format;
[error release];
[request release];
return (NSDictionary*)[NSPropertyListSerialization propertyListFromData:responseData mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
}else{
NSLog(#"%#", error);
return nil;
}
}
- (NSDictionary*)getById:(NSInteger*)ident
{
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#", self.modelUrl, ident]];
return [self HTTPRequest:url];
}
- (NSDictionary*)getAll
{
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:#"%#", self.modelUrl]];
return [self HTTPRequest:url];
}
- (void)dealloc
{
[_modelUrl release];
// [_responseData release];
// [_responseDict release];
[super dealloc];
}
#end
Edit: Now it isn't crashing, but my local NSDictionary has a count of 0. I'm calling the following in -viewDidLoad in my controller:
RestModel* rm = [[RestModel alloc] initWithModel:#"user"];
self.dict = [rm getAll];
[rm release];
I planted NSLog of [self.dict count] throughout the controller. It is always 0. The RestModel rm is called, the functions are called (again more NSLogs), but no data. Any ideas?
[error release];
[request release];
Those should be auto-released objects, as you got them by convenience methods, so releasing them explicitly will make your application crash.
NSInteger isn't an object so it isn't necessary to pass it using NSInteger * and certainly you do not want to use the %# format specifier. Instead try this:
- (NSDictionary*)getById:(NSInteger)ident
{
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%d", self.modelUrl, ident]];
return [self HTTPRequest:url];
}

Need to call retain for a property with retain flag specified

I'm newbie in Objective-C, and as most of the newbies I have a questions about references management.
I've written a class which downloads data using NSURLConnection. The code is similar to Apple's example in http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html. The only difference is that receivedData variable is declared as "#property (nonatomic,retain) NSMutableData *receivedData;" In .m file I have "#synthesize receivedData = _receivedData;".
I have connectionStart function which starts downloading data. In this function I have this code:
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
self.receivedData = [NSMutableData data];
} else {
// Inform the user that the connection failed.
}
The program crashes with this message:
2011-06-12 12:47:22.298 WebGallery[1212:207] *** -[NSConcreteMutableData release]: message sent to deallocated instance 0x118a6fe0
If I change receivedData assignment to this code:
self.receivedData = [[NSMutableData data] retain];
Then the program works correctly and no memory leaks are detected.
As you see I need to call retain on NSMutableData and I'm using property, which is declared as "retain".
Why does this happen?
EDIT: Full contents of .m file:
#import "GalleryData.h"
#import "XmlParser.h"
#implementation GalleryData
#synthesize receivedData = _receivedData;
#synthesize imagesData = _imagesData;
#synthesize delegate = _delegate;
#synthesize currentObjectType = _currentObjectType;
#synthesize currentObjectIndex = _currentObjectIndex;
- (id) init
{
[super init];
_imagesData = [[NSMutableArray alloc] init];
return self;
}
- (void) dealloc
{
[_imagesData release];
_imagesData = nil;
[super dealloc];
}
- (void) connectionStart:(NSURL *)theURL
{
NSURLRequest *theRequest = [NSURLRequest requestWithURL:theURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
//ASK: Kodėl čia reikia daryti retain jei #property jau nustatyta retain?
self.receivedData = [[NSMutableData data] retain];
} else {
// Inform the user that the connection failed.
}
}
- (void) startLoading
{
NSLog(#"Loading started");
self.currentObjectIndex = 0;
self.currentObjectType = ObjectTypeXML;
[self connectionStart:[NSURL URLWithString:#"http://www.aleksandr.lt/gallery/data.xml"]];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[self.receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[connection release];
[self.receivedData release];
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[connection release];
if (self.currentObjectType == ObjectTypeXML) {
NSXMLParser *nsXmlParser = [[NSXMLParser alloc] initWithData:self.receivedData];
XmlParser *parser = [[XmlParser alloc] initXmlParser:self.imagesData];
[nsXmlParser setDelegate:parser];
[nsXmlParser parse];
[nsXmlParser release];
[parser release];
[self.receivedData release];
self.receivedData = nil;
if ([self.imagesData count]) {
self.currentObjectIndex = 0;
self.currentObjectType = ObjectTypeThumbImage;
ImageData *firstImage = [self.imagesData objectAtIndex:0];
NSURL *theURL = [NSURL URLWithString:firstImage.thumbImageURL];
[self connectionStart:theURL];
} else {
[self.delegate loadingFinished];
return;
}
} else if (self.currentObjectType == ObjectTypeThumbImage) {
ImageData *currentImage;
currentImage = [self.imagesData objectAtIndex:self.currentObjectIndex];
UIImage *thumbImage = [[UIImage alloc] initWithData:self.receivedData];
if (thumbImage == nil) {
NSLog(#"image was not created");
}
[currentImage setThumbImageScaled:thumbImage];
[thumbImage release];
[self.receivedData release];
self.receivedData = nil;
if (self.currentObjectIndex == ([self.imagesData count] - 1)) {
[self.delegate loadingFinished];
return;
}
self.currentObjectIndex++;
currentImage = [self.imagesData objectAtIndex:self.currentObjectIndex];
NSLog(#"'%#'", currentImage.thumbImageURL);
NSURL *theURL = [NSURL URLWithString:currentImage.thumbImageURL];
[self connectionStart:theURL];
}
}
#end
Do not call [self.receivedData release] - this leaves the internal pointer dangling. The whole point of a retained property is that it releases itself. Just do self.receivedData = nil.
Here is your problem:
[self.receivedData release];
self.receivedData = nil;
You're releasing the attribute twice (first time explicitly and second time implicitly by assigning nil).

Adding a new row in a UITableView

HI Everyone,
It is my first post here and this is my problem:
I am trying to get some data from a REST API call and show then in a UITableView.
This s what I am doing:
in the viewDidLoad: 1) here I initialize my array of things to show in the Table (that is empty at the beginning) 2) the table is loaded (with 0 rows) and 3) then the HTTP async call is issued.
Done this I do my stuff with the HTTP Response and when ready I call the reloadData on my table. Here the strange happens.
numberOfRowsInSelection returns me the correct number of rows
but
tableView:cellForRowAtIndexPath:indexPath for the indexPath.row always returns me zero!
so no new row is added to the table.
- (void)doJSONRequest{
responseData = [[NSMutableData data] retain];
NSString *addr = [[NSString alloc] initWithFormat:#"http://localhost:8080/blabla/v1/items?count=10"];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:addr]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[addr release];
}
- (void)doJSONRequestWithURL:(NSString *)url{
responseData = [[NSMutableData data] retain];
NSString *addr = [[NSString alloc] initWithFormat:url];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:addr]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[addr release];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
//NSLog(#"[%#] connection:didReceiveResponse %#",[self class], response);
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
//NSLog(#"[%#] connection:didReceiveData %#",[self class], data);
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"[%#]",[NSString stringWithFormat:#"Connection failed: %#", [error description]]);
UIAlertView *alert = [[UIAlertView alloc] init];
[alert setTitle:#"NETWORK ERROR!"];
[alert setMessage:#"App will close"];
[alert setDelegate:self];
[alert addButtonWithTitle:#"Close"];
[alert show];
[alert release];
[alert show];
[alert release];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//NSLog(#"[%#] connectionDidFinishLoading %#",[self class], connection);
[connection release];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSDictionary *res = [NSDictionary dictionaryWithDictionary:[responseString JSONValue]];
NSDictionary *tmp = [res valueForKey:#"reviews"];
tmp = [tmp valueForKey:#"reviews"];
NSEnumerator *e = [tmp objectEnumerator];
NSDictionary *tmp_review;
int i=0;
while (tmp_review = [e nextObject]) {
//NSLog(#"tmp_review %#", tmp_review);
//NSLog(#"count %d", i++);
MyObject r = [... doing my staff...]
[reviews addObject: r];
[r release];
};
[reviewListTableView reloadData];
[responseString release];
}
- (void)viewDidLoad {
//- (void)initUI {
//review is the array where I put my data
//it is empty at the beginning
reviews = [[NSMutableArray alloc] initWithObjects:nil];
[super viewDidLoad];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [reviews count]
}
Sounds like you may not have properly implemented numberOfSectionsInTableView:. You should log indexPath.section in tableView:cellForRowAtIndexPath:indexPath to see what section value you are passing. You will have multiple indexPath.row values of zero because there is a zero row for each section.
add new object in the array and [mytableview reloadData];
Implement following data source return number of section to be present in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
In tableView:cellForRowAtIndexPath:indexPath check section number and do your operation.
Note: If you don't need sections you can remove it.