进阶:后台文件 I/O
篇幅中的示例应用中有点小错误,这里纠正一下:
1.读取文件流的四个阶段事件处理代码如下,我做了简单的注解:
#pragma mark- 处理事件流
- (void)stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventOpenCompleted: {
NSLog(@"NSStreamEventOpenCompleted");
// open 打开完成
break;
}
case NSStreamEventEndEncountered: {
NSLog(@"NSStreamEventEndEncountered");
// 读取结束
NSLog(@"waitUntilAllOperationsAreFinished");
[self.queue waitUntilAllOperationsAreFinished];
NSLog(@"waitUntilAllOperationsAreFinished >> Finished.");
[self emitLineWithData:self.remainder];
self.remainder = nil;
[self.inputStream close];
self.inputStream = nil;
[self.queue addOperationWithBlock:^{
self.completion(self.lineNumber);
}];
break;
}
case NSStreamEventErrorOccurred: {
NSLog(@"NSStreamEventErrorOccurred");
// 读取错误
NSLog(@"error"); // TODO
break;
}
case NSStreamEventHasBytesAvailable: {
NSLog(@"NSStreamEventHasBytesAvailable");
// 读取数据块过程
NSMutableData *buffer = [NSMutableData dataWithLength:4 * 1024];
// 至少读取4 * 1024字节数据,返回实际读取的数据长度
NSUInteger length = (NSUInteger) [self.inputStream read:[buffer mutableBytes] maxLength:[buffer length]];
if (0 < length) {
[buffer setLength:length];
__weak id weakSelf = self;
[self.queue addOperationWithBlock:^{
// 读取到数据之后交给队列处理
[weakSelf processDataChunk:buffer];
}];
}
break;
}
default: {
break;
}
}
}
在NSStreamEventHasBytesAvailable阶段,通过inputStream读取到数据之后,都将数据交由self.queue addOperationWithBlock在子线程中处理(注意:这个线程不会并发执行,是一个串行队列,所以不用担心processDataChunk的读取会错乱),因此有可能在子线程还没有完全处理结束的时候,就已经进入了NSStreamEventEndEncountered阶段,这会造成数据错乱的,因此我们需要做一个简单的处理,那就是当执行到NSStreamEventEndEncountered阶段时,让queue等待所有的block被执行完毕,再执行接下来的扫尾工作。一行代码:
[self.queue waitUntilAllOperationsAreFinished];
另外这个lineNumber计算的也不太正确,总是比正常的行数大1,也做修正了。
修改过的代码我后续提交到Github上,先粘贴在这里吧:
AppDelegate.m
//
// AppDelegate.m
// InputStreamTest
//
// Created by Chris Eidhof on 06/17/13.
// Copyright (c) 2013 Chris Eidhof. All rights reserved.
//
#import "AppDelegate.h"
#import "Reader.h"
@interface AppDelegate ()
@property (nonatomic, strong) Reader *reader;
@property (nonatomic, strong) UIButton *button;
@property (nonatomic, strong) UILabel *label;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self addViews];
[self.window makeKeyAndVisible];
return YES;
}
- (void)addViews
{
UIViewController *controller = [[UIViewController alloc] init];
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:controller];
self.button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.button.frame = CGRectMake(0, 100, 300, 100);
[self.button addTarget:self action:@selector(import:) forControlEvents:UIControlEventTouchUpInside];
[self.button setTitle:@"Press Me" forState:UIControlStateNormal];
[controller.view addSubview:self.button];
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(0, 220, 300, 64)];
slider.continuous = YES;
[slider addTarget:self action:@selector(sliderMoved:) forControlEvents:UIControlEventValueChanged];
[controller.view addSubview:slider];
self.label = [[UILabel alloc] initWithFrame:CGRectMake(0, 300, 200, 64)];
self.label.textAlignment = NSTextAlignmentCenter;
[controller.view addSubview:self.label];
}
- (void)sliderMoved:(UISlider *)sender;
{
self.label.text = [NSString stringWithFormat:@"%g", [sender value]];
}
- (void)import:(id)sender
{
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"Clarissa Harlowe" withExtension:@"txt"];
NSAssert([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]], @"Please download the sample data");
self.reader = [[Reader alloc] initWithFileAtURL:fileURL];
[self.reader enumerateLinesWithBlock:^(NSUInteger i, NSString *line){
if ((i % 2000ull) == 0) {
// NSLog(@"i: %lu", (unsigned long)i);
// NSLog(@"i: %lu, line -> %@", (unsigned long)i, line);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.button setTitle:line forState:UIControlStateNormal];
}];
}
} completionHandler:^(NSUInteger numberOfLines){
// NSLog(@"lines: %lu", (unsigned long)numberOfLines);
[self.button setTitle:@"Done" forState:UIControlStateNormal];
}];
}
@end
Reader.m
//
// Created by chris on 6/17/13.
//
#import "Reader.h"
#import "NSData+EnumerateComponents.h"
@interface Reader () <NSStreamDelegate>
@property (nonatomic, strong) NSInputStream* inputStream;
@property (nonatomic, strong) NSURL *fileURL;
@property (nonatomic, copy) NSData *delimiter;
@property (nonatomic, strong) NSMutableData *remainder;
@property (nonatomic, copy) void (^callback) (NSUInteger lineNumber, NSString* line);
@property (nonatomic, copy) void (^completion) (NSUInteger numberOfLines);
@property (nonatomic) NSUInteger lineNumber;
@property (nonatomic, strong) NSOperationQueue *queue;
@end
@implementation Reader
- (void)enumerateLinesWithBlock:(void (^)(NSUInteger lineNumber, NSString *line))block completionHandler:(void (^)(NSUInteger numberOfLines))completion;
{
// 负责读取数据的自定义队列
if (self.queue == nil) {
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;
}
NSAssert(self.queue.maxConcurrentOperationCount == 1, @"Queue can't be concurrent.");
NSAssert(self.inputStream == nil, @"Cannot process multiple input streams in parallel");
self.callback = block;
self.completion = completion;
self.inputStream = [NSInputStream inputStreamWithURL:self.fileURL];
self.inputStream.delegate = self;
NSAssert([NSRunLoop mainRunLoop] == [NSRunLoop currentRunLoop], @"Must in Main Run Loop.");
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
}
- (id)initWithFileAtURL:(NSURL *)fileURL;
{
if (![fileURL isFileURL]) {
return nil;
}
self = [super init];
if (self) {
self.fileURL = fileURL;
self.delimiter = [@"
" dataUsingEncoding:NSUTF8StringEncoding];
}
return self;
}
#pragma mark- 处理事件流
- (void)stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventOpenCompleted: {
NSLog(@"NSStreamEventOpenCompleted");
// open 打开完成
break;
}
case NSStreamEventEndEncountered: {
NSLog(@"NSStreamEventEndEncountered");
// 读取结束
NSLog(@"waitUntilAllOperationsAreFinished");
[self.queue waitUntilAllOperationsAreFinished];
NSLog(@"waitUntilAllOperationsAreFinished >> Finished.");
[self emitLineWithData:self.remainder];
self.remainder = nil;
[self.inputStream close];
self.inputStream = nil;
[self.queue addOperationWithBlock:^{
self.completion(self.lineNumber);
}];
break;
}
case NSStreamEventErrorOccurred: {
NSLog(@"NSStreamEventErrorOccurred");
// 读取错误
NSLog(@"error"); // TODO
break;
}
case NSStreamEventHasBytesAvailable: {
NSLog(@"NSStreamEventHasBytesAvailable");
// 读取数据块过程
NSMutableData *buffer = [NSMutableData dataWithLength:4 * 1024];
// 至少读取4 * 1024字节数据,返回实际读取的数据长度
NSUInteger length = (NSUInteger) [self.inputStream read:[buffer mutableBytes] maxLength:[buffer length]];
if (0 < length) {
[buffer setLength:length];
__weak id weakSelf = self;
[self.queue addOperationWithBlock:^{
// 读取到数据之后交给队列处理
[weakSelf processDataChunk:buffer];
}];
}
break;
}
default: {
break;
}
}
}
- (void)processDataChunk:(NSMutableData *)buffer;
{
if (self.remainder != nil) {
[self.remainder appendData:buffer];
} else {
self.remainder = buffer;
}
[self.remainder obj_enumerateComponentsSeparatedBy:self.delimiter usingBlock:^(NSData* component, BOOL last){
// 读取数据块过程中,遇到一个完整的新行时,做提交处理(!last -> emitLineWithData)
if (!last) {
[self emitLineWithData:component];
// 如果读取到末尾时发现没有换行符,则把剩下的这些暂存,下次读取到块数据时,remainder会appendData进新的块
} else if (0 < [component length]) {
self.remainder = [component mutableCopy];
} else {
// 有可能末尾刚好是一个换行符
self.remainder = nil;
}
// if (last) {
// NSLog(@"last is true");
// }
}];
}
// 读到一行数据
- (void)emitLineWithData:(NSData *)data;
{
NSUInteger lineNumber = self.lineNumber + 1;
self.lineNumber = lineNumber;
if (0 < data.length) {
// 转换成字符串并回调
NSString *line = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@">> %@", line);
self.callback(lineNumber, line);
}
}
@end
NSData+EnumerateComponents.m
//
// Created by chris on 6/17/13.
//
#import "NSData+EnumerateComponents.h"
@implementation NSData (EnumerateComponents)
/**
* 读取数据
*
* @param delimiter 分隔符
* @param block 数据回调
*/
- (void)obj_enumerateComponentsSeparatedBy:(NSData*)delimiter usingBlock:(void (^)(NSData*, BOOL finalBlock) )block
{
NSUInteger loc = 0;
while (YES) {
// 查找新行
NSRange rangeOfNewline = [self rangeOfData:delimiter options:0 range:NSMakeRange(loc, self.length - loc)];
if (rangeOfNewline.location == NSNotFound) {
break;
}
NSRange rangeWithDelimiter = NSMakeRange(loc, rangeOfNewline.location - loc + delimiter.length);
NSData *chunkWithDelimiter = [self subdataWithRange:rangeWithDelimiter];
block(chunkWithDelimiter, NO);
loc = NSMaxRange(rangeWithDelimiter);
}
// 读取剩下的
NSData *remainder = [self subdataWithRange:NSMakeRange(loc, self.length - loc)];
block(remainder, YES);
}
@end
需要调试的话,把那个示例txt(Clarissa Harlowe.txt)中的内容修改一下,简单放置几行即可。