Skip to content

Commit 013a1b9

Browse files
authored
Merge pull request #163 from SDWebImage/bugfix_webimage_animated_leak
Fix the leak of WebImage with animation and NavigationLink.
2 parents cf8d308 + ad067f7 commit 013a1b9

File tree

2 files changed

+109
-53
lines changed

2 files changed

+109
-53
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* This file is part of the SDWebImage package.
3+
* (c) DreamPiggy <[email protected]>
4+
*
5+
* For the full copyright and license information, please view the LICENSE
6+
* file that was distributed with this source code.
7+
*/
8+
9+
import SwiftUI
10+
import SDWebImage
11+
12+
/// A Image observable object for handle aniamted image playback. This is used to avoid `@State` update may capture the View struct type and cause memory leak.
13+
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
14+
public final class ImagePlayer : ObservableObject {
15+
var player: SDAnimatedImagePlayer?
16+
17+
/// Max buffer size
18+
public var maxBufferSize: UInt?
19+
20+
/// Custom loop count
21+
public var customLoopCount: UInt?
22+
23+
/// Animation runloop mode
24+
public var runLoopMode: RunLoop.Mode = .common
25+
26+
/// Animation playback rate
27+
public var playbackRate: Double = 1.0
28+
29+
deinit {
30+
player?.stopPlaying()
31+
currentFrame = nil
32+
}
33+
34+
/// Current playing frame image
35+
@Published public var currentFrame: PlatformImage?
36+
37+
/// Start the animation
38+
public func startPlaying() {
39+
player?.startPlaying()
40+
}
41+
42+
/// Pause the animation
43+
public func pausePlaying() {
44+
player?.pausePlaying()
45+
}
46+
47+
/// Stop the animation
48+
public func stopPlaying() {
49+
player?.stopPlaying()
50+
}
51+
52+
/// Clear the frame buffer
53+
public func clearFrameBuffer() {
54+
player?.clearFrameBuffer()
55+
}
56+
57+
58+
/// Setup the player using Animated Image
59+
/// - Parameter image: animated image
60+
public func setupPlayer(image: PlatformImage?) {
61+
if player != nil {
62+
return
63+
}
64+
if let animatedImage = image as? SDAnimatedImageProvider & PlatformImage {
65+
if let imagePlayer = SDAnimatedImagePlayer(provider: animatedImage) {
66+
imagePlayer.animationFrameHandler = { [weak self] (_, frame) in
67+
self?.currentFrame = frame
68+
}
69+
// Setup configuration
70+
if let maxBufferSize = maxBufferSize {
71+
imagePlayer.maxBufferSize = maxBufferSize
72+
}
73+
if let customLoopCount = customLoopCount {
74+
imagePlayer.totalLoopCount = UInt(customLoopCount)
75+
}
76+
imagePlayer.runLoopMode = runLoopMode
77+
imagePlayer.playbackRate = playbackRate
78+
79+
self.player = imagePlayer
80+
81+
// Setup poster frame
82+
if let cgImage = animatedImage.cgImage {
83+
currentFrame = PlatformImage(cgImage: cgImage, scale: animatedImage.scale, orientation: .up)
84+
} else {
85+
currentFrame = .empty
86+
}
87+
}
88+
}
89+
}
90+
}

SDWebImageSwiftUI/Classes/WebImage.swift

Lines changed: 19 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,10 @@ public struct WebImage : View {
2424
/// True to start animation, false to stop animation.
2525
@Binding public var isAnimating: Bool
2626

27-
@State var currentFrame: PlatformImage? = nil
28-
@State var imagePlayer: SDAnimatedImagePlayer? = nil
27+
@ObservedObject var imagePlayer: ImagePlayer
2928

30-
var maxBufferSize: UInt?
31-
var customLoopCount: UInt?
32-
var runLoopMode: RunLoop.Mode = .common
3329
var pausable: Bool = true
3430
var purgeable: Bool = false
35-
var playbackRate: Double = 1.0
3631

3732
/// Create a web image with url, placeholder, custom options and context.
3833
/// - Parameter url: The image url
@@ -57,6 +52,7 @@ public struct WebImage : View {
5752
}
5853
}
5954
self.imageManager = ImageManager(url: url, options: options, context: context)
55+
self.imagePlayer = ImagePlayer()
6056
}
6157

6258
public var body: some View {
@@ -67,30 +63,30 @@ public struct WebImage : View {
6763
return Group {
6864
if imageManager.image != nil {
6965
if isAnimating && !imageManager.isIncremental {
70-
if currentFrame != nil {
71-
configure(image: currentFrame!)
66+
if imagePlayer.currentFrame != nil {
67+
configure(image: imagePlayer.currentFrame!)
7268
.onAppear {
73-
self.imagePlayer?.startPlaying()
69+
imagePlayer.startPlaying()
7470
}
7571
.onDisappear {
7672
if self.pausable {
77-
self.imagePlayer?.pausePlaying()
73+
imagePlayer.pausePlaying()
7874
} else {
79-
self.imagePlayer?.stopPlaying()
75+
imagePlayer.stopPlaying()
8076
}
8177
if self.purgeable {
82-
self.imagePlayer?.clearFrameBuffer()
78+
imagePlayer.clearFrameBuffer()
8379
}
8480
}
8581
} else {
8682
configure(image: imageManager.image!)
8783
.onReceive(imageManager.$image) { image in
88-
self.setupPlayer(image: image)
84+
imagePlayer.setupPlayer(image: image)
8985
}
9086
}
9187
} else {
92-
if currentFrame != nil {
93-
configure(image: currentFrame!)
88+
if imagePlayer.currentFrame != nil {
89+
configure(image: imagePlayer.currentFrame!)
9490
} else {
9591
configure(image: imageManager.image!)
9692
}
@@ -191,32 +187,6 @@ public struct WebImage : View {
191187
return AnyView(configure(image: .empty))
192188
}
193189
}
194-
195-
/// Animated Image Support
196-
func setupPlayer(image: PlatformImage?) {
197-
if imagePlayer != nil {
198-
return
199-
}
200-
if let animatedImage = image as? SDAnimatedImageProvider {
201-
if let imagePlayer = SDAnimatedImagePlayer(provider: animatedImage) {
202-
imagePlayer.animationFrameHandler = { (_, frame) in
203-
self.currentFrame = frame
204-
}
205-
// Setup configuration
206-
if let maxBufferSize = maxBufferSize {
207-
imagePlayer.maxBufferSize = maxBufferSize
208-
}
209-
if let customLoopCount = customLoopCount {
210-
imagePlayer.totalLoopCount = UInt(customLoopCount)
211-
}
212-
imagePlayer.runLoopMode = runLoopMode
213-
imagePlayer.playbackRate = playbackRate
214-
215-
self.imagePlayer = imagePlayer
216-
imagePlayer.startPlaying()
217-
}
218-
}
219-
}
220190
}
221191

222192
// Layout
@@ -378,9 +348,8 @@ extension WebImage {
378348
/// - Note: Pass nil to disable customization, use the image itself loop count (`animatedImageLoopCount`) instead
379349
/// - Parameter loopCount: The animation loop count
380350
public func customLoopCount(_ loopCount: UInt?) -> WebImage {
381-
var result = self
382-
result.customLoopCount = loopCount
383-
return result
351+
self.imagePlayer.customLoopCount = loopCount
352+
return self
384353
}
385354

386355
/// Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is nil.
@@ -390,19 +359,17 @@ extension WebImage {
390359
/// `UInt.max` means cache all the buffer. (Lowest CPU and Highest Memory)
391360
/// - Parameter bufferSize: The max buffer size
392361
public func maxBufferSize(_ bufferSize: UInt?) -> WebImage {
393-
var result = self
394-
result.maxBufferSize = bufferSize
395-
return result
362+
self.imagePlayer.maxBufferSize = bufferSize
363+
return self
396364
}
397365

398366
/// The runLoopMode when animation is playing on. Defaults is `.common`
399367
/// You can specify a runloop mode to let it rendering.
400368
/// - Note: This is useful for some cases, for example, always specify NSDefaultRunLoopMode, if you want to pause the animation when user scroll (for Mac user, drag the mouse or touchpad)
401369
/// - Parameter runLoopMode: The runLoopMode for animation
402370
public func runLoopMode(_ runLoopMode: RunLoop.Mode) -> WebImage {
403-
var result = self
404-
result.runLoopMode = runLoopMode
405-
return result
371+
self.imagePlayer.runLoopMode = runLoopMode
372+
return self
406373
}
407374

408375
/// Whether or not to pause the animation (keep current frame), instead of stop the animation (frame index reset to 0). When `isAnimating` binding value changed to false. Defaults is true.
@@ -431,9 +398,8 @@ extension WebImage {
431398
/// `< 0.0` is not supported currently and stop animation. (may support reverse playback in the future)
432399
/// - Parameter playbackRate: The animation playback rate.
433400
public func playbackRate(_ playbackRate: Double) -> WebImage {
434-
var result = self
435-
result.playbackRate = playbackRate
436-
return result
401+
self.imagePlayer.playbackRate = playbackRate
402+
return self
437403
}
438404
}
439405

0 commit comments

Comments
 (0)