项目作者: pkh0225

项目描述 :
🚧 WATCHOUT Memory Leak (Swift)
高级语言: Swift
项目地址: git://github.com/pkh0225/DeinitManager.git
创建时间: 2018-08-10T13:54:41Z
项目社区:https://github.com/pkh0225/DeinitManager

开源协议:

下载


🚥 Deinit Manager

SwiftPM compatibleLicense: MIT

목표

  • 모든 푸시&팝 이벤트에 대해 직관적으로 메모리 해제를 확인하세요!
  • 🚧 Check Memory Leak in every push & pop events!

🚁 작동 방식

  1. Navigation Push 후 Pop을 하면 Pop 후 약 1.5초 간 터치가 막혀 클릭이 불가합니다.
  2. 만약 해제 되지 않은 view 와 controller가 있다면 해당 이름이 팝업에 리스트됩니다.
  3. 만약 모든 인스턴스가 정상 해제 되었다면 💯점 토스트 팝업이 띄워집니다. ⛱

🚁 How it works

  1. There is 1.5 sec UI leg following the pop action. You are not able to touch the screen for the time being.
  2. If memory leaked happens, leaked views and controllers will be listed on the popup.
  3. If all the instances deinited, then ok💯🆗 popup will be toasted! 🥪

Test Cases

blogimg

☹︎ deinit fail

blogimg

  1. // self 가 weak 처리 되지 않아 self deinit이 호출되지 않는 경우
  2. testClosure = {
  3. print(self)
  4. }

☺︎ deinit success

blogimg

  1. public class BaseView: UIView, DeinitChecker {
  2. public var deinitNotifier: DeinitNotifier?
  3. override init(frame: CGRect) {
  4. super.init(frame: frame)
  5. setDeinitNotifier()
  6. }
  7. required public init?(coder aDecoder: NSCoder) {
  8. super.init(coder: aDecoder)
  9. setDeinitNotifier()
  10. }
  11. }
  12. public class BaseViewController: UIViewController, DeinitChecker {
  13. public var deinitNotifier: DeinitNotifier?
  14. override public func viewDidLoad() {
  15. super.viewDidLoad()
  16. setDeinitNotifier()
  17. }
  18. }
  19. DeinitChecker Protocol 채택 객체 생성자에서 setDeinitNotifier() 호출해 주면
  20. - base처럼 상속 구조 아니여도 상관 없음 그냥 프로토콜만 체택하면 객체는 체크가 가능해
  21. - 푸시, 팝을 하는 기준이 되는 ViewController 하나는 있어야
  22. DeinitManager.shared.isRun = true 해준 동작함 끄고 싶을땐 false 처리 하면
  23. 아래처럼 weak 처리 안되어 있을 메모리 해제 해지 않아서 오류 팝업 생성됨
  24. testClosure = {
  25. guard let `self` = self else { return }
  26. print(self)
  27. }




