Внутренние экземпляры URLSessionURLSessionWebSocketTask не освобождаются в памяти.
у меня проблема сURLSession
иURLSessionWebSocketTask
что внутренние экземпляры классов не освобождаются после признания их недействительными и отмены в обоих классах.
Я ожидал, что экземпляры классовNSURLError
,NSURL
,CFURLCache
... быть освобождены после аннулирования и отмены их, но, как видно на скриншоте, они не освобождаются. Я сделал скриншот ниже после двойного подключения и отключения.
Кажется, я что-то пропустил в своем коде, может кто-нибудь взглянуть на мой код? Есть идеи или предложения? Спасибо!
Вот мой репозиторий для этого случая: https://github.com/Joohae/URLSessionIssueSample
и исходный код, файлы — это тот же файл из репозитория выше.
ViewController.swift
import UIKit
class ViewController: UIViewController {
public var webSocket: VKWebsocket?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let button = UIButton()
button.layer.borderColor = UIColor.black.cgColor
button.layer.borderWidth = 1
button.setTitle("Hit me!", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.addTarget(self, action: #selector(didTapButton(_:)), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
view.addConstraints([
NSLayoutConstraint(item: button, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0),
NSLayoutConstraint(item: button, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0),
NSLayoutConstraint(item: button, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 200),
NSLayoutConstraint(item: button, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 100),
])
}
@objc func didTapButton(_ button: UIButton) {
if webSocket == nil {
connect()
} else {
disconnect()
}
}
private func connect() {
Task {
webSocket = VKWebsocket(request: URLRequest(url: URL(string: "wss://socketsbay.com/wss/v2/1/demo/")!))
await webSocket?.setDelegate(self)
await webSocket?.connect()
}
}
private func disconnect() {
Task {
await webSocket?.disconnect()
}
}
}
extension ViewController: WebsocketDelegate {
func websocket(_ websocket: VKWebsocket, didReceive message: URLSessionWebSocketTask.Message) {
print("\(#function): \(message)")
}
func websocket(_ websocket: VKWebsocket, didError error: Error?) {
if error != nil {
print("\(#function): \(String(describing: error))")
} else {
print("\(#function): the message has been sent")
}
}
func websocket(_ websocket: VKWebsocket, didChange connectionState: VKWebsocket.ConnectionState) {
Task {
print("\(#function): \(connectionState)")
switch connectionState {
case .connected:
let message = "Have a good day!"
print("Connected, and seinding message '\(message)'")
await websocket.sendMessage(message: .string(message))
case .connecting:
print("Connecting...")
case .disconnected:
print("Disconnected!")
self.webSocket = nil
}
}
}
}
VKSocket.swift
import Foundation
public protocol WebsocketDelegate: AnyObject {
func websocket(_ websocket: VKWebsocket, didReceive message: URLSessionWebSocketTask.Message)
func websocket(_ websocket: VKWebsocket, didError error: Error?)
func websocket(_ websocket: VKWebsocket, didChange connectionState: VKWebsocket.ConnectionState)
}
public actor VKWebsocket {
public enum ConnectionState {
case connected
case connecting
case disconnected
}
private(set) weak var delegate: WebsocketDelegate?
private(set) var reconnectOnFailure = true
private(set) var connectionState = ConnectionState.disconnected {
didSet {
guard oldValue != connectionState else { return }
let newState = connectionState
self.delegate?.websocket(self, didChange: newState)
}
}
private let request: URLRequest
private var session: URLSession?
private var task: URLSessionWebSocketTask?
private var messagesQueue: [URLSessionWebSocketTask.Message] = []
private lazy var urlSessionDelegate = WebsocketURLSessionDelegate(websocket: self)
public init(request: URLRequest) {
self.request = request
}
deinit {
print("deinit \(type(of: self))")
task?.cancel()
session?.finishTasksAndInvalidate()
}
public func setDelegate(_ delegate: WebsocketDelegate) {
self.delegate = delegate
}
public func connect() {
guard connectionState == .disconnected else { return }
connectionState = .connecting
session = URLSession(configuration: .ephemeral,
delegate: urlSessionDelegate,
delegateQueue: nil)
task = session?.webSocketTask(with: request)
task?.resume()
}
public func disconnect() {
task?.cancel(with: .normalClosure, reason: nil)
session?.finishTasksAndInvalidate()
session = nil
task = nil
connectionState = .disconnected
}
public func sendMessage(message: URLSessionWebSocketTask.Message) {
guard connectionState == .connected else {
messagesQueue.append(message)
return
}
// VKWebsocket does not re-enqueue messages that fail. Clients implementing VKWebsocket
// should handle resending failed messages.
task?.send(message, completionHandler: { [weak self] error in
guard let self = self else { return }
Task {
await self.delegate?.websocket(self, didError: error)
}
})
}
private func awaitMessage() {
guard connectionState == .connected else { return }
task?.receive(completionHandler: { [weak self] result in
guard let self = self else { return }
Task {
await self.onReceive(result: result)
}
})
}
private func onReceive(result: Result<URLSessionWebSocketTask.Message, Error>) {
switch result {
case .success(let message):
self.delegate?.websocket(self, didReceive: message)
self.awaitMessage()
case .failure(let error):
self.delegate?.websocket(self, didError: error)
self.handleFailure()
}
}
private func handleFailure() {
disconnect()
}
fileprivate func websocketConnectionDidOpen() {
connectionState = .connected
awaitMessage()
messagesQueue.forEach { message in
self.sendMessage(message: message)
}
messagesQueue = []
}
fileprivate func websocketConnectionDidClose() {
connectionState = .disconnected
}
fileprivate func websocketConnectionDidError(_ error: Error?) {
delegate?.websocket(self, didError: error)
handleFailure()
}
}
// URLSession strongly retains its delegate, for whatever reason. We use an intermediate
// class to break the resulting retain cycle.
private class WebsocketURLSessionDelegate: NSObject, URLSessionWebSocketDelegate {
weak var websocket: VKWebsocket?
init(websocket: VKWebsocket) {
self.websocket = websocket
}
deinit {
print("deinit \(type(of: self))")
}
func urlSession(_ session: URLSession,
webSocketTask: URLSessionWebSocketTask,
didOpenWithProtocol protocol: String?) {
Task {
await self.websocket?.websocketConnectionDidOpen()
}
}
func urlSession(_ session: URLSession,
webSocketTask: URLSessionWebSocketTask,
didCloseWith closeCode: URLSessionWebSocketTask.CloseCode,
reason: Data?) {
Task {
await self.websocket?.websocketConnectionDidClose()
}
}
func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?) {
// This method will be called when you deinit VKWebsocket while connection is open.
Task {
await self.websocket?.websocketConnectionDidError(error)
}
}
}
Я отследил стек распределения памяти с помощью инструмента и вижу, что память хранится во внутреннем массиве, но понятия не имею, где хранится этот массив.
Я пытался сделать недействительной задачу после отмены, но это не помогло
Я ценю любой намек или совет, спасибо!
1 ответ
Я нашел документ, в котором сказано не использоватьURLSessionWebSocketTask
Если у вас нет особой причины использовать URLSession, используйте сетевую платформу для нового кода WebSocket. Дополнительные параметры см. в разделе альтернативы WebSocket. https://developer.apple.com/documentation/technotes/tn3151-choosing-the-right-networking-api
и получил по электронной почте совет использовать NWConnectionhttps://developer.apple.com/documentation/network/nwconnection
Я бы хотел, чтобы мой ответ сэкономил время другим