- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
我在做大部分的工作.由于某些原因,有时我可以看到NSInputStream刚停止工作.我知道,因为我有一条线
- NSLog(@"stream %@ got event %x",stream,(unsigned)eventCode);
在(void)stream:(NSStream *)的开头处理streamEvent:(NSStreamEvent)eventCode,有时候我会看到输出
- stream <__NSCFInputStream: 0x1f020b00> got event 2
(对应于事件NSStreamEventHasBytesAvailable),然后再没有.不是事件10,对应于NSStreamEventEndEnmitted,不是错误事件,没有!还有时候我甚至得到一个EXC_BAD_ACCESS异常,我现在不知道如何调试.任何帮助将不胜感激.
这是实现.当我点击“提交”按钮时,一切都会启动,这会触发:
- - (IBAction)submit:(id)sender {
- [p_spinner startAnimating];
- [self performSelector: @selector(sendData)
- withObject: nil
- afterDelay: 0];
- }
这里是sendData:
- -(void)sendData{
- ...
- _tempFilePath = ... ;
- [[NSFileManager defaultManager] createFileAtPath:_tempFilePath contents:nil attributes:nil];
- [self setUpStreamsForInputFile: [self.p_mediaURL path] outputFile:_tempFilePath];
- [p_spinner stopAnimating];
- //Pop back to prevIoUs VC
- [self.navigationController popViewControllerAnimated:NO] ;
- }
这里是上面提到的setUpStreamsForInputFile:
- - (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath {
- self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath];
- [p_iStream setDelegate:self];
- [p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
- forMode:NSDefaultRunLoopMode];
- [p_iStream open];
- }
最后,这是大多数逻辑发生的地方:
- - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
- NSLog(@"stream %@ got event %x",(unsigned)eventCode);
- switch(eventCode) {
- case NSStreamEventHasBytesAvailable:
- {
- if (stream == self.p_iStream){
- if(!_tempMutableData) {
- _tempMutableData = [NSMutableData data];
- }
- if ([_streamdata length]==0){ //we want to write to the buffer only when it has been emptied by the output stream
- unsigned int buffer_len = 24000;//read in chunks of 24000
- uint8_t buf[buffer_len];
- unsigned int len = 0;
- len = [p_iStream read:buf maxLength:buffer_len];
- if(len) {
- [_tempMutableData appendBytes:(const void *)buf length:len];
- NSString* base64encData = [Base64 encodeBase64WithData:_tempMutableData];
- _streamdata = [base64encData dataUsingEncoding:NSUTF8StringEncoding]; //encode the data as Base64 string
- [_tempFileHandle writeData:_streamdata];//write the data
- [_tempFileHandle seekToEndOfFile];// and move to the end
- _tempMutableData = [NSMutableData data]; //reset mutable data buffer
- _streamdata = [[NSData alloc] init]; //release the data buffer
- }
- }
- }
- break;
- case NSStreamEventEndEncountered:
- {
- [stream close];
- [stream removeFromRunLoop:[NSRunLoop currentRunLoop]
- forMode:NSDefaultRunLoopMode];
- stream = nil;
- //do some more stuff here...
- ...
- break;
- }
- case NSStreamEventHasSpaceAvailable:
- case NSStreamEventOpenCompleted:
- case NSStreamEventNone:
- {
- ...
- }
- }
- case NSStreamEventErrorOccurred:{
- ...
- }
- }
- }
注意:当我第一次发布时,我的错误印象是这个问题与使用GCD有关.根据Rob在下面的回答,我删除了GCD代码,问题依然存在.
解决方法
当您安排NSInputStream运行在默认的runloop(因此,主线程的runloop)时,主线程处于默认模式(NSDefaultRunLoopMode)时会收到事件.
但是,如果您检查,在某些情况下(例如,在UIScrollView滚动和其他UI更新期间),默认运行环境更改模式.当主runloop处于与NSDefaultRunLoopMode不同的模式时,不会收到您的事件.
您的旧代码与dispatch_async几乎相当(但是在主线程上移动UI更新).您只需添加一些更改:
>在后台调度,这样的东西:
:
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
- dispatch_async(queue,^{
- // your background code
- //end of your code
- [[NSRunLoop currentRunLoop] run]; // start a run loop,look at the next point
- });
>在该线程上启动一个运行循环.必须使用此代码在调度异步调用的最后一行(最后一行)完成此操作
:
- [[NSRunLoop currentRunLoop] run]; // note: this method never returns,so it must be THE LAST LINE of your dispatch
尝试让我知道
为了更清楚,我复制粘贴您的原始代码更新:
- - (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath {
- self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath];
- [p_iStream setDelegate:self];
- // here: change the queue type and use a background queue (you can change priority)
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
- dispatch_async(queue,^ {
- [p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
- forMode:NSDefaultRunLoopMode];
- [p_iStream open];
- // here: start the loop
- [[NSRunLoop currentRunLoop] run];
- // note: all code below this line won't be executed,because the above method NEVER returns.
- });
- }
进行此修改后,您的
- - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {}
方法,将在您启动运行循环的同一线程上调用一个后台线程:如果需要更新UI,重要的是再次发送到主线程.
额外信息:
在我的代码中,我在随机的后台队列中使用dispatch_async(在一个可用的后台线程上调度你的代码,或者如果需要的话可以启动一个新的代码),所有这些都是“自动的”).如果您愿意,您可以启动自己的线程,而不是使用dispatch异步.
此外,在发送“运行”消息之前,我不检查一个运行循环是否已经运行(但是您可以使用currentMode方法检查它,查看NSRunLoop参考以获取更多信息).它不应该是必要的,因为每个线程只有一个关联的NSRunLoop实例,所以发送另一个运行(如果已经运行)没有什么不好:-)
你甚至可以避免直接使用runLoops,并使用dispatch_source切换到一个完整的GCD方法,但是我从未直接使用它,所以我现在不能给你一个“好的示例代码”