Swift 如何处理异步流响应

Swift 如何处理异步流响应,swift,asynchronous,grand-central-dispatch,Swift,Asynchronous,Grand Central Dispatch,我打开了一个到我拥有的服务器的流。当我发送文本“PING”时,它的响应是“PONG”。我已成功连接、发送消息并收到回复。下面我有一个非常简单的测试 问题 我想处理来自服务器的消息。目前,我发了三个乒乓球,后来我收到了三个乒乓球。服务器会立即用PONG响应,但我的代码在主线程完成之前不会处理响应。预期的结果是在发送PING之后立即看到PONG消息,因为它们是并发处理的 我尝试的 差不多,你在下面看到的。我想“我想在发送消息的同时处理流响应,所以我需要在另一个线程上这样做”。因此,我通过GCD将Ru

我打开了一个到我拥有的服务器的流。当我发送文本“PING”时,它的响应是“PONG”。我已成功连接、发送消息并收到回复。下面我有一个非常简单的测试

问题
我想处理来自服务器的消息。目前,我发了三个乒乓球,后来我收到了三个乒乓球。服务器会立即用PONG响应,但我的代码在主线程完成之前不会处理响应。预期的结果是在发送PING之后立即看到PONG消息,因为它们是并发处理的

我尝试的
差不多,你在下面看到的。我想“我想在发送消息的同时处理流响应,所以我需要在另一个线程上这样做”。因此,我通过GCD将RunLoop放在另一个线程中。这没有帮助…无法理解如何使
StreamDelegate
在单独的线程中处理其委托方法
stream

当前控制台结果

PING
PING
PING
PONG
PONG
PONG
所需的控制台结果

PING
PONG
PING
PONG
PING
PONG
代码

import Foundation
import XCTest

class StreamTests: XCTestCase, StreamDelegate {

    var inputStream: InputStream?
    var outputStream: OutputStream?

    let url: URL = URL(string: "http://theserver.com:4222")!

    func testAsyncStream() {

        self.setupStream()

        let ping = "PING".data(using: String.Encoding.utf8)!

        print("PING")
        self.outputStream?.writeStreamWhenReady(ping)
        sleep(1)

        print("PING")
        self.outputStream?.writeStreamWhenReady(ping)
        sleep(1)

        print("PING")
        self.outputStream?.writeStreamWhenReady(ping)
        sleep(1)

    }

    private func setupStream() {

        guard let host = url.host, let port = url.port else { print("Failed URL parse"); return }

        var readStream: Unmanaged<CFReadStream>?
        var writeStream: Unmanaged<CFWriteStream>?

        CFStreamCreatePairWithSocketToHost(nil, host as CFString!, UInt32(port), &readStream, &writeStream)

        self.inputStream = readStream!.takeRetainedValue() as InputStream
        self.outputStream = writeStream!.takeRetainedValue() as OutputStream

        guard let inStream = self.inputStream, let outStream = self.outputStream else { return }

        inStream.open()
        outStream.open()

        DispatchQueue.global(qos: .utility).sync { [weak self] in

            for stream in [inStream, outStream] {
                stream.delegate = self
                stream.schedule(in: .current, forMode: .defaultRunLoopMode)
            }

            RunLoop.current.run(mode: .defaultRunLoopMode, before: Date.distantFuture)

        }
    }

    func stream(_ aStream: Stream, handle eventCode: Stream.Event) {

        switch aStream {
        case inputStream!:

            switch eventCode {
            case [.hasBytesAvailable]:
                print("PONG")
                break
            default:
                break
            }

        default:
            break
        }

    }

}
<代码>导入基础 导入测试 类StreamTests:XCTestCase,StreamDelegate{ 变量inputStream:inputStream? var outputStream:outputStream? 让url:url=url(字符串:http://theserver.com:4222")! func testAsyncStream(){ self.setupStream() 让ping=“ping”。数据(使用:String.Encoding.utf8)! 打印(“PING”) self.outputStream?.writeStreamWhenReady(ping) 睡眠(1) 打印(“PING”) self.outputStream?.writeStreamWhenReady(ping) 睡眠(1) 打印(“PING”) self.outputStream?.writeStreamWhenReady(ping) 睡眠(1) } 专用函数setupStream(){ guard let host=url.host,let port=url.port else{print(“url解析失败”);return} var readStream:非托管? var writeStream:非托管? CFStreamCreatePairWithSocketToHost(无,主机为CFString!,UInt32(端口),&readStream,&writeStream) self.inputStream=readStream!。将retainedvalue()作为inputStream self.outputStream=writeStream!.takeRetainedValue()作为outputStream 保护let-inStream=self.inputStream,let-outtream=self.outputStream-else{return} 流内开放() 外扩 DispatchQueue.global(qos:.utility).sync{[weak self]在中 用于流入[流入,流出]{ stream.delegate=self stream.schedule(在:.current,forMode:.defaultRunLoopMode中) } RunLoop.current.run(模式:.defaultRunLoopMode,在:Date.distantFuture之前) } } func stream(aStream:stream,句柄事件代码:stream.Event){ 转辙机{ 案例输入流!: 开关事件代码{ 案例[.hasBytesAvailable]: 打印(“PONG”) 打破 违约: 打破 } 违约: 打破 } } }
不要尝试为多线程代码编写单元测试。以后它会咬你的屁股

在多个线程上运行的单元测试代码之所以很难,是因为您无法控制线程的执行顺序,也无法控制每个线程分配的时间——这是操作系统的决定

因此,为了确保在另一个线程上提交的代码执行并填充预期的数据,您需要在足够长的时间内阻止主线程(单元测试通常在其中运行),以确保另一个线程完成工作

现在,棘手的部分是找出这段时间应该是多少。如果设置得太短,您将看到单元测试的随机失败;如果设置得太长,您将越来越多地增加单元测试的持续时间。理论上,等待另一个线程完成所需的时间没有上限,因为这超出了我们的控制范围(请记住,操作系统决定下一个选择哪个线程以及分配给它的时间)

更糟糕的是,当这样的单元测试在CI机器上开始失败,但在您的机器上没有失败时,谁该负责:CI机器太慢,或者您的代码在某些情况下仅在CI机器上出现错误?这种模糊性会导致浪费大量时间,试图找出被测试代码的黑客行为

结论:不要试图为在不同线程上执行部分工作的代码编写单元测试。原因很简单:健壮的单元测试需要控制测试代码的所有输入,而第二个线程不是它可以控制的(除非您模拟线程调度,但这是另一回事)


相反,将尽可能多的逻辑放入单线程方法中,并测试这些方法。最后,大多数错误都是由于不正确的业务逻辑造成的。

不要尝试为多线程代码编写单元测试。以后它会咬你的屁股

在多个线程上运行的单元测试代码之所以很难,是因为您无法控制线程的执行顺序,也无法控制每个线程分配的时间——这是操作系统的决定

因此,为了确保在另一个线程上提交的代码执行并填充预期的数据,您需要在足够长的时间内阻止主线程(单元测试通常在其中运行),以确保另一个线程完成工作

现在,棘手的部分是找出这段时间应该是多少。如果设置得太短,您将看到单元测试的随机失败;如果设置得太长,您将越来越多地增加单元测试的持续时间。理论上,等待另一个线程完成所需的时间没有上限,因为这超出了我们的控制范围(记住,