8

我有一个应用程序在另一个线程上使用 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];

任何意见是极大的赞赏。

4

1 回答 1

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

于 2020-07-08T19:07:40.550 回答