Classes/CocoaXMLParser.m
/* |
Copyright (C) 2015 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Subclass of iTunesRSSParser that uses the Foundation framework's NSXMLParser for parsing the XML data. |
*/ |
#import "CocoaXMLParser.h" |
#import "Song.h" |
@interface CocoaXMLParser () |
// A string containing the contents of the current song data to be parsed. |
@property (nonatomic, strong) NSMutableString *currentString; |
// A reference to the current song the parser is working with. |
@property (nonatomic, strong) Song *currentSong; |
// The following state variable deals with getting character data from XML elements. |
@property (nonatomic, assign) BOOL storingCharacters; |
@property (nonatomic, strong) NSDateFormatter *parseFormatter; |
// Overall state of the parser, used to exit the run loop. |
@property (nonatomic, assign) BOOL done; |
// the queue to run our parse operation |
@property (nonatomic, strong) NSOperationQueue *queue; |
@end |
#pragma mark - |
@implementation CocoaXMLParser |
+ (NSString *)parserName { |
return @"NSXMLParser"; |
} |
+ (XMLParserType)parserType { |
return XMLParserTypeNSXMLParser; |
} |
@synthesize currentString, currentSong, parseFormatter; |
- (void)startDownload:(NSURL *)url { |
NSURLRequest *request = [NSURLRequest requestWithURL:url]; |
// create a session data task to obtain and the XML feed |
NSURLSessionDataTask *sessionTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { |
// in case we want to know the response status code |
//NSInteger HTTPStatusCode = [(NSHTTPURLResponse *)response statusCode]; |
_done = YES; |
if (error != nil) |
{ |
[[NSOperationQueue mainQueue] addOperationWithBlock: ^{ |
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO; |
if (error.code == NSURLErrorAppTransportSecurityRequiresSecureConnection) |
{ |
// if you get error NSURLErrorAppTransportSecurityRequiresSecureConnection (-1022), |
// then your Info.plist has not been properly configured to match the target server. |
// |
abort(); |
} |
else |
{ |
NSLog(@"An error occured in '%@': error[%ld] %@", |
NSStringFromSelector(_cmd), (long)error.code, error.localizedDescription); |
} |
}]; |
} |
else |
{ |
dispatch_async(dispatch_get_main_queue(), ^(void) { |
[self downloadEnded]; |
}); |
// continue our work by pasing the resulting data |
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; |
parser.delegate = self; |
self.currentString = [NSMutableString string]; |
NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate]; |
[parser parse]; |
NSTimeInterval duration = [NSDate timeIntervalSinceReferenceDate] - start; |
[self performSelectorOnMainThread:@selector(addToParseDuration:) |
withObject:@(duration) |
waitUntilDone:NO]; |
dispatch_async(dispatch_get_main_queue(), ^(void) { |
[self parseEnded]; |
}); |
self.currentString = nil; |
} |
}]; |
// start loading the data |
[self downloadStarted]; |
[sessionTask resume]; |
} |
- (void)downloadAndParse:(NSURL *)url { |
_done = NO; |
self.parseFormatter = [[NSDateFormatter alloc] init]; |
parseFormatter.dateStyle = NSDateFormatterLongStyle; |
parseFormatter.timeStyle = 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.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"US"]; |
[[NSURLCache sharedURLCache] removeAllCachedResponses]; |
// call startDownload, which starts downloading the songs |
[self performSelectorOnMainThread:@selector(startDownload:) withObject:url waitUntilDone:NO]; |
// this loop runs until all the data is downloaded |
// done is set to YES in the completion block once the downloading is finished |
do { |
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; |
}while (!_done); |
} |
#pragma mark Parsing support methods |
- (void)finishedCurrentSong { |
// 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 performSelectorOnMainThread:@selector(parsedSong:) |
withObject:currentSong |
waitUntilDone:NO]; |
self.currentSong = nil; |
} |
#pragma mark NSXMLParser Parsing Callbacks |
// Constants for the XML element names that will be considered during the parse. |
// Declaring these as static constants reduces the number of objects created during the run |
// and is less prone to programmer error. |
// |
static NSString *kName_Item = @"item"; |
static NSString *kName_Title = @"title"; |
static NSString *kName_Category = @"category"; |
static NSString *kName_Artist = @"itms:artist"; |
static NSString *kName_Album = @"itms:album"; |
static NSString *kName_ReleaseDate = @"itms:releasedate"; |
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *) qualifiedName attributes:(NSDictionary *)attributeDict { |
if ([elementName isEqualToString:kName_Item]) { |
self.currentSong = [[Song alloc] init]; |
} else if ([elementName isEqualToString:kName_Title] || [elementName isEqualToString:kName_Category] || [elementName isEqualToString:kName_Artist] || [elementName isEqualToString:kName_Album] || [elementName isEqualToString:kName_ReleaseDate]) { |
[currentString setString:@""]; |
_storingCharacters = YES; |
} |
} |
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { |
if ([elementName isEqualToString:kName_Item]) { |
[self finishedCurrentSong]; |
} else if ([elementName isEqualToString:kName_Title]) { |
currentSong.title = currentString; |
} else if ([elementName isEqualToString:kName_Category]) { |
currentSong.category = currentString; |
} else if ([elementName isEqualToString:kName_Artist]) { |
currentSong.artist = currentString; |
} else if ([elementName isEqualToString:kName_Album]) { |
currentSong.album = currentString; |
} else if ([elementName isEqualToString:kName_ReleaseDate]) { |
currentSong.releaseDate = [parseFormatter dateFromString:currentString]; |
} |
_storingCharacters = NO; |
} |
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { |
if (_storingCharacters) [currentString appendString:string]; |
} |
/* |
A production application should include robust error handling as part of its parsing implementation. |
The specifics of how errors are handled depends on the application. |
*/ |
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError { |
// Handle errors as appropriate for your application. |
} |
@end |
Copyright © 2015 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2015-09-16