【问题标题】:Where are My 421 Memory Leaks Coming from?我的 421 内存泄漏来自哪里?
【发布时间】:2017-02-19 15:49:10
【问题描述】:

我正在使用 Apple 的 Instruments 工具来检查我的应用程序的当前进度并尽早管理任何泄漏。我似乎有 很多 泄漏,但我无法弄清楚它们来自哪里。

在我的应用程序中,我有一个SignInOperation,它是Operation 的子类。它还符合URLSessionDataDelegate,因此它可以处理我的请求,而无需使用完成处理程序。例如,当将SignInOperation 的实例添加到OperationQueue 实例时,执行UI 更新的操作只需检查SignInOperation 上的error 和user 属性并相应地处理UI 更新,因为它将 SignInOperation 实例作为依赖项。

类如下:

import UIKit

/// Manages a sign-in operation.
internal final class SignInOperation: Operation, URLSessionDataDelegate {

    // MARK: - Properties

    /// An internal flag that indicates whether the operation is currently executing.
    private var _executing = false

    /// An internal flag that indicates wheterh the operation is finished.
    private var _finished = false

    /// The received data from the operation.
    private var receivedData = Data()

    /// The data task used for sign-in.
    private var sessionTask: URLSessionDataTask?

    /// The URL session that is used for coordinating tasks used for sign-in.
    private var localURLSession: URLSession { return URLSession(configuration: localConfiguration, delegate: self, delegateQueue: nil) }

    /// The configuration used for configuring the URL session used for sign-in.
    private var localConfiguration: URLSessionConfiguration { return .ephemeral }

    /// The credentials used for user-sign-in.
    private var credentials: UserCredentials

    /// The current user.
    internal var currentUser: User?

    /// The error encountered while attempting sign-in.
    internal var error: NetworkRequestError?

    /// The cookie storage used for persisting an authentication cookie.
    internal var cookieStorage: HTTPCookieStorage?

    /// A Boolean value indicating whether the operation is currently executing.
    override internal(set) var isExecuting: Bool {
        get { return _executing }
        set {
            willChangeValue(forKey: "isExecuting")
            _executing = newValue
            didChangeValue(forKey: "isExecuting")
        }
    }

    /// A Boolean value indicating whether the operation has finished executing its task.
    override internal(set) var isFinished: Bool {
        get { return _finished }
        set {
            willChangeValue(forKey: "isFinished")
            _finished = newValue
            didChangeValue(forKey: "isFinished")
        }
    }

    /// A Boolean value indicating whether the operation executes its task asynchronously.
    override var isAsynchronous: Bool { return true }

    // MARK: - Initialization

    /// Returns an instane of `SignInOperation`.
    /// - parameter credentials: The credentials for user-sign-in.
    init(credentials: UserCredentials, cookieStorage: HTTPCookieStorage = CookieStorage.defaultStorage) {
        self.credentials = credentials
        self.cookieStorage = cookieStorage
        super.init()
        localURLSession.configuration.httpCookieAcceptPolicy = .never
    }

    // MARK: - Operation Lifecycle

    override func start() {
        if isCancelled {
            isFinished = true
            return
        }
        isExecuting = true
        let request = NetworkingRouter.signIn(credentials: credentials).urlRequest
        sessionTask = localURLSession.dataTask(with: request)
        guard let task = sessionTask else { fatalError("Failed to get task") }
        task.resume()
    }

