forked from eway2012/SampleCode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathParseOperation.m
More file actions
350 lines (297 loc) · 16.1 KB
/
ParseOperation.m
File metadata and controls
350 lines (297 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
/*
File: ParseOperation.m
Abstract: The NSOperation class used to perform the XML parsing of earthquake data.
Version: 1.0
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2011 Apple Inc. All Rights Reserved.
*/
#import "ParseOperation.h"
#import "Earthquake.h"
#import "SeismicXMLAppDelegate.h"
// NSNotification name for sending earthquake data back to the app delegate
NSString *kAddEarthquakesNotif = @"AddEarthquakesNotif";
// NSNotification userInfo key for obtaining the earthquake data
NSString *kEarthquakeResultsKey = @"EarthquakeResultsKey";
// NSNotification name for reporting errors
NSString *kEarthquakesErrorNotif = @"EarthquakeErrorNotif";
// NSNotification userInfo key for obtaining the error message
NSString *kEarthquakesMsgErrorKey = @"EarthquakesMsgErrorKey";
@interface ParseOperation () <NSXMLParserDelegate>
@property (nonatomic, retain) Earthquake *currentEarthquakeObject;
@property (nonatomic, retain) NSMutableArray *currentParseBatch;
@property (nonatomic, retain) NSMutableString *currentParsedCharacterData;
@end
@implementation ParseOperation
@synthesize earthquakeData, currentEarthquakeObject, currentParsedCharacterData, currentParseBatch, managedObjectContext;
- (id)initWithData:(NSData *)parseData
{
if (self = [super init]) {
earthquakeData = [parseData copy];
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
[dateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
// setup our Core Data scratch pad and persistent store
managedObjectContext = [[NSManagedObjectContext alloc] init];
[self.managedObjectContext setUndoManager:nil];
SeismicXMLAppDelegate *appDelegate = (SeismicXMLAppDelegate *)[[UIApplication sharedApplication] delegate];
[self.managedObjectContext setPersistentStoreCoordinator:appDelegate.persistentStoreCoordinator];
parsedEarthquakesCounter = 0;
}
return self;
}
// a batch of earthquakes are ready to be added
- (void)addEarthquakesToList:(NSArray *)earthquakes {
assert([NSThread isMainThread]);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *ent = [NSEntityDescription entityForName:@"Earthquake" inManagedObjectContext:self.managedObjectContext];
fetchRequest.entity = ent;
// narrow the fetch to these two properties
fetchRequest.propertiesToFetch = [NSArray arrayWithObjects:@"location", @"date", nil];
// before adding the earthquake, first check if there's a duplicate in the backing store
NSError *error = nil;
Earthquake *earthquake = nil;
for (earthquake in earthquakes) {
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"location = %@ AND date = %@", earthquake.location, earthquake.date];
NSArray *fetchedItems = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedItems.count == 0) {
// we found no duplicate earthquakes, so insert this new one
[self.managedObjectContext insertObject:earthquake];
}
}
[fetchRequest release];
if (![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate.
// You should not use this function in a shipping application, although it may be useful
// during development. If it is not possible to recover from the error, display an alert
// panel that instructs the user to quit the application by pressing the Home button.
//
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
// the main function for this NSOperation, to start the parsing
- (void)main {
self.currentParseBatch = [NSMutableArray array];
self.currentParsedCharacterData = [NSMutableString string];
// It's also possible to have NSXMLParser download the data, by passing it a URL, but this is
// not desirable because it gives less control over the network, particularly in responding to
// connection errors.
//
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:self.earthquakeData];
[parser setDelegate:self];
[parser parse];
// depending on the total number of earthquakes parsed, the last batch might not have been a
// "full" batch, and thus not been part of the regular batch transfer. So, we check the count of
// the array and, if necessary, send it to the main thread.
//
// first check if the operation has been cancelled, proceed if not
//
if (![self isCancelled]) {
if ([self.currentParseBatch count] > 0) {
[self performSelectorOnMainThread:@selector(addEarthquakesToList:)
withObject:self.currentParseBatch
waitUntilDone:NO];
}
}
self.currentParseBatch = nil;
self.currentEarthquakeObject = nil;
self.currentParsedCharacterData = nil;
[parser release];
}
- (void)dealloc {
[earthquakeData release];
[currentEarthquakeObject release];
[currentParsedCharacterData release];
[currentParseBatch release];
[dateFormatter release];
[managedObjectContext release];
[super dealloc];
}
#pragma mark -
#pragma mark Parser constants
// Limit the number of parsed earthquakes to 100
// (a given day may have more than 100 earthquakes around the world, so we only take the first 100)
//
static const const NSUInteger kMaximumNumberOfEarthquakesToParse = 100;
// When an Earthquake object has been fully constructed, it must be passed to the main thread and
// the table view in RootViewController must be reloaded to display it. It is not efficient to do
// this for every Earthquake object - the overhead in communicating between the threads and reloading
// the table exceed the benefit to the user. Instead, we pass the objects in batches, sized by the
// constant below. In your application, the optimal batch size will vary
// depending on the amount of data in the object and other factors, as appropriate.
//
static NSUInteger const kSizeOfEarthquakeBatch = 20;
// Reduce potential parsing errors by using string constants declared in a single place.
static NSString * const kEntryElementName = @"entry";
static NSString * const kLinkElementName = @"link";
static NSString * const kTitleElementName = @"title";
static NSString * const kUpdatedElementName = @"updated";
static NSString * const kGeoRSSPointElementName = @"georss:point";
#pragma mark -
#pragma mark NSXMLParser delegate methods
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
// If the number of parsed earthquakes is greater than
// kMaximumNumberOfEarthquakesToParse, abort the parse.
//
if (parsedEarthquakesCounter >= kMaximumNumberOfEarthquakesToParse) {
// Use the flag didAbortParsing to distinguish between this deliberate stop
// and other parser errors.
//
didAbortParsing = YES;
[parser abortParsing];
}
if ([elementName isEqualToString:kEntryElementName]) {
// insert new earthquake entities as we discover them
NSEntityDescription *ent = [NSEntityDescription entityForName:@"Earthquake" inManagedObjectContext:self.managedObjectContext];
// create an earthquake managed object, but don't insert it in our moc yet
Earthquake *earthquake = [[Earthquake alloc] initWithEntity:ent insertIntoManagedObjectContext:nil];
self.currentEarthquakeObject = earthquake;
[earthquake release];
} else if ([elementName isEqualToString:kLinkElementName]) {
NSString *relAttribute = [attributeDict valueForKey:@"rel"];
if ([relAttribute isEqualToString:@"alternate"]) {
NSString *USGSWebLink = [attributeDict valueForKey:@"href"];
self.currentEarthquakeObject.USGSWebLink = USGSWebLink;
}
} else if ([elementName isEqualToString:kTitleElementName] ||
[elementName isEqualToString:kUpdatedElementName] ||
[elementName isEqualToString:kGeoRSSPointElementName]) {
// For the 'title', 'updated', or 'georss:point' element begin accumulating parsed character data.
// The contents are collected in parser:foundCharacters:.
accumulatingParsedCharacterData = YES;
// The mutable string needs to be reset to empty.
[currentParsedCharacterData setString:@""];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:kEntryElementName]) {
// note: we keep a temporary array of managed objects while we are parsing.
// We do this to avoid inserting objects into our moc ahead of time, just in case we find duplicate earthquakes
[self.currentParseBatch addObject:self.currentEarthquakeObject];
parsedEarthquakesCounter++;
if ([self.currentParseBatch count] >= kMaximumNumberOfEarthquakesToParse) {
[self performSelectorOnMainThread:@selector(addEarthquakesToList:)
withObject:self.currentParseBatch
waitUntilDone:NO];
self.currentParseBatch = [NSMutableArray array];
}
} else if ([elementName isEqualToString:kTitleElementName]) {
// The title element contains the magnitude and location in the following format:
// <title>M 3.6, Virgin Islands region<title/>
// Extract the magnitude and the location using a scanner:
NSScanner *scanner = [NSScanner scannerWithString:self.currentParsedCharacterData];
// Scan past the "M " before the magnitude.
if ([scanner scanString:@"M " intoString:NULL]) {
CGFloat magnitude;
if ([scanner scanFloat:&magnitude]) {
self.currentEarthquakeObject.magnitude = [NSNumber numberWithFloat:magnitude];
// Scan past the ", " before the title.
if ([scanner scanString:@", " intoString:NULL]) {
NSString *location = nil;
// Scan the remainer of the string.
if ([scanner scanUpToCharactersFromSet:
[NSCharacterSet illegalCharacterSet] intoString:&location]) {
self.currentEarthquakeObject.location = location;
}
}
}
}
} else if ([elementName isEqualToString:kUpdatedElementName]) {
if (self.currentEarthquakeObject != nil) {
self.currentEarthquakeObject.date =
[dateFormatter dateFromString:self.currentParsedCharacterData];
}
else {
// kUpdatedElementName can be found outside an entry element (i.e. in the XML header)
// so don't process it here.
}
} else if ([elementName isEqualToString:kGeoRSSPointElementName]) {
// The georss:point element contains the latitude and longitude of the earthquake epicenter.
// 18.6477 -66.7452
//
NSScanner *scanner = [NSScanner scannerWithString:self.currentParsedCharacterData];
double latitude, longitude;
if ([scanner scanDouble:&latitude]) {
if ([scanner scanDouble:&longitude]) {
self.currentEarthquakeObject.latitude = [NSNumber numberWithDouble:latitude];
self.currentEarthquakeObject.longitude = [NSNumber numberWithDouble:longitude];
}
}
}
// Stop accumulating parsed character data. We won't start again until specific elements begin.
accumulatingParsedCharacterData = NO;
}
// This method is called by the parser when it find parsed character data ("PCDATA") in an element.
// The parser is not guaranteed to deliver all of the parsed character data for an element in a single
// invocation, so it is necessary to accumulate character data until the end of the element is reached.
//
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (accumulatingParsedCharacterData) {
// If the current element is one whose content we care about, append 'string'
// to the property that holds the content of the current element.
//
[self.currentParsedCharacterData appendString:string];
}
}
// an error occurred while parsing the earthquake data,
// post the error as an NSNotification to our app delegate.
//
- (void)handleEarthquakesError:(NSError *)parseError {
[[NSNotificationCenter defaultCenter] postNotificationName:kEarthquakesErrorNotif
object:self
userInfo:[NSDictionary dictionaryWithObject:parseError
forKey:kEarthquakesMsgErrorKey]];
}
// an error occurred while parsing the earthquake data,
// pass the error to the main thread for handling.
// (note: don't report an error if we aborted the parse due to a max limit of earthquakes)
//
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
if ([parseError code] != NSXMLParserDelegateAbortedParseError && !didAbortParsing)
{
[self performSelectorOnMainThread:@selector(handleEarthquakesError:)
withObject:parseError
waitUntilDone:NO];
}
}
@end