123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- //
- // SLImageView.m
- // WSLImageView
- //
- // Created by 王双龙 on 2018/10/26.
- // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved.
- //
- #import "SLImageView.h"
- #include <mach/mach.h>
- #define SL_LOCK_VIEW(...) dispatch_semaphore_wait(view->_lock, DISPATCH_TIME_FOREVER); \
- __VA_ARGS__; \
- dispatch_semaphore_signal(view->_lock);
- #define SL_LOCK(...) dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER); \
- __VA_ARGS__; \
- dispatch_semaphore_signal(self->_lock);
- #define SL_BUFFER_SIZE (10 * 1024 * 1024) // 10MB (minimum memory buffer size)
- #define SL_CLAMP(_x_, _low_, _high_) (((_x_) > (_high_)) ? (_high_) : (((_x_) < (_low_)) ? (_low_) : (_x_)))
- #pragma mark - 弱引用对象
- //临时弱引用对象,解决循环引用的问题 引自 YYWeakProxy
- @interface SLWeakProxy : NSProxy <NSObject>
- @property (nullable, nonatomic, weak, readonly) id target;
- @end
- @implementation SLWeakProxy
- + (instancetype)proxyWithTarget:(id)target {
- return [[SLWeakProxy alloc] initWithTarget:target];
- }
- - (instancetype)initWithTarget:(id)target {
- _target = target;
- return self;
- }
- //将消息接收对象改为 _target
- - (id)forwardingTargetForSelector:(SEL)selector {
- return _target;
- }
- //self 对 target 是弱引用,一旦 target 被释放将调用下面两个方法,如果不实现的话会 crash
- - (void)forwardInvocation:(NSInvocation *)invocation {
- void *null = NULL;
- [invocation setReturnValue:&null];
- }
- - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
- return [NSObject instanceMethodSignatureForSelector:@selector(init)];
- }
- - (BOOL)respondsToSelector:(SEL)aSelector {
- return [_target respondsToSelector:aSelector];
- }
- - (BOOL)isEqual:(id)object {
- return [_target isEqual:object];
- }
- - (NSUInteger)hash {
- return [_target hash];
- }
- - (Class)superclass {
- return [_target superclass];
- }
- - (Class)class {
- return [_target class];
- }
- - (BOOL)isKindOfClass:(Class)aClass {
- return [_target isKindOfClass:aClass];
- }
- - (BOOL)isMemberOfClass:(Class)aClass {
- return [_target isMemberOfClass:aClass];
- }
- - (BOOL)conformsToProtocol:(Protocol *)aProtocol {
- return [_target conformsToProtocol:aProtocol];
- }
- - (BOOL)isProxy {
- return YES;
- }
- - (NSString *)description {
- return [_target description];
- }
- - (NSString *)debugDescription {
- return [_target debugDescription];
- }
- @end
- #pragma mark - 帧动画ImageView
- @interface SLImageView () {
- @package
- CADisplayLink *_displayLink; /// 帧动画切换器
- NSTimeInterval _time; /// 上一帧展示完剩余的时间
- SLImage *_curAnimatedImage; /// 当前动画的image
- NSMutableDictionary *_buffer; ///< 帧缓冲池
- dispatch_semaphore_t _lock; ///< 给帧缓冲池_buffer加锁
- NSInteger _incrBufferCount; ///< 当前帧缓存池插入的帧数
- BOOL _bufferMiss; ///< 是否丢帧
- NSUInteger _maxBufferCount; ///< 最大的缓冲帧数量
- NSUInteger _totalFrameCount; ///< 总帧数
- SLImageFrame *_curFrame; ///< 当前展示的帧
- NSUInteger _curIndex; ///< 当前展示的帧索引
- dispatch_once_t _onceToken;
- NSOperationQueue *_requestQueue; ///< 连续获取某帧image的操作队列
- }
- /**
- 根据当前内存大小动态计算适合的缓存帧数
- */
- - (void)calcMaxBufferCount;
- @end
- #pragma mark - 解码操作
- //解码获取某帧image的线程操作
- @interface SLImageFrameDecodeOperation : NSOperation
- @property (nonatomic, weak) SLImageView *view;
- @property (nonatomic, assign) NSUInteger nextIndex; //解码下一帧
- @property (nonatomic, strong) SLImage *curImage;
- @end
- @implementation SLImageFrameDecodeOperation
- - (void)main {
- __strong SLImageView *view = _view;
- if (!view) return;
- if ([self isCancelled]) return;
- view->_incrBufferCount++;
- if (view->_incrBufferCount == 0) [view calcMaxBufferCount];
- if (view->_incrBufferCount > (NSInteger)view->_maxBufferCount) {
- view->_incrBufferCount = view->_maxBufferCount;
- }
- NSUInteger idx = _nextIndex;
- NSUInteger max = view->_incrBufferCount < 1 ? 1 : view->_incrBufferCount;
- NSUInteger total = view->_totalFrameCount;
- view = nil;
-
- for (int i = 0; i < max; i++, idx++) {
- @autoreleasepool {
- if (idx >= total) idx = 0;
- if ([self isCancelled]) break;
- __strong SLImageView *view = _view;
- if (!view) break;
- SL_LOCK_VIEW(BOOL miss = (view->_buffer[@(idx)] == nil));
- if (miss) {
- SLImageFrame *imageFrame = [_curImage imageFrameAtIndex:idx];
- if ([self isCancelled]) break;
- SL_LOCK_VIEW(view->_buffer[@(idx)] = imageFrame ? imageFrame : [NSNull null]);
- view = nil;
- }
- }
- }
- }
- @end
- @implementation SLImageView
- /**
- 重置动画
- */
- - (void)resetAnimated {
- dispatch_once(&_onceToken, ^{
- self->_lock = dispatch_semaphore_create(1);
- self->_buffer = [NSMutableDictionary new];
- self->_requestQueue = [[NSOperationQueue alloc] init];
- self->_requestQueue.maxConcurrentOperationCount = 1;
- self->_displayLink = [CADisplayLink displayLinkWithTarget:[SLWeakProxy proxyWithTarget:self] selector:@selector(playAnimationImage:)];
- [self->_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
- self->_displayLink.paused = YES;
-
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
- });
-
- if (_curIndex != 0) {
- [self willChangeValueForKey:@"currentAnimatedImageIndex"];
- _curIndex = 0;
- [self didChangeValueForKey:@"currentAnimatedImageIndex"];
- }
-
- _displayLink.paused = !_currentIsPlaying;
- _time = 0;
- _curFrame = nil;
- _bufferMiss = NO;
- _incrBufferCount = 0;
- [_buffer removeAllObjects];
- [_requestQueue cancelAllOperations];
- }
- #pragma mark - Event Handle
- //切换图片帧
- - (void)playAnimationImage:(CADisplayLink *)displayLink {
- if (!_autoPlayAnimatedImage) {
- return;
- }
- NSMutableDictionary *buffer = _buffer;
- SLImageFrame *nextBufferedImage = nil;
- SLImageFrame *curBufferedImage = nil;
- BOOL bufferIsFull = NO;
- NSUInteger nextIndex = (_curIndex + 1) % _curAnimatedImage.frameCount;
- curBufferedImage = buffer[@(_curIndex)];
- nextBufferedImage = buffer[@(nextIndex)];
-
- NSTimeInterval imageDuration = 0;
- if (!_bufferMiss) {
- _time += _displayLink.duration;
- imageDuration = curBufferedImage == nil ? [_curAnimatedImage imageDurationAtIndex:_curIndex] : curBufferedImage.duration;
- if (_time < imageDuration) return;
- _time -= imageDuration;
- if (nextIndex == 0) {
- //一个循环完成
- }
- imageDuration = nextBufferedImage == nil ? [_curAnimatedImage imageDurationAtIndex:nextIndex] : nextBufferedImage.duration;
- if (_time > imageDuration) _time = imageDuration; // do not jump over frame
- }
-
- SL_LOCK(
- if (nextBufferedImage) {
- if ((int)_incrBufferCount < _totalFrameCount) {
- [buffer removeObjectForKey:@(nextIndex)];
- }
- [self willChangeValueForKey:@"currentAnimatedImageIndex"];
- _curIndex = nextIndex;
- [self didChangeValueForKey:@"currentAnimatedImageIndex"];
- _curFrame = nextBufferedImage == (id)[NSNull null] ? nil : nextBufferedImage;
- super.image = _curFrame.image == nil ? [UIImage new] :_curFrame.image ;
- nextIndex = (_curIndex + 1) % _totalFrameCount;
- _bufferMiss = NO;
- if (buffer.count == _totalFrameCount) {
- bufferIsFull = YES;
- }
- } else {
- _bufferMiss = YES;
- }
- )//LOCK
-
- if (!bufferIsFull && _requestQueue.operationCount == 0) {
- //异步串行队列去执行下一帧的解码任务
- SLImageFrameDecodeOperation *operation = [SLImageFrameDecodeOperation new];
- operation.view = self;
- operation.nextIndex = nextIndex;
- operation.curImage = _curAnimatedImage;
- [_requestQueue addOperation:operation];
- }
-
- }
- //收到内存警告,缓冲池里只保留下一帧
- - (void)didReceiveMemoryWarning:(NSNotification *)notification{
- [_requestQueue cancelAllOperations];
- [_requestQueue addOperationWithBlock: ^{
- self->_incrBufferCount = -60 - (int)(arc4random() % 120); // about 1~3 seconds to grow back..
- NSNumber *next = @((self->_curIndex + 1) % self->_totalFrameCount);
- SL_LOCK(
- NSArray * keys = self->_buffer.allKeys;
- for (NSNumber * key in keys) {
- if (![key isEqualToNumber:next]) { // keep the next frame for smoothly animation
- [self->_buffer removeObjectForKey:key];
- }
- }
- )//LOCK
- }];
- }
- //进入后台,缓冲池里只保留下一帧
- - (void)didEnterBackground:(NSNotification *)notification{
- [_requestQueue cancelAllOperations];
- NSNumber *next = @((_curIndex + 1) % _totalFrameCount);
- SL_LOCK(
- NSArray * keys = _buffer.allKeys;
- for (NSNumber * key in keys) {
- if (![key isEqualToNumber:next]) { // keep the next frame for smoothly animation
- [_buffer removeObjectForKey:key];
- }
- }
- )//LOCK
- }
- #pragma mark - Help Methods
- // 根据当前内存大小动态计算适合的缓存帧数
- - (void)calcMaxBufferCount {
- int64_t bytes = (int64_t)[_curAnimatedImage imageFrameBytes];
- if (bytes == 0) bytes = 1024;
- int64_t total = [self memoryTotal];
- int64_t free = [self memoryFree];
- int64_t max = MIN(total * 0.2, free * 0.6);
- max = MAX(max, SL_BUFFER_SIZE);
- if (_maxBufferSize) max = max > _maxBufferSize ? _maxBufferSize : max;
- double maxBufferCount = (double)max / (double)bytes;
- maxBufferCount = SL_CLAMP(maxBufferCount, 1, 100);
- _maxBufferCount = maxBufferCount;
- }
- // 总共的内存大小
- - (int64_t)memoryTotal {
- int64_t mem = [[NSProcessInfo processInfo] physicalMemory];
- if (mem < -1) mem = -1;
- return mem;
- }
- // 空闲的内存大小
- - (int64_t)memoryFree {
- mach_port_t host_port = mach_host_self();
- mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
- vm_size_t page_size;
- vm_statistics_data_t vm_stat;
- kern_return_t kern;
-
- kern = host_page_size(host_port, &page_size);
- if (kern != KERN_SUCCESS) return -1;
- kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
- if (kern != KERN_SUCCESS) return -1;
- return vm_stat.free_count * page_size;
- }
- #pragma mark - Setter And Getter
- - (void)setImage:(SLImage *)image{
- if ([image isMemberOfClass:[UIImage class]]) {
- image = [SLImage imageWithData:UIImagePNGRepresentation(image)];
- }
- [self resetAnimated];
- _curAnimatedImage = (SLImage *)image;
- _totalFrameCount = _curAnimatedImage.frameCount;
- super.image = [_curAnimatedImage imageAtIndex:_curIndex];
- [self calcMaxBufferCount];
- [self didMoved];
- }
- - (void)setCurrentImageIndex:(NSUInteger)currentImageIndex{
- if (!_curAnimatedImage) return;
- if (currentImageIndex >= _curAnimatedImage.frameCount) return;
- if (_curIndex == currentImageIndex) return;
-
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- dispatch_async(dispatch_get_main_queue(), ^{
- SL_LOCK(
- [self->_requestQueue cancelAllOperations];
- [self->_buffer removeAllObjects];
- [self willChangeValueForKey:@"currentAnimatedImageIndex"];
- self->_curIndex = currentImageIndex;
- [self didChangeValueForKey:@"currentAnimatedImageIndex"];
- self->_curFrame = [self->_curAnimatedImage imageFrameAtIndex:self->_curIndex];
- self->_time = 0;
- self->_bufferMiss = NO;
- super.image = [self->_curAnimatedImage imageAtIndex:self->_curIndex];
- )//LOCK
- });
- });
- }
- - (SLImage *)animatedImage {
- return _curAnimatedImage;
- }
- - (NSUInteger)currentImageIndex{
- return _curIndex;
- }
- - (SLImageType)imageType {
- return _curAnimatedImage.imageType;
- }
- #pragma mark - Overrice NSObject(NSKeyValueObservingCustomization)
- + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
- if ([key isEqualToString:@"currentAnimatedImageIndex"]) {
- return NO;
- }
- return [super automaticallyNotifiesObserversForKey:key];
- }
- #pragma mark - 重写系统方法
- - (instancetype)init {
- self = [super init];
- if (self) {
- _autoPlayAnimatedImage = YES;
- }
- return self;
- }
- - (instancetype)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- _autoPlayAnimatedImage = YES;
- }
- return self;
- }
- /**
- 开始动画
- */
- - (void)startAnimating{
- if(_curAnimatedImage.frameCount > 1 && _totalFrameCount != 0){
- _displayLink.paused = NO;
- _currentIsPlaying = YES;
- }else{
- [self stopAnimating];
- _currentIsPlaying = NO;
- }
- }
- /**
- 关闭动画
- */
- - (void)stopAnimating{
- [super stopAnimating];
- [_requestQueue cancelAllOperations];
- _displayLink.paused = YES;
- _currentIsPlaying = NO;
- }
- - (void)dealloc {
- [self stopAnimating];
- [_displayLink invalidate];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
- }
- - (void)didMoveToWindow {
- [super didMoveToWindow];
- [self didMoved];
- }
- - (void)didMoveToSuperview {
- [super didMoveToSuperview];
- [self didMoved];
- }
- - (void)didMoved {
- if (self.autoPlayAnimatedImage) {
- if(self.superview && self.window) {
- [self startAnimating];
- } else {
- [self stopAnimating];
- }
- }
- }
- @end
|