Core functions

  1. public final class DeinitManager {
  2. final class VCInfoClass: Equatable {
  3. static func == (lhs: VCInfoClass, rhs: VCInfoClass) -> Bool {
  4. lhs === rhs
  5. }
  6. final class ObjectInfo: Equatable {
  7. static func == (lhs: ObjectInfo, rhs: ObjectInfo) -> Bool {
  8. lhs === rhs
  9. }
  10. var name: String
  11. var count: Int = 1
  12. init(name: String) {
  13. self.name = name
  14. }
  15. }
  16. var address: Int
  17. var vcName: String
  18. var objects = [ObjectInfo]()
  19. init(_ vc: String, address: Int) {
  20. self.vcName = vc
  21. self.address = address
  22. }
  23. }
  24. static let shared: DeinitManager = { return DeinitManager() }()
  25. private init() {}
  26. public var isRun: Bool = false {
  27. didSet {
  28. if isRun {
  29. UIViewController.enableSwizzleMethodForViewWillDisappear()
  30. startMemoryReport()
  31. }
  32. else {
  33. removeAll()
  34. UIViewController.disableSwizzleMethodForViewWillDisappear()
  35. }
  36. }
  37. }
  38. private var workItem: DispatchWorkItem? // 작업을 관리할 변수
  39. private var vcInfos = [VCInfoClass]()
  40. private(set) var isMemoryRepory: Bool = false
  41. private var memoryLabel: UILabel?
  42. private func removeAll() {
  43. self.vcInfos.removeAll()
  44. }
  45. public func checkPopViewController(_ name: String, address: Int) {
  46. guard isRun else { return }
  47. guard self.vcInfos.last?.vcName == name, self.vcInfos.last?.address == address else { return }
  48. // print("checkPopViewController name: \(name), address: \(address)")
  49. // 이전 작업 취소 (있다면)
  50. workItem?.cancel()
  51. // 새 작업 생성
  52. workItem = DispatchWorkItem { [weak self] in
  53. guard let self else { return }
  54. if self.vcInfos.contains(where: { $0.vcName == name && $0.address == address }) {
  55. let string = """
  56. ------ Warning -------
  57. 👊🏻 \(name) 👊🏻
  58. 💣 deinit Check Fail -----
  59. ⬇️ 해제 되지 않은 메모리를 빼주세요 -----
  60. --------------------------------
  61. \(name)
  62. -------------------------------
  63. """
  64. print(string)
  65. self.makeView(value: string)
  66. }
  67. }
  68. // 작업 디스패치
  69. if let workItem = workItem {
  70. DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: workItem)
  71. }
  72. }
  73. public func pushViewController(_ name: String, address: Int) {
  74. guard isRun else { return }
  75. print()
  76. print(" 🧲 pushViewController \(name) 🧲 address: \(address)")
  77. self.vcInfos.append(VCInfoClass(name, address: address))
  78. }
  79. public func popViewController(_ name: String, address: Int) {
  80. guard isRun else { return }
  81. print()
  82. print(" ✴️ popViewController \(name) ✴️ ")
  83. checkDeinit(name, address: address)
  84. }
  85. public func initObject(_ name: String) {
  86. guard isRun else { return }
  87. guard let vcInfo = vcInfos.last else { return }
  88. if let viewInfo = vcInfo.objects.first(where: { $0.name == name }) {
  89. viewInfo.count += 1
  90. print("add Object \(name) count: \(viewInfo.count)")
  91. }
  92. else {
  93. vcInfo.objects.append(.init(name: name))
  94. print("add Object \(name) count: 1")
  95. }
  96. }
  97. public func deinitObject(_ name: String) {
  98. guard isRun else { return }
  99. guard let vcInfo = vcInfos.last else { return }
  100. if let viewInfo = vcInfo.objects.first(where: { $0.name == name }) {
  101. viewInfo.count -= 1
  102. print("deinit Object \(name) count: \(viewInfo.count)")
  103. }
  104. }
  105. private func checkDeinit(_ name: String, address: Int) {
  106. guard isRun else { return }
  107. workItem?.cancel()
  108. workItem = nil
  109. var objects = [VCInfoClass.ObjectInfo]()
  110. var removeVi = [VCInfoClass]()
  111. for vi in self.vcInfos.reversed() {
  112. objects.append(contentsOf: vi.objects)
  113. removeVi.append(vi)
  114. if vi.vcName == name, vi.address == address { break }
  115. }
  116. let deadline = Double(objects.count) * 0.3
  117. DispatchQueue.main.asyncAfter(deadline: .now() + deadline) {
  118. print()
  119. print(" ⚠️ deinit checker start ⚠️")
  120. var list: [String] = [String]()
  121. list.reserveCapacity(objects.count)
  122. for vi in objects {
  123. if vi.count > 0 {
  124. list.append("\t\(vi.name) count: \(vi.count)")
  125. }
  126. }
  127. self.vcInfos.removeAll(where: { removeVi.contains($0) })
  128. if list.count > 0 {
  129. let string = """
  130. ------ Warning -------
  131. 👊🏻 \(name) 👊🏻
  132. 💣 deinit Check Fail -----
  133. ⬇️ 해제 되지 않은 메모리를 빼주세요 -----
  134. --------------------------------
  135. \(list.joined(separator: "\n"))
  136. -------------------------------
  137. """
  138. print(string)
  139. self.makeView(value: string)
  140. }
  141. else {
  142. self.checkOK(name)
  143. }
  144. print(" ⚠️ deinit checker end ⚠️")
  145. print()
  146. }
  147. }
  148. }