SeismicXML例子程序演示如何使用NSXMLParser分析XML数据,主要分析从USGS的RSS feed。USGS提供世界各地的最近地震。它显示每个地震的位置,日期,震级。同时用颜色图形显示地震的严重性。此XML在后台分析且用分析的对象更新地震table view。
此代码中包含了一些比较好的思想,如多线程分析xml数据,异步下载,NSNotificationCente(在两个类中传递数据),UIActionSheet类实现,和其它一些技术细节。
官方例子相关:
SeismicXML地址:http://developer.apple.com/iphone/library/samplecode/SeismicXML/SeismicXML.zip
一、程序的运行效果
二、程序的思路
多线程的好处即提高程序执行效率,防止界面阻塞。
1、进入程序后,建立给定网址(RSS feed)的连接,程序会自动下载对应的RSS feed数据,如果网络连接无误,数据自动全部下载,这是XCode提供的内部回调函数。
2、增加消息中心,以便地震RSS feed解析成功或失败时通知
3、建立一个线程,把下载好的数据传递给新建线程类,把此线程添加到线程队列,通过注册的消息中心来通知被处理的数据,这点与LazyTableImages有区别,LazyTableImage是利用XCode的delegate等待线程结束后返回被处理后的数据。
3、在创建的线程中用NSXMLParser根据程序要求分析下载的数据,并添加到数组中。全部解析之后,通知AppDelegate
4、从线程类接到解析成功通知后,根据通知参数NSNotification指针,调用table view视图rootViewController,插入解析好的数组
下面记录一些具体的代码实现
1、网络连接并下载数据
声明
@interface SeismicXMLAppDelegate : NSObject
{
NSURLConnection *earthquakeFeedConnection;
NSMutableData *earthquakeData;
}
实现
@interface SeismicXMLAppDelegate ()
@property (nonatomic, retain) NSURLConnection *earthquakeFeedConnection;
@property (nonatomic, retain) NSMutableData *earthquakeData;
@end
#pragma mark -
#pragma mark SeismicXMLAppDelegate
@implementation SeismicXMLAppDelegate
@synthesize earthquakeFeedConnection;
@synthesize earthquakeData;
- (void)dealloc
{
[earthquakeFeedConnection cancel];
[earthquakeFeedConnection release];
[earthquakeData release];
[super dealloc];
}
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
static NSString *feedURLString = @"http://earthquake.usgs.gov/eqcenter/catalogs/7day-M2.5.xml";
NSURLRequest *earthquakeURLRequest =
[NSURLRequest requestWithURL:[NSURL URLWithString:feedURLString]];
self.earthquakeFeedConnection =
[[[NSURLConnection alloc] initWithRequest:earthquakeURLRequest delegate:self] autorelease];
NSAssert(self.earthquakeFeedConnection != nil, @"Failure to create URL connection.");
}
#pragma mark -
#pragma mark NSURLConnection delegate methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ((([httpResponse statusCode]/100) == 2) && [[response MIMEType] isEqual:@"application/atom+xml"])
{
self.earthquakeData = [NSMutableData data];
}
else
{
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:
NSLocalizedString(@"HTTP Error",
@"Error message displayed when receving a connection error.")
forKey:NSLocalizedDescriptionKey];
NSError *error = [NSError errorWithDomain:@"HTTP" code:[httpResponse statusCode] userInfo:userInfo];
[self handleError:error];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[earthquakeData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if ([error code] == kCFURLErrorNotConnectedToInternet)
{
NSDictionary *userInfo =
[NSDictionary dictionaryWithObject:
NSLocalizedString(@"No Connection Error",
@"Error message displayed when not connected to the Internet.")
forKey:NSLocalizedDescriptionKey];
NSError *noConnectionError = [NSError errorWithDomain:NSCocoaErrorDomain
code:kCFURLErrorNotConnectedToInternet
userInfo:userInfo];
[self handleError:noConnectionError];
}
else
{
// otherwise handle the error generically
[self handleError:error];
}
self.earthquakeFeedConnection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
self.earthquakeFeedConnection = nil;
ParseOperation *parseOperation = [[ParseOperation alloc] initWithData:self.earthquakeData];
[self.parseQueue addOperation:parseOperation];
[parseOperation release]; // once added to the NSOperationQueue it's retained, we don't need it anymore
self.earthquakeData = nil;
}
2、创建线程队列并把新建线程增加到队列中
头文件
@interface SeismicXMLAppDelegate : NSObject
{
@private
NSOperationQueue *parseQueue;
}
实现
#import "ParseOperation.h"
#pragma mark SeismicXMLAppDelegate ()
@interface SeismicXMLAppDelegate ()
@property (nonatomic, retain) NSOperationQueue *parseQueue;
@end
#pragma mark -
#pragma mark SeismicXMLAppDelegate
@implementation SeismicXMLAppDelegate
@synthesize parseQueue;
- (void)dealloc
{
[parseQueue release];
[super dealloc];
}
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
parseQueue = [NSOperationQueue new];
}
#pragma mark -
#pragma mark NSURLConnection delegate methods
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
ParseOperation *parseOperation = [[ParseOperation alloc] initWithData:self.earthquakeData];
[self.parseQueue addOperation:parseOperation];
[parseOperation release];
}
@end
3、增加通知中心
AppDelegate中实现
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:kAddEarthquakesNotif object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kEarthquakesErrorNotif object:nil];
[super dealloc];
}
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(addEarthquakes:)
name:kAddEarthquakesNotif
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(earthquakesError:)
name:kEarthquakesErrorNotif
object:nil];
}
- (void)addEarthquakes:(NSNotification *)notif
{
assert([NSThread isMainThread]);
[self addEarthquakesToList:[[notif userInfo] valueForKey:kEarthquakeResultsKey]];
}
- (void)earthquakesError:(NSNotification *)notif
{
assert([NSThread isMainThread]);
[self handleError:[[notif userInfo] valueForKey:kEarthquakesMsgErrorKey]];
}
- (void)addEarthquakesToList:(NSArray *)earthquakes
{
[self.rootViewController insertEarthquakes:earthquakes];
}
线程头文件ParseOperation.h
extern NSString *kAddEarthquakesNotif;
extern NSString *kEarthquakeResultsKey;
extern NSString *kEarthquakesErrorNotif;
extern NSString *kEarthquakesMsgErrorKey;
线程实现文件ParseOperation.m
NSString *kAddEarthquakesNotif = @"AddEarthquakesNotif";
NSString *kEarthquakeResultsKey = @"EarthquakeResultsKey";
NSString *kEarthquakesErrorNotif = @"EarthquakeErrorNotif";
NSString *kEarthquakesMsgErrorKey = @"EarthquakesMsgErrorKey";
- (void)addEarthquakesToList:(NSArray *)earthquakes
{
assert([NSThread isMainThread]);
[[NSNotificationCenter defaultCenter] postNotificationName:kAddEarthquakesNotif
object:self
userInfo:[NSDictionary dictionaryWithObject:earthquakes
forKey:kEarthquakeResultsKey]];
}
- (void)handleEarthquakesError:(NSError *)parseError {
[[NSNotificationCenter defaultCenter] postNotificationName:kEarthquakesErrorNotif
object:self
userInfo:[NSDictionary dictionaryWithObject:parseError
forKey:kEarthquakesMsgErrorKey]];
}
4、格式化日期
头文件:ParseOperation.h
@private
NSDateFormatter *dateFormatter;
实现文件ParseOperation.m
- (id)initWithData:(NSData *)parseData
{
if (self = [super init])
{
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
//Aug 5,2011 11:39:30 PM
[dateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
}
}
- (void)dealloc
{
[dateFormatter release];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
self.currentEarthquakeObject.date =
[dateFormatter dateFromString:self.currentParsedCharacterData];
}
头文件:RootViewController.h
@interface RootViewController : UITableViewController {
NSDateFormatter *dateFormatter;
}
@property (nonatomic, retain, readonly) NSDateFormatter *dateFormatter;
实现文件RootViewController.m
- (void)dealloc {
[dateFormatter release];
[super dealloc];
}
- (NSDateFormatter *)dateFormatter {
if (dateFormatter == nil) {
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
}
return dateFormatter;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
dateLabel.text = [NSString stringWithFormat:@"%@", [self.dateFormatter stringFromDate:earthquake.date]];
return cell;
}
5、分解字符串
// M 3.6, Virgin Islands region
NSScanner *scanner = [NSScanner scannerWithString:self.currentParsedCharacterData];
if ([scanner scanString:@"M " intoString:NULL])
{
CGFloat magnitude;
if ([scanner scanFloat:&magnitude])
{
self.currentEarthquakeObject.magnitude = magnitude;
if ([scanner scanString:@", " intoString:NULL])
{
NSString *location = nil;
if ([scanner scanUpToCharactersFromSet:[NSCharacterSet illegalCharacterSet] intoString:&location])
{
self.currentEarthquakeObject.location = location;
}
}
}
}
// 18.6477 -66.7452
NSScanner *scanner = [NSScanner scannerWithString:self.currentParsedCharacterData];
double latitude, longitude;
if ([scanner scanDouble:&latitude])
{
if ([scanner scanDouble:&longitude])
{
self.currentEarthquakeObject.latitude = latitude;
self.currentEarthquakeObject.longitude = longitude;
}
}
6、KVO模型
- (void)viewDidLoad {
[super viewDidLoad];
self.earthquakeList = [NSMutableArray array];
// KVO: listen for changes to our earthquake data source for table view updates
[self addObserver:self forKeyPath:@"earthquakeList" options:0 context:NULL];
}
- (void)viewDidUnload {
[super viewDidUnload];
self.earthquakeList = nil;
[self removeObserver:self forKeyPath:@"earthquakeList"];
}
#pragma mark -
#pragma mark KVO support
- (void)insertEarthquakes:(NSArray *)earthquakes
{
// this will allow us as an observer to notified (see observeValueForKeyPath)
// so we can update our UITableView
//
[self willChangeValueForKey:@"earthquakeList"];
[self.earthquakeList addObjectsFromArray:earthquakes];
[self didChangeValueForKey:@"earthquakeList"];
}
// listen for changes to the earthquake list coming from our app delegate.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
[self.tableView reloadData];
}
7、自定义cell,使用view的Tags而不是子类化cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Each subview in the cell will be identified by a unique tag.
static NSUInteger const kLocationLabelTag = 2;
static NSUInteger const kDateLabelTag = 3;
static NSUInteger const kMagnitudeLabelTag = 4;
static NSUInteger const kMagnitudeImageTag = 5;
// Declare references to the subviews which will display the earthquake data.
UILabel *locationLabel = nil;
UILabel *dateLabel = nil;
UILabel *magnitudeLabel = nil;
UIImageView *magnitudeImage = nil;
static NSString *kEarthquakeCellID = @"EarthquakeCellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kEarthquakeCellID];
if (cell == nil)
{
// No reusable cell was available, so we create a new cell and configure its subviews.
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:kEarthquakeCellID] autorelease];
locationLabel = [[[UILabel alloc] initWithFrame:CGRectMake(10, 3, 190, 20)] autorelease];
locationLabel.tag = kLocationLabelTag;
locationLabel.font = [UIFont boldSystemFontOfSize:14];
[cell.contentView addSubview:locationLabel];
dateLabel = [[[UILabel alloc] initWithFrame:CGRectMake(10, 28, 170, 14)] autorelease];
dateLabel.tag = kDateLabelTag;
dateLabel.font = [UIFont systemFontOfSize:10];
[cell.contentView addSubview:dateLabel];
magnitudeLabel = [[[UILabel alloc] initWithFrame:CGRectMake(277, 9, 170, 29)] autorelease];
magnitudeLabel.tag = kMagnitudeLabelTag;
magnitudeLabel.font = [UIFont boldSystemFontOfSize:24];
magnitudeLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
[cell.contentView addSubview:magnitudeLabel];
magnitudeImage = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"5.0.png"]] autorelease];
CGRect imageFrame = magnitudeImage.frame;
imageFrame.origin = CGPointMake(180, 2);
magnitudeImage.frame = imageFrame;
magnitudeImage.tag = kMagnitudeImageTag;
magnitudeImage.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
[cell.contentView addSubview:magnitudeImage];
}
else
{
// A reusable cell was available, so we just need to get a reference to the subviews
// using their tags.
locationLabel = (UILabel *)[cell.contentView viewWithTag:kLocationLabelTag];
dateLabel = (UILabel *)[cell.contentView viewWithTag:kDateLabelTag];
magnitudeLabel = (UILabel *)[cell.contentView viewWithTag:kMagnitudeLabelTag];
magnitudeImage = (UIImageView *)[cell.contentView viewWithTag:kMagnitudeImageTag];
}
// Get the specific earthquake for this row.
Earthquake *earthquake = [earthquakeList objectAtIndex:indexPath.row];
// Set the relevant data for each subview in the cell.
locationLabel.text = earthquake.location;
dateLabel.text = [NSString stringWithFormat:@"%@", [self.dateFormatter stringFromDate:earthquake.date]];
magnitudeLabel.text = [NSString stringWithFormat:@"%.1f", earthquake.magnitude];
magnitudeImage.image = [self imageForMagnitude:earthquake.magnitude];
return cell;
}
8、UIActionSheet的使用
@interface RootViewController : UITableViewController {
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIActionSheet *sheet =
[[UIActionSheet alloc] initWithTitle:
NSLocalizedString(@"External App Sheet Title",
@"Title for sheet displayed with options for displaying Earthquake data in other applications")
delegate:self
cancelButtonTitle:NSLocalizedString(@"Cancel", @"Cancel")
destructiveButtonTitle:nil
otherButtonTitles:NSLocalizedString(@"Show USGS Site in Safari", @"Show USGS Site in Safari"),
NSLocalizedString(@"Show Location in Maps", @"Show Location in Maps"),
nil];
[sheet showInView:self.view];
[sheet release];
}
// Called when the user selects an option in the sheet. The sheet will automatically be dismissed.
- (void)actionSheet:(UIActionSheet *)actionSheet willDismissWithButtonIndex:(NSInteger)buttonIndex
{
NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow];
Earthquake *earthquake = (Earthquake *)[earthquakeList objectAtIndex:selectedIndexPath.row];
switch (buttonIndex)
{
case 0:
{
NSURL *webLink = [earthquake USGSWebLink];
[[UIApplication sharedApplication] openURL:webLink];
}
break;
case 1:
{
NSString *mapsQuery =
[NSString stringWithFormat:@"http://maps.google.com/maps?z=6&t=h&ll=%f,%f",
earthquake.latitude, earthquake.longitude];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:mapsQuery]];
} break;
default:
break;
}
[self.tableView deselectRowAtIndexPath:selectedIndexPath animated:YES];
}
三、结束语
官方例子中一般有比较好的程序思路和结构设计,代码健壮性也比较好。