    // MARK: - URL Session Delegate

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        if isCancelled {
            isFinished = true
            sessionTask?.cancel()
            return
        }
        guard let statusCode = (response as? HTTPURLResponse)?.statusCode else { fatalError("Could not determine status code") }
        setError(from: statusCode)
        completionHandler(disposition(from: statusCode))
    }

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        if isCancelled {
            guard let task = sessionTask else { fatalError("Failed to get task") }
            task.cancel()
            return
        }
        receivedData.append(data)
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        defer { isFinished = true }
        if isCancelled {
            guard let task = sessionTask else { fatalError("Failed to get task") }
            task.cancel()
        }
        if let statusCode = (task.response as? HTTPURLResponse)?.statusCode { setError(from: statusCode) } else if let taskError = error as? NSError { setError(from: taskError) }
        if self.error == nil {
            guard let taskResponse = task.response else { fatalError("Invalid response") }
            setAuthenticationCookie(from: taskResponse)
            processData()
        }
    }

    // MARK: - Helpers

    /// Handles the processing of the data received from the data task.
    private func processData() {
        currentUser = UserModelCreator.user(from: receivedData)
    }

    /// Handles the persistence of the returned cookie from the request's response.
    private func setAuthenticationCookie(from response: URLResponse) {
        guard let storage = cookieStorage else { fatalError() }
        let cookiePersistenceManager = ResponseCookiePersistenceManger(storage: storage)
        cookiePersistenceManager.removePreviousCookies()
        guard let httpURLResponse = response as? HTTPURLResponse else { fatalError("Invalid response type") }
        if let cookie = ResponseCookieParser.cookie(from: httpURLResponse) {cookiePersistenceManager.persistCookie(cookie: cookie) }
    }

    /// Handles the return of a specified HTTP status code.
    /// - parameter statusCode: The status code.
    private func setError(from statusCode: Int) {
        switch statusCode {
        case 200: error = nil
        case 401: error = .invalidCredentials
        default: error = .generic
        }
    }

    /// Returns a `URLResponse.ResponseDisposition` for the specified HTTP status code.
    /// - parameter code: The status code.
    /// - Returns: A disposition.
    private func disposition(from code: Int) -> URLSession.ResponseDisposition {
        switch code {
        case 200: return .allow
        default: return .cancel
        }
    }

    /// Handles the return of an error from a network request.
    /// - parameter error: The error.
    private func setError(from error: NSError) {
        switch error.code {
        case Int(CFNetworkErrors.cfurlErrorTimedOut.rawValue): self.error = .requestTimedOut
        case Int(CFNetworkErrors.cfurlErrorNotConnectedToInternet.rawValue): self.error = .noInternetConnection
        default: self.error = .generic
        }
    }

}

然后,为了查看是否一切正常,我调用viewDidAppear: 中的操作,这会导致打印所有预期的数据:

import UIKit

class ViewController: UIViewController {

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let credentials = UserCredentials(emailAddress: "xxxxxxx@xxxx.xx", password: "xxxxxxxxxxxxxxxxxx")
        let signInOp = SignInOperation(credentials: credentials)

        let printOperation = Operation()
        printOperation.addDependency(signInOp)
        printOperation.completionBlock = {
            if let error = signInOp.error { return print("\n====> Sign-in Error: \(error.message)\n") }
            if let user = signInOp.currentUser { print("\n====> User: \(user)\n") }
    }

        let queue = OperationQueue()
        queue.addOperations([signInOp, printOperation], waitUntilFinished: false)
    }

}

但是,在 Instruments 中使用泄漏分析器时,我得到了一些令人担忧的数据。

我真的不知道从哪里开始。当我单击任何检测到的泄漏时,我不会被带到泄漏源自的代码。我看过一些教程并阅读了 Apple 的文档,但我一直在试图找出泄漏的来源。这似乎是一个荒谬的数量/

我在我的代码中没有看到我有强引用循环的任何地方,所以我请求一些帮助以试图弄清楚如何解决检测到的 421 泄漏。

【问题讨论】:

    标签: ios memory-leaks


    【解决方案1】:

    事实证明,我确实有两个强引用循环,它们是我的SignInOperation 子类中的以下两个属性:sessionTask 和localURLSession。

    创建这些属性weak 后,我不再检测到泄漏:

    /// The URL session that is used for coordinating tasks used for sign-in.
    private weak var localURLSession: URLSession { return URLSession(configuration: localConfiguration, delegate: self, delegateQueue: nil) }
    
    /// The configuration used for configuring the URL session used for sign-in.
    private weak var localConfiguration: URLSessionConfiguration { return .ephemeral }
    

    【讨论】:

    • 感谢您回来并提供答案。您可以发布您必须修复的代码以便其他人可以从中学习吗? (请务必在 SO 允许后尽快返回并接受您的回答。)
    • @DuncanC 绝对!我编辑了答案以提供代码更改。
    猜你喜欢
    • 1970-01-01
    • 2014-04-02
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    相关资源
    最近更新 更多