2017-02-19 42 views
0

我正在使用Apple的儀器工具檢查我的應用程序的當前進度並儘早處理任何泄漏。我似乎有很多泄漏,但我不知道他們來自哪裏。我的421內存泄漏從哪裏來?

在我的申請中,我有一個SignInOperation,它是Operation的子類。它也符合URLSessionDataDelegate,以便它可以處理我的請求,而無需使用完成處理程序。例如,將SignInOperation的實例添加到OperationQueue實例時,執行UI更新的操作只能檢查SignInOperation上的erroruser屬性,並相應地處理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: "[email protected]", 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檢測到的泄漏。

回答

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

+0

謝謝回來,並提供一個答案。你能發佈你必須修復的代碼,以便其他人可以從中學習嗎? (並且一定要讓你回來接受你的回答。) –

+0

@DuncanC絕對!我編輯了我的答案以提供代碼更改。 –