I am new to iOS development. I am trying to implement NSURLConnectionDataDelegate Protocol but it seems that none of the delegate methods ever get called. I had to type the delegate methods in myself, is it supposed to be automatically generated?
I have an NSLog command in each delegate method but nothing prints. I am using NSURLConnection to Asynchronously download and keep track of the progress so I can update a progressView later.
SearchFeed.h file (Notice I have tried to implement the protocol when I typed NSURLConnectionDataDelegate
#import <Foundation/Foundation.h>
#import "Doc.h"
#interface SearchFeed : NSObject <NSXMLParserDelegate, NSURLConnectionDataDelegate>
{
NSMutableString * currentElementValue;
Doc *currentDoc;
}
#property(strong,nonatomic) NSURL * searchUrl;
#property(strong,nonatomic) NSArray * searchResults;
//#property(retain, nonatomic) Doc * currentDoc;
#property(retain, nonatomic) NSMutableArray *docs;
//#property(retain, nonatomic) NSURLConnection *urlConnection;
#property(retain, nonatomic) UIProgressView * progressBar;
-(void)retrieveFromInternet;
-(double) getProgress;
+(NSString *)pathToDocuments;
+(void)downloadPDFToMyDocumentsFrom:(NSString*) PDFUrl filename:(NSString *) title;
+(NSArray *)listFilesAtPath:(NSString *)path;
#end
SearchFeed.m file:
#import "SearchFeed.h"
#implementation SearchFeed
#synthesize searchUrl = _searchUrl; //where to search from
#synthesize searchResults = _searchResults; // Not being used -- I think
//#synthesize currentDoc = _currentDoc; //current Doc
#synthesize docs = _docs; //array of Docs
#synthesize progressBar = _progressBar;
NSURLConnection *urlConnection;
double fileLength =0;
double lastProgress =0;
double currentLength =0;
NSOutputStream *fileStream;
+(void)downloadPDFToMyDocumentsFrom:(NSString*) PDFUrl filename:(NSString *) title {
NSURL *url = [[NSURL alloc] initWithString:PDFUrl];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
NSString *fileName = [title stringByAppendingPathExtension:#"pdf"];
NSString *filePath = [[self pathToDocuments] stringByAppendingPathComponent:fileName];
fileStream = [[NSOutputStream alloc] initToFileAtPath:filePath append:YES];
[fileStream open];
}
//handling incoming data
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
double length = [data length];
currentLength += length;
double progress = currentLength/fileLength;
NSLog(#"Receiving data");
if(lastProgress < progress)
{
//progressBar WRITE code to update the progress for the progress bar
lastProgress = progress;
self.progressBar.progress = lastProgress;
NSLog(#"%f -------------------------------------------------------", lastProgress);
}
NSUInteger left = [data length];
NSUInteger nwr = 0;
do {
nwr = [fileStream write:[data bytes] maxLength:left];
if(nwr == -1)
break;
left -= nwr;
}while(left>0);
if(left)
{
NSLog(#"Stream error: %#", [fileStream streamError]);
}
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
long length = [response expectedContentLength];
fileLength = length;
NSLog(#"%f ------------------------------------------------------- is the fileLength", fileLength);
}
//handling connection progress
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
//WRITE code to set the progress bar to 1.0
self.progressBar.progress = 1.0;
[fileStream close];
NSLog(#"%f -------------------------------------------------------", lastProgress);
}
I have set the delegate for NSURLConnection urlConnection to self which is SearchFeed.m class.
In SearchFeed.h, I tried to implement the NSURLConnectionDataDelegate protocol.
I had to create connectionDidFinishLoading, didReceiveResponse and didReceiveData methods but those methods don't get called.
I either have not implemented the protocol properly OR I have declared some methods as + and some as - (some methods are class methods while some are instance methods)
downloadPDFToMyDocumentsFrom is a class method which is invoked when the user clicks download.
This method sets the NSURLConnection, sets the URL etc and the delegate and opens the fileStream to receive data. However, none of the other methods get called.
Your downloadPDFToMyDocumentsFrom method is setup as a class method (+), and you setup your delegate to be self, meaning the Class in this case. You should make the downloadPDFToMyDocumentsFrom method a instance method (-) so that self is an instantiated object.
I have successfully made a connection from an iPhone to a Server (which is a Windows machine) using TCP sockets. Currently, I'm using a button to execute the following code:
while(1)
{
Socket *socket;
int port = 11005;
NSString *host = #"9.5.3.63";
socket = [Socket socket];
#try
{
NSMutableData *data;
[socket connectToHostName:host port:port];
[socket readData:data];
// [socket writeString:#"Hello World!"];
//** Connection was successful **//
[socket retain]; // Must retain if want to use out of this action block.
}
#catch (NSException* exception)
{
NSString *errMsg = [NSString stringWithFormat:#"%#",[exception reason]];
NSLog(errMsg);
socket = nil;
}
}
That was the easy part... I'm trying to establish the sockets connection as soon as the app loads. I tried putting this code in my viewDidLoad, but the loop is infinite and the view never loads. I have several views in my project, and I'd like to open the connection keep the connection open at all times, across all views.
Objective:
Open TCP Sockets connection when app first loads
Maintain connection infinitely, no matter what view I am in (Multiple Views in Project)
I'm still rather new to iOS development, so I appreciate as much clarity as possible. It should be noted that I am using the SmallSockets library to open my Sockets connection. Thanks for the help!
* EDIT *
Based off the answer below, this is what I've got going so far:
SocketConnection.h
#import <Foundation/Foundation.h>
#interface SocketConnection : NSObject
{
}
+ (SocketConnection *)getInstance;
#end
SocketConnection.m
static SocketConnection *sharedInstance = nil;
#implementation SocketConnection
- (id)init
{
self = [super init];
if (self)
{
while(1)
{
Socket *socket;
int port = 11005;
NSString *host = #"9.5.3.63";
socket = [Socket socket];
#try
{
NSMutableData *data;
[socket connectToHostName:host port:port];
[socket readData:data];
// [socket writeString:#"Hello World!"];
//** Connection was successful **//
[socket retain]; // Must retain if want to use out of this action block.
}
#catch (NSException* exception)
{
NSString *errMsg = [NSString stringWithFormat:#"%#",[exception reason]];
NSLog(errMsg);
socket = nil;
}
}
}
return self;
}
+ (SocketConnection *)getInstance
{
#synchronized(self)
{
if (sharedInstance == nil)
{
sharedInstance = [[SocketConnection alloc] init];
}
}
return sharedInstance;
}
#end
I still have not figured out how the singleton class gets invoked. I fired up my app with the code from above and it's not connecting to the server. Any ideas?
Thanks!
You should create a singleton classes to keep your connection like below code:
h file:
#import <Foundation/Foundation.h>
#interface SocketConnection : NSObject
{
}
+ (SocketConnection *)getInstance;
#end;
m file:
#import "SocketConnection.h"
static SocketConnection *sharedInstance = nil;
#implementation SocketConnection
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
+ (SocketConnection *)getInstance
{
#synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [[SocketConnection alloc] init];
}
}
return sharedInstance;
}
#end;
I'm trying to send some NSData over Bluetooth through GameKit.
While I've got GameKit set up and are able to send small messages across, I now would like to expand and send across whole files.
I've been reading that you have to split large files up into packets before sending them across individually.
So I decided to create a struct to make it easier to decode the packets when they're received at the other end:
typedef struct {
const char *fileName;
NSData *contents;
int fileType;
int packetnumber;
int totalpackets;
} file_packet;
However, for small files (8KB and less) I thought one packet will be enough.
So for one packet, I thought I would be able to create a file_packet, set its properties, and send it via -sendDataToAllPeers:withDataMode:error:
NSData *fileData;
file_packet *packet = (file_packet *)malloc(sizeof(file_packet));
packet->fileName = [filename cStringUsingEncoding:NSASCIIStringEncoding];
packet->contents = [NSData dataWithContentsOfFile:selectedFilePath];
packet->packetnumber = 1;
packet->totalpackets = 1;
packet->fileType = 56; //txt document
fileData = [NSData dataWithBytes:(const void *)packet length:sizeof(file_packet)];
free(packet);
NSError *error = nil;
[self.connectionSession sendDataToAllPeers:fileData withDataMode:GKSendDataReliable error:&error];
if (error) {
NSLog(#"An error occurred: %#", [error localizedDescription]);
}
However, I don't think something's right setting fileData - and error displays nothing.
When a file's received, I do the following:
file_packet *recievedPacket = (file_packet *)malloc(sizeof(file_packet));
recievedPacket = (file_packet *)[data bytes];
NSLog(#"packetNumber = %d", recievedPacket->packetnumber);
...
However, the output on the console is packetNumber = 0, even when I set packetNumber to 1.
Am I missing the obvious?
I don't know much about NSData or GameKit.
So my question is - Can I add a file_packet in NSData, and if so, How do I do it successfully - and How do you split files up into multiple packets?
To add on:
What you ought to do here is make an NSObject subclass to represent your packet, and then adopt NSCoding to serialize it to an NSData in the way that you want. Doing this with a struct isn't buying you anything, and makes things even harder. It's also fragile, since packing a struct into an NSData doesn't account for things like endian-ness, etc.
The tricky part of the packetizing process using NSCoding is that you don't really know what the overhead of the coding process is, so being as big as possible, but still under the max packet size is tricky...
I present this without testing, or warranty, but if you want a decent start on that approach, this may be it. Be warned, I didn't check to see if my arbitrary 100 bytes for overhead was realistic. You'll have to play with the numbers a little bit.
Packet.h:
#interface Packet : NSObject <NSCoding>
{
NSString* fileName;
NSInteger fileType;
NSUInteger totalPackets;
NSUInteger packetIndex;
NSData* packetContents;
}
#property (readonly, copy) NSString* fileName;
#property (readonly, assign) NSInteger fileType;
#property (readonly, assign) NSUInteger totalPackets;
#property (readonly, assign) NSUInteger packetIndex;
#property (readonly, retain) NSData* packetContents;
+ (NSArray*)packetsForFile: (NSString*)name ofType: (NSInteger)type withData: (NSData*)fileContents;
#end
Packet.m:
#import "Packet.h"
#interface Packet ()
#property (readwrite, assign) NSUInteger totalPackets;
#property (readwrite, retain) NSData* packetContents;
#end
#implementation Packet
- (id)initWithFileName: (NSString*)pFileName ofType: (NSInteger)pFileType index: (NSUInteger)pPacketIndex
{
if (self = [super init])
{
fileName = [pFileName copy];
fileType = pFileType;
packetIndex = pPacketIndex;
totalPackets = NSUIntegerMax;
packetContents = [[NSData alloc] init];
}
return self;
}
- (void)dealloc
{
[fileName release];
[packetContents release];
[super dealloc];
}
#synthesize fileName;
#synthesize fileType;
#synthesize totalPackets;
#synthesize packetIndex;
#synthesize packetContents;
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject: self.fileName forKey: #"fileName"];
[aCoder encodeInt64: self.fileType forKey:#"fileType"];
[aCoder encodeInt64: self.totalPackets forKey:#"totalPackets"];
[aCoder encodeInt64: self.packetIndex forKey:#"packetIndex"];
[aCoder encodeObject: self.packetContents forKey:#"totalPackets"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init])
{
fileName = [[aDecoder decodeObjectForKey: #"fileName"] copy];
fileType = [aDecoder decodeInt64ForKey:#"fileType"];
totalPackets = [aDecoder decodeInt64ForKey:#"totalPackets"];
packetIndex = [aDecoder decodeInt64ForKey:#"packetIndex"];
packetContents = [[aDecoder decodeObjectForKey:#"totalPackets"] retain];
}
return self;
}
+ (NSArray*)packetsForFile: (NSString*)name ofType: (NSInteger)type withData: (NSData*)fileContents
{
const NSUInteger quanta = 8192;
Packet* first = [[[Packet alloc] initWithFileName:name ofType:type index: 0] autorelease];
// Find out how big the NON-packet payload is...
NSMutableData* data = [NSMutableData data];
NSKeyedArchiver* coder = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
[first encodeWithCoder: coder];
[coder finishEncoding];
const NSUInteger nonPayloadSize = [data length];
NSMutableArray* packets = [NSMutableArray array];
NSUInteger bytesArchived = 0;
while (bytesArchived < [fileContents length])
{
Packet* nextPacket = [[[Packet alloc] initWithFileName: name ofType: type index: packets.count] autorelease];
NSRange subRange = NSMakeRange(bytesArchived, MIN(quanta - nonPayloadSize - 100, fileContents.length - bytesArchived));
NSData* payload = [fileContents subdataWithRange: subRange];
nextPacket.packetContents = payload;
bytesArchived += [payload length];
[packets addObject: nextPacket];
}
for (Packet* packet in packets)
{
packet.totalPackets = packets.count;
}
return packets;
}
- (NSData*)dataForSending
{
NSMutableData* data = [NSMutableData data];
NSKeyedArchiver* coder = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
[self encodeWithCoder: coder];
[coder finishEncoding];
return [NSData dataWithData:data];
}
+ (Packet*)packetObjectFromRxdData:(NSData*)data
{
NSKeyedUnarchiver* decoder = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
return [[[Packet alloc] initWithCoder:decoder] autorelease];
}
#end
The reassemblage of the original file from these packets can be done using much the same approach as splitting it up... Iterate over the packets, copying from the individual packet payload NSDatas into a big NSMutableData.
In closing, I feel compelled to say that when you find yourself doing something like this, that boils down to implementing a primitive TCP stack, it's usually time to stop yourself and ask if there aren't better ways to do this. Put differently, if GameKit were the best way to transfer files between devices over bluetooth, one would expect that the API would have a method for doing just that, but instead it has this 8K limit.
I'm not being intentionally cryptic -- I don't know what the right API would be for your situation, but the exercise of cooking up this Packet class left me thinking, "there's gotta be a better way."
Hope this helps.
You create the NSData with size sizeof(packet), which is only the pointer's size. Change it to sizeof(file_packet).
BTW, you're not really sending the filename and the contents. Only the pointers to them.
I have a UITableView which is setup in Interface Builder and connected properly to its class in Xcode. I also have a "Importer" Class which downloads and parses an RSS feed and stores the information in an NSMutableArray.
However I have verified the parsing is working properly (using breakpoints and NSlog) but no data is showing in the UITable View. Any ideas as to what the problem could be? I'm almost out of them.
It's based on the XML performance Apple example.
Update 2:
OK so after working through the below information, here's what I know:
The didParseIncidents:(NSArray *)parsedIncidents is not being called, but read on as to why.
When I launch the app, and reload the data, (void)parsedIncident:(Incident *)incident runs, but misses a break point in if (self.delegate != nil && [self.delegate respondsToSelector:#selector(parser:didParseIncidents:)]) {
[self.delegate parser:self didParseIncidents:parsedIncidents];
I suspect my table issues come from the fact that the above code isn't working properly - in the sample the breakpoint in if (self.delegate != nil && [self.delegate respondsToSelector:#selector(parser:didParseIncidents:)]) {
[self.delegate parser:self didParseIncidents:parsedIncidents]; runs
Doing a po parsedIncidents here results in a "Cannot access Memory 0x0" error, whereas in sample app gives an NS Array
In fact, it seems as though parsedIncidents count is 0 - but can't work out why.
Update:
Alright so here are the results:
"po incidents" on the parserDidEndParsingData breakpoint gave me a reply of "cannot access memory at address 0x0".
The po [self tableView] displays ">
Current language: auto; currently objective-c"
In the breakpoint under number of rows in sections, I got the following "()" message.
The IB items I checked and all were as you said. I suspect the array isn't being loaded from the above errors?
I've also added the parsing code, if that helps.
Here's the code for TableView.h:
#import <UIKit/UIKit.h>
#import "IncidentsImporter.h"
#class SongDetailsController;
#interface CurrentIncidentsTableViewController : UITableViewController <IncidentsImporterDelegate>{
NSMutableArray *incidents;
SongDetailsController *detailController;
UITableView *ctableView;
IncidentsImporter *parser;
}
#property (nonatomic, retain) NSMutableArray *incidents;
#property (nonatomic, retain, readonly) SongDetailsController *detailController;
#property (nonatomic, retain) IncidentsImporter *parser;
#property (nonatomic, retain) IBOutlet UITableView *ctableView;
// Called by the ParserChoiceViewController based on the selected parser type.
- (void)beginParsing;
#end
And the code for .m:
#import "CurrentIncidentsTableViewController.h"
#import "SongDetailsController.h"
#import "Incident.h"
#implementation CurrentIncidentsTableViewController
#synthesize ctableView, incidents, parser, detailController;
#pragma mark -
#pragma mark View lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
self.parser = [[IncidentsImporter alloc] init];
parser.delegate = self;
[parser start];
UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:#selector(beginParsing)];
self.navigationItem.rightBarButtonItem = refreshButton;
[refreshButton release];
// Uncomment the following line to preserve selection between presentations.
//self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)viewWillAppear:(BOOL)animated {
NSIndexPath *selectedRowIndexPath = [ctableView indexPathForSelectedRow];
if (selectedRowIndexPath != nil) {
[ctableView deselectRowAtIndexPath:selectedRowIndexPath animated:NO];
}
}
// This method will be called repeatedly - once each time the user choses to parse.
- (void)beginParsing {
NSLog(#"Parsing has begun");
//self.navigationItem.rightBarButtonItem.enabled = NO;
// Allocate the array for song storage, or empty the results of previous parses
if (incidents == nil) {
NSLog(#"Grabbing array");
self.incidents = [NSMutableArray array];
} else {
[incidents removeAllObjects];
[ctableView reloadData];
}
// Create the parser, set its delegate, and start it.
self.parser = [[IncidentsImporter alloc] init];
parser.delegate = self;
[parser start];
}
/*
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
}
*/
/*
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
*/
/*
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
}
*/
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Override to allow orientations other than the default portrait orientation.
return YES;
}
#pragma mark -
#pragma mark Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [incidents count];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Table Cell Sought");
static NSString *kCellIdentifier = #"MyCell";
UITableViewCell *cell = [ctableView dequeueReusableCellWithIdentifier:kCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellIdentifier] autorelease];
cell.textLabel.font = [UIFont boldSystemFontOfSize:14.0];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
cell.textLabel.text = #"Test";//[[incidents objectAtIndex:indexPath.row] title];
return cell;
}
/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the specified item to be editable.
return YES;
}
*/
/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
#pragma mark -
#pragma mark Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.detailController.incident = [incidents objectAtIndex:indexPath.row];
[self.navigationController pushViewController:self.detailController animated:YES];
}
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
// For example: self.myOutlet = nil;
}
- (void)parserDidEndParsingData:(IncidentsImporter *)parser {
[ctableView reloadData];
self.navigationItem.rightBarButtonItem.enabled = YES;
self.parser = nil;
}
- (void)parser:(IncidentsImporter *)parser didParseIncidents:(NSArray *)parsedIncidents {
//[incidents addObjectsFromArray: parsedIncidents];
// Three scroll view properties are checked to keep the user interface smooth during parse. When new objects are delivered by the parser, the table view is reloaded to display them. If the table is reloaded while the user is scrolling, this can result in eratic behavior. dragging, tracking, and decelerating can be checked for this purpose. When the parser finishes, reloadData will be called in parserDidEndParsingData:, guaranteeing that all data will ultimately be displayed even if reloadData is not called in this method because of user interaction.
if (!ctableView.dragging && !ctableView.tracking && !ctableView.decelerating) {
self.title = [NSString stringWithFormat:NSLocalizedString(#"Top %d Songs", #"Top Songs format"), [parsedIncidents count]];
[ctableView reloadData];
}
}
- (void)parser:(IncidentsImporter *)parser didFailWithError:(NSError *)error {
// handle errors as appropriate to your application...
}
- (void)dealloc {
[super dealloc];
}
#end
Here's the code for parser.h:
#import <UIKit/UIKit.h>
#import <libxml/tree.h>
#class IncidentsImporter, Incident;
// Protocol for the parser to communicate with its delegate.
#protocol IncidentsImporterDelegate <NSObject>
#optional
// Called by the parser when parsing is finished.
- (void)parserDidEndParsingData:(IncidentsImporter *)parser;
// Called by the parser in the case of an error.
- (void)parser:(IncidentsImporter *)parser didFailWithError:(NSError *)error;
// Called by the parser when one or more songs have been parsed. This method may be called multiple times.
- (void)parser:(IncidentsImporter *)parser didParseIncidents:(NSArray *)parsedIncidents;
#end
// This approach to parsing uses NSURLConnection to asychronously retrieve the XML data. libxml's SAX parsing supports chunked parsing, with no requirement for the chunks to be discrete blocks of well formed XML. The primary purpose of this class is to start the download, configure the parser with a set of C callback functions, and pass downloaded data to it. In addition, the class maintains a number of state variables for the parsing.
#interface IncidentsImporter : NSObject {
#private
id <IncidentsImporterDelegate> delegate;
// Reference to the libxml parser context
xmlParserCtxtPtr context;
NSURLConnection *rssConnection;
NSMutableArray *parsedIncidents;
// Overall state of the parser, used to exit the run loop.
BOOL done;
// State variable used to determine whether or not to ignore a given XML element
BOOL parsingAIncident;
// The following state variables deal with getting character data from XML elements. This is a potentially expensive
// operation. The character data in a given element may be delivered over the course of multiple callbacks, so that
// data must be appended to a buffer. The optimal way of doing this is to use a C string buffer that grows exponentially.
// When all the characters have been delivered, an NSString is constructed and the buffer is reset.
BOOL storingCharacters;
NSMutableData *characterBuffer;
// A reference to the current song the parser is working with.
Incident *currentIncident;
// The number of parsed songs is tracked so that the autorelease pool for the parsing thread can be periodically
// emptied to keep the memory footprint under control.
NSUInteger countOfParsedIncidents;
NSAutoreleasePool *downloadAndParsePool;
NSDateFormatter *parseFormatter;
}
#property (nonatomic, assign) id <IncidentsImporterDelegate> delegate;
#property (nonatomic, retain) NSMutableArray *parsedIncidents;
#property BOOL storingCharacters;
#property (nonatomic, retain) NSMutableData *characterBuffer;
#property BOOL done;
#property BOOL parsingAIncident;
#property NSUInteger countOfParsedIncidents;
#property (nonatomic, retain) Incident *currentIncident;
#property (nonatomic, retain) NSURLConnection *rssConnection;
#property (nonatomic, retain) NSDateFormatter *parseFormatter;
// The autorelease pool property is assign because autorelease pools cannot be retained.
#property (nonatomic, assign) NSAutoreleasePool *downloadAndParsePool;
- (void)downloadAndParse:(NSURL *)url;
- (void)finishedCurrentIncident;
- (void)start;
- (void)downloadStarted;
- (void)downloadEnded;
- (void)parseEnded;
- (void)parsedIncident:(Incident *)incident;
- (void)parseError:(NSError *)error;
- (void)addToParseDuration:(NSNumber *)duration;
#end
And parser.m:
#import "IncidentsImporter.h"
#import "Incident.h"
#import <libxml/tree.h>
static NSUInteger kCountForNotification = 10;
// Function prototypes for SAX callbacks. This sample implements a minimal subset of SAX callbacks.
// Depending on your application's needs, you might want to implement more callbacks.
static void startElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes);
static void endElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI);
static void charactersFoundSAX(void * ctx, const xmlChar * ch, int len);
static void errorEncounteredSAX(void * ctx, const char * msg, ...);
// Forward reference. The structure is defined in full at the end of the file.
static xmlSAXHandler simpleSAXHandlerStruct;
#implementation IncidentsImporter
#synthesize delegate, rssConnection, done, parsingAIncident, parsedIncidents, storingCharacters, currentIncident, countOfParsedIncidents, characterBuffer, parseFormatter, downloadAndParsePool;
- (void)start {
NSLog(#"URL Gained");
[[NSURLCache sharedURLCache] removeAllCachedResponses];
NSURL *url = [NSURL URLWithString:#"http://ax.phobos.apple.com.edgesuite.net/WebObjects/MZStore.woa/wpa/MRSS/newreleases/limit=300/rss.xml"];
[NSThread detachNewThreadSelector:#selector(downloadAndParse:) toTarget:self withObject:url];
}
- (void)downloadStarted {
NSLog(#"Download has begun");
NSAssert2([NSThread isMainThread], #"%s at line %d called on secondary thread", __FUNCTION__, __LINE__);
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
- (void)downloadEnded {
NSLog(#"Download has ended");
NSAssert2([NSThread isMainThread], #"%s at line %d called on secondary thread", __FUNCTION__, __LINE__);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)parseEnded {
NSLog(#"Parsing has ended");
NSAssert2([NSThread isMainThread], #"%s at line %d called on secondary thread", __FUNCTION__, __LINE__);
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(parser:didParseIncidents:)] && [parsedIncidents count] > 0) {
[self.delegate parser:self didParseIncidents:parsedIncidents];
}
[self.parsedIncidents removeAllObjects];
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(parserDidEndParsingData:)]) {
[self.delegate parserDidEndParsingData:self];
}
}
- (void)parsedIncident:(Incident *)incident {
NSLog(#"Parsing has begun on thread");
NSAssert2([NSThread isMainThread], #"%s at line %d called on secondary thread", __FUNCTION__, __LINE__);
[self.parsedIncidents addObject:incident];
if (self.parsedIncidents.count > kCountForNotification) {
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(parser:didParseIncidents:)]) {
[self.delegate parser:self didParseIncidents:parsedIncidents];
}
[self.parsedIncidents removeAllObjects];
}
}
- (void)parseError:(NSError *)error {
NSLog(#"Parsing has an error");
NSAssert2([NSThread isMainThread], #"%s at line %d called on secondary thread", __FUNCTION__, __LINE__);
if (self.delegate != nil && [self.delegate respondsToSelector:#selector(parser:didFailWithError:)]) {
[self.delegate parser:self didFailWithError:error];
}
}
- (void)addToParseDuration:(NSNumber *)duration {
NSAssert2([NSThread isMainThread], #"%s at line %d called on secondary thread", __FUNCTION__, __LINE__);
}
/*
This method is called on a secondary thread by the superclass. We have asynchronous work to do here with downloading and parsing data, so we will need a run loop to prevent the thread from exiting before we are finished.
*/
- (void)downloadAndParse:(NSURL *)url {
NSLog(#"Downloading and Parsing");
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
done = NO;
self.parseFormatter = [[[NSDateFormatter alloc] init] autorelease];
[parseFormatter setDateStyle:NSDateFormatterLongStyle];
[parseFormatter setTimeStyle:NSDateFormatterNoStyle];
// necessary because iTunes RSS feed is not localized, so if the device region has been set to other than US
// the date formatter must be set to US locale in order to parse the dates
[parseFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:#"AU"] autorelease]];
self.characterBuffer = [NSMutableData data];
[[NSURLCache sharedURLCache] removeAllCachedResponses];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:url];
// create the connection with the request and start loading the data
rssConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
// This creates a context for "push" parsing in which chunks of data that are not "well balanced" can be passed
// to the context for streaming parsing. The handler structure defined above will be used for all the parsing.
// The second argument, self, will be passed as user data to each of the SAX handlers. The last three arguments
// are left blank to avoid creating a tree in memory.
context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, self, NULL, 0, NULL);
[self performSelectorOnMainThread:#selector(downloadStarted) withObject:nil waitUntilDone:NO];
if (rssConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
xmlFreeParserCtxt(context);
self.characterBuffer = nil;
self.parseFormatter = nil;
self.rssConnection = nil;
self.currentIncident = nil;
[downloadAndParsePool release];
self.downloadAndParsePool = nil;
}
#pragma mark NSURLConnection Delegate methods
/*
Disable caching so that each time we run this app we are starting with a clean slate. You may not want to do this in your application.
*/
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
return nil;
}
// Forward errors to the delegate.
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
done = YES;
[self performSelectorOnMainThread:#selector(parseError:) withObject:error waitUntilDone:NO];
}
// Called when a chunk of data has been downloaded.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"Did receive data");
NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
// Process the downloaded chunk of data.
xmlParseChunk(context, (const char *)[data bytes], [data length], 0);
NSTimeInterval duration = [NSDate timeIntervalSinceReferenceDate] - start;
[self performSelectorOnMainThread:#selector(addToParseDuration:) withObject:[NSNumber numberWithDouble:duration] waitUntilDone:NO];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"Connection done loading");
[self performSelectorOnMainThread:#selector(downloadEnded) withObject:nil waitUntilDone:NO];
NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
// Signal the context that parsing is complete by passing "1" as the last parameter.
xmlParseChunk(context, NULL, 0, 1);
NSTimeInterval duration = [NSDate timeIntervalSinceReferenceDate] - start;
[self performSelectorOnMainThread:#selector(addToParseDuration:) withObject:[NSNumber numberWithDouble:duration] waitUntilDone:NO];
[self performSelectorOnMainThread:#selector(parseEnded) withObject:nil waitUntilDone:NO];
// Set the condition which ends the run loop.
done = YES;
}
#pragma mark Parsing support methods
static const NSUInteger kAutoreleasePoolPurgeFrequency = 20;
- (void)finishedCurrentIncident {
[self performSelectorOnMainThread:#selector(parsedIncident:) withObject:currentIncident waitUntilDone:NO];
// performSelectorOnMainThread: will retain the object until the selector has been performed
// setting the local reference to nil ensures that the local reference will be released
self.currentIncident = nil;
countOfParsedIncidents++;
// Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the
// size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but
// taking this action too frequently would be wasteful and reduce performance.
if (countOfParsedIncidents == kAutoreleasePoolPurgeFrequency) {
[downloadAndParsePool release];
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
countOfParsedIncidents = 0;
}
}
/*
Character data is appended to a buffer until the current element ends.
*/
- (void)appendCharacters:(const char *)charactersFound length:(NSInteger)length {
[characterBuffer appendBytes:charactersFound length:length];
}
- (NSString *)currentString {
// Create a string with the character data using UTF-8 encoding. UTF-8 is the default XML data encoding.
NSString *currentString = [[[NSString alloc] initWithData:characterBuffer encoding:NSUTF8StringEncoding] autorelease];
[characterBuffer setLength:0];
return currentString;
}
#end
#pragma mark SAX Parsing Callbacks
// The following constants are the XML element names and their string lengths for parsing comparison.
// The lengths include the null terminator, to ensure exact matches.
static const char *kName_Item = "item";
static const NSUInteger kLength_Item = 5;
static const char *kName_Title = "title";
static const NSUInteger kLength_Title = 6;
static const char *kName_Category = "link";
static const NSUInteger kLength_Category = 9;
static const char *kName_Itms = "itms";
static const NSUInteger kLength_Itms = 5;
static const char *kName_Artist = "description";
static const NSUInteger kLength_Artist = 7;
static const char *kName_Album = "guid";
static const NSUInteger kLength_Album = 6;
static const char *kName_ReleaseDate = "pubDate";
static const NSUInteger kLength_ReleaseDate = 12;
/*
This callback is invoked when the parser finds the beginning of a node in the XML. For this application,
out parsing needs are relatively modest - we need only match the node name. An "item" node is a record of
data about a song. In that case we create a new Song object. The other nodes of interest are several of the
child nodes of the Song currently being parsed. For those nodes we want to accumulate the character data
in a buffer. Some of the child nodes use a namespace prefix.
*/
static void startElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI,
int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes) {
IncidentsImporter *parser = (IncidentsImporter *)ctx;
// The second parameter to strncmp is the name of the element, which we known from the XML schema of the feed.
// The third parameter to strncmp is the number of characters in the element name, plus 1 for the null terminator.
if (prefix == NULL && !strncmp((const char *)localname, kName_Item, kLength_Item)) {
NSLog(#"Found node");
Incident *newIncident = [[Incident alloc] init];
parser.currentIncident = newIncident;
[newIncident release];
parser.parsingAIncident = YES;
} else if (parser.parsingAIncident && ( (prefix == NULL && (!strncmp((const char *)localname, kName_Title, kLength_Title) || !strncmp((const char *)localname, kName_Category, kLength_Category))) || ((prefix != NULL && !strncmp((const char *)prefix, kName_Itms, kLength_Itms)) && (!strncmp((const char *)localname, kName_Artist, kLength_Artist) || !strncmp((const char *)localname, kName_Album, kLength_Album) || !strncmp((const char *)localname, kName_ReleaseDate, kLength_ReleaseDate))) )) {
parser.storingCharacters = YES;
}
}
/*
This callback is invoked when the parse reaches the end of a node. At that point we finish processing that node,
if it is of interest to us. For "item" nodes, that means we have completed parsing a Song object. We pass the song
to a method in the superclass which will eventually deliver it to the delegate. For the other nodes we
care about, this means we have all the character data. The next step is to create an NSString using the buffer
contents and store that with the current Song object.
*/
static void endElementSAX(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI) {
IncidentsImporter *parser = (IncidentsImporter *)ctx;
if (parser.parsingAIncident == NO) return;
if (prefix == NULL) {
if (!strncmp((const char *)localname, kName_Item, kLength_Item)) {
[parser finishedCurrentIncident];
parser.parsingAIncident = NO;
} else if (!strncmp((const char *)localname, kName_Title, kLength_Title)) {
NSLog(#"Parsing title");
parser.currentIncident.title = [parser currentString];
} else if (!strncmp((const char *)localname, kName_Category, kLength_Category)) {
NSLog(#"Parsing url");
parser.currentIncident.link = [NSURL URLWithString: [parser currentString]];
}
else if (!strncmp((const char *)localname, kName_Artist, kLength_Artist)) {
NSLog(#"Parsing description");
parser.currentIncident.description = [parser currentString];
} else if (!strncmp((const char *)localname, kName_Album, kLength_Album)) {
NSLog(#"Parsing guid");
parser.currentIncident.guid = [parser currentString];
} else if (!strncmp((const char *)localname, kName_ReleaseDate, kLength_ReleaseDate)) {
NSLog(#"Parsing date");
NSString *dateString = [parser currentString];
parser.currentIncident.pubDate = [parser.parseFormatter dateFromString:dateString];
}
}
parser.storingCharacters = NO;
}
/*
This callback is invo
With that line commented out, how are you getting the incidents?
- (void)parser:(IncidentsImporter *)parser didParseIncidents:(NSArray *)parsedIncidents {
//[incidents addObjectsFromArray: parsedIncidents];
Calling reloadData when incidents has not changed does not really do anything.
In that same function, you only call reloadData if the table is stationary. If you do not call it, you should set a flag to call reloadData once the table is stationary. There are a few UIScrollView delegate callbacks to detect that.
Some things to check:
If you set a breakpoint in parserDidEndParsingData:, is ctableView pointing at a valid UITableView object, not nil?
If you type "po incidents" at your gdb prompt on that line, are your incidents all loaded?
If you type "po [self tableView]", does it display the same object as "po ctableView"?
If you set a breakpoint in tableView:numberOfRowsInSection: and do "po incidents", are they all loaded there? (It will hit that breakpoint multiple times, so keep hitting continue and make sure the incidents eventually show up there.)
If you have IB in list view mode, your tableView should appear to be nested under your CurrentIncidentsTableViewController. This indicates that the tableView is connected to the UITableViewController's view property. (BTW, if you have this configured property, you can access the tableView from your controller using self.tableView, so you technically don't need ctableView.)
The tableView's dataSource and delegate properties should be connected to your CurrentIncidentsTableViewController in IB.
Try reloading the table after parsing is done .
[table reloadData];
table is the instance of the table view created by interface builder.
This link gives a sample program for parsing the xml and displaying its contents in the table.Have a look at this.Hope this helps you also.
All the best.