2010-09-24 30 views
13

我正在用Dave DeLong的極好的CHCSVParser爲Objective-C打造一個非常長的.CSV文件,並且遇到了一些使用它的麻煩。我會使用arrayWithContentsOfCSVFile方法,但我在iPhone上運行代碼並將整個文件解析到內存中會佔用比可用內存更多的內存。如何使用CHCSVParser類

在我的代碼中,解析器打開文檔並完美地調用委託方法,但委託中的哪些位置會在每行之後停止並訪問數據(以創建Core數據對象並將其保存到數據存儲) ?我認爲這將在- (void) parser:(CHCSVParser *)parser didEndLine:(NSUInteger)lineNumber中,但是如何在解析器完成該行時獲得NSArray(或其他)的數據?

這裏是我到目前爲止的代碼:

// 
// The code from a method in my view controller: 
// 
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
NSString *documentsDirectory = [paths objectAtIndex:0]; 
NSFileManager *manager = [NSFileManager defaultManager]; 
NSError *err = nil; 
NSArray *fileList = [manager contentsOfDirectoryAtPath:documentsDirectory error:&err]; 
NSString *fileName = [fileList objectAtIndex:1]; 
NSURL *inputFileURL = [NSURL fileURLWithPath: [documentsDirectory stringByAppendingPathComponent:fileName]]; 


NSStringEncoding encoding = 0; 
CHCSVParser *p = [[CHCSVParser alloc] initWithContentsOfCSVFile:[inputFileURL path] usedEncoding:&encoding error:nil]; 
[p setParserDelegate:self]; 
[p parse]; 
[p release]; 

... 

#pragma mark - 
#pragma mark CHCSVParserDelegate methods 

- (void) parser:(CHCSVParser *)parser didStartDocument:(NSString *)csvFile { 
    NSLog(@"Parser started!"); 
} 

- (void) parser:(CHCSVParser *)parser didStartLine:(NSUInteger)lineNumber { 
    //NSLog(@"Parser started line: %i", lineNumber); 
} 

- (void) parser:(CHCSVParser *)parser didEndLine:(NSUInteger)lineNumber { 
    NSLog(@"Parser ended line: %i", lineNumber); 
} 

- (void) parser:(CHCSVParser *)parser didReadField:(NSString *)field { 
    //NSLog(@"Parser didReadField: %@", field); 
} 

- (void) parser:(CHCSVParser *)parser didEndDocument:(NSString *)csvFile { 
    NSLog(@"Parser ended document: %@", csvFile); 
} 

- (void) parser:(CHCSVParser *)parser didFailWithError:(NSError *)error { 
    NSLog(@"Parser failed with error: %@ %@", [error localizedDescription], [error userInfo]); 
} 

謝謝!

回答

17

我很高興看到我的代碼被證明是有用的! :)

CHCSVParser行爲類似於NSXMLParser,因爲每次發現有趣的事情時,它都會通過其中一個委託回調讓您知道。但是,如果您選擇忽略它在回調中提供的數據,那麼它就消失了。這些解析器(CHCSVParserNSXMLParser)非常愚蠢。他們只知道他們試圖解析的東西的格式,但除此之外並沒有太多的作用。

所以,簡而言之,答案就是「你必須自己保存」。如果您查看NSArray類別的代碼,您將在.m文件中看到它使用a simple NSObject subclass as the parser delegate,並且該子類是將這些字段集合到一個數組中,然後將該數組添加到整個數組中。你需要做類似的事情。

實例代表:

@interface CSVParserDelegate : NSObject <CHCSVParserDelegate> { 
    NSMutableArray * currentRow; 
} 
@end 

@implementation CSVParserDelegate 

- (void) parser:(CHCSVParser *)parser didStartLine:(NSUInteger)lineNumber { 
    currentRow = [[NSMutableArray alloc] init]; 
} 
- (void) parser:(CHCSVParser *)parser didReadField:(NSString *)field { 
    [currentRow addObject:field]; 
} 
- (void) parser:(CHCSVParser *)parser didEndLine:(NSUInteger)lineNumber { 
    NSLog(@"finished line! %@", currentRow); 
    [self doSomethingWithLine:currentRow]; 
    [currentRow release], currentRow = nil; 
} 
@end 

不過,我可以說服修改解析器聚集該行本身的行爲,但如果我走這條路線,爲什麼不把語法分析器合計整個文件? (答:不應該)

+0

感謝您的答案 - 我不知道解析器是否爲您做了這項工作(通過'currentChunk'或其他方式)!不必手動進行聚合就可以了,但對於大多數應用程序而言,CSV解析僅在少數幾個地方用於I/O。如果有人想只寫一點代碼,是否可以子類化或者在CHCSVParserDelegate上寫一個類別? – 2010-09-24 18:41:57

+0

@Neal'CHCSVParserDelegate'是一個協議,所以你不能繼承它。爲了使這種行爲發揮作用,你必須直接改變'CHCSVParser'或者對其進行子類化。最簡單的答案是自己總結線路(就像我在答案中一樣) – 2010-09-24 18:45:44

+0

不這麼認爲......非常感謝這裏的精彩工作!實際上,對你的代碼進行小幅更正......在'didEndLine'中,我們需要將'currentLine'改爲'currentRow'。 (我會改變它,但沒有足夠的XP;)) – 2010-09-24 18:50:02

1

我今天試着用這個,基於@ DaveDeLong的優秀答案和代碼,但是我認爲這個軟件自從他(2010)的回答以來就已經被修改了。在寫這篇文章的時候,我發現我不得不使用這樣的:

@interface CSVParserDelegate : NSObject <CHCSVParserDelegate> { 
    NSMutableArray * currentRow; 
} 
@end 

@implementation CSVParserDelegate 

- (void) parser:(CHCSVParser *)parser didBeginLine:(NSUInteger)lineNumber { 
    currentRow = [[NSMutableArray alloc] init]; 
} 
- (void) parser:(CHCSVParser *)parser didReadField:(NSString *)field atIndex:(NSInteger)fieldIndex { 
    [currentRow addObject:field]; 
} 
- (void) parser:(CHCSVParser *)parser didEndLine:(NSUInteger)lineNumber { 
    NSLog(@"finished line! %@", currentRow); 
    [self doSomethingWithLine:currentRow]; 
    [currentRow release], currentRow = nil; 
} 
@end 

parser:didStartLine:lineNumber:已成爲parser:didBeginLine:lineNumber:parser:didReadField:已成爲parser:didReadField:atIndex: