Merge pull request #2867 from dreampiggy/refactory_animated_player

Refactory animated player phase 1 - cross-platform display link implementation
This commit is contained in:
DreamPiggy 2019-10-21 17:46:59 +08:00 committed by GitHub
commit bbf2c28f84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 262 additions and 95 deletions

View File

@ -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 = "<group>"; };
32D3CDCC21DDE87300C4DB49 /* UIImage+MemoryCacheCost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+MemoryCacheCost.m"; path = "Core/UIImage+MemoryCacheCost.m"; sourceTree = "<group>"; };
32D3CDCD21DDE87300C4DB49 /* UIImage+MemoryCacheCost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+MemoryCacheCost.h"; path = "Core/UIImage+MemoryCacheCost.h"; sourceTree = "<group>"; };
32E6730F235765B500DB4987 /* SDDisplayLink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDDisplayLink.h; sourceTree = "<group>"; };
32E67310235765B500DB4987 /* SDDisplayLink.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDDisplayLink.m; sourceTree = "<group>"; };
32F21B4F20788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageDownloaderRequestModifier.h; path = Core/SDWebImageDownloaderRequestModifier.h; sourceTree = "<group>"; };
32F21B5020788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageDownloaderRequestModifier.m; path = Core/SDWebImageDownloaderRequestModifier.m; sourceTree = "<group>"; };
32F7C06D2030114C00873181 /* SDImageTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDImageTransformer.h; path = Core/SDImageTransformer.h; sourceTree = "<group>"; };
@ -547,7 +552,7 @@
name = Decoder;
sourceTree = "<group>";
};
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 = "<group>";
};
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 */,

View File

@ -12,16 +12,11 @@
#import "UIImage+Metadata.h"
#import "NSImage+Compatibility.h"
#import "SDWeakProxy.h"
#import "SDDisplayLink.h"
#import "SDInternalMacros.h"
#import <mach/mach.h>
#import <objc/runtime.h>
#if SD_MAC
#import <CoreVideo/CoreVideo.h>
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

View File

@ -0,0 +1,30 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#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;
@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

View File

@ -0,0 +1,207 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* 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 <CoreVideo/CoreVideo.h>
#elif SD_IOS || SD_TV
#import <QuartzCore/QuartzCore.h>
#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;
@property (nonatomic, strong) NSRunLoop *runloop;
@property (nonatomic, strong) NSRunLoopMode runloopMode;
@property (nonatomic, assign) NSTimeInterval currentFireDate;
#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;
if (!self.displayLink.isValid || self.currentFireDate == 0) {
duration = kSDDisplayLinkInterval;
} else {
NSTimeInterval nextFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
duration = nextFireDate - self.currentFireDate;
}
#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
self.runloop = runloop;
self.runloopMode = mode;
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
self.runloop = nil;
self.runloopMode = nil;
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];
[self addToRunLoop:self.runloop forMode:self.runloopMode];
}
#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
#if SD_WATCH
self.currentFireDate = CFRunLoopTimerGetNextFireDate((__bridge CFRunLoopTimerRef)self.displayLink);
#endif
}
@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