I’m trying to use the new ImageIO API for animating some gifs in a SwiftUI app. I created a wrapper in Objective-C to handle the ImageIO API:
// ImageFrameScheduler.h
@import CoreGraphics;
@import Foundation;
NS_ASSUME_NONNULL_BEGIN
@interface ImageFrameScheduler : NSObject
@property (readonly) NSURL *imageURL;
- (instancetype)initWithURL:(NSURL *)imageURL NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (BOOL)startWithFrameHandler:(void (^)(NSInteger, CGImageRef))handler
error:(NSError * _Nullable *)error;
- (void)stop;
@end
NS_ASSUME_NONNULL_END
// ImageFrameScheduler.m
#import "ImageFrameScheduler.h"
@import ImageIO;
@interface ImageFrameScheduler()
@property (readwrite) NSURL *imageURL;
@property (getter=isStopping) BOOL stopping;
@end
@implementation ImageFrameScheduler
- (instancetype)initWithURL:(NSURL *)imageURL
{
self = [super init];
if (self) {
_imageURL = imageURL;
_stopping = NO;
}
return self;
}
- (BOOL)startWithFrameHandler:(void (^)(NSInteger, CGImageRef _Nonnull))handler
error:(NSError * _Nullable *)error
{
__weak typeof(self) welf = self;
CFURLRef urlRef = (__bridge CFURLRef)self.imageURL;
OSStatus status = CGAnimateImageAtURLWithBlock(urlRef,
nil,
^(size_t index,
CGImageRef _Nonnull image,
bool* _Nonnull stop) {
if (welf == nil || welf.stopping) {
*stop = true;
return;
}
handler(index, image);
});
if (status != noErr) {
if (error != NULL) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:status
userInfo:nil];
}
}
return status == noErr;
}
- (void)stop
{
self.stopping = YES;
}
@end
When I finish loading gifs from the network, I use this in a SwiftUI Image, called from my view body’s onAppear property:
private func startAnimating() {
imageFrameScheduler = ImageFrameScheduler(url: cachedImageURL)
do {
try imageFrameScheduler?.start { (_, image) in
self.image = Image(decorative: image, scale: 1)
}
}
catch {
print("Error animating image: \(error)")
}
}
These gifs are in a List, and if I scroll quickly, I eventually get a crash on an IOSurface zombie object:
*** -[IOSurface retain]: message sent to deallocated instance 0x60200004de50
Is there something I’m missing in the memory management of the SwiftUI Image struct or the ImageIO framework?
I think I figured it out. In the callback for the animation frame, I’m setting an image property to an Image struct with the CGImage I get from ImageIO. As you scroll, if a row comes back into view that had a cached CGImage from this animation block, and the animation has since been stopped, then accessing it crashed. So, setting the image property back to nil when I stop the animation fixed the issue.