diff --git a/SVGAPlayer.xcodeproj/project.pbxproj b/SVGAPlayer.xcodeproj/project.pbxproj index d9bf999..29465e1 100644 --- a/SVGAPlayer.xcodeproj/project.pbxproj +++ b/SVGAPlayer.xcodeproj/project.pbxproj @@ -58,6 +58,7 @@ 90D7CA1C1F7FB114006E74F0 /* rose_1.5.0.svga in Resources */ = {isa = PBXBuildFile; fileRef = 90D7CA1A1F7FB114006E74F0 /* rose_1.5.0.svga */; }; 90D7CA1E1F7FB34E006E74F0 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 90D7CA1D1F7FB34E006E74F0 /* libz.tbd */; }; 90DB59B51F96026E00894727 /* SVGAImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 90DB59B41F96026E00894727 /* SVGAImageView.m */; }; + FA44BB1A244B1394001B5D75 /* SVGAVideoEntityMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = FA44BB19244B1394001B5D75 /* SVGAVideoEntityMemoryCache.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -119,6 +120,8 @@ 90DB59B41F96026E00894727 /* SVGAImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SVGAImageView.m; sourceTree = ""; }; 92332F7A897BF4379D765B05 /* libPods-SVGAPlayer React.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SVGAPlayer React.a"; sourceTree = BUILT_PRODUCTS_DIR; }; E02B8713B25C0283C736EE03 /* Pods-SVGAPlayer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SVGAPlayer.release.xcconfig"; path = "Pods/Target Support Files/Pods-SVGAPlayer/Pods-SVGAPlayer.release.xcconfig"; sourceTree = ""; }; + FA44BB18244B1394001B5D75 /* SVGAVideoEntityMemoryCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SVGAVideoEntityMemoryCache.h; sourceTree = ""; }; + FA44BB19244B1394001B5D75 /* SVGAVideoEntityMemoryCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SVGAVideoEntityMemoryCache.m; sourceTree = ""; }; FF89C40C3E9839DA5DE71191 /* Pods-SVGAPlayer React.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SVGAPlayer React.release.xcconfig"; path = "Pods/Target Support Files/Pods-SVGAPlayer React/Pods-SVGAPlayer React.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -233,6 +236,8 @@ 9052FC621E6EB8D4007BC925 /* SVGAExporter.m */, 904D41F61D223DD20085A21A /* SVGABezierPath.h */, 904D41F71D223DD20085A21A /* SVGABezierPath.m */, + FA44BB18244B1394001B5D75 /* SVGAVideoEntityMemoryCache.h */, + FA44BB19244B1394001B5D75 /* SVGAVideoEntityMemoryCache.m */, ); path = Source; sourceTree = ""; @@ -443,6 +448,7 @@ 904D41F81D223DD20085A21A /* SVGABezierPath.m in Sources */, 90A364D71E5AECBD009347F1 /* SVGAVideoSpriteEntity.m in Sources */, 90A676E51D13A6DF008A69F3 /* AppDelegate.m in Sources */, + FA44BB1A244B1394001B5D75 /* SVGAVideoEntityMemoryCache.m in Sources */, 90A364DD1E5D33F8009347F1 /* SVGAContentLayer.m in Sources */, 90DB59B51F96026E00894727 /* SVGAImageView.m in Sources */, 90A676FA1D13A81F008A69F3 /* SVGA.m in Sources */, diff --git a/Source/SVGAVideoEntity.h b/Source/SVGAVideoEntity.h index 7749820..739f7ff 100644 --- a/Source/SVGAVideoEntity.h +++ b/Source/SVGAVideoEntity.h @@ -17,6 +17,8 @@ @property (nonatomic, readonly) CGSize videoSize; @property (nonatomic, readonly) int FPS; @property (nonatomic, readonly) int frames; +// 所有图片的总像素数(像素数决定了占用内存的大小) +@property (nonatomic, readonly) NSUInteger totalPixelCount; @property (nonatomic, readonly) NSDictionary *images; @property (nonatomic, readonly) NSDictionary *audiosData; @property (nonatomic, readonly) NSArray *sprites; @@ -32,7 +34,7 @@ - (void)resetAudiosWithProtoObject:(SVGAProtoMovieEntity *)protoObject; + (SVGAVideoEntity *)readCache:(NSString *)cacheKey; -// NSCache缓存 + - (void)saveCache:(NSString *)cacheKey; // NSMapTable弱缓存 - (void)saveWeakCache:(NSString *)cacheKey; diff --git a/Source/SVGAVideoEntity.m b/Source/SVGAVideoEntity.m index 66ab2ec..f903846 100644 --- a/Source/SVGAVideoEntity.m +++ b/Source/SVGAVideoEntity.m @@ -11,6 +11,7 @@ #import "SVGABezierPath.h" #import "SVGAVideoSpriteEntity.h" #import "SVGAAudioEntity.h" +#import "SVGAVideoEntityMemoryCache.h" #import "Svga.pbobjc.h" #define MP3_MAGIC_NUMBER "ID3" @@ -20,6 +21,7 @@ @interface SVGAVideoEntity () @property (nonatomic, assign) CGSize videoSize; @property (nonatomic, assign) int FPS; @property (nonatomic, assign) int frames; +@property (nonatomic, readwrite) NSUInteger totalPixelCount; @property (nonatomic, copy) NSDictionary *images; @property (nonatomic, copy) NSDictionary *audiosData; @property (nonatomic, copy) NSArray *sprites; @@ -30,16 +32,16 @@ @interface SVGAVideoEntity () @implementation SVGAVideoEntity -static NSCache *videoCache; static NSMapTable * weakCache; +static dispatch_semaphore_t cacheLock; + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - videoCache = [[NSCache alloc] init]; weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory - capacity:64]; + capacity:128]; + cacheLock = dispatch_semaphore_create(1); }); } @@ -50,6 +52,7 @@ - (instancetype)initWithJSONObject:(NSDictionary *)JSONObject cacheDir:(NSString _FPS = 20; _images = @{}; _cacheDir = cacheDir; + _totalPixelCount = 0; [self resetMovieWithJSONObject:JSONObject]; } return self; @@ -80,6 +83,7 @@ - (void)resetMovieWithJSONObject:(NSDictionary *)JSONObject { } - (void)resetImagesWithJSONObject:(NSDictionary *)JSONObject { + _totalPixelCount = 0; if ([JSONObject isKindOfClass:[NSDictionary class]]) { NSMutableDictionary *images = [[NSMutableDictionary alloc] init]; NSDictionary *JSONImages = JSONObject[@"images"]; @@ -87,13 +91,13 @@ - (void)resetImagesWithJSONObject:(NSDictionary *)JSONObject { [JSONImages enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) { if ([obj isKindOfClass:[NSString class]]) { NSString *filePath = [self.cacheDir stringByAppendingFormat:@"/%@.png", obj]; -// NSData *imageData = [NSData dataWithContentsOfFile:filePath]; NSData *imageData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:NULL]; if (imageData != nil) { UIImage *image = [[UIImage alloc] initWithData:imageData scale:2.0]; if (image != nil) { [images setObject:image forKey:[key stringByDeletingPathExtension]]; } + [self increasePixelCount:image]; } } }]; @@ -147,6 +151,7 @@ + (BOOL)isMP3Data:(NSData *)data { } - (void)resetImagesWithProtoObject:(SVGAProtoMovieEntity *)protoObject { + _totalPixelCount = 0; NSMutableDictionary *images = [[NSMutableDictionary alloc] init]; NSMutableDictionary *audiosData = [[NSMutableDictionary alloc] init]; NSDictionary *protoImages = [protoObject.images copy]; @@ -158,13 +163,13 @@ - (void)resetImagesWithProtoObject:(SVGAProtoMovieEntity *)protoObject { filePath = [self.cacheDir stringByAppendingFormat:@"/%@", fileName]; } if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { -// NSData *imageData = [NSData dataWithContentsOfFile:filePath]; NSData *imageData = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:NULL]; if (imageData != nil) { UIImage *image = [[UIImage alloc] initWithData:imageData scale:2.0]; if (image != nil) { [images setObject:image forKey:key]; } + [self increasePixelCount:image]; } } } @@ -177,6 +182,7 @@ - (void)resetImagesWithProtoObject:(SVGAProtoMovieEntity *)protoObject { if (image != nil) { [images setObject:image forKey:key]; } + [self increasePixelCount:image]; } } } @@ -208,20 +214,30 @@ - (void)resetAudiosWithProtoObject:(SVGAProtoMovieEntity *)protoObject { self.audios = audios; } +- (void)increasePixelCount:(UIImage *)image { + if (image) { + _totalPixelCount += image.size.width * image.size.height * image.scale * image.scale; + } +} + + (SVGAVideoEntity *)readCache:(NSString *)cacheKey { - SVGAVideoEntity * object = [videoCache objectForKey:cacheKey]; + SVGAVideoEntity * object = [SVGAVideoEntityMemoryCache videoEntityForKey:cacheKey]; if (!object) { + dispatch_semaphore_wait(cacheLock, DISPATCH_TIME_FOREVER); object = [weakCache objectForKey:cacheKey]; + dispatch_semaphore_signal(cacheLock); } return object; } - (void)saveCache:(NSString *)cacheKey { - [videoCache setObject:self forKey:cacheKey]; + [SVGAVideoEntityMemoryCache setVideoEntity:self forKey:cacheKey]; } - (void)saveWeakCache:(NSString *)cacheKey { + dispatch_semaphore_wait(cacheLock, DISPATCH_TIME_FOREVER); [weakCache setObject:self forKey:cacheKey]; + dispatch_semaphore_signal(cacheLock) } @end diff --git a/Source/SVGAVideoEntityMemoryCache.h b/Source/SVGAVideoEntityMemoryCache.h new file mode 100644 index 0000000..40a49ac --- /dev/null +++ b/Source/SVGAVideoEntityMemoryCache.h @@ -0,0 +1,33 @@ +// +// SVGAVideoEntityMemoryCache.h +// SVGAPlayer +// +// Created by song.meng on 2020/4/18. +// Copyright © 2020 UED Center. All rights reserved. +// + +#import +#import "SVGAVideoEntity.h" +NS_ASSUME_NONNULL_BEGIN + +@interface SVGAVideoEntityMemoryCache : NSObject + +// 内存缓存最大像素数限制,默认为:50 * 1024 * 1024 / 4,大约在内存中占用50M +@property (nonatomic, assign) NSUInteger maxPixelLimit; +//自动清理周期,默认0,即不清理,最小限制10s +@property (nonatomic, assign) NSUInteger autoClearInterval; +// 是否使用强引用缓存,默认YES +@property (nonatomic, assign) BOOL useStrongCache; + ++ (instancetype)defaultCache; + ++ (void)clearCache; + ++ (void)setVideoEntity:(SVGAVideoEntity *)object forKey:(id)key; ++ (void)removeVideoEntityWithKey:(id)key; ++ (SVGAVideoEntity *)videoEntityForKey:(id)key; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/SVGAVideoEntityMemoryCache.m b/Source/SVGAVideoEntityMemoryCache.m new file mode 100644 index 0000000..b2504a7 --- /dev/null +++ b/Source/SVGAVideoEntityMemoryCache.m @@ -0,0 +1,154 @@ +// +// SVGAVideoEntityMemoryCache.m +// SVGAPlayer +// +// Created by song.meng on 2020/4/18. +// Copyright © 2020 UED Center. All rights reserved. +// + +#import "SVGAVideoEntityMemoryCache.h" + + +#define MemoryLock(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); +#define MemoryUnLock(lock) dispatch_semaphore_signal(lock); + + +@interface SVGAVideoEntityMemoryCache() + +@property (nonatomic, strong) NSCache *strongCache; +@property (nonatomic, strong) NSMapTable *weakCache; +@property (nonatomic, strong) dispatch_semaphore_t lock; +@property (nonatomic, assign) BOOL startedAutoClear; + +@end + +@implementation SVGAVideoEntityMemoryCache + +static SVGAVideoEntityMemoryCache * instance = nil; ++ (instancetype)defaultCache +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc] init]; + }); + return instance; +} + ++ (void)clearCache +{ + [[self defaultCache] clearCache]; +} + ++ (void)setVideoEntity:(SVGAVideoEntity *)object forKey:(id)key +{ + [[self defaultCache] setVideoEntity:object forKey:key]; +} + ++ (void)removeVideoEntityWithKey:(id)key +{ + [[self defaultCache] removeVideoEntityWithKey:key]; +} + ++ (id)videoEntityForKey:(id)key +{ + return [[self defaultCache] videoEntityForKey:key]; +} + +#pragma mark - instance + +- (instancetype)init +{ + if (self = [super init]) { + _strongCache = [[NSCache alloc] init]; + _weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory + valueOptions:NSPointerFunctionsWeakMemory + capacity:128]; + _lock = dispatch_semaphore_create(1); + _useStrongCache = YES; + _maxPixelLimit = 50 * 1024 * 1024 / 4; + _autoClearInterval = 0; + } + return self; +} + +- (void)clearCache +{ + [_strongCache removeAllObjects]; +} + +- (void)setVideoEntity:(SVGAVideoEntity *)object forKey:(id)key +{ + if (!key) { + return; + } + + if ([object isKindOfClass:[SVGAVideoEntity class]]) { + if (_useStrongCache && (_maxPixelLimit == 0 || object.totalPixelCount < _maxPixelLimit)) { + [_strongCache setObject:object forKey:key]; + } + + MemoryLock(_lock); + [_weakCache setObject:object forKey:key]; + MemoryUnLock(_lock); + } else { + [self removeVideoEntityWithKey:key]; + } +} + +- (void)removeVideoEntityWithKey:(id)key +{ + if (key) { + [self.strongCache removeObjectForKey:key]; + } +} + +- (SVGAVideoEntity *)videoEntityForKey:(id)key +{ + if (!key) { + return nil; + } + + SVGAVideoEntity * object = [_strongCache objectForKey:key]; + if (!object) { + MemoryLock(_lock); + object = [_weakCache objectForKey:key]; + MemoryUnLock(_lock); + if (object && _useStrongCache && (_maxPixelLimit == 0 || object.totalPixelCount < _maxPixelLimit)) { + [_strongCache setObject:object forKey:key]; + } + } + return object; +} + +#pragma mark - auto clear +- (void)setAutoClearInterval:(NSUInteger)autoClearInterval +{ + if (autoClearInterval < 10) { + autoClearInterval = 10; + } + + _autoClearInterval = autoClearInterval; + [self clearCache]; + + if (!_startedAutoClear) { + [self _autoClear]; + } +} + +- (void)_autoClear +{ + _startedAutoClear = YES; + __weak typeof(self) weak_self = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoClearInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + __strong typeof(weak_self) s = weak_self; + if (!s) { + return; + } + + [s clearCache]; + [s _autoClear]; + }); + +} + +@end