From 7e85869e6c21a7d006c30e2e537db91e2adbd697 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 17 Oct 2019 00:56:32 +0800 Subject: [PATCH 1/3] Refactory the display link with a cross-platform implementation, CADisplayLink. for iOS/tvOS, CVDDisplayLink for macOS, NSTimer for watchOS --- SDWebImage.xcodeproj/project.pbxproj | 16 ++- SDWebImage/Private/SDDisplayLink.h | 27 ++++ SDWebImage/Private/SDDisplayLink.m | 190 +++++++++++++++++++++++++++ 3 files changed, 230 insertions(+), 3 deletions(-) create mode 100644 SDWebImage/Private/SDDisplayLink.h create mode 100644 SDWebImage/Private/SDDisplayLink.m diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index 1a36cba3..e79b9beb 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -196,6 +196,9 @@ 32D3CDCF21DDE87300C4DB49 /* UIImage+MemoryCacheCost.m in Sources */ = {isa = PBXBuildFile; fileRef = 32D3CDCC21DDE87300C4DB49 /* UIImage+MemoryCacheCost.m */; }; 32D3CDD121DDE87300C4DB49 /* UIImage+MemoryCacheCost.h in Headers */ = {isa = PBXBuildFile; fileRef = 32D3CDCD21DDE87300C4DB49 /* UIImage+MemoryCacheCost.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32E5690822B1FFCA00CBABC6 /* SDWebImageOptionsProcessor.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 324406292296C5F400A36084 /* SDWebImageOptionsProcessor.h */; }; + 32E67311235765B500DB4987 /* SDDisplayLink.h in Headers */ = {isa = PBXBuildFile; fileRef = 32E6730F235765B500DB4987 /* SDDisplayLink.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 32E67312235765B500DB4987 /* SDDisplayLink.m in Sources */ = {isa = PBXBuildFile; fileRef = 32E67310235765B500DB4987 /* SDDisplayLink.m */; }; + 32E67313235765B500DB4987 /* SDDisplayLink.m in Sources */ = {isa = PBXBuildFile; fileRef = 32E67310235765B500DB4987 /* SDDisplayLink.m */; }; 32EB6D8E206D132E005CAEF6 /* SDAnimatedImageRep.m in Sources */ = {isa = PBXBuildFile; fileRef = 320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */; }; 32EB6D91206D132E005CAEF6 /* SDAnimatedImageRep.m in Sources */ = {isa = PBXBuildFile; fileRef = 320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */; }; 32F21B5320788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F21B4F20788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -433,6 +436,8 @@ 32D1221D2080B2EB003685A3 /* SDImageCachesManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDImageCachesManager.h; path = Core/SDImageCachesManager.h; sourceTree = ""; }; 32D3CDCC21DDE87300C4DB49 /* UIImage+MemoryCacheCost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+MemoryCacheCost.m"; path = "Core/UIImage+MemoryCacheCost.m"; sourceTree = ""; }; 32D3CDCD21DDE87300C4DB49 /* UIImage+MemoryCacheCost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+MemoryCacheCost.h"; path = "Core/UIImage+MemoryCacheCost.h"; sourceTree = ""; }; + 32E6730F235765B500DB4987 /* SDDisplayLink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDDisplayLink.h; sourceTree = ""; }; + 32E67310235765B500DB4987 /* SDDisplayLink.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDDisplayLink.m; sourceTree = ""; }; 32F21B4F20788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageDownloaderRequestModifier.h; path = Core/SDWebImageDownloaderRequestModifier.h; sourceTree = ""; }; 32F21B5020788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageDownloaderRequestModifier.m; path = Core/SDWebImageDownloaderRequestModifier.m; sourceTree = ""; }; 32F7C06D2030114C00873181 /* SDImageTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDImageTransformer.h; path = Core/SDImageTransformer.h; sourceTree = ""; }; @@ -547,7 +552,7 @@ name = Decoder; sourceTree = ""; }; - 32484756201775CE00AF9E5A /* ImageView */ = { + 32484756201775CE00AF9E5A /* AnimatedImage */ = { isa = PBXGroup; children = ( 3248475B201775F600AF9E5A /* SDAnimatedImage.h */, @@ -559,7 +564,7 @@ 320224B9203979BA00E9F285 /* SDAnimatedImageRep.h */, 320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */, ); - name = ImageView; + name = AnimatedImage; sourceTree = ""; }; 328BB6972081FDAB00760D6C /* Manager */ = { @@ -602,6 +607,8 @@ 32B5CC5F222F89C2005EB74E /* SDAsyncBlockOperation.m */, 325C460622339426004CAE11 /* SDWeakProxy.h */, 325C460722339426004CAE11 /* SDWeakProxy.m */, + 32E6730F235765B500DB4987 /* SDDisplayLink.h */, + 32E67310235765B500DB4987 /* SDDisplayLink.m */, 325C460022339330004CAE11 /* SDImageAssetManager.h */, 325C460122339330004CAE11 /* SDImageAssetManager.m */, 325C460C223394D8004CAE11 /* SDImageCachesManagerOperation.h */, @@ -706,7 +713,7 @@ 321E60831F38E88F00405457 /* Decoder */, 328BB6982081FDD800760D6C /* Prefetcher */, 328BB6992081FDDF00760D6C /* Transformer */, - 32484756201775CE00AF9E5A /* ImageView */, + 32484756201775CE00AF9E5A /* AnimatedImage */, 53922DAC148C56DD0056699D /* Utils */, 53922DA9148C562D0056699D /* Categories */, 4369C2851D9811BB007E863A /* WebCache Categories */, @@ -880,6 +887,7 @@ 80B6DF7F2142B43300BCB334 /* NSImage+Compatibility.h in Headers */, 32C0FDE32013426C001B8F2D /* SDWebImageIndicator.h in Headers */, 32F7C0712030114C00873181 /* SDImageTransformer.h in Headers */, + 32E67311235765B500DB4987 /* SDDisplayLink.h in Headers */, 4A2CAE2D1AB4BB7500B6BC39 /* UIImage+GIF.h in Headers */, 4A2CAE291AB4BB7500B6BC39 /* NSData+ImageContentType.h in Headers */, 328BB69E2081FED200760D6C /* SDWebImageCacheKeyFilter.h in Headers */, @@ -1076,6 +1084,7 @@ 3244062E2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */, 3250C9F02355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */, 328BB6A42081FED200760D6C /* SDWebImageCacheKeyFilter.m in Sources */, + 32E67313235765B500DB4987 /* SDDisplayLink.m in Sources */, 4A2CAE2E1AB4BB7500B6BC39 /* UIImage+GIF.m in Sources */, 80B6DF822142B44400BCB334 /* NSButton+WebCache.m in Sources */, 32D3CDCF21DDE87300C4DB49 /* UIImage+MemoryCacheCost.m in Sources */, @@ -1143,6 +1152,7 @@ 3244062D2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */, 3250C9EF2355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */, 328BB6A22081FED200760D6C /* SDWebImageCacheKeyFilter.m in Sources */, + 32E67312235765B500DB4987 /* SDDisplayLink.m in Sources */, 53761309155AD0D5005750A4 /* SDImageCache.m in Sources */, 80B6DF832142B44500BCB334 /* NSButton+WebCache.m in Sources */, 32D3CDCE21DDE87300C4DB49 /* UIImage+MemoryCacheCost.m in Sources */, diff --git a/SDWebImage/Private/SDDisplayLink.h b/SDWebImage/Private/SDDisplayLink.h new file mode 100644 index 00000000..2da9de16 --- /dev/null +++ b/SDWebImage/Private/SDDisplayLink.h @@ -0,0 +1,27 @@ +/* +* This file is part of the SDWebImage package. +* (c) Olivier Poitrey +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +#import +#import "SDWebImageCompat.h" + +@interface SDDisplayLink : NSObject + +@property (readonly, nonatomic, weak, nullable) id target; +@property (readonly, nonatomic, assign, nonnull) SEL selector; +@property (readonly, nonatomic) CFTimeInterval duration; +@property (readonly, nonatomic) BOOL isRunning; + ++ (nonnull instancetype)displayLinkWithTarget:(nonnull id)target selector:(nonnull SEL)sel; + +- (void)addToRunLoop:(nonnull NSRunLoop *)runloop forMode:(nonnull NSRunLoopMode)mode; +- (void)removeFromRunLoop:(nonnull NSRunLoop *)runloop forMode:(nonnull NSRunLoopMode)mode; + +- (void)start; +- (void)stop; + +@end diff --git a/SDWebImage/Private/SDDisplayLink.m b/SDWebImage/Private/SDDisplayLink.m new file mode 100644 index 00000000..f64e38b1 --- /dev/null +++ b/SDWebImage/Private/SDDisplayLink.m @@ -0,0 +1,190 @@ +/* +* This file is part of the SDWebImage package. +* (c) Olivier Poitrey +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +#import "SDDisplayLink.h" +#import "SDWeakProxy.h" +#if SD_MAC +#import +#elif SD_IOS || SD_TV +#import +#endif + +#if SD_MAC +static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext); +#endif + +#if SD_WATCH +#define kSDDisplayLinkInterval 1.0 / 60 +#endif + +@interface SDDisplayLink () + +#if SD_MAC +@property (nonatomic, assign) CVDisplayLinkRef displayLink; +#elif SD_IOS || SD_TV +@property (nonatomic, strong) CADisplayLink *displayLink; +#else +@property (nonatomic, strong) NSTimer *displayLink; +#endif + +@end + +@implementation SDDisplayLink + +- (void)dealloc { +#if SD_MAC + if (_displayLink) { + CVDisplayLinkRelease(_displayLink); + _displayLink = NULL; + } +#elif SD_IOS || SD_TV + [_displayLink invalidate]; + _displayLink = nil; +#else + [_displayLink invalidate]; + _displayLink = nil; +#endif +} + +- (instancetype)initWithTarget:(id)target selector:(SEL)sel { + self = [super init]; + if (self) { + _target = target; + _selector = sel; +#if SD_MAC + CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); + CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void *)self); +#elif SD_IOS || SD_TV + SDWeakProxy *weakProxy = [SDWeakProxy proxyWithTarget:self]; + _displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayLinkDidRefresh:)]; +#else + SDWeakProxy *weakProxy = [SDWeakProxy proxyWithTarget:self]; + _displayLink = [NSTimer timerWithTimeInterval:kSDDisplayLinkInterval target:weakProxy selector:@selector(displayLinkDidRefresh:) userInfo:nil repeats:YES]; +#endif + } + return self; +} + ++ (instancetype)displayLinkWithTarget:(id)target selector:(SEL)sel { + SDDisplayLink *displayLink = [[SDDisplayLink alloc] initWithTarget:target selector:sel]; + return displayLink; +} + +- (CFTimeInterval)duration { +#if SD_MAC + CVTimeStamp nowTime; + CVDisplayLinkGetCurrentTime(_displayLink, &nowTime); + NSTimeInterval duration = (double)nowTime.videoRefreshPeriod / ((double)nowTime.videoTimeScale * nowTime.rateScalar); +#elif SD_IOS || SD_TV + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + NSTimeInterval duration = self.displayLink.duration * self.displayLink.frameInterval; + #pragma clang diagnostic pop +#else + NSTimeInterval duration = 1.0 / 60; +#endif + return duration; +} + +- (BOOL)isRunning { +#if SD_MAC + return CVDisplayLinkIsRunning(self.displayLink); +#elif SD_IOS || SD_TV + return !self.displayLink.isPaused; +#else + return self.displayLink.isValid; +#endif +} + +- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode { + if (!runloop || !mode) { + return; + } +#if SD_MAC + // CVDisplayLink does not use runloop +#elif SD_IOS || SD_TV + [self.displayLink addToRunLoop:runloop forMode:mode]; +#else + CFRunLoopMode cfMode; + if ([mode isEqualToString:NSDefaultRunLoopMode]) { + cfMode = kCFRunLoopDefaultMode; + } else if ([mode isEqualToString:NSRunLoopCommonModes]) { + cfMode = kCFRunLoopCommonModes; + } else { + cfMode = (__bridge CFStringRef)mode; + } + CFRunLoopAddTimer(runloop.getCFRunLoop, (__bridge CFRunLoopTimerRef)self.displayLink, cfMode); +#endif +} + +- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode { + if (!runloop || !mode) { + return; + } +#if SD_MAC + // CVDisplayLink does not use runloop +#elif SD_IOS || SD_TV + [self.displayLink removeFromRunLoop:runloop forMode:mode]; +#else + CFRunLoopMode cfMode; + if ([mode isEqualToString:NSDefaultRunLoopMode]) { + cfMode = kCFRunLoopDefaultMode; + } else if ([mode isEqualToString:NSRunLoopCommonModes]) { + cfMode = kCFRunLoopCommonModes; + } else { + cfMode = (__bridge CFStringRef)mode; + } + CFRunLoopRemoveTimer(runloop.getCFRunLoop, (__bridge CFRunLoopTimerRef)self.displayLink, cfMode); +#endif +} + +- (void)start { +#if SD_MAC + CVDisplayLinkStart(self.displayLink); +#elif SD_IOS || SD_TV + self.displayLink.paused = NO; +#else + if (self.displayLink.isValid) { + [self.displayLink fire]; + } else { + SDWeakProxy *weakProxy = [SDWeakProxy proxyWithTarget:self]; + self.displayLink = [NSTimer timerWithTimeInterval:kSDDisplayLinkInterval target:weakProxy selector:@selector(displayLinkDidRefresh:) userInfo:nil repeats:YES]; + } +#endif +} + +- (void)stop { +#if SD_MAC + CVDisplayLinkStop(self.displayLink); +#elif SD_IOS || SD_TV + self.displayLink.paused = YES; +#else + [self.displayLink invalidate]; +#endif +} + +- (void)displayLinkDidRefresh:(id)displayLink { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [_target performSelector:_selector withObject:self]; +#pragma clang diagnostic pop +} + +@end + +#if SD_MAC +static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { + // CVDisplayLink callback is not on main queue + SDDisplayLink *object = (__bridge SDDisplayLink *)displayLinkContext; + __weak SDDisplayLink *weakObject = object; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakObject displayLinkDidRefresh:(__bridge id)(displayLink)]; + }); + return kCVReturnSuccess; +} +#endif From 37bf9f66d1784703444bc2b319be3a9da04de719 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 17 Oct 2019 01:21:42 +0800 Subject: [PATCH 2/3] Change the implementation to use the cross-platform SDDisplayLink --- SDWebImage/Core/SDAnimatedImageView.m | 104 +++----------------------- 1 file changed, 12 insertions(+), 92 deletions(-) diff --git a/SDWebImage/Core/SDAnimatedImageView.m b/SDWebImage/Core/SDAnimatedImageView.m index c64f831e..8ea54d92 100644 --- a/SDWebImage/Core/SDAnimatedImageView.m +++ b/SDWebImage/Core/SDAnimatedImageView.m @@ -12,16 +12,11 @@ #import "UIImage+Metadata.h" #import "NSImage+Compatibility.h" -#import "SDWeakProxy.h" +#import "SDDisplayLink.h" #import "SDInternalMacros.h" #import #import -#if SD_MAC -#import -static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext); -#endif - static NSUInteger SDDeviceTotalMemory() { return (NSUInteger)[[NSProcessInfo processInfo] physicalMemory]; } @@ -60,11 +55,7 @@ static NSUInteger SDDeviceFreeMemory() { @property (nonatomic, strong) NSOperationQueue *fetchQueue; @property (nonatomic, strong) dispatch_semaphore_t lock; @property (nonatomic, assign) CGFloat animatedImageScale; -#if SD_MAC -@property (nonatomic, assign) CVDisplayLinkRef displayLink; -#else -@property (nonatomic, strong) CADisplayLink *displayLink; -#endif +@property (nonatomic, strong) SDDisplayLink *displayLink; @property (nonatomic) CALayer *imageViewLayer; // The actual rendering layer. @end @@ -248,7 +239,6 @@ static NSUInteger SDDeviceFreeMemory() { } } -#if SD_UIKIT - (void)setRunLoopMode:(NSRunLoopMode)runLoopMode { if ([_runLoopMode isEqual:runLoopMode]) { @@ -272,7 +262,6 @@ static NSUInteger SDDeviceFreeMemory() { } return _runLoopMode; } -#endif - (BOOL)shouldIncrementalLoad { if (!_initFinished) { @@ -306,47 +295,19 @@ static NSUInteger SDDeviceFreeMemory() { return _lock; } -#if SD_MAC -- (CVDisplayLinkRef)displayLink -{ +- (SDDisplayLink *)displayLink { if (!_displayLink) { - CVReturn error = CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); - if (error) { - return NULL; - } - CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void *)self); - } - return _displayLink; -} -#else -- (CADisplayLink *)displayLink -{ - if (!_displayLink) { - // It is important to note the use of a weak proxy here to avoid a retain cycle. `-displayLinkWithTarget:selector:` - // will retain its target until it is invalidated. We use a weak proxy so that the image view will get deallocated - // independent of the display link's lifetime. Upon image view deallocation, we invalidate the display - // link which will lead to the deallocation of both the display link and the weak proxy. - SDWeakProxy *weakProxy = [SDWeakProxy proxyWithTarget:self]; - _displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayDidRefresh:)]; + _displayLink = [SDDisplayLink displayLinkWithTarget:self selector:@selector(displayDidRefresh:)]; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:self.runLoopMode]; } return _displayLink; } -#endif #pragma mark - Life Cycle - (void)dealloc { - // Removes the display link from all run loop modes. -#if SD_MAC - if (_displayLink) { - CVDisplayLinkRelease(_displayLink); - _displayLink = NULL; - } -#else - [_displayLink invalidate]; - _displayLink = nil; +#if SD_UIKIT [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif } @@ -448,11 +409,7 @@ static NSUInteger SDDeviceFreeMemory() { - (void)startAnimating { if (self.animatedImage) { -#if SD_MAC - CVDisplayLinkStart(self.displayLink); -#else - self.displayLink.paused = NO; -#endif + [self.displayLink start]; } else { #if SD_UIKIT [super startAnimating]; @@ -465,11 +422,7 @@ static NSUInteger SDDeviceFreeMemory() { if (self.animatedImage) { [_fetchQueue cancelAllOperations]; // Using `_displayLink` here because when UIImageView dealloc, it may trigger `[self stopAnimating]`, we already release the display link in SDAnimatedImageView's dealloc method. -#if SD_MAC - CVDisplayLinkStop(_displayLink); -#else - _displayLink.paused = YES; -#endif + [_displayLink stop]; if (self.resetFrameIndexWhenStopped) { [self resetCurrentFrameIndex]; } @@ -487,11 +440,7 @@ static NSUInteger SDDeviceFreeMemory() { { BOOL isAnimating = NO; if (self.animatedImage) { -#if SD_MAC - isAnimating = CVDisplayLinkIsRunning(self.displayLink); -#else - isAnimating = !self.displayLink.isPaused; -#endif + isAnimating = self.displayLink.isRunning; } else { #if SD_UIKIT isAnimating = [super isAnimating]; @@ -579,11 +528,7 @@ static NSUInteger SDDeviceFreeMemory() { } } -#if SD_MAC -- (void)displayDidRefresh:(CVDisplayLinkRef)displayLink -#else -- (void)displayDidRefresh:(CADisplayLink *)displayLink -#endif +- (void)displayDidRefresh:(SDDisplayLink *)displayLink { // If for some reason a wild call makes it through when we shouldn't be animating, bail. // Early return! @@ -591,16 +536,8 @@ static NSUInteger SDDeviceFreeMemory() { return; } // Calculate refresh duration -#if SD_MAC - CVTimeStamp nowTime; - CVDisplayLinkGetCurrentTime(displayLink, &nowTime); - NSTimeInterval duration = (double)nowTime.videoRefreshPeriod / ((double)nowTime.videoTimeScale * nowTime.rateScalar); -#else -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - NSTimeInterval duration = displayLink.duration * displayLink.frameInterval; -#pragma clang diagnostic pop -#endif + NSTimeInterval duration = self.displayLink.duration; + NSUInteger totalFrameCount = self.totalFrameCount; NSUInteger currentFrameIndex = self.currentFrameIndex; NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount; @@ -692,12 +629,7 @@ static NSUInteger SDDeviceFreeMemory() { } UIImage *frame = [animatedImage animatedImageFrameAtIndex:fetchFrameIndex]; - BOOL isAnimating = NO; -#if SD_MAC - isAnimating = CVDisplayLinkIsRunning(self.displayLink); -#else - isAnimating = !self.displayLink.isPaused; -#endif + BOOL isAnimating = self.displayLink.isRunning; if (isAnimating) { SD_LOCK(self.lock); self.frameBuffer[@(fetchFrameIndex)] = frame; @@ -791,16 +723,4 @@ static NSUInteger SDDeviceFreeMemory() { @end -#if SD_MAC -static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { - // CVDisplayLink callback is not on main queue - SDAnimatedImageView *imageView = (__bridge SDAnimatedImageView *)displayLinkContext; - __weak SDAnimatedImageView *weakImageView = imageView; - dispatch_async(dispatch_get_main_queue(), ^{ - [weakImageView displayDidRefresh:displayLink]; - }); - return kCVReturnSuccess; -} -#endif - #endif From 4d917547acce9b0d17b83c575899dfcedeeb50f7 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 17 Oct 2019 01:29:55 +0800 Subject: [PATCH 3/3] Fix the NSTimer implementation for display link --- SDWebImage/Private/SDDisplayLink.h | 3 +++ SDWebImage/Private/SDDisplayLink.m | 25 +++++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/SDWebImage/Private/SDDisplayLink.h b/SDWebImage/Private/SDDisplayLink.h index 2da9de16..60d4e80e 100644 --- a/SDWebImage/Private/SDDisplayLink.h +++ b/SDWebImage/Private/SDDisplayLink.h @@ -9,6 +9,9 @@ #import #import "SDWebImageCompat.h" +// Cross-platform display link wrapper. Do not retain the target +// Use `CADisplayLink` on iOS/tvOS, `CVDisplayLink` on macOS, `NSTimer` on watchOS + @interface SDDisplayLink : NSObject @property (readonly, nonatomic, weak, nullable) id target; diff --git a/SDWebImage/Private/SDDisplayLink.m b/SDWebImage/Private/SDDisplayLink.m index f64e38b1..5ab09d51 100644 --- a/SDWebImage/Private/SDDisplayLink.m +++ b/SDWebImage/Private/SDDisplayLink.m @@ -30,6 +30,9 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt @property (nonatomic, strong) CADisplayLink *displayLink; #else @property (nonatomic, strong) NSTimer *displayLink; +@property (nonatomic, strong) NSRunLoop *runloop; +@property (nonatomic, strong) NSRunLoopMode runloopMode; +@property (nonatomic, assign) NSTimeInterval currentFireDate; #endif @end @@ -81,12 +84,18 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt CVDisplayLinkGetCurrentTime(_displayLink, &nowTime); NSTimeInterval duration = (double)nowTime.videoRefreshPeriod / ((double)nowTime.videoTimeScale * nowTime.rateScalar); #elif SD_IOS || SD_TV - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" NSTimeInterval duration = self.displayLink.duration * self.displayLink.frameInterval; - #pragma clang diagnostic pop +#pragma clang diagnostic pop #else - NSTimeInterval duration = 1.0 / 60; + NSTimeInterval duration; + if (!self.displayLink.isValid || self.currentFireDate == 0) { + duration = kSDDisplayLinkInterval; + } else { + NSTimeInterval nextFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink); + duration = nextFireDate - self.currentFireDate; + } #endif return duration; } @@ -110,6 +119,8 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt #elif SD_IOS || SD_TV [self.displayLink addToRunLoop:runloop forMode:mode]; #else + self.runloop = runloop; + self.runloopMode = mode; CFRunLoopMode cfMode; if ([mode isEqualToString:NSDefaultRunLoopMode]) { cfMode = kCFRunLoopDefaultMode; @@ -131,6 +142,8 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt #elif SD_IOS || SD_TV [self.displayLink removeFromRunLoop:runloop forMode:mode]; #else + self.runloop = nil; + self.runloopMode = nil; CFRunLoopMode cfMode; if ([mode isEqualToString:NSDefaultRunLoopMode]) { cfMode = kCFRunLoopDefaultMode; @@ -154,6 +167,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt } else { SDWeakProxy *weakProxy = [SDWeakProxy proxyWithTarget:self]; self.displayLink = [NSTimer timerWithTimeInterval:kSDDisplayLinkInterval target:weakProxy selector:@selector(displayLinkDidRefresh:) userInfo:nil repeats:YES]; + [self addToRunLoop:self.runloop forMode:self.runloopMode]; } #endif } @@ -173,6 +187,9 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [_target performSelector:_selector withObject:self]; #pragma clang diagnostic pop +#if SD_WATCH + self.currentFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink); +#endif } @end