2017-02-19 2 views
0

Ich benutze Apples Instrument-Tool, um den aktuellen Fortschritt meiner Anwendung zu überprüfen und Lecks frühzeitig zu beseitigen. Ich habe anscheinend viel von Lecks, aber ich kann nicht herausfinden, woher sie kommen. In meiner Anwendung habe ich eine SignInOperation welche eine Unterklasse von Operation ist. Es entspricht auch URLSessionDataDelegate, so dass es meine Anforderungen verarbeiten kann, ohne die Beendigungshandler verwenden zu müssen. Wenn zum Beispiel eine Instanz SignInOperation zu einer OperationQueue Instanz hinzugefügt wird, kann der Vorgang, der Aktualisierungen an der UI durchführt, einfach die error und user Eigenschaften auf SignInOperation überprüfen und UI-Aktualisierungen entsprechend behandeln, da die Instanz SignInOperation als Abhängigkeit vorliegt.Woher kommen meine 421 Speicherlecks?

Die Klasse folgt:

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 
     } 
    } 

} 

Dann, um zu sehen, ob alles funktioniert, nenne ich den Betrieb in viewDidAppear:, die in allen von den erwarteten Daten ergibt gedruckt werden:

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) 
    } 

} 

jedoch Wenn ich den Leaks-Profiler in Instruments verwende, erhalte ich einige alarmierende Daten.

Screenshot of Leaks profiler run

Ich weiß nicht wirklich, wo hier zu starten. Wenn ich auf eines der erkannten Lecks klicke, werde ich nicht zu meinem Code geführt, aus dem das Leck stammt. Ich habe mir ein paar Tutorials angeschaut und die Dokumentation von Apple gelesen, aber ich muss herausfinden, woher die Lecks kommen. Es scheint wie ein lächerlicher Betrag/

Ich sehe nirgendwo in meinem Code, wo ich starke Referenzzyklen habe, so dass ich um etwas Hilfe mit dem Versuch, herauszufinden, wie die 421 erkannten Lecks zu lösen bitten.

Antwort

2

Es stellt sich heraus, dass ich haben zwei starke Referenzzyklen tun, die die beiden folgenden Eigenschaften in meiner SignInOperation Unterklasse sind: sessionTask & localURLSession.

Nachdem diese Eigenschaften weak ich nicht mehr haben Lecks festgestellt:

/// 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

Dank für das Kommen zurück und bietet eine Antwort. Können Sie den Code posten, den Sie beheben mussten, damit andere daraus lernen können? (Und seien Sie sicher, zurück zu kommen und Ihre Antwort zu akzeptieren, sobald SO Ihnen das erlaubt.) –

+0

@DuncanC Absolut! Ich habe meine Antwort bearbeitet, um Codeänderungen vorzunehmen. –

Verwandte Themen