You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift URLSession连接立即关闭,Postman可保持(索尼电视PIN请求异常)

索尼电视PIN请求功能在iOS中的异常问题

问题描述

我在iOS应用中实现索尼电视的PIN请求功能,目标是让电视上的PIN输入窗口保持打开状态,直到用户输入PIN。但遇到异常:使用Swift的URLSession时连接会立即关闭,而相同请求在Postman中可正常运行。

Postman中发送请求时,电视上的PIN窗口能正常保持打开,会收到预期的401响应;仅当连续快速发送两次请求时,才会关闭PIN窗口。但在iPhone模拟器中运行Swift代码时,PIN窗口短暂出现后立即关闭。

我的Swift代码

let parameters = """
        {
            "method": "actRegister",
            "params": [
                {
                    "clientid": "MyDevice:1",
                    "nickname": "My Device",
                    "level": "private"
                },
                [
                    {
                        "value": "yes",
                        "function": "WOL"
                    }
                ]
            ],
            "id": 1,
            "version": "1.0"
        }
        """
        
guard let postData = parameters.data(using: .utf8) else {
    completion(.failure(NSError(domain: "Invalid data", code: 0, userInfo: nil)))
    return
}
        
var request = URLRequest(url: URL(string: "http://\(ipAddress)/sony/accessControl")!,timeoutInterval: 30)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = postData
        
let task = URLSession.shared.dataTask(with: request) { data, response, error in
    if let error = error {
        completion(.failure(error))
        return
    }
            
    guard let data = data else {
        completion(.failure(NSError(domain: "No data received", code: 0, userInfo: nil)))
        return
    }
            
    if let responseString = String(data: data, encoding: .utf8) {
        print("Response: \(responseString)")
    }
}
    
task.resume()

已尝试的解决方法

  • 增加timeoutInterval时长
  • 使用URLSession.shared.dataTask和自定义配置的URLSession
  • 实现URLSessionDataDelegate相关方法

预期与实际行为

  • 预期行为:PIN窗口应保持打开,直到用户输入PIN或超时;
  • 实际行为:PIN窗口短暂出现后立即关闭。

问题

  1. 为何Postman与Swift代码的行为存在差异?
  2. 如何修改Swift代码以保持连接并让电视上的PIN窗口持续显示?
  3. 是否可以阻止URLSession在收到401响应后自动关闭连接?

环境信息

  • iOS 15+
  • Swift 5
  • Xcode 13+

解答

1. Postman与Swift代码行为差异的原因

Postman在处理401响应时,默认不会立即关闭连接,甚至会保持连接等待后续身份验证交互;而URLSession的默认行为是收到响应后立即终止连接,尤其是当索尼电视API需要持续连接来维持PIN窗口时,Swift请求会快速结束,触发电视关闭PIN窗口。

另外,URLSession可能自动处理连接复用或关闭逻辑,比如收到401后直接断开;而Postman的底层HTTP客户端默认开启Keep-Alive且不会主动断开,连接保持策略存在差异。

2. 修改Swift代码的方案

要维持PIN窗口,核心是让HTTP连接保持打开状态,直到用户输入PIN或超时,可尝试以下方式:

方式一:通过自定义Delegate控制连接生命周期

实现URLSessionDataDelegate,在收到401响应时保持连接活跃,不立即结束任务:

class PinSessionDelegate: NSObject, URLSessionDataDelegate {
    var completion: ((Result<Data, Error>) -> Void)?
    var receivedData = Data()
    
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        // 收到401时允许继续保持连接
        if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 401 {
            completionHandler(.allow)
        } else {
            completionHandler(.cancel)
        }
    }
    
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        receivedData.append(data)
    }
    
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error {
            completion?(.failure(error))
        } else {
            completion?(.success(receivedData))
        }
    }
}

// 发起请求的方法
func sendPinRequest(ipAddress: String, completion: @escaping (Result<Data, Error>) -> Void) {
    let parameters = """
        {
            "method": "actRegister",
            "params": [
                {
                    "clientid": "MyDevice:1",
                    "nickname": "My Device",
                    "level": "private"
                },
                [
                    {
                        "value": "yes",
                        "function": "WOL"
                    }
                ]
            ],
            "id": 1,
            "version": "1.0"
        }
        """
    
    guard let postData = parameters.data(using: .utf8),
          let url = URL(string: "http://\(ipAddress)/sony/accessControl") else {
        completion(.failure(NSError(domain: "Invalid data or URL", code: 0, userInfo: nil)))
        return
    }
    
    let config = URLSessionConfiguration.default
    config.timeoutIntervalForRequest = 60 // 延长超时时间
    config.httpShouldUsePipelining = false
    config.httpMaximumConnectionsPerHost = 1
    
    let delegate = PinSessionDelegate()
    delegate.completion = completion
    
    let session = URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
    var request = URLRequest(url: url, timeoutInterval: 60)
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("keep-alive", forHTTPHeaderField: "Connection") // 显式指定保持连接
    request.httpMethod = "POST"
    request.httpBody = postData
    
    let task = session.dataTask(with: request)
    task.resume()
}
方式二:完全复刻Postman的请求头

检查Postman发送请求时的所有请求头,将其全部复制到Swift请求中,尤其是User-Agent、Connection等字段——索尼电视API可能对这些字段有校验,导致不同行为:

// 添加Postman风格的请求头
request.addValue("PostmanRuntime/7.32.3", forHTTPHeaderField: "User-Agent")
request.addValue("keep-alive", forHTTPHeaderField: "Connection")
request.addValue("gzip, deflate, br", forHTTPHeaderField: "Accept-Encoding")

3. 是否可以阻止URLSession在收到401后自动关闭连接?

可以。通过自定义URLSessionDataDelegate的didReceive response:方法,在收到401响应时调用completionHandler(.allow)而非.cancel,即可让URLSession保持连接。不过需要手动管理任务生命周期,比如用户输入PIN后手动取消任务,或等待超时结束。


内容的提问来源于stack exchange,提问作者Sorrow91

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 09:35:03