diff --git a/Podfile.lock b/Podfile.lock index c78a8c2c..46ef050d 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - Protobuf (3.6.1) + - Protobuf (3.12.0) - React/ART (0.45.1): - React/Core - React/Core (0.45.1): @@ -27,8 +27,8 @@ PODS: - React/Core - React/RCTWebSocket (0.45.1): - React/Core - - SSZipArchive (2.1.4) - - Yoga (1.9.0) + - SSZipArchive (2.1.5) + - Yoga (1.14.0) DEPENDENCIES: - Protobuf (~> 3.4) @@ -47,7 +47,7 @@ DEPENDENCIES: - Yoga SPEC REPOS: - https://github.com/cocoapods/specs.git: + trunk: - Protobuf - SSZipArchive - Yoga @@ -57,11 +57,11 @@ EXTERNAL SOURCES: :podspec: https://raw.githubusercontent.com/yyued/react-native-runtime-ios/0.45.1/React.podspec SPEC CHECKSUMS: - Protobuf: 1eb9700044745f00181c136ef21b8ff3ad5a0fd5 + Protobuf: 2793fcd0622a00b546c60e7cbbcc493e043e9bb9 React: 505e0132cd9aaba1a56e47ef509220dd794ec9be - SSZipArchive: 41455d4b8d2b6ab93990820b50dc697c2554a322 - Yoga: aaae8abea68951f60bee05f6277d3eed90bb91bb + SSZipArchive: cefe1364104a0231268a5deb8495bdf2861f52f0 + Yoga: cff67a400f6b74dc38eb0bad4f156673d9aa980c PODFILE CHECKSUM: 7f6714245d47e69d2933463289e4c4d6de65b831 -COCOAPODS: 1.7.4 +COCOAPODS: 1.9.1 diff --git a/SVGAPlayer.xcodeproj/project.pbxproj b/SVGAPlayer.xcodeproj/project.pbxproj index d9bf999d..54fe71bc 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 */; }; + FA7FA4DF24B09BE600413DF0 /* SVGAVideoMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7FA4DE24B09BE600413DF0 /* SVGAVideoMemoryCache.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 = ""; }; + FA7FA4DD24B09BE600413DF0 /* SVGAVideoMemoryCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SVGAVideoMemoryCache.h; sourceTree = ""; }; + FA7FA4DE24B09BE600413DF0 /* SVGAVideoMemoryCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SVGAVideoMemoryCache.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 */, + FA7FA4DD24B09BE600413DF0 /* SVGAVideoMemoryCache.h */, + FA7FA4DE24B09BE600413DF0 /* SVGAVideoMemoryCache.m */, ); path = Source; sourceTree = ""; @@ -434,6 +439,7 @@ buildActionMask = 2147483647; files = ( 90A676E81D13A6DF008A69F3 /* ViewController.m in Sources */, + FA7FA4DF24B09BE600413DF0 /* SVGAVideoMemoryCache.m in Sources */, 9052FC631E6EB8D4007BC925 /* SVGAExporter.m in Sources */, 90A677031D13AE19008A69F3 /* SVGAVideoEntity.m in Sources */, 90A364DA1E5AED04009347F1 /* SVGAVideoSpriteFrameEntity.m in Sources */, diff --git a/SVGAPlayer.xcodeproj/xcuserdata/momo.xcuserdatad/xcschemes/xcschememanagement.plist b/SVGAPlayer.xcodeproj/xcuserdata/momo.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..1a7b4228 --- /dev/null +++ b/SVGAPlayer.xcodeproj/xcuserdata/momo.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,19 @@ + + + + + SchemeUserState + + SVGAPlayer React.xcscheme_^#shared#^_ + + orderHint + 7 + + SVGAPlayer.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/SVGAPlayer.xcworkspace/xcuserdata/momo.xcuserdatad/UserInterfaceState.xcuserstate b/SVGAPlayer.xcworkspace/xcuserdata/momo.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 00000000..b991e354 Binary files /dev/null and b/SVGAPlayer.xcworkspace/xcuserdata/momo.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/SVGAPlayer.xcworkspace/xcuserdata/momo.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/SVGAPlayer.xcworkspace/xcuserdata/momo.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 00000000..c36ea773 --- /dev/null +++ b/SVGAPlayer.xcworkspace/xcuserdata/momo.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,24 @@ + + + + + + + + + diff --git a/Source/SVGAParser.m b/Source/SVGAParser.m index 0f29f04f..7c3249c4 100644 --- a/Source/SVGAParser.m +++ b/Source/SVGAParser.m @@ -31,6 +31,13 @@ + (void)load { unzipQueue.maxConcurrentOperationCount = 1; } +- (instancetype)init { + if (self = [super init]) { + _enabledMemoryCache = YES; + } + return self; +} + - (void)parseWithURL:(nonnull NSURL *)URL completionBlock:(void ( ^ _Nonnull )(SVGAVideoEntity * _Nullable videoItem))completionBlock failureBlock:(void ( ^ _Nullable)(NSError * _Nullable error))failureBlock { diff --git a/Source/SVGAVideoEntity.h b/Source/SVGAVideoEntity.h index 55f03046..b166eea7 100644 --- a/Source/SVGAVideoEntity.h +++ b/Source/SVGAVideoEntity.h @@ -21,6 +21,7 @@ @property (nonatomic, readonly) NSDictionary *audiosData; @property (nonatomic, readonly) NSArray *sprites; @property (nonatomic, readonly) NSArray *audios; +@property (nonatomic, readonly) NSUInteger memoryCost; - (instancetype)initWithJSONObject:(NSDictionary *)JSONObject cacheDir:(NSString *)cacheDir; - (void)resetImagesWithJSONObject:(NSDictionary *)JSONObject; diff --git a/Source/SVGAVideoEntity.m b/Source/SVGAVideoEntity.m index 8ed736f5..bf848767 100644 --- a/Source/SVGAVideoEntity.m +++ b/Source/SVGAVideoEntity.m @@ -12,6 +12,7 @@ #import "SVGAVideoSpriteEntity.h" #import "SVGAAudioEntity.h" #import "Svga.pbobjc.h" +#import "SVGAVideoMemoryCache.h" #define MP3_MAGIC_NUMBER "ID3" @@ -26,19 +27,13 @@ @interface SVGAVideoEntity () @property (nonatomic, copy) NSArray *audios; @property (nonatomic, copy) NSString *cacheDir; +@property (nonatomic, copy) NSString * cacheKey; +@property (nonatomic, readwrite) NSUInteger memoryCost; + @end @implementation SVGAVideoEntity -static NSCache *videoCache; - -+ (void)load { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - videoCache = [[NSCache alloc] init]; - }); -} - - (instancetype)initWithJSONObject:(NSDictionary *)JSONObject cacheDir:(NSString *)cacheDir { self = [super init]; if (self) { @@ -202,12 +197,50 @@ - (void)resetAudiosWithProtoObject:(SVGAProtoMovieEntity *)protoObject { self.audios = audios; } +#pragma mark - memory + +- (void)setImages:(NSDictionary *)images { + _images = images; + + // cache存在说明以缓存,本次更新内存占用情况即可 + if (_cacheKey) { + NSUInteger formerCost = self.memoryCost; + [self resetMemoryCost]; + [[SVGAVideoMemoryCache sharedCache] updateCost:self.memoryCost - formerCost]; + } else { + [self resetMemoryCost]; + } +} + + (SVGAVideoEntity *)readCache:(NSString *)cacheKey { - return [videoCache objectForKey:cacheKey]; + return [[SVGAVideoMemoryCache sharedCache] objectForKey:cacheKey]; } - (void)saveCache:(NSString *)cacheKey { - [videoCache setObject:self forKey:cacheKey]; + _cacheKey = cacheKey; + [[SVGAVideoMemoryCache sharedCache] setObject:self forKey:cacheKey]; +} + +/// 重置内存占用计算 +- (void)resetMemoryCost { + _memoryCost = 0; + + [self.images enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, UIImage * _Nonnull obj, BOOL * _Nonnull stop) { + if ([obj isKindOfClass:[UIImage class]]) { + NSUInteger cost = [self costForImage:obj]; + _memoryCost += cost; + } + }]; +} + +- (NSUInteger)costForImage:(UIImage *)image { + CGImageRef imageRef = image.CGImage; + if (!imageRef) { + return 0; + } + NSUInteger bytesPerFrame = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef); + NSUInteger frameCount = image.images.count > 0 ? image.images.count : 1; + return bytesPerFrame * frameCount; } @end diff --git a/Source/SVGAVideoMemoryCache.h b/Source/SVGAVideoMemoryCache.h new file mode 100644 index 00000000..28a4cb98 --- /dev/null +++ b/Source/SVGAVideoMemoryCache.h @@ -0,0 +1,54 @@ +// +// SVGAVideoMemoryCache.h +// SVGAPlayer +// +// Created by MOMO@song.meng on 2020/7/4. +// Copyright © 2020 UED Center. All rights reserved. +// +// 用于处理SVGA内存缓存 +// + +#import +#import "SVGAVideoEntity.h" +#import "SVGAVideoEntity.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SVGAVideoMemoryCache : NSObject + + +/// 是否显示内存占用tip,开启统计后才生效,默认为NO,debug环境下默认为YES +@property (nonatomic, assign) BOOL showMemoryCostTip; + +/// 设置内存占用显示位置 +@property (nonatomic, assign) CGRect memoryCostTipFrame; + +/// 内存使用限制 +@property (nonatomic, assign) NSUInteger memoryCostLimit; + +/// 内存使用情况,在statisticsMemoryCost为YES的情况下有效 +@property (nonatomic, readonly) NSUInteger totalMemoryCost; + +/// 内存缓存是否生效,默认为YES,为NO时将使用weak缓存 +/// 以避免同一时刻多个SVGAPlayer使用同一资源造成重复的内存占用 +/// 关闭后内存使用统计和内存占用提示将失效 +@property (nonatomic, assign) BOOL memoryCacheEnable; + +/// 退后台是否自动清理,默认为YES +@property (nonatomic, assign) BOOL clearInBackground; + ++ (instancetype)sharedCache; + + +- (void)setObject:(SVGAVideoEntity *)object forKey:(id)key; +- (SVGAVideoEntity *)objectForKey:(id)key; +- (void)removeAllObjects; +- (void)removeObjectForKey:(id)key; + +/// 当图片资源被替换时更新内存占用 +/// @param cost 内存占用情况 +- (void)updateCost:(NSInteger)cost; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/SVGAVideoMemoryCache.m b/Source/SVGAVideoMemoryCache.m new file mode 100644 index 00000000..9af20ec7 --- /dev/null +++ b/Source/SVGAVideoMemoryCache.m @@ -0,0 +1,202 @@ +// +// SVGAVideoMemoryCache.m +// SVGAPlayer +// +// Created by MOMO@song.meng on 2020/7/4. +// Copyright © 2020 UED Center. All rights reserved. +// +// 用于处理SVGA内存缓存 +// + +#define safe_async_to_main(block) \ +if ([NSThread isMainThread]) { \ + block(); \ +} else { \ + dispatch_async(dispatch_get_main_queue(), block); \ +} + +#import "SVGAVideoMemoryCache.h" + +@interface SVGAVideoMemoryCache() + +@property (nonatomic, strong) NSCache *strongCache; +@property (nonatomic, strong) NSMapTable *weakCache; +@property (nonatomic, readwrite) NSUInteger totalMemoryCost; +@property (nonatomic, strong) dispatch_semaphore_t dispatchLock; +/// 内存占用Tip +@property (nonatomic, strong) UILabel *memoryCostTip; + +@end + +@implementation SVGAVideoMemoryCache + +static SVGAVideoMemoryCache *instance; ++ (instancetype)sharedCache { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[SVGAVideoMemoryCache alloc] init]; + }); + return instance; +} + +- (instancetype)init { + if (self = [super init]) { + [self cutomeInit]; + } + + return self; +} + +- (void)cutomeInit { + _weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:128]; + _dispatchLock = dispatch_semaphore_create(1); + _memoryCacheEnable = YES; + _clearInBackground = YES; + +#ifdef DEBUG + _showMemoryCostTip = YES; +#endif + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; +} + +#pragma mark - public + +- (void)setObject:(SVGAVideoEntity *)object forKey:(id)key { + if (object && key) { + [self saveStrongCache:object forKey:key]; + + dispatch_semaphore_wait(_dispatchLock, DISPATCH_TIME_FOREVER); + [self.weakCache setObject:object forKey:key]; + dispatch_semaphore_signal(_dispatchLock); + } +} + +- (SVGAVideoEntity *)objectForKey:(id)key { + id obj = [self.strongCache objectForKey:key]; + if (!obj) { + obj = [self.weakCache objectForKey:key]; + if ([obj isKindOfClass:[SVGAVideoEntity class]]) { + [self saveStrongCache:obj forKey:key]; + return obj; + } + } + + return nil; +} + +- (void)removeAllObjects { + if (_memoryCacheEnable) { + [self.strongCache removeAllObjects]; + + dispatch_semaphore_wait(_dispatchLock, DISPATCH_TIME_FOREVER); + NSUInteger cost = 0; + NSEnumerator *enu = self.weakCache.objectEnumerator; + SVGAVideoEntity *ent; + while ((ent = enu.nextObject)) { + cost += ent.memoryCost; + } + self.totalMemoryCost = cost; + dispatch_semaphore_signal(_dispatchLock); + } +} + +- (void)removeObjectForKey:(id)key { + SVGAVideoEntity * ent = [self objectForKey:key]; + if (ent) { + self.totalMemoryCost -= ent.memoryCost; + } + + [self.strongCache removeObjectForKey:key]; +} + +- (void)updateCost:(NSInteger)cost { + self.totalMemoryCost += cost; +} + +- (void)didEnterBackground { + if (_clearInBackground) { + [self removeAllObjects]; + } +} + +#pragma mark - ptivate + +- (SVGAVideoEntity *)readStrongCache:(id)key { + if (_memoryCacheEnable) { + return [self.strongCache objectForKey:key]; + } + return nil; +} + +- (void)saveStrongCache:(SVGAVideoEntity *)object forKey:(id)key { + if (_memoryCacheEnable) { + if ([self.strongCache objectForKey:key]) { + return; + } + NSUInteger cost = object.memoryCost; + [self.strongCache setObject:object forKey:key cost:cost]; + self.totalMemoryCost += cost; + } +} + + +#pragma mark - NSCache delegate + +/// NSCache将要移除元素 +- (void)cache:(NSCache *)cache willEvictObject:(id)obj { + SVGAVideoEntity * ent = (SVGAVideoEntity *)obj; + self.totalMemoryCost -= ent.memoryCost; +} + +#pragma mark - setter & getter + +- (void)setTotalMemoryCost:(NSUInteger)totalMemoryCost { + _totalMemoryCost = totalMemoryCost; + + if (_showMemoryCostTip) { + void(^showTip)() = ^{ + NSString * tip = [NSString stringWithFormat:@"svga cost: %.2fM",_totalMemoryCost/1024.f/1024.f]; + self.memoryCostTip.text = tip; + if (!self.memoryCostTip.superview) { + [[UIApplication sharedApplication].keyWindow addSubview:self.memoryCostTip]; + } + }; + safe_async_to_main(showTip); + } else if (self.memoryCostTip && self.memoryCostTip.superview) { + void(^removeView)() = ^{ + [self.memoryCostTip removeFromSuperview]; + }; + safe_async_to_main(removeView); + } +} + +- (UILabel *)memoryCostTip { + if (!_memoryCostTip) { + _memoryCostTip = [[UILabel alloc] initWithFrame:self.memoryCostTipFrame]; + _memoryCostTip.textColor = [UIColor redColor]; + _memoryCostTip.font = [UIFont systemFontOfSize:12]; + } + return _memoryCostTip; +} + +- (CGRect)memoryCostTipFrame { + if (CGRectEqualToRect(_memoryCostTipFrame, CGRectZero)) { + _memoryCostTipFrame = CGRectMake(10, [UIApplication sharedApplication].keyWindow.bounds.size.height - 75, 150, 15); + } + return _memoryCostTipFrame; +} + +- (void)setMemoryCostLimit:(NSUInteger)memoryCostLimit { + _memoryCostLimit = memoryCostLimit; + self.strongCache.totalCostLimit = memoryCostLimit; +} + +- (NSCache *)strongCache { + if (!_strongCache) { + _strongCache = [NSCache new]; + } + return _strongCache; +} + +@end