Skip to content

Commit b1192a2

Browse files
authored
fix: cleanup kvo (#146)
## 📜 Description Remove KVO when keyboard disappears. ## 💡 Motivation and Context It seems like we can not attach a single listener to the keyboard when it appeared for the first time. iOS may throw `KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_SMASHED`, which means, that the memory for the listener was released and thus a handler is corrupted (KVO was released while still observing the key path). To overcome the problem I've decided to remove observation when keyboard becomes hidden. To detect the moment when keyboard got hidden I used `UIWindow.didBecomeHiddenNotification` (since for attaching a KVO listener I've used `UIWindow.didBecomeVisibleNotification` - the motivation behind "why I've used these lifecycles" is the fact, that these lifecycle methods will be triggered only one time when keyboard appears/disappears, unlike other methods, which can be called several times). Also I've added a local variable called `hasKVObserver` and two helper functions: - `setupKVObserver`; - `removeKVObserver`. These functions control the value of `hasKVObserver` and assure that we will not call `.removeObserver` if listener is not attached (such situation will cause a crash and using `hasKVObserver` value we avoid it). Fixes #143 ## 📢 Changelog ### iOS - added `UIWindow.didBecomeVisibleNotification` listener; - clean KVO when `UIWindow.didBecomeVisibleNotification` is emitted and KVO is present; ## 🤔 How Has This Been Tested? Tested on: - iPhone 14 Pro (iOS 16.2, simulator); - iPhone 6s (iOS 15.7, real device); ## 📝 Checklist - [x] CI successfully passed
1 parent 81c959d commit b1192a2

File tree

1 file changed

+27
-4
lines changed

1 file changed

+27
-4
lines changed

ios/KeyboardMovementObserver.swift

+27-4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class KeyboardMovementObserver: NSObject {
4242
private var prevKeyboardPosition = 0.0
4343
private var displayLink: CADisplayLink?
4444
private var keyboardHeight: CGFloat = 0.0
45+
private var hasKVObserver = false
4546

4647
@objc public init(
4748
handler: @escaping (NSString, NSNumber, NSNumber) -> Void,
@@ -82,18 +83,40 @@ public class KeyboardMovementObserver: NSObject {
8283
name: UIWindow.didBecomeVisibleNotification,
8384
object: nil
8485
)
86+
NotificationCenter.default.addObserver(
87+
self,
88+
selector: #selector(windowDidBecomeHidden),
89+
name: UIWindow.didBecomeHiddenNotification,
90+
object: nil
91+
)
92+
}
93+
94+
@objc func windowDidBecomeHidden(_: Notification) {
95+
removeKVObserver()
8596
}
8697

8798
@objc func windowDidBecomeVisible(_: Notification) {
88-
let keyboardView = findKeyboardView()
99+
setupKVObserver()
100+
}
89101

90-
if keyboardView == _keyboardView {
102+
private func setupKVObserver() {
103+
if hasKVObserver {
91104
return
92105
}
93106

94-
_keyboardView = keyboardView
107+
if keyboardView != nil {
108+
hasKVObserver = true
109+
keyboardView?.addObserver(self, forKeyPath: "center", options: .new, context: nil)
110+
}
111+
}
112+
113+
private func removeKVObserver() {
114+
if !hasKVObserver {
115+
return
116+
}
95117

96-
keyboardView?.addObserver(self, forKeyPath: "center", options: .new, context: nil)
118+
hasKVObserver = false
119+
keyboardView?.removeObserver(self, forKeyPath: "center", context: nil)
97120
}
98121

99122
// swiftlint:disable:next block_based_kvo

0 commit comments

Comments
 (0)