如何在另一个线程上正确打开和关闭NSStream

时间:2012-06-11 13:18:24

标签: ios multithreading nsstream

我有一个应用程序在另一个线程上使用NSStream连接到服务器。如果用户决定退出,应用程序也会关闭连接。问题是我永远无法在用户断开连接时成功关闭流或线程。下面是关于我如何为网络创建线程并尝试关闭流的代码示例:

+ (NSThread*)networkThread
{
    static NSThread *networkThread = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        networkThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkThreadMain:) object:nil];

        [networkThread start];
    });

    return networkThread;
}

+ (void)networkThreadMain:(id)sender
{
    while (YES)
    {
        @autoreleasepool {
            [[NSRunLoop currentRunLoop] run];
        }
    }
}

- (void)scheduleInThread:(id)sender
{
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    [inputStream open];
}

- (void)closeThread
{    
    [inputStream close];
    [inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    [inputStream release];
    inputStream = nil;
}

尝试连接输入流时调用:

[self performSelector:@selector(scheduleInThread:) onThread:[[self class] networkThread] withObject:nil waitUntilDone:YES];

非常感谢任何建议。

1 个答案:

答案 0 :(得分:1)

混合静态变量和实例变量的方式令人困惑。你愿意那样做吗?如果将其放在NSOperation中,然后使用NSOperationQueue运行它,我认为您将获得更加整洁的封装。该操作将管理自己的异步线程,因此您不必这样做。另外,我强烈建议您使用ARC。

一些注意事项:

  1. 确保设置流的委托并处理委托事件。您可能应该在操作内部执行此操作(使操作成为委托),并关闭流并在必要时完成操作。
  2. 除了NSStreamStatusClosed之外,流还可能存在其他故障情况,例如NSStreamStatusNotOpen等。您可能需要添加其他处理,这可以通过侦听委托方法来完成。
  3. 您的代码可能无法正常工作,主要是因为while循环永远运行runloop。您必须具备突破的条件。 NSOperation为您提供了一些相当不错的标准化方法。
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface AsyncStreamOperation : NSOperation

@end

NS_ASSUME_NONNULL_END
#import "AsyncStreamOperation.h"

@interface AsyncStreamOperation ()

@property (atomic, strong) AsyncStreamOperation *config;

@property (atomic, strong) NSInputStream *stream;

@property (atomic, assign, getter=isExecuting) BOOL executing;
@property (atomic, assign, getter=isFinished) BOOL finished;

@end

@implementation AsyncStreamOperation

@synthesize executing = _executing;
@synthesize finished = _finished;

- (instancetype)initWithStream:(NSInputStream *)stream
{
    self = [super init];
    
    if(self) {
        _stream = stream;
    }
    
    return self;
}

- (BOOL)isAsynchronous
{
    return YES;
}

- (BOOL)isExecuting
{
    @synchronized (self) {
        return _executing;
    }
}

- (void)setExecuting:(BOOL)executing
{
    @synchronized (self) {
        [self willChangeValueForKey:@"isExecuting"];
        _executing = executing;
        [self didChangeValueForKey:@"isExecuting"];
    }
}

- (BOOL)isFinished
{
    @synchronized (self) {
        return _finished;
    }
}

- (void)setFinished:(BOOL)finished
{
    @synchronized (self) {
        [self willChangeValueForKey:@"isFinished"];
        _finished = finished;
        [self didChangeValueForKey:@"isFinished"];
    }
}

- (void)start
{
    // Get runloop
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    
    // Schedule stream
    [self.stream scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
    [self.stream open];
    
    // Loop until finished
    // NOTE: If -cancel is not called, you need to add your own logic to close the stream so this loop ends and the operation completes
    while(self.executing && !self.finished && !self.cancelled && self.stream.streamStatus != NSStreamStatusClosed) {
        @autoreleasepool {
            // Maximum speed once per second or CPU goes through the roof
            [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
        }
    }

    self.executing = NO;
    self.finished = YES;
}

- (void)cancel
{
    [super cancel];
    
    [self.stream close];
    
    self.executing = NO;
    self.finished = YES;
}

@end