Swift Combine - CFString(存储)内存泄漏

Kev*_*inP 5 memory-leaks swift swiftui combine

在 xcode 中使用内存图调试器时,我看到了一些内存泄漏。Backtrace 没有直接链接到我的任何代码,而是通过跟踪猜测它的保存以假设它与组合和一些 DataTaskPublisher 相关。

在此处输入图片说明

接下来我检查了 Instruments 内部,在那里我也看到了一些内存泄漏。所有泄漏都在堆栈跟踪中提到“专门的静态 UIApplicationDelegate.main()”,但它并没有真正链接到可能导致内存泄漏的东西。

在此处输入图片说明

删除负责从 API 加载数据的 ViewModel 可以消除泄漏。内存图调试器显示了一个 dataTaskPublisher,所以这有点道理。

import Foundation
import Combine

enum API {
    static func games() -> AnyPublisher<[GameResult], Error> {
        let requestHeaderGames = gamesRequest()
        
        return URLSession.shared.dataTaskPublisher(for: requestHeaderGames)
            .map(\.data)
            .decode(type: [GameResult].self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
    
    private static func gamesRequest() -> URLRequest {
        let url = URL(string: "http://localhost:8080/api/games")!
        var requestHeader = URLRequest.init(url: url)
        requestHeader.httpBody =
            "filter ..."
            .data(using: .utf8, allowLossyConversion: false)

        requestHeader.httpMethod = "POST"
        requestHeader.setValue("application/json", forHTTPHeaderField: "Accept")

        return requestHeader
    }
}

struct GameResult: Decodable, Identifiable, Equatable, Hashable {
    let id: Int
    // ...
}

final class ViewModel: ObservableObject {
    @Published private(set) var games: [GameResult] = []
    
    private var subscriptions = Set<AnyCancellable>()
    
    public func unsubscribe() -> Void {
        subscriptions.forEach {
            $0.cancel()
        }
        subscriptions.removeAll()
    }
    
    func load() -> Void {
        API.games()
            .sink(receiveCompletion: { _ in }, receiveValue: { [weak self] results in
                self?.games = results
            })
            .store(in: &subscriptions)
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经花了很多时间来弄清楚是什么导致了这个泄漏,但我比其他任何事情都更困惑。CFString 不是我使用的任何东西。我也无法找出为什么我的代码似乎导致了这种泄漏。有什么我只是缺少的东西,或者有人可以通过给我一些建议来帮助我解决这个问题吗?

Rob*_*Rob 1

您发布的第一张图片似乎表明这是解码器发布者的问题。我会尝试几种不同的场景,看看我们是否可以通过解码来隔离问题:

enum API {

    static var fakeGameData: Data {
        let encoder = JSONEncoder()
        return try! encoder.encode(fakeGames)
    }

    static var fakeGames: [GameResult] {
        return [GameResult(id: 0), GameResult(id: 2), GameResult(id: 3)]
    }

    static func gamesFromData() -> AnyPublisher<[GameResult], Error> {
        return Just(fakeGameData)
            .decode(type: [GameResult].self, decoder: JSONDecoder())
            .mapError({ $0 as Error })
            .eraseToAnyPublisher()
    }

    static func gamesFromArray() -> AnyPublisher<[GameResult], Error> {
        return Just(fakeGames)
            .mapError({ $0 as Error })
            .eraseToAnyPublisher()
    }
}
Run Code Online (Sandbox Code Playgroud)

看看如果您订阅 API.gamesFromData() 会发生什么。如果仍然发现泄漏,则可能是解码器存在问题。如果不是,则更有可能是 dataTaskPublisher 出现问题。

然后,看看如果您订阅 API.gamesFromArray() 会发生什么。如果您仍然看到泄漏,那么您就知道问题不在于解码器发布者。