我的421内存泄漏来自哪里?

时间:2017-02-19 15:49:10

标签: ios memory-leaks

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

在我的应用程序中,我有一个SignInOperation,它是Operation的子类。它也符合URLSessionDataDelegate,因此它可以处理我的请求而无需使用完成处理程序。例如,在向SignInOperation实例添加OperationQueue实例时,执行UI更新的操作只需检查erroruser属性即可SignInOperation 1}}并相应地处理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)
    }

}

然而,当在仪器中使用Leaks Profiler时,我得到了一些令人震惊的数据。

Screenshot of Leaks profiler run

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

我在代码中没有看到任何我有强引用周期的地方,所以我在寻求如何解决421检测到的泄漏问题时寻求帮助。

1 个答案:

答案 0 :(得分:2)

事实证明,我有两个强引用周期,这是我的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 }

Screenshot of Leaks profiler run