尝试导入包含问题解答的问题,答案和类别的XML文件。当我尝试运行它时,LB,OP和DP应该有3个类别(它们在XML文件中可见)。当我运行应用程序时,它似乎将所有方法分配给LB类别。我错过了什么导致所有人被分配到LB类别?感谢。
howToXMLImporter.h
#import <UIKit/UIKit.h>
#import <libxml/tree.h>
@class howToXMLImporter, HowTos, HowToCategory, HowToCategoryCache;
@protocol howToXMLImporterDelegate <NSObject>
@optional
- (void)importerDidSave:(NSNotification *)saveNotification;
- (void)importerDidFinishParsingData:(howToXMLImporter *)importer;
- (void)importer:(howToXMLImporter *)importer didFailWithError:(NSError *)error;
@end
@interface howToXMLImporter : NSOperation {
@private
id <howToXMLImporterDelegate> __unsafe_unretained delegate;
// Reference to the libxml parser context
xmlParserCtxtPtr context;
NSURLConnection *xmlConnection;
BOOL done;
BOOL parsingAHowTo;
BOOL storingCharacters;
NSMutableData *characterBuffer;
HowTos *currentHowTo; // Current how-to importer working with
// The number of parsed how-tos is tracked so that the autorelease pool for the parsing thread can be periodically emptied to keep the memory footprint under control.
NSUInteger countForCurrentBatch;
NSManagedObjectContext *insertionContext;
NSPersistentStoreCoordinator *persistentStoreCoordinator;
NSEntityDescription *howToEntityDescription;
HowToCategoryCache *theCache;
NSURL *aogURL; // Source of the how-to XML file
}
@property (nonatomic, retain) NSURL *aogURL;
@property (nonatomic, assign) id <howToXMLImporterDelegate> delegate;
@property (nonatomic, retain) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (nonatomic, retain, readonly) NSManagedObjectContext *insertionContext;
@property (nonatomic, retain, readonly) NSEntityDescription *howToEntityDescription;
@property (nonatomic, retain, readonly) HowToCategoryCache *theCache;
- (void)main;
@end
howToXMLImporter.m
#import "howToXMLImporter.h"
#import "HowTos.h"
#import "HowToCategory.h"
#import "HowToCategoryCache.h"
#import <libxml/tree.h>
// Function prototypes for SAX callbacks.
static void startElementSAX(void *context, 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 *context, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI);
static void charactersFoundSAX(void *context, const xmlChar *characters, int length);
static void errorEncounteredSAX(void *context, const char *errorMessage, ...);
// Forward reference. The structure is defined in full at the end of the file.
static xmlSAXHandler simpleSAXHandlerStruct;
// Class extension for private properties and methods.
@interface howToXMLImporter ()
@property BOOL storingCharacters;
@property (nonatomic, retain) NSMutableData *characterBuffer;
@property BOOL done;
@property BOOL parsingAHowTo;
@property NSUInteger countForCurrentBatch;
@property (nonatomic, retain) HowTos *currentHowTo;
@property (nonatomic, retain) NSURLConnection *xmlConnection;
@property (nonatomic, retain) NSDateFormatter *dateFormatter;
@end
static double lookuptime = 0;
@implementation howToXMLImporter
// Pull in these variables
@synthesize aogURL, delegate, persistentStoreCoordinator;
@synthesize xmlConnection, done, parsingAHowTo, storingCharacters, currentHowTo, countForCurrentBatch, characterBuffer;
- (void)main {
if (delegate && [delegate respondsToSelector:@selector(importerDidSave:)]) {
[[NSNotificationCenter defaultCenter] addObserver:delegate selector:@selector(importerDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.insertionContext];
}
done = NO;
self.characterBuffer = [NSMutableData data];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:aogURL];
// create the connection with the request and start loading the data
xmlConnection = [[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.
context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, (__bridge void *)(self), NULL, 0, NULL);
if (xmlConnection != nil) {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}
// Release resources used only in this thread.
xmlFreeParserCtxt(context);
self.characterBuffer = nil;
self.dateFormatter = nil;
self.xmlConnection = nil;
self.currentHowTo = nil;
theCache = nil;
NSError *saveError = nil;
NSAssert1([insertionContext save:&saveError], @"Unhandled error saving managed object context in import thread: %@", [saveError localizedDescription]);
if (delegate && [delegate respondsToSelector:@selector(importerDidSave:)]) {
[[NSNotificationCenter defaultCenter] removeObserver:delegate name:NSManagedObjectContextDidSaveNotification object:self.insertionContext];
}
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(importerDidFinishParsingData:)]) {
[self.delegate importerDidFinishParsingData:self];
}
}
- (NSManagedObjectContext *)insertionContext {
if (insertionContext == nil) {
insertionContext = [[NSManagedObjectContext alloc] init];
[insertionContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
return insertionContext;
}
- (void)forwardError:(NSError *)error {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(importer:didFailWithError:)]) {
[self.delegate importer:self didFailWithError:error];
}
}
- (NSEntityDescription *)howToEntityDescription {
if (howToEntityDescription == nil) {
howToEntityDescription = [NSEntityDescription entityForName:@"HowTo" inManagedObjectContext:self.insertionContext];
}
return howToEntityDescription;
}
- (HowToCategoryCache *)theCache {
if (theCache == nil) {
theCache = [[HowToCategoryCache alloc] init];
theCache.managedObjectContext = self.insertionContext;
}
return theCache;
}
- (HowTos *)currentHowTo {
if (currentHowTo == nil) {
currentHowTo = [[HowTos alloc] initWithEntity:self.howToEntityDescription insertIntoManagedObjectContext:self.insertionContext];
}
return currentHowTo;
}
// Forward errors to the delegate.
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self performSelectorOnMainThread:@selector(forwardError:) withObject:error waitUntilDone:NO];
done = YES; // Ends the run loop.
}
// Called when a chunk of data has been downloaded.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Process downloaded chunk of data.
xmlParseChunk(context, (const char *)[data bytes], [data length], 0);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Signal the context that parsing is complete by passing "1" as the last parameter.
xmlParseChunk(context, NULL, 0, 1);
context = NULL;
done = YES; // Ends the run loop.
}
static const NSUInteger kImportBatchSize = 20;
- (void)finishedCurrentHowTo {
parsingAHowTo = NO;
self.currentHowTo = nil;
countForCurrentBatch++;
if (countForCurrentBatch == kImportBatchSize) {
NSError *saveError = nil;
NSAssert1([insertionContext save:&saveError], @"Unhandled error saving managed object context in how-to import thread: %@", [saveError localizedDescription]);
countForCurrentBatch = 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
NSString *currentString = [[NSString alloc] initWithData:characterBuffer encoding:NSUTF8StringEncoding];
[characterBuffer setLength:0];
return currentString;
}
@end
// XML element names and their string lengths for parsing comparison; include null terminator
static const char *kName_HowTos = "How_Tos"; // Container tag for product
static const NSUInteger kLength_HowTos = 8;
static const char *kName_Question = "how-to_question"; // Didn't include how-to_id for now
static const NSUInteger kLength_Question = 16;
static const char *kName_Answer = "how-to_answer";
static const NSUInteger kLength_Answer = 14;
static const char *kName_Category = "how-to_category";
static const NSUInteger kLength_Category = 16;
static void startElementSAX(void *parsingContext, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI,
int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes) {
howToXMLImporter *importer = (__bridge howToXMLImporter *)parsingContext;
// The second parameter to strncmp is the name of the element, which we know 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 (!strncmp((const char *)localname, kName_HowTos, kLength_HowTos)) {
importer.parsingAHowTo = YES;
// May want to set default variable values here
} else if (importer.parsingAHowTo &&
(
(!strncmp((const char *)localname, kName_Question, kLength_Question) ||
!strncmp((const char *)localname, kName_Answer, kLength_Answer) ||
!strncmp((const char *)localname, kName_Category, kLength_Category)
)
)
) {
importer.storingCharacters = YES;
}
}
// This callback is invoked when the parser reaches the end of a node
static void endElementSAX(void *parsingContext, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI) {
howToXMLImporter *importer = (__bridge howToXMLImporter *)parsingContext;
if (importer.parsingAHowTo == NO) return;
if (!strncmp((const char *)localname, kName_HowTos, kLength_HowTos)) {
[importer finishedCurrentHowTo];
} else if (!strncmp((const char *)localname, kName_Question, kLength_Question)) {
importer.currentHowTo.question = importer.currentString;
NSLog(@"Question: %@",importer.currentHowTo.question);
} else if (!strncmp((const char *)localname, kName_Answer, kLength_Answer)) {
importer.currentHowTo.answer = importer.currentString;
NSLog(@"Answer: %@",importer.currentHowTo.answer);
} else if (!strncmp((const char *)localname, kName_Category, kLength_Category)) {
double before = [NSDate timeIntervalSinceReferenceDate];
HowToCategory *category = [importer.theCache howToCategoryWithName:importer.currentString];
double delta = [NSDate timeIntervalSinceReferenceDate] - before;
lookuptime += delta;
importer.currentHowTo.category = category;
NSLog(@"Category: %@",importer.currentHowTo.category.name);
}
importer.storingCharacters = NO;
}
// This callback is invoked when the parser encounters character data inside a node. Importer class determines how to use the character data.
static void charactersFoundSAX(void *parsingContext, const xmlChar *characterArray, int numberOfCharacters) {
howToXMLImporter *importer = (__bridge howToXMLImporter *)parsingContext;
// A state variable, "storingCharacters", is set when nodes of interest begin and end. Determines whether character data is handled or ignored.
if (importer.storingCharacters == NO) return;
[importer appendCharacters:(const char *)characterArray length:numberOfCharacters];
}
static void errorEncounteredSAX(void *parsingContext, const char *errorMessage, ...) {
// Handle errors as appropriate
NSCAssert(NO, @"Unhandled error encountered during SAX parse.");
}
HowToCategoryCache.h
#import <Foundation/Foundation.h>
@class HowToCategory;
@interface HowToCategoryCache : NSObject {
NSManagedObjectContext *managedObjectContext;
NSUInteger cacheSize; // Number of objects that can be cached
NSMutableDictionary *cache; // A dictionary holds the actual cached items
NSEntityDescription *howToCategoryEntityDescription;
NSPredicate *howToCategoryNamePredicateTemplate;
NSUInteger accessCounter; // Counter used to determine the least recently touched item.
// Some basic metrics are tracked to help determine the optimal cache size for the problem.
CGFloat totalCacheHitCost;
CGFloat totalCacheMissCost;
NSUInteger cacheHitCount;
NSUInteger cacheMissCount;
}
@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
@property NSUInteger cacheSize;
@property (nonatomic, strong) NSMutableDictionary *cache;
@property (nonatomic, strong, readonly) NSEntityDescription *howToCategoryEntityDescription;
@property (nonatomic, strong, readonly) NSPredicate *howToCategoryNamePredicateTemplate;
- (HowToCategory *)howToCategoryWithName:(NSString *)name;
@end
HowToCategoryCache.m
#import "HowToCategoryCache.h"
#import "HowToCategory.h"
// CacheNode is a simple object to help with tracking cached items
@interface HowToCacheNode : NSObject {
NSManagedObjectID *objectID;
NSUInteger accessCounter;
}
@property (nonatomic, strong) NSManagedObjectID *objectID;
@property NSUInteger accessCounter;
@end
@implementation HowToCacheNode
@synthesize objectID, accessCounter;
@end
@implementation HowToCategoryCache
@synthesize managedObjectContext, cacheSize, cache;
- (id)init {
self = [super init];
if (self != nil) {
cacheSize = 15;
accessCounter = 0;
cache = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (cacheHitCount > 0) NSLog(@"average cache hit cost: %f", totalCacheHitCost/cacheHitCount);
if (cacheMissCount > 0) NSLog(@"average cache miss cost: %f", totalCacheMissCost/cacheMissCount);
howToCategoryEntityDescription = nil;
howToCategoryNamePredicateTemplate = nil;
}
// Implement the "set" accessor rather than depending on @synthesize so that we can set up registration for context save notifications.
- (void)setManagedObjectContext:(NSManagedObjectContext *)aContext {
if (managedObjectContext) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:managedObjectContext];
}
managedObjectContext = aContext;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(managedObjectContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:managedObjectContext];
}
// When a managed object is first created, it has a temporary managed object ID. When the managed object context in which it was created is saved, the temporary ID is replaced with a permanent ID. The temporary IDs can no longer be used to retrieve valid managed objects. The cache handles the save notification by iterating through its cache nodes and removing any nodes with temporary IDs.
- (void)managedObjectContextDidSave:(NSNotification *)notification {
HowToCacheNode *cacheNode = nil;
NSMutableArray *keys = [NSMutableArray array];
for (NSString *key in cache) {
cacheNode = [cache objectForKey:key];
if ([cacheNode.objectID isTemporaryID]) {
[keys addObject:key];
}
}
[cache removeObjectsForKeys:keys];
}
- (NSEntityDescription *)howToCategoryEntityDescription {
if (howToCategoryEntityDescription == nil) {
howToCategoryEntityDescription = [NSEntityDescription entityForName:@"HowToCategory" inManagedObjectContext:managedObjectContext];
}
return howToCategoryEntityDescription;
}
static NSString * const kCategoryNameSubstitutionVariable = @"NAME";
- (NSPredicate *)categoryNamePredicateTemplate {
if (howToCategoryNamePredicateTemplate == nil) {
NSExpression *leftHand = [NSExpression expressionForKeyPath:@"name"];
NSExpression *rightHand = [NSExpression expressionForVariable:kCategoryNameSubstitutionVariable];
howToCategoryNamePredicateTemplate = [[NSComparisonPredicate alloc] initWithLeftExpression:leftHand rightExpression:rightHand modifier:NSDirectPredicateModifier type:NSLikePredicateOperatorType options:0];
}
return howToCategoryNamePredicateTemplate;
}
#define USE_CACHING // Undefine this macro to compare performance without caching
- (HowToCategory *)howToCategoryWithName:(NSString *)name {
NSTimeInterval before = [NSDate timeIntervalSinceReferenceDate];
#ifdef USE_CACHING
// check cache
HowToCacheNode *cacheNode = [cache objectForKey:name];
if (cacheNode != nil) {
cacheNode.accessCounter = accessCounter++; // cache hit, update access counter
HowToCategory *category = (HowToCategory *)[self.managedObjectContext objectWithID:cacheNode.objectID];
totalCacheHitCost += ([NSDate timeIntervalSinceReferenceDate] - before);
cacheHitCount++;
return category;
}
#endif
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // cache missed, fetch from store - if not found in store there is no category object for the name and we must create one
[fetchRequest setEntity:self.howToCategoryEntityDescription];
NSLog(@"Fetch Request: %@",fetchRequest);
NSPredicate *predicate = [self.howToCategoryNamePredicateTemplate predicateWithSubstitutionVariables:[NSDictionary dictionaryWithObject:name forKey:kCategoryNameSubstitutionVariable]];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSAssert1(fetchResults != nil, @"Unhandled error executing fetch request in import thread: %@", [error localizedDescription]);
HowToCategory *category = nil;
if ([fetchResults count] > 0) {
category = [fetchResults objectAtIndex:0]; // get category from fetch
} else if ([fetchResults count] == 0) {
// category not in store, must create a new category object
category = [[HowToCategory alloc] initWithEntity:self.howToCategoryEntityDescription insertIntoManagedObjectContext:managedObjectContext];
category.name = name; //name
// Set the sort order for the category
if ([category.name isEqualToString:@"LB"]) {
category.order = [NSNumber numberWithInt:1];
} else if ([category.name isEqualToString:@"DP"]) {
category.order = [NSNumber numberWithInt:2];
} else if ([category.name isEqualToString:@"OP"]) {
category.order = [NSNumber numberWithInt:3];
}
}
#ifdef USE_CACHING
// add to cache
// first check to see if cache is full
if ([cache count] >= cacheSize) {
// evict least recently used (LRU) item from cache
NSUInteger oldestAccessCount = UINT_MAX;
NSString *key = nil, *keyOfOldestCacheNode = nil;
for (key in cache) {
HowToCacheNode *tmpNode = [cache objectForKey:key];
if (tmpNode.accessCounter < oldestAccessCount) {
oldestAccessCount = tmpNode.accessCounter;
keyOfOldestCacheNode = key;
}
}
// retain the cache node for reuse
cacheNode = [cache objectForKey:keyOfOldestCacheNode];
// remove from the cache
if (keyOfOldestCacheNode != nil)
[cache removeObjectForKey:keyOfOldestCacheNode];
} else {
// create a new cache node
cacheNode = [[HowToCacheNode alloc] init];
}
cacheNode.objectID = [category objectID];
cacheNode.accessCounter = accessCounter++;
[cache setObject:cacheNode forKey:name];
#endif
totalCacheMissCost += ([NSDate timeIntervalSinceReferenceDate] - before);
cacheMissCount++;
return category;
}
@end