Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit 48e46f9

Browse files
committed
KVO instead of displayLink to fix perfomance problem
1 parent 8aacebe commit 48e46f9

File tree

4 files changed

+43
-39
lines changed

4 files changed

+43
-39
lines changed

widgets/src/iosMain/def/objcAddtition.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ NSURLSessionDataTask* dataTask(NSURLSession* session, NSURL* url, void (^complet
3636
}
3737
];
3838
}
39+
40+
@protocol KeyValueObserver
41+
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
42+
@end

widgets/src/iosMain/kotlin/dev/icerock/moko/widgets/core/factory/ButtonWithIconViewFactory.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import dev.icerock.moko.widgets.core.style.view.WidgetSize
2020
import dev.icerock.moko.widgets.core.utils.applyStateBackgroundIfNeeded
2121
import dev.icerock.moko.widgets.core.utils.applyTextStyleIfNeeded
2222
import dev.icerock.moko.widgets.core.utils.bind
23-
import dev.icerock.moko.widgets.core.utils.displayLink
23+
import dev.icerock.moko.widgets.core.utils.onBoundsChanged
2424
import dev.icerock.moko.widgets.core.utils.setEventHandler
2525
import dev.icerock.moko.widgets.core.widget.ButtonWidget
2626
import kotlinx.cinterop.useContents
@@ -141,9 +141,8 @@ actual class ButtonWithIconViewFactory actual constructor(
141141
}
142142

143143
private fun setupLayoutUpdate(button: UIButton, viewFactory: ButtonWithIconViewFactory) {
144-
button.displayLink(
144+
button.onBoundsChanged(
145145
context = viewFactory,
146-
objectForSkipCheck = { it.bounds }
147146
) { button, viewFactory ->
148147
val icPadding: Double = viewFactory.iconPadding?.toDouble() ?: 0.0
149148

widgets/src/iosMain/kotlin/dev/icerock/moko/widgets/core/utils/BackgroundExt.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,8 @@ fun UIButton.applyStateBackgroundIfNeeded(background: PressableState<Background<
9898

9999
stateLayers.update(this)
100100

101-
this.displayLink(
101+
this.onBoundsChanged(
102102
context = stateLayers,
103-
objectForSkipCheck = { it.layer.bounds }
104103
) { button, stateLayers ->
105104
val (width: CGFloat, height: CGFloat) = button.layer.bounds.useContents {
106105
this.size.width to this.size.height
@@ -165,9 +164,8 @@ fun UIView.applyBackgroundIfNeeded(background: Background<out Fill>?) {
165164
val bgLayer: CALayer = background.caLayer()
166165
layer.insertSublayer(bgLayer, 0U)
167166

168-
this.displayLink(
167+
this.onBoundsChanged(
169168
context = bgLayer,
170-
objectForSkipCheck = { it.layer.bounds }
171169
) { view, bgLayer ->
172170
val (width: CGFloat, height: CGFloat) = view.layer.bounds.useContents {
173171
this.size.width to this.size.height

widgets/src/iosMain/kotlin/dev/icerock/moko/widgets/core/utils/UIControlExt.kt

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44

55
package dev.icerock.moko.widgets.core.utils
66

7+
import dev.icerock.moko.widgets.objc.KeyValueObserverProtocol
8+
import kotlinx.cinterop.COpaquePointer
79
import kotlinx.cinterop.ObjCAction
810
import kotlinx.cinterop.cstr
9-
import platform.Foundation.NSRunLoop
10-
import platform.Foundation.NSRunLoopCommonModes
11+
import platform.Foundation.NSKeyValueObservingOptionNew
1112
import platform.Foundation.NSSelectorFromString
12-
import platform.QuartzCore.CADisplayLink
13+
import platform.Foundation.addObserver
1314
import platform.UIKit.UIControl
1415
import platform.UIKit.UIControlEvents
1516
import platform.UIKit.UIGestureRecognizer
@@ -52,49 +53,51 @@ fun UIGestureRecognizer.setHandler(action: () -> Unit) {
5253
)
5354
}
5455

55-
fun <T : Any, CTX> T.displayLink(
56+
fun <V : UIView, CTX> V.onBoundsChanged(
5657
context: CTX,
57-
objectForSkipCheck: (T) -> Any,
58-
action: (T, CTX) -> Unit
58+
action: (V, CTX) -> Unit
5959
) {
60-
val ref: WeakReference<T> = WeakReference(this)
60+
val ref: WeakReference<V> = WeakReference(this)
6161

62-
var displayLink: CADisplayLink? = null
63-
64-
var oldState: Any = objectForSkipCheck(this)
65-
val target = LambdaTarget {
66-
val strongRef: T? = ref.get()
67-
if (strongRef == null) {
68-
displayLink?.removeFromRunLoop(NSRunLoop.currentRunLoop, NSRunLoopCommonModes)
69-
displayLink = null
70-
return@LambdaTarget
71-
}
72-
73-
val newState: Any = objectForSkipCheck(strongRef)
74-
if (newState == oldState) return@LambdaTarget
62+
val target = ObserverObject {
63+
val strongRef: V = ref.get() ?: return@ObserverObject
7564

7665
action(strongRef, context)
7766
}
7867

79-
CADisplayLink.displayLinkWithTarget(
80-
target = target,
81-
selector = NSSelectorFromString("displayLink:")
82-
).apply {
83-
frameInterval = 1
84-
addToRunLoop(NSRunLoop.currentRunLoop, NSRunLoopCommonModes)
85-
}
68+
objc_setAssociatedObject(
69+
`object` = this,
70+
key = "onBoundsChanged".cstr,
71+
value = target,
72+
policy = OBJC_ASSOCIATION_RETAIN
73+
)
74+
75+
this.addObserver(
76+
observer = target,
77+
forKeyPath = "bounds",
78+
options = NSKeyValueObservingOptionNew,
79+
context = null
80+
)
8681
}
8782

88-
class LambdaTarget(val lambda: () -> Unit) : NSObject() {
83+
private class ObserverObject(
84+
private val lambda: () -> Unit
85+
) : NSObject(), KeyValueObserverProtocol {
8986

90-
@ObjCAction
91-
fun action() {
87+
override fun observeValueForKeyPath(
88+
keyPath: String?,
89+
ofObject: Any?,
90+
change: Map<Any?, *>?,
91+
context: COpaquePointer?
92+
) {
9293
lambda()
9394
}
95+
}
96+
97+
class LambdaTarget(val lambda: () -> Unit) : NSObject() {
9498

9599
@ObjCAction
96-
@Suppress("UnusedPrivateMember")
97-
fun displayLink(link: CADisplayLink) {
100+
fun action() {
98101
lambda()
99102
}
100103
}

0 commit comments

Comments
 (0)