笔者曾用过Instrument检测内存泄漏,泄漏
并非完全能检测
出来;
几番周折之后发觉 在每个dealloc
或者deinit
析构函数 打印日志能解决问题,虽然笨拙但能解决在编码时候的自我检测规范。
废话少说,本篇介绍uber/RIBs内存泄漏工具(LeakDetector.swift)很优雅的解决我的需求
核心思路:
///存储即将释放变量
private let trackingObjects = NSMapTable.strongToWeakObjects()
/// Sets up an expectation for the given object to be deallocated within the given time.
///
/// - parameter object: The object to track for deallocation.
/// - parameter inTime: The time the given object is expected to be deallocated within.
/// - returns: The handle that can be used to cancel the expectation.
@discardableResult
public func expectDeallocate(object: AnyObject, inTime time: TimeInterval = LeakDefaultExpectationTime.deallocation) -> LeakDetectionHandle {
expectationCount.value += 1
let objectDescription = String(describing: object)
let objectId = String(ObjectIdentifier(object).hashValue) as NSString
trackingObjects.setObject(object, forKey: objectId)
let handle = LeakDetectionHandleImpl {
self.expectationCount.value -= 1
}
Executor.execute(withDelay: time) {
// Retain the handle so we can check for the cancelled status. Also cannot use the cancellable
// concurrency API since the returned handle must be retained to ensure closure is executed.
if !handle.cancelled {
let didDeallocate = (self.trackingObjects.object(forKey: objectId) == nil)
let message = "<\(objectDescription): \(objectId)> has leaked. Objects are expected to be deallocated at this time: \(self.trackingObjects)"
if self.disableLeakDetector {
if !didDeallocate {
print("Leak detection is disabled. This should only be used for debugging purposes.")
print(message)
}
} else {
assert(didDeallocate, message)
}
}
self.expectationCount.value -= 1
}
return handle
}
NSMapTable需要了解一下
总结如下:
将需要释放的对象添加到trackingObjects,当添加的对象引用计数执行一次-1,添加的对象在内存中就会自动释放,并且相应的trackingObjects对象中的对象也会被自动移除.
接下来,在
deinit
方法调用expectDeallocate()
方法,延迟一定时间检测trackingObjects
对象的是否消失即可
so easy , look look deinit里的代码:
///PresentableInteractor.swift
deinit {
LeakDetector.instance.expectDeallocate(object: presenter as AnyObject)
}
///Router.swift
deinit {
interactable.deactivate()
if !children.isEmpty {
detachAllChildren()
}
lifecycleSubject.onCompleted()
deinitDisposable.dispose()
LeakDetector.instance.expectDeallocate(object: interactable)
}
///ViewableRouter.swift
deinit {
LeakDetector.instance.expectDeallocate(object: viewControllable.uiviewController, inTime: LeakDefaultExpectationTime.viewDisappear)
}