我正在使用Apple的Instruments工具检查我的应用程序的当前进度并尽早管理任何泄漏。我似乎有很多很多泄漏,但我无法弄清楚它们来自哪里。
在我的应用程序中,我有一个SignInOperation
,它是Operation
的子类。它也符合URLSessionDataDelegate
,因此它可以处理我的请求而无需使用完成处理程序。例如,在向SignInOperation
实例添加OperationQueue
实例时,执行UI更新的操作只需检查error
和user
属性即可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时,我得到了一些令人震惊的数据。
我真的不知道从哪里开始。当我点击任何检测到的泄漏时,我没有接受泄漏源自我的代码。我已经看过一些教程并阅读了Apple的文档,但是我一直试图找出漏洞的来源。这似乎是一个荒谬的数量/
我在代码中没有看到任何我有强引用周期的地方,所以我在寻求如何解决421检测到的泄漏问题时寻求帮助。
答案 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 }