2 Replies
      Latest reply on Dec 2, 2019 2:33 PM by SlaunchaMan
      SlaunchaMan Level 1 Level 1 (0 points)

        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?

        • Re: Zombie IOSurface Objects with SwiftUI and ImageIO
          SlaunchaMan Level 1 Level 1 (0 points)

          Also, if it helps, I’m getting some output in the Xcode console while this happens:

           

          0000000A:  0100  4          4          128
          00000016:  0101  4          4          128
          00000022:  0102  3          6          110
          0000002E:  011A  5          8          116
          0000003A:  011B  5          8          124
          00000046:  0128  3          2            2
          00000052:  0131  2         12          132
          0000005E:  0132  2         20          144
          000000A6:  0100  4          4          256
          000000B2:  0101  4          4          256
          000000BE:  0102  3          6          266
          000000CA:  0103  3          2            6
          000000D6:  0106  3          2            6
          000000E2:  0115  3          2            3
          000000EE:  0201  4          4          272
          000000FA:  0202  4          4         3957
          0000000A:  0100  4          4          128
          00000016:  0101  4          4          128
          00000022:  0102  3          6          110
          0000002E:  011A  5          8          116
          0000003A:  011B  5          8          124
          00000046:  0128  3          2            2
          00000052:  0131  2         12          132
          0000005E:  0132  2         20          144
          000000A6:  0100  4          4          256
          000000B2:  0101  4          4          256
          000000BE:  0102  3          6          266
          000000CA:  0103  3          2            6
          000000D6:  0106  3          2            6
          000000E2:  0115  3          2            3
          000000EE:  0201  4          4          272
          000000FA:  0202  4          4         3957
          
            • Re: Zombie IOSurface Objects with SwiftUI and ImageIO
              SlaunchaMan Level 1 Level 1 (0 points)

              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.