Skip to content

Instantly share code, notes, and snippets.

@rpassis
Last active July 14, 2019 20:32
Show Gist options
  • Save rpassis/a123a397173dcd8b95a811147f38a4cd to your computer and use it in GitHub Desktop.
Save rpassis/a123a397173dcd8b95a811147f38a4cd to your computer and use it in GitHub Desktop.
Unit testing memory leaks
//
// DeallocationSentinel.swift
//
// Created by Rogerio de Paula Assis on 7/14/19.
// Extracted from this CCH Melbourne talk: https://www.youtube.com/watch?v=514rJ1efv84
//
public func expectDeallocation(
of object: AnyObject,
using deallocationExpectations: inout [XCTestExpectation]
) {
let expectation = XCTestExpectation(description: "Object \(object) deallocated")
objc_setAssociatedObject(
object,
&deallocatedExpectationKey,
DeallocationSentinel(deallocClosure: expectation.fulfill),
.OBJC_ASSOCIATION_RETAIN
)
deallocationExpectations.append(expectation)
}
// MARK: Private
private var deallocatedExpectationKey = 0
private final class DeallocationSentinel {
private let deallocClosure: () -> Void
init(deallocClosure: @escaping () -> Void) {
self.deallocClosure = deallocClosure
}
deinit {
deallocClosure()
}
}
//
// DeallocTests.swift
//
// Created by Rogerio de Paula Assis on 7/14/19.
// Extracted from this CCH Melbourne talk: https://www.youtube.com/watch?v=514rJ1efv84
//
import XCTest
class DeallocTests: XCTestCase {
private var deallocationExpectations: [XCTestExpectation] = []
var subject: ViewModel!
override func setUp() {
subject = ViewModel()
let vc = ViewController(viewModel: subject)
subject.vc = vc
expectDeallocation(of: subject, using: deallocationExpectations)
}
override func tearDown() {
wait(for: self.deallocatioExpectations, timeout: 1)
}
func testDeallocation() {
XCTAssert(subject != nil)
subject = nil
}
}
// Dummy classes created to simulate a retain cycle for the test case above
// Add/remove weak from view model to verify different test result
class ViewModel {
/* weak */ var vc: ViewController?
}
class ViewController: UIViewController {
let viewModel: ViewModel
init(viewModel :ViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment