├── .gitattributes ├── Images ├── model.png └── ShortMediaCache.png ├── ShortMediaCacheDemo ├── Assets.xcassets │ ├── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── ViewController.h ├── AppDelegate.h ├── main.m ├── PlayerCell.h ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── AppDelegate.m ├── PlayerCell.m └── ViewController.m ├── ShortMediaCacheDemo.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── dangercheng.xcuserdatad │ │ └── IDEFindNavigatorScopes.plist └── project.pbxproj ├── ShortMediaCache ├── AVAssetResourceLoadingDataRequest+ShortMediaCache.h ├── ShortMediaWeakProxy.h ├── ShortMediaCacheConfig.h ├── ShortMediaCacheConfig.m ├── ShortMediaResourceLoader.h ├── AVAssetResourceLoadingDataRequest+ShortMediaCache.m ├── ShortMediaDownloadOperation.h ├── ShortMediaManager.h ├── ShortMediaCache.h ├── ShortMediaDiskCache.h ├── ShortMediaDownloader.h ├── ShortMediaWeakProxy.m ├── ShortMediaManager.m ├── ShortMediaCache.m ├── ShortMediaResourceLoader.m ├── ShortMediaDownloader.m ├── ShortMediaDownloadOperation.m └── ShortMediaDiskCache.m ├── ShortMediaCache.podspec ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Images/model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/ShortMediaCache/HEAD/Images/model.png -------------------------------------------------------------------------------- /Images/ShortMediaCache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dangercheng/ShortMediaCache/HEAD/Images/ShortMediaCache.png -------------------------------------------------------------------------------- /ShortMediaCacheDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ShortMediaCacheDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ShortMediaCacheDemo.xcodeproj/project.xcworkspace/xcuserdata/dangercheng.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ShortMediaCacheDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/30. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /ShortMediaCacheDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/30. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /ShortMediaCacheDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/30. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ShortMediaCache/AVAssetResourceLoadingDataRequest+ShortMediaCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // AVAssetResourceLoadingDataRequest+ShortMediaCache.h 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/8/9. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AVAssetResourceLoadingDataRequest (ShortMediaCache) 12 | @property (nonatomic, assign) NSInteger respondedSize; 13 | @end 14 | -------------------------------------------------------------------------------- /ShortMediaCacheDemo/PlayerCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerCell.h 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/8/9. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PlayerCell : UICollectionViewCell 12 | @property (weak, nonatomic) IBOutlet UIView *playerView; 13 | 14 | - (void)stopPlayWithUrl:(NSURL *)videoUrl; 15 | 16 | - (void)playVideoWithUrl:(NSURL *)videoUrl; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaWeakProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaWeakProxy.h 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/8/7. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ShortMediaWeakProxy : NSProxy 14 | 15 | @property (nullable, readonly, nonatomic, weak) id target; 16 | 17 | + (instancetype)proxyWithtTarget:(id)target; 18 | 19 | - (instancetype)initWithTarget:(id)target; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /ShortMediaCache.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "ShortMediaCache" 3 | s.version = "0.1.0" 4 | s.summary = "A cache for short video while playing." 5 | s.homepage = "https://github.com/dangercheng/ShortMediaCache" 6 | s.license = "MIT" 7 | s.author = { "DandJ" => "877478415@qq.com" } 8 | s.source = { :git => "https://github.com/dangercheng/ShortMediaCache.git", :tag => "#{s.version}" } 9 | s.requires_arc = true 10 | s.ios.deployment_target = "8.0" 11 | s.source_files = "ShortMediaCache/*.{h,m}" 12 | end -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaCacheConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaCacheConfig.h 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/8/16. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ShortMediaCacheConfig : NSObject 12 | @property (nonatomic, assign) NSInteger maxTempCacheSize; 13 | @property (nonatomic, assign) NSInteger maxFinalCacheSize; 14 | @property (nonatomic, assign) NSInteger maxTempCacheTimeInterval; 15 | @property (nonatomic, assign) NSInteger maxFinalCacheTimeInterval; 16 | 17 | + (instancetype)defaultConfig; 18 | @end 19 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaCacheConfig.m: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaCacheConfig.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/8/16. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "ShortMediaCacheConfig.h" 10 | 11 | @implementation ShortMediaCacheConfig 12 | 13 | + (instancetype)defaultConfig { 14 | ShortMediaCacheConfig *config = [ShortMediaCacheConfig new]; 15 | config.maxTempCacheSize = 1024 * 1024 * 100;//100MB 16 | config.maxFinalCacheSize = 1024 * 1024 * 200;//200MB 17 | config.maxTempCacheTimeInterval = 1 * 24 * 60 * 60;//1days 18 | config.maxFinalCacheTimeInterval = 1 * 24 * 60 * 60;//2days 19 | return config; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaResourceLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaResourceLoader.h 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/30. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "ShortMediaManager.h" 12 | 13 | @protocol ShortMediaResourceLoaderDelegate 14 | @end 15 | 16 | @interface ShortMediaResourceLoader : NSObject 17 | 18 | - (instancetype)initWithDelegate:(id)delegate; 19 | 20 | @property (nonatomic, strong, readonly) NSURL *url; 21 | 22 | @property (nonatomic, weak) id delegate; 23 | 24 | - (AVPlayerItem *)playItemWithUrl:(NSURL *)url; 25 | 26 | - (AVPlayerItem *)playItemWithUrl:(NSURL *)url options:(ShortMediaOptions)options; 27 | 28 | - (void)endLoading; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /ShortMediaCache/AVAssetResourceLoadingDataRequest+ShortMediaCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // AVAssetResourceLoadingDataRequest+ShortMediaCache.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/8/9. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "AVAssetResourceLoadingDataRequest+ShortMediaCache.h" 10 | #import 11 | 12 | static const NSString *RespondedSizeKey = @"RespondedSizeKey"; 13 | 14 | @implementation AVAssetResourceLoadingDataRequest (ShortMediaCache) 15 | 16 | - (void)setRespondedSize:(NSInteger)respondedSize { 17 | objc_setAssociatedObject(self, &RespondedSizeKey, [NSNumber numberWithInteger:respondedSize], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 18 | } 19 | 20 | - (NSInteger)respondedSize { 21 | NSNumber *value = objc_getAssociatedObject(self, &RespondedSizeKey); 22 | return [value integerValue]; 23 | } 24 | 25 | 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaDownloadOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaDownloadOperation.h 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/30. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ShortMediaCache.h" 11 | #import "ShortMediaDownloader.h" 12 | 13 | @interface ShortMediaDownloadOperation : NSOperation 14 | 15 | @property (nonatomic, assign) ShortMediaOptions options; 16 | 17 | @property (nonatomic, strong) NSURLCredential *credential; 18 | 19 | @property (nonatomic, assign) BOOL isPreloading; 20 | 21 | - (instancetype)initWithRequest:(NSURLRequest *)request 22 | session:(NSURLSession *)session 23 | options:(ShortMediaOptions)options 24 | progress:(ShortMediaProgressBlock)progress 25 | completion:(ShortMediaCompletionBlock)completion; 26 | 27 | - (void)cancelOperation; 28 | @end 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 DandJ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaManager.h 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/31. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ShortMediaDownloader.h" 11 | #import "ShortMediaCacheConfig.h" 12 | 13 | @interface ShortMediaManager : NSObject 14 | 15 | @property (nonatomic, strong) ShortMediaCacheConfig *cacheConfig; 16 | 17 | @property (nonatomic, strong, readonly) NSArray *prloadingMediaUrls; 18 | 19 | + (instancetype)shareManager; 20 | 21 | - (void)loadMediaWithUrl:(NSURL *)url 22 | options:(ShortMediaOptions)options 23 | progress:(ShortMediaProgressBlock)progress 24 | completion:(ShortMediaCompletionBlock)completion; 25 | 26 | - (void)endLoadMediaWithUrl:(NSURL *)url; 27 | 28 | - (NSData *)cacheDataFromOffset:(NSUInteger)offset 29 | length:(NSUInteger)length 30 | withUrl:(NSURL *)url; 31 | 32 | - (NSInteger)totalCachedSize; 33 | 34 | - (NSString *)totalCachedSizeStr; 35 | 36 | - (void)cleanCache; 37 | 38 | - (void)resetPreloadingWithMediaUrls:(NSArray *)mediaUrls; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShortMediaCache 2 | 3 | A Cache Library based on AVPLayer for short video on ios, you can creat AVPlayerItem with it directly. 4 | 5 | [中文介绍](https://segmentfault.com/a/1190000016228456) 6 | 7 | ## Main features 8 | - **1.Designed for short video, easy to access, does not encroach on business** 9 | - **2.cache video while playing, Play directly after caching** 10 | - **3.Support preloading, Play the next video in one second** 11 | - **4.Automatic cache management** 12 | 13 | ## Installation 14 | 15 | **cocoapods** 16 | 17 | ``` 18 | pod 'ShortMediaCache' 19 | ``` 20 | 21 | ## Usage 22 | 23 | **Normal** 24 | 25 | ``` 26 | #import "ShortMediaResourceLoader.h" 27 | ``` 28 | 29 | ``` 30 | ShortMediaResourceLoader _resourceLoader = [ShortMediaResourceLoader new]; 31 | AVPlayerItem _playerItem = [_resourceLoader playItemWithUrl:videoUrl]; 32 | AVPlayer _player = [AVPlayer playerWithPlayerItem:_playerItem]; 33 | ``` 34 | 35 | tips: should hold the _resourceLoader object. 36 | 37 | **Preloading** 38 | 39 | ``` 40 | [[ShortMediaManager shareManager] resetPreloadingWithMediaUrls:preloadUrls]; 41 | ``` 42 | 43 | tips:'preloadUrls' is an array with video urls 44 | 45 | **More detail** 46 | 47 | The source code 48 | 49 | **License** 50 | 51 | MIT 52 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaCache.h 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/30. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ShortMediaDiskCache.h" 11 | 12 | @class ShortMediaCacheConfig; 13 | 14 | typedef void(^CompletionBlock)(void); 15 | 16 | @interface ShortMediaCache : NSObject 17 | 18 | @property (nonatomic, strong) ShortMediaDiskCache *diskCache; 19 | 20 | + (instancetype)shareCache; 21 | 22 | - (NSString *)createCacheFileWithUrl:(NSURL *)url; 23 | 24 | - (void)appendWithData:(NSData *)data url:(NSURL *)url; 25 | 26 | - (NSData *)cacheDataFromOffset:(NSUInteger)offset 27 | length:(NSUInteger)length 28 | withUrl:(NSURL *)url; 29 | 30 | - (void)cacheCompletedWithUrl:(NSURL *)url; 31 | 32 | - (BOOL)isCacheCompletedWithUrl:(NSURL *)url; 33 | 34 | - (NSInteger)cachedSizeWithUrl:(NSURL *)url; 35 | 36 | - (NSInteger)finalCachedSizeWithUrl:(NSURL *)url; 37 | 38 | - (NSInteger)totalCachedSize; 39 | 40 | - (void)cleanCache; 41 | 42 | - (void)resetCacheWithConfig:(ShortMediaCacheConfig *)cacheConfig completion:(CompletionBlock)completion; 43 | 44 | - (void)resetFinalCacheWithConfig:(ShortMediaCacheConfig *)cacheConfig completion:(CompletionBlock)completion; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaDiskCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaDiskCache.h 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/31. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class ShortMediaCacheConfig; 12 | 13 | @interface ShortMediaDiskCache : NSObject 14 | 15 | @property (nonatomic, copy) NSString *path; 16 | 17 | - (instancetype)initWithPath:(NSString *)path; 18 | 19 | - (void)appendData:(NSData *)data tempFileWithName:(NSString *)fileName; 20 | 21 | - (void)moveTempFileToFinalWithName:(NSString *)fileName; 22 | 23 | - (BOOL)fileExistAtFinalWithName:(NSString *)fileName; 24 | 25 | - (NSData *)dataFromOffset:(NSUInteger)offset 26 | length:(NSUInteger)length 27 | withName:(NSString *)fileName; 28 | 29 | - (NSString *)createTempFileWithName:(NSString *)fileName; 30 | 31 | - (NSInteger)tempCachedSizehWithName:(NSString *)fileName; 32 | 33 | - (NSInteger)finalCachedSizeWithName:(NSString *)fileName; 34 | 35 | - (NSInteger)totalCachedSize; 36 | 37 | - (void)moveAllCacheToTrash; 38 | 39 | - (void)cleanTrash; 40 | 41 | - (void)resetCacheWithConfig:(ShortMediaCacheConfig *)config; 42 | 43 | - (void)resetTempCacheWitchConfig:(ShortMediaCacheConfig *)config; 44 | 45 | - (void)resetFinalCacheWitchConfig:(ShortMediaCacheConfig *)config; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaDownloader.h 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/8/7. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ShortMediaCache.h" 11 | 12 | typedef NS_OPTIONS(NSUInteger, ShortMediaOptions) { 13 | ShortMediaOptionsHandleCookies = 1 << 0, 14 | ShortMediaOptionsOptionAllowInvalidSSLCertificates = 1 << 1, 15 | }; 16 | 17 | typedef void(^ShortMediaProgressBlock)(NSInteger receivedSize, NSInteger expectedSize); 18 | typedef void(^ShortMediaCompletionBlock)(NSError *error); 19 | 20 | @interface ShortMediaDownloader : NSObject 21 | 22 | @property (nonatomic, copy) NSString *userName; 23 | @property (nonatomic, copy) NSString *password; 24 | @property (nonatomic, assign, readonly) BOOL canPreload; 25 | 26 | + (instancetype)shareDownloader; 27 | 28 | - (void)downloadMediaWithUrl:(NSURL *)url 29 | options:(ShortMediaOptions)options 30 | progress:(ShortMediaProgressBlock)progress 31 | completion:(ShortMediaCompletionBlock)completion; 32 | 33 | - (void)preloadMediaWithUrl:(NSURL *)url 34 | options:(ShortMediaOptions)options 35 | progress:(ShortMediaProgressBlock)progress 36 | completion:(ShortMediaCompletionBlock)completion; 37 | 38 | - (void)cancelDownloadWithUrl:(NSURL *)url; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /ShortMediaCacheDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleVersion 25 | 1 26 | LSRequiresIPhoneOS 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaWeakProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaWeakProxy.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/8/7. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "ShortMediaWeakProxy.h" 10 | #import 11 | 12 | @implementation ShortMediaWeakProxy 13 | 14 | + (instancetype)proxyWithtTarget:(id)target { 15 | return [[ShortMediaWeakProxy alloc] initWithTarget:target]; 16 | } 17 | 18 | - (instancetype)initWithTarget:(id)target { 19 | _target = target; 20 | return self; 21 | } 22 | 23 | #pragma mark - runtime 24 | - (id)forwardingTargetForSelector:(SEL)aSelector { 25 | return _target; 26 | } 27 | 28 | - (void)forwardInvocation:(NSInvocation *)invocation { 29 | void *null = NULL; 30 | [invocation setReturnValue:&null]; 31 | } 32 | 33 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { 34 | return [NSObject instanceMethodSignatureForSelector:@selector(init)]; 35 | } 36 | 37 | #pragma mark - override 38 | - (BOOL)respondsToSelector:(SEL)aSelector { 39 | return [_target respondsToSelector:aSelector]; 40 | } 41 | 42 | - (BOOL)isEqual:(id)object { 43 | return [_target isEqual:object]; 44 | } 45 | 46 | - (NSUInteger)hash { 47 | return [_target hash]; 48 | } 49 | 50 | - (Class)class { 51 | return [_target class]; 52 | } 53 | 54 | - (Class)superclass { 55 | return [_target superclass]; 56 | } 57 | 58 | - (BOOL)isKindOfClass:(Class)aClass { 59 | return [_target isKindOfClass:aClass]; 60 | } 61 | 62 | - (BOOL)isMemberOfClass:(Class)aClass { 63 | return [_target isMemberOfClass:aClass]; 64 | } 65 | 66 | - (BOOL)conformsToProtocol:(Protocol *)aProtocol { 67 | return [_target conformsToProtocol:aProtocol]; 68 | } 69 | 70 | - (BOOL)isProxy { 71 | return YES; 72 | } 73 | 74 | - (NSString *)description { 75 | return [_target description]; 76 | } 77 | 78 | - (NSString *)debugDescription { 79 | return [_target debugDescription]; 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /ShortMediaCacheDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /ShortMediaCacheDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /ShortMediaCacheDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/30. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /ShortMediaCacheDemo/PlayerCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerCell.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/8/9. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "PlayerCell.h" 10 | #import "ShortMediaResourceLoader.h" 11 | 12 | #import 13 | 14 | @implementation PlayerCell { 15 | AVPlayer *_player; 16 | AVPlayerItem *_playerItem; 17 | AVPlayerLayer *_playerLayer; 18 | ShortMediaResourceLoader *_resourceLoader; 19 | } 20 | 21 | - (void)awakeFromNib { 22 | [super awakeFromNib]; 23 | self.playerView.backgroundColor = [UIColor colorWithRed:arc4random() % 100 / 256.0 green:arc4random() % 100 / 256.0 blue:arc4random() % 100 / 256.0 alpha:1.0]; 24 | } 25 | 26 | - (void)playVideoWithUrl:(NSURL *)videoUrl { 27 | if(!videoUrl) { 28 | return; 29 | } 30 | if(_player) { 31 | return; 32 | } 33 | _resourceLoader = [ShortMediaResourceLoader new]; 34 | _playerItem = [_resourceLoader playItemWithUrl:videoUrl]; 35 | 36 | _player = [AVPlayer playerWithPlayerItem:_playerItem]; 37 | 38 | _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; 39 | _playerLayer.frame = _playerView.bounds; 40 | [self.playerView.layer addSublayer:_playerLayer]; 41 | [_player play]; 42 | 43 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem]; 44 | } 45 | 46 | -(void)playbackFinished:(NSNotification *)notification{ 47 | NSLog(@"Complete."); 48 | [_player seekToTime:CMTimeMake(0, 1)]; 49 | [_player play]; 50 | } 51 | 52 | - (void)dealloc { 53 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 54 | [self stopPlayWithUrl:_resourceLoader.url]; 55 | } 56 | 57 | - (void)stopPlayWithUrl:(NSURL *)videoUrl { 58 | if(![videoUrl isEqual:_resourceLoader.url]) { 59 | return; 60 | } 61 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 62 | if(_player) { 63 | [_player pause]; 64 | [_resourceLoader endLoading]; 65 | AVURLAsset *asset = (AVURLAsset *)_playerItem.asset; 66 | [asset.resourceLoader setDelegate:nil queue:dispatch_get_main_queue()]; 67 | [_playerLayer removeFromSuperlayer]; 68 | _resourceLoader = nil; 69 | _playerItem = nil; 70 | _player = nil; 71 | _playerLayer = nil; 72 | } 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaManager.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/31. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "ShortMediaManager.h" 10 | #import 11 | 12 | #define Lock() dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER) 13 | #define UnLock() dispatch_semaphore_signal(self.lock) 14 | 15 | @interface ShortMediaManager() 16 | @property (nonatomic, strong) NSMutableArray *waitingPreloadingUrls; 17 | @property (nonatomic, strong) dispatch_semaphore_t lock; 18 | @end 19 | 20 | @implementation ShortMediaManager 21 | + (instancetype)shareManager { 22 | static ShortMediaManager *manager; 23 | static dispatch_once_t onceToken; 24 | dispatch_once(&onceToken, ^{ 25 | manager = [[ShortMediaManager alloc] init]; 26 | }); 27 | return manager; 28 | } 29 | 30 | - (instancetype)init { 31 | self = [super init]; 32 | if(!self) return nil; 33 | _lock = dispatch_semaphore_create(1); 34 | _cacheConfig = [ShortMediaCacheConfig defaultConfig]; 35 | [[NSNotificationCenter defaultCenter] addObserver:self 36 | selector:@selector(resetCache) 37 | name:UIApplicationWillTerminateNotification 38 | object:nil]; 39 | [[NSNotificationCenter defaultCenter] addObserver:self 40 | selector:@selector(resetCacheInbackgroud) 41 | name:UIApplicationDidEnterBackgroundNotification 42 | object:nil]; 43 | return self; 44 | } 45 | 46 | - (void)loadMediaWithUrl:(NSURL *)url 47 | options:(ShortMediaOptions)options 48 | progress:(ShortMediaProgressBlock)progress 49 | completion:(ShortMediaCompletionBlock)completion { 50 | [[ShortMediaDownloader shareDownloader] downloadMediaWithUrl:url options:options progress:^(NSInteger receivedSize, NSInteger expectedSize) { 51 | dispatch_async(dispatch_get_main_queue(), ^{ 52 | if(progress) progress(receivedSize, expectedSize); 53 | }); 54 | } completion:^(NSError *error) { 55 | if(!error) { 56 | [self startPreloading]; 57 | } 58 | dispatch_async(dispatch_get_main_queue(), ^{ 59 | if(completion) completion(error); 60 | }); 61 | }]; 62 | } 63 | 64 | - (void)endLoadMediaWithUrl:(NSURL *)url { 65 | [[ShortMediaDownloader shareDownloader] cancelDownloadWithUrl:url]; 66 | } 67 | 68 | - (NSData *)cacheDataFromOffset:(NSUInteger)offset 69 | length:(NSUInteger)length 70 | withUrl:(NSURL *)url { 71 | return [[ShortMediaCache shareCache] cacheDataFromOffset:offset length:length withUrl:url]; 72 | } 73 | 74 | - (void)resetCache { 75 | [[ShortMediaCache shareCache] resetCacheWithConfig:_cacheConfig completion:nil]; 76 | } 77 | 78 | - (void)resetCacheInbackgroud { 79 | Class UIApplicationClass = NSClassFromString(@"UIApplication"); 80 | if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) { 81 | return; 82 | } 83 | UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)]; 84 | __block UIBackgroundTaskIdentifier backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{ 85 | [application endBackgroundTask:backgroundTask]; 86 | backgroundTask = UIBackgroundTaskInvalid; 87 | }]; 88 | 89 | [[ShortMediaCache shareCache] resetFinalCacheWithConfig:_cacheConfig completion:^{ 90 | [application endBackgroundTask:backgroundTask]; 91 | backgroundTask = UIBackgroundTaskInvalid; 92 | }]; 93 | } 94 | 95 | - (NSInteger)totalCachedSize { 96 | return [[ShortMediaCache shareCache] totalCachedSize]; 97 | } 98 | 99 | - (NSString *)totalCachedSizeStr { 100 | NSInteger size = [self totalCachedSize]; 101 | if (size <= 0) { 102 | return @"0.00M"; 103 | } 104 | if (size < 1023 && size > 0) 105 | return([NSString stringWithFormat:@"%libytes",(long)size]); 106 | CGFloat floatSize = size / 1024.0; 107 | if (floatSize < 1023) return ([NSString stringWithFormat:@"%.2fKB", floatSize]); 108 | floatSize = floatSize / 1024.0; 109 | if (floatSize < 1023) return ([NSString stringWithFormat:@"%.2fMB", floatSize]); 110 | floatSize = floatSize / 1024.0; 111 | 112 | return ([NSString stringWithFormat:@"%.2fGB", floatSize]); 113 | } 114 | 115 | - (void)cleanCache { 116 | [[ShortMediaCache shareCache] cleanCache]; 117 | } 118 | 119 | - (void)resetPreloadingWithMediaUrls:(NSArray *)mediaUrls { 120 | for (NSURL *url in mediaUrls) { 121 | [[ShortMediaDownloader shareDownloader] cancelDownloadWithUrl:url]; 122 | } 123 | Lock(); 124 | _prloadingMediaUrls = mediaUrls; 125 | _waitingPreloadingUrls = [NSMutableArray arrayWithArray:mediaUrls]; 126 | UnLock(); 127 | [self startPreloading]; 128 | } 129 | 130 | - (void)startPreloading { 131 | Lock(); 132 | BOOL canPreload = YES; 133 | if(!([_waitingPreloadingUrls count] > 0)) { 134 | NSLog(@"No preloading waitting"); 135 | canPreload = NO; 136 | } 137 | else if(![ShortMediaDownloader shareDownloader].canPreload) { 138 | NSLog(@"Can not start preloading"); 139 | canPreload = NO; 140 | } 141 | 142 | if(canPreload) { 143 | NSLog(@"start preloading"); 144 | NSURL *url = _waitingPreloadingUrls.firstObject; 145 | __weak typeof(self) _self = self; 146 | UnLock(); 147 | [[ShortMediaDownloader shareDownloader] preloadMediaWithUrl:url options:kNilOptions progress:nil completion:^(NSError *error) { 148 | __strong typeof(_self) self = _self; 149 | NSLog(@"End preloading"); 150 | if(!error) { 151 | Lock(); 152 | [self.waitingPreloadingUrls removeObject:url]; 153 | UnLock(); 154 | [self startPreloading]; 155 | } 156 | }]; 157 | } 158 | else { 159 | UnLock(); 160 | } 161 | } 162 | 163 | @end 164 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaCache.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/30. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "ShortMediaCache.h" 10 | #import 11 | 12 | #define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER) 13 | #define UnLock() dispatch_semaphore_signal(self->_lock) 14 | 15 | 16 | @implementation ShortMediaCache { 17 | NSMutableDictionary *_urlFileNameCache; 18 | dispatch_queue_t _fileQueue; 19 | dispatch_semaphore_t _lock; 20 | } 21 | 22 | + (instancetype)shareCache { 23 | static ShortMediaCache *cache; 24 | static dispatch_once_t onceToken; 25 | dispatch_once(&onceToken, ^{ 26 | NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; 27 | cachePath = [cachePath stringByAppendingPathComponent:@"com.DandJ.shortMedia"]; 28 | ShortMediaDiskCache *diskCache = [[ShortMediaDiskCache alloc] initWithPath:cachePath]; 29 | cache = [[ShortMediaCache alloc] initWithDiskCache:diskCache]; 30 | }); 31 | return cache; 32 | } 33 | 34 | #pragma mark - pubulic 35 | - (instancetype)initWithDiskCache:(ShortMediaDiskCache *)diskCache { 36 | self = [super init]; 37 | if(!self) return nil; 38 | self.diskCache = diskCache; 39 | _lock = dispatch_semaphore_create(1); 40 | _urlFileNameCache = [NSMutableDictionary dictionary]; 41 | _fileQueue = dispatch_queue_create("com.DandJ.shortMedia.cache", DISPATCH_QUEUE_SERIAL); 42 | return self; 43 | } 44 | 45 | - (NSString *)createCacheFileWithUrl:(NSURL *)url { 46 | if(!url) { 47 | return nil; 48 | } 49 | Lock(); 50 | NSString *fileName = [self fileNameWithUrl:url]; 51 | NSString *filePath = [_diskCache createTempFileWithName:fileName]; 52 | UnLock(); 53 | return filePath; 54 | } 55 | 56 | - (void)appendWithData:(NSData *)data url:(NSURL *)url { 57 | __weak typeof(self) _self = self; 58 | dispatch_async(_fileQueue, ^{ 59 | __strong typeof(_self) self = _self; 60 | if(!self) return; 61 | Lock(); 62 | NSString *fileName = [self fileNameWithUrl:url]; 63 | [self.diskCache appendData:data tempFileWithName:fileName]; 64 | UnLock(); 65 | }); 66 | } 67 | 68 | - (NSData *)cacheDataFromOffset:(NSUInteger)offset 69 | length:(NSUInteger)length 70 | withUrl:(NSURL *)url { 71 | Lock(); 72 | NSString *fileName = [self fileNameWithUrl:url]; 73 | NSData *retData = [_diskCache dataFromOffset:offset length:length withName:fileName]; 74 | UnLock(); 75 | return retData; 76 | } 77 | 78 | - (void)cacheCompletedWithUrl:(NSURL *)url { 79 | NSString *fileName = [self fileNameWithUrl:url]; 80 | __weak typeof(self) _self = self; 81 | dispatch_sync(_fileQueue, ^{ 82 | __strong typeof(_self) self = _self; 83 | if(!self) return; 84 | Lock(); 85 | [self.diskCache moveTempFileToFinalWithName:fileName]; 86 | UnLock(); 87 | }); 88 | } 89 | 90 | - (BOOL)isCacheCompletedWithUrl:(NSURL *)url { 91 | NSString *fileName = [self fileNameWithUrl:url]; 92 | Lock(); 93 | BOOL ret = [_diskCache fileExistAtFinalWithName:fileName]; 94 | UnLock(); 95 | return ret; 96 | } 97 | 98 | - (NSInteger)cachedSizeWithUrl:(NSURL *)url { 99 | NSString *fileName = [self fileNameWithUrl:url]; 100 | Lock(); 101 | NSInteger size = [_diskCache tempCachedSizehWithName:fileName]; 102 | UnLock(); 103 | return size; 104 | } 105 | 106 | - (NSInteger)finalCachedSizeWithUrl:(NSURL *)url { 107 | NSString *fileName = [self fileNameWithUrl:url]; 108 | Lock(); 109 | NSInteger size = [_diskCache finalCachedSizeWithName:fileName]; 110 | UnLock(); 111 | return size; 112 | } 113 | 114 | - (NSInteger)totalCachedSize { 115 | Lock(); 116 | NSInteger size = [_diskCache totalCachedSize]; 117 | UnLock(); 118 | return size; 119 | } 120 | 121 | - (void)cleanCache { 122 | Lock(); 123 | [_diskCache moveAllCacheToTrash]; 124 | UnLock(); 125 | __weak typeof(self) _self = self; 126 | dispatch_async(_fileQueue, ^{ 127 | __strong typeof(_self) self = _self; 128 | if(!self) return; 129 | Lock(); 130 | [self.diskCache cleanTrash]; 131 | UnLock(); 132 | }); 133 | } 134 | 135 | - (void)resetCacheWithConfig:(ShortMediaCacheConfig *)cacheConfig completion:(CompletionBlock)completion { 136 | __weak typeof(self) _self = self; 137 | dispatch_async(_fileQueue, ^{ 138 | __strong typeof(_self) self = _self; 139 | if(!self) return; 140 | Lock(); 141 | [self.diskCache resetCacheWithConfig:cacheConfig]; 142 | UnLock(); 143 | 144 | if(completion) { 145 | completion(); 146 | } 147 | }); 148 | } 149 | 150 | - (void)resetFinalCacheWithConfig:(ShortMediaCacheConfig *)cacheConfig completion:(CompletionBlock)completion { 151 | __weak typeof(self) _self = self; 152 | dispatch_async(_fileQueue, ^{ 153 | __strong typeof(_self) self = _self; 154 | if(!self) return; 155 | 156 | Lock(); 157 | [self.diskCache resetFinalCacheWitchConfig:cacheConfig]; 158 | UnLock(); 159 | 160 | if(completion) { 161 | completion(); 162 | } 163 | }); 164 | } 165 | 166 | 167 | #pragma mark - private 168 | - (NSString *)fileNameWithUrl:(NSURL *)url { 169 | NSString *fileName = [_urlFileNameCache objectForKey:url]; 170 | if(!fileName) { 171 | fileName = [NSString stringWithFormat:@"%@.%@", [self md5:url.absoluteString], url.pathExtension]; 172 | [_urlFileNameCache setObject:fileName forKey:url]; 173 | } 174 | return fileName; 175 | } 176 | 177 | - (NSString *)md5:(NSString *)str { 178 | const char *cStr = [str UTF8String]; 179 | unsigned char result[16]; 180 | CC_MD5(cStr, (CC_LONG)strlen(cStr), result); 181 | return [NSString stringWithFormat: 182 | @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 183 | result[0], result[1], result[2], result[3], 184 | result[4], result[5], result[6], result[7], 185 | result[8], result[9], result[10], result[11], 186 | result[12], result[13], result[14], result[15] 187 | ]; 188 | } 189 | 190 | @end 191 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaResourceLoader.m: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaResourceLoader.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/30. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "ShortMediaResourceLoader.h" 10 | #import "AVAssetResourceLoadingDataRequest+ShortMediaCache.h" 11 | #import 12 | 13 | @interface ShortMediaResourceLoader() 14 | @property (nonatomic, strong) NSMutableArray *pendingRequests; 15 | @property (nonatomic, assign) NSInteger expectedSize; 16 | @property (nonatomic, assign) NSInteger receivedSize; 17 | @property (nonatomic, assign) ShortMediaOptions options; 18 | @end 19 | 20 | @implementation ShortMediaResourceLoader 21 | 22 | - (instancetype)init { 23 | self = [super init]; 24 | if(!self) return nil; 25 | _pendingRequests = [NSMutableArray array]; 26 | return self; 27 | } 28 | 29 | - (instancetype)initWithDelegate:(id)delegate { 30 | _delegate = delegate; 31 | return [self init]; 32 | } 33 | 34 | - (AVPlayerItem *)playItemWithUrl:(NSURL *)url { 35 | return [self playItemWithUrl:url options:kNilOptions]; 36 | } 37 | 38 | - (AVPlayerItem *)playItemWithUrl:(NSURL *)url options:(ShortMediaOptions)options { 39 | _url = url; 40 | _options = options; 41 | [self startLoading]; 42 | AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[self unRecognizerUrl] options:nil]; 43 | [asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()]; 44 | AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset]; 45 | return item; 46 | } 47 | 48 | - (void)startLoading { 49 | __weak typeof(self) _self = self; 50 | [[ShortMediaManager shareManager] loadMediaWithUrl:_url options:_options progress:^(NSInteger receivedSize, NSInteger expectedSize) { 51 | __strong typeof(_self) self = _self; 52 | if(!self) return; 53 | self.receivedSize = receivedSize; 54 | self.expectedSize = expectedSize; 55 | [self dealPendingRequests]; 56 | } completion:^(NSError *error) { 57 | __strong typeof(_self) self = _self; 58 | if(!self) return; 59 | if(!error) { 60 | self.receivedSize = self.expectedSize; 61 | } 62 | [self dealPendingRequests]; 63 | }]; 64 | } 65 | 66 | - (void)endLoading { 67 | [[ShortMediaManager shareManager] endLoadMediaWithUrl:_url]; 68 | } 69 | 70 | - (void)dealloc { 71 | [self endLoading]; 72 | } 73 | 74 | #pragma mark - AVAssetResourceLoaderDelegate 75 | - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest { 76 | if (resourceLoader && loadingRequest) { 77 | loadingRequest.dataRequest.respondedSize = 0; 78 | [self.pendingRequests addObject:loadingRequest]; 79 | [self dealPendingRequests]; 80 | } 81 | return YES; 82 | } 83 | 84 | - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest { 85 | if (!loadingRequest.isFinished) { 86 | [loadingRequest finishLoadingWithError:[self loaderCancelledError]]; 87 | } 88 | [self.pendingRequests removeObject:loadingRequest]; 89 | } 90 | 91 | #pragma mark - private 92 | - (void)dealPendingRequests { 93 | NSMutableArray *finishedRequests = [NSMutableArray array]; 94 | [self.pendingRequests enumerateObjectsUsingBlock:^(AVAssetResourceLoadingRequest *loadingRequest, NSUInteger idx, BOOL * _Nonnull stop) { 95 | [self fillInContentInformation:loadingRequest.contentInformationRequest]; 96 | BOOL finish = [self respondWithDataForRequest:loadingRequest]; 97 | if (finish) { 98 | [finishedRequests addObject:loadingRequest]; 99 | [loadingRequest finishLoading]; 100 | } 101 | }]; 102 | if (finishedRequests.count) { 103 | [self.pendingRequests removeObjectsInArray:finishedRequests]; 104 | } 105 | } 106 | 107 | - (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingRequest *)loadingRequest { 108 | AVAssetResourceLoadingDataRequest *dataRequest = loadingRequest.dataRequest; 109 | NSInteger startOffset = (NSInteger)dataRequest.requestedOffset; 110 | if (dataRequest.currentOffset != 0) { 111 | startOffset = (NSInteger)dataRequest.currentOffset; 112 | } 113 | startOffset = MAX(0, startOffset); 114 | if(startOffset > _receivedSize) { 115 | return NO; 116 | } 117 | NSInteger canReadsize = _receivedSize - startOffset; 118 | canReadsize = MAX(0, canReadsize); 119 | NSInteger realReadSize = MIN(dataRequest.requestedLength, canReadsize); 120 | NSData *respondData = [NSData data]; 121 | if(realReadSize > 2) { 122 | NSData *cacheData = [[ShortMediaManager shareManager] cacheDataFromOffset:startOffset length:realReadSize withUrl:_url]; 123 | if(cacheData) { 124 | respondData = cacheData; 125 | } 126 | } 127 | dataRequest.respondedSize += realReadSize; 128 | [dataRequest respondWithData:respondData]; 129 | if(dataRequest.respondedSize >= dataRequest.requestedLength) { 130 | return YES; 131 | } else { 132 | return NO; 133 | } 134 | } 135 | 136 | 137 | - (void)fillInContentInformation:(AVAssetResourceLoadingContentInformationRequest * _Nonnull)contentInformationRequest{ 138 | if (contentInformationRequest && !contentInformationRequest.contentType && _expectedSize > 0) { 139 | NSString *fileExtension = [self.url pathExtension]; 140 | NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL); 141 | NSString *contentTypeS = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); 142 | if (!contentTypeS) { 143 | contentTypeS = @"application/octet-stream"; 144 | } 145 | NSString *mimetype = contentTypeS; 146 | CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef _Nonnull)(mimetype), NULL); 147 | contentInformationRequest.byteRangeAccessSupported = YES; 148 | contentInformationRequest.contentType = CFBridgingRelease(contentType); 149 | contentInformationRequest.contentLength = _expectedSize; 150 | } 151 | } 152 | 153 | - (NSURL *)unRecognizerUrl { 154 | NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[NSURL URLWithString:@"www.dandj.top"] resolvingAgainstBaseURL:NO]; 155 | components.scheme = @"SystemCannotRecognition"; 156 | return [components URL]; 157 | } 158 | 159 | - (NSError *)loaderCancelledError{ 160 | NSError *error = [[NSError alloc] initWithDomain:@"dandj.top" 161 | code:-3 162 | userInfo:@{NSLocalizedDescriptionKey:@"Resource loader cancelled"}]; 163 | return error; 164 | } 165 | 166 | @end 167 | -------------------------------------------------------------------------------- /ShortMediaCacheDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/30. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "PlayerCell.h" 11 | #import "ShortMediaManager.h" 12 | 13 | @interface ViewController () 14 | @property (weak, nonatomic) IBOutlet UICollectionView *collectionView; 15 | @property (nonatomic, strong) NSArray *videoUrls; 16 | @end 17 | 18 | @implementation ViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | // Do any additional setup after loading the view, typically from a nib. 23 | _collectionView.delegate = self; 24 | _collectionView.dataSource = self; 25 | _collectionView.pagingEnabled = YES; 26 | 27 | UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; 28 | flowLayout.minimumLineSpacing = 0.0; 29 | flowLayout.minimumInteritemSpacing = 0.0; 30 | flowLayout.footerReferenceSize = CGSizeZero; 31 | flowLayout.headerReferenceSize = CGSizeZero; 32 | flowLayout.sectionInset = UIEdgeInsetsZero; 33 | flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical; 34 | 35 | flowLayout.itemSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); 36 | _collectionView.collectionViewLayout = flowLayout; 37 | 38 | NSArray *videoUrls = @[ 39 | @"http://jmdianbostag.ks3-cn-beijing.ksyun.com/MjQ0NzY5NDU2/MTUxNjY5NzAzNTgwNg_E_E/OTQ2NTc2MA_E_E/MDY2OTVDOUItMzYwNi00MUM1LUJFRUQtRjJCQkJGOThFOTIyLk1PVg_E_E_default.mp4", 40 | @"http://jmdianbostag.ks3-cn-beijing.ksyun.com/MTE1NzIzNzAz/MTUxNjUzNzA4MDk4Nw_E_E/MTAyNzE1NDM_E/dHJpbS4wN0JDOTEzMy01M0VELTQ5OUQtOTY2RS1GNUM4NDZEMUY5OTAuTU9W_default.mp4", 41 | @"http://jmvideo1.jumei.com/MQ_E_E/MTUxOTY0MjA2ODI4OQ_E_E/Mjg1NTk4OQ_E_E/L2hvbWUvd3d3L2xvZ3MvdmlkZW8vZmlsZV84OTkyMTMtOThiNGUyNzEzMzIyMGMyMTdhZmU2Y2FhMWEyYTZlZDUvdmlkZW8ubXA0.mp4", 42 | @"http://jmvideo1.jumei.com/MQ_E_E/MTUxOTY0MTA4MjUxMA_E_E/Mzg0NTAxMA_E_E/L2hvbWUvd3d3L2xvZ3MvdmlkZW8vZmlsZV84OTkyMTMtOTdkNDRlNTQ5NTM5NjZhMWZmZDA1OTRlYzhlNzQwYmMvdmlkZW8ubXA0.mp4", 43 | @"http://jmvideo1.jumei.com/MQ_E_E/MTUxOTM1NTUwNDYxNQ_E_E/MjEyNzc3MA_E_E/L2hvbWUvd3d3L2xvZ3MvdmlkZW8vZG91eWluXzY1MjQwOTQ0MzEzMjk1MjA5MDQvdmlkZW8ubXA0.mp4", 44 | @"http://jmvideo1.jumei.com/MQ_E_E/MTUxOTY0MjA5MzQ4Ng_E_E/MjcwNTgwOA_E_E/L2hvbWUvd3d3L2xvZ3MvdmlkZW8vZmlsZV84OTkyMTMtOThiYTA0MTZiNTU3NGVhN2QxMjA4MGZlMzdiYmI0MWIvdmlkZW8ubXA0.mp4", 45 | @"http://jmvideo1.jumei.com/MQ_E_E/MTUxOTY0MTkwMDA3MA_E_E/NDA0NDkzNA_E_E/L2hvbWUvd3d3L2xvZ3MvdmlkZW8vZmlsZV84OTkyMTMtOTg3N2Y3ZTM2YTYzN2I2ZjY2OTE0ZGU1YjIxNDFkZDQvdmlkZW8ubXA0.mp4", 46 | @"http://jmvideo1.jumei.com/MQ_E_E/MTUxOTY0MTA2NTQ1OA_E_E/NDE0OTE2Nw_E_E/L2hvbWUvd3d3L2xvZ3MvdmlkZW8vZmlsZV84OTkyMTMtOTdjZWI5ODk3Yzk1NTY1MjBmY2E0NjZmZTI4MmQ0MmUvdmlkZW8ubXA0.mp4", 47 | @"http://jmvideo.jumei.com/MQ_E_E/MTUzMzcxMjMxMzcxNw_E_E/MTEwODU4Nw_E_E/L2hvbWUvd3d3L2xvZ3MvdmlkZW8vZG91eWluXzY1MjcxNjgzNTE2NjIyNDcxODEvdmlkZW8ubXA0.mp4", 48 | @"http://jmvideo.jumei.com/MQ_E_E/MTUzMjQyMTU4NzczMQ_E_E/MjE1MzE0Mw_E_E/L2hvbWUvd3d3L2xvZ3MvdmlkZW8vZG91eWluXzY1MzgxNzU0MzQ5MDU4ODE4NjMvdmlkZW8ubXA0.mp4", 49 | @"http://jmvideo.jumei.com/MQ_E_E/MTUzMjQzNDc5MTUwOA_E_E/ODk3Mjg1/L2hvbWUvd3d3L2xvZ3MvdmlkZW8vZG91eWluXzY1ODA4OTQwOTI1Mzg5NDA2NzYvdmlkZW8ubXA0.mp4", 50 | @"http://jmvideo.jumei.com/MQ_E_E/MTUzMjUxMjE1NDY2Nw_E_E/OTI1ODU4Mg_E_E/L2hvbWUvd3d3L2xvZ3MvdmlkZW8vZG91eWluXzY1Njc5ODM4NTg0Mzg5MDA5OTUvdmlkZW8ubXA0.mp4", 51 | @"http://jmvideo.jumei.com/MQ_E_E/MTUyMjY1NDAwOTg3Nw_E_E/MjYxMTg2Mw_E_E/L2hvbWUvd3d3L2xvZ3MvdmlkZW8vZG91eWluXzY1MTkyNDcwMzQ4NjY3MzIzMDIvdmlkZW8ubXA0.mp4"]; 52 | _videoUrls = videoUrls; 53 | } 54 | 55 | - (void)viewDidAppear:(BOOL)animated { 56 | [super viewDidAppear:animated]; 57 | [self scrollViewDidEndDecelerating:_collectionView]; 58 | _collectionView.contentOffset = CGPointZero; 59 | } 60 | 61 | - (IBAction)clickCleanCacheBtn:(UIButton *)sender { 62 | NSString *cachedSizeStr = [[ShortMediaManager shareManager] totalCachedSizeStr]; 63 | NSString *message = [NSString stringWithFormat:@"Confirm to clean cache? \n cache size:%@", cachedSizeStr]; 64 | UIAlertController *alertControoler = [UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]; 65 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"NO" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 66 | 67 | }]; 68 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"YES" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 69 | [[ShortMediaManager shareManager] cleanCache]; 70 | }]; 71 | [alertControoler addAction:cancelAction]; 72 | [alertControoler addAction:okAction]; 73 | [self presentViewController:alertControoler animated:YES completion:nil]; 74 | } 75 | 76 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 77 | return _videoUrls.count; 78 | } 79 | 80 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 81 | PlayerCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"PlayerCell" forIndexPath:indexPath]; 82 | return cell; 83 | } 84 | 85 | - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { 86 | NSLog(@"1====>enddisplay index: %ld, cell: %@, visibleCells: %@",(long)indexPath.row, cell, collectionView.visibleCells); 87 | PlayerCell *playerCell = (PlayerCell*)cell; 88 | NSString *videoStr = [_videoUrls objectAtIndex:indexPath.row]; 89 | [playerCell stopPlayWithUrl:[NSURL URLWithString:videoStr]]; 90 | } 91 | 92 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 93 | __weak typeof(self) _self = self; 94 | dispatch_async(dispatch_get_main_queue(), ^{ 95 | __strong typeof(_self) self = _self; 96 | NSInteger index = scrollView.contentOffset.y / self.collectionView.frame.size.height; 97 | NSArray *visibleCells = self.collectionView.visibleCells; 98 | NSString *videoStr = [self.videoUrls objectAtIndex:index]; 99 | PlayerCell *cell = visibleCells.lastObject; 100 | NSLog(@"2====>scroll to cell %@ index: %ld, visibleCells:%d",cell, (long)index, visibleCells.count); 101 | [cell playVideoWithUrl:[NSURL URLWithString:videoStr]]; 102 | [self resetPreloadWithIndex:index]; 103 | }); 104 | } 105 | 106 | - (void)resetPreloadWithIndex:(NSInteger)index { 107 | index ++; 108 | if(index >= _videoUrls.count) { 109 | return; 110 | } 111 | NSInteger maxPreloadCount = 3; 112 | NSMutableArray *preloadUrls = [NSMutableArray arrayWithCapacity:maxPreloadCount]; 113 | for(NSInteger i = index; i < _videoUrls.count; i++) { 114 | NSString *videoUrlStr = _videoUrls[i]; 115 | NSURL *videoUrl = [NSURL URLWithString:videoUrlStr]; 116 | if(videoUrl) { 117 | [preloadUrls addObject:videoUrl]; 118 | if(preloadUrls.count == maxPreloadCount) { 119 | break; 120 | } 121 | } 122 | } 123 | [[ShortMediaManager shareManager] resetPreloadingWithMediaUrls:preloadUrls]; 124 | } 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaDownloader.m: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaDownloader.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/8/7. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "ShortMediaDownloader.h" 10 | #import "ShortMediaDownloadOperation.h" 11 | 12 | #define Lock() dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER) 13 | #define UnLock() dispatch_semaphore_signal(self.lock) 14 | 15 | @interface ShortMediaDownloader() 16 | @property (nonatomic, strong) NSURLSession *session; 17 | @property (nonatomic, strong) NSOperationQueue *queue; 18 | @property (nonatomic, strong) NSMutableDictionary *operationCache; 19 | 20 | @property (nonatomic, strong) dispatch_semaphore_t lock; 21 | @end 22 | 23 | @implementation ShortMediaDownloader 24 | 25 | + (instancetype)shareDownloader { 26 | static ShortMediaDownloader *instance; 27 | static dispatch_once_t onceToken; 28 | dispatch_once(&onceToken, ^{ 29 | instance = [[ShortMediaDownloader alloc] init]; 30 | }); 31 | return instance; 32 | } 33 | 34 | - (instancetype)init { 35 | self = [super init]; 36 | if(!self) return nil; 37 | _lock = dispatch_semaphore_create(1); 38 | _queue = [NSOperationQueue new]; 39 | _operationCache = [NSMutableDictionary dictionary]; 40 | NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; 41 | _session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil]; 42 | return self; 43 | } 44 | 45 | - (void)downloadMediaWithUrl:(NSURL *)url 46 | options:(ShortMediaOptions)options 47 | progress:(ShortMediaProgressBlock)progress 48 | completion:(ShortMediaCompletionBlock)completion { 49 | [self downloadMediaWithUrl:url options:options preload:NO progress:progress completion:completion]; 50 | } 51 | 52 | - (void)preloadMediaWithUrl:(NSURL *)url 53 | options:(ShortMediaOptions)options 54 | progress:(ShortMediaProgressBlock)progress 55 | completion:(ShortMediaCompletionBlock)completion { 56 | [self downloadMediaWithUrl:url options:options preload:YES progress:progress completion:completion]; 57 | } 58 | 59 | - (void)downloadMediaWithUrl:(NSURL *)url 60 | options:(ShortMediaOptions)options 61 | preload:(BOOL)preload 62 | progress:(ShortMediaProgressBlock)progress 63 | completion:(ShortMediaCompletionBlock)completion { 64 | BOOL cached = [[ShortMediaCache shareCache] isCacheCompletedWithUrl:url]; 65 | if(cached) { 66 | NSInteger fileSize = [[ShortMediaCache shareCache] finalCachedSizeWithUrl:url]; 67 | if(progress) progress(fileSize, fileSize); 68 | if(completion) completion(nil); 69 | return; 70 | } 71 | 72 | [self cancelDownloadWithUrl:url]; 73 | NSInteger cachedSize = [[ShortMediaCache shareCache] cachedSizeWithUrl:url]; 74 | NSMutableURLRequest *downloadRequest = [NSMutableURLRequest requestWithURL:url]; 75 | NSString *range = [NSString stringWithFormat:@"bytes=%ld-", (long)cachedSize]; 76 | [downloadRequest setValue:range forHTTPHeaderField:@"Range"]; 77 | downloadRequest.HTTPShouldHandleCookies = (options & ShortMediaOptionsHandleCookies); 78 | downloadRequest.HTTPShouldUsePipelining = YES; 79 | 80 | __weak typeof(self) _self = self; 81 | ShortMediaDownloadOperation *downloadOperation = [[ShortMediaDownloadOperation alloc] initWithRequest:downloadRequest session:_session options:options progress:progress completion:^(NSError *error) { 82 | __strong typeof(_self) self = _self; 83 | if(!self) return; 84 | Lock(); 85 | [self.operationCache removeObjectForKey:url]; 86 | UnLock(); 87 | if(completion) completion(error); 88 | }]; 89 | if(_userName && _password) { 90 | downloadOperation.credential = [NSURLCredential credentialWithUser:_userName password:_password persistence:NSURLCredentialPersistenceForSession]; 91 | } 92 | downloadOperation.isPreloading = preload; 93 | Lock(); 94 | if(!preload) { 95 | NSMutableArray *needCancelUrls = [NSMutableArray array]; 96 | for (NSURL *url in _operationCache.allKeys) { 97 | ShortMediaDownloadOperation *operation = _operationCache[url]; 98 | if(operation.isPreloading) { 99 | [needCancelUrls addObject:url]; 100 | } 101 | } 102 | for (NSURL *url in needCancelUrls) { 103 | [_operationCache removeObjectForKey:url]; 104 | } 105 | } 106 | [_operationCache setObject:downloadOperation forKey:url]; 107 | [_queue addOperation:downloadOperation]; 108 | UnLock(); 109 | } 110 | 111 | - (void)cancelDownloadWithUrl:(NSURL *)url { 112 | if(!url) { 113 | return; 114 | } 115 | Lock(); 116 | ShortMediaDownloadOperation *operation = [_operationCache objectForKey:url]; 117 | if(operation) { 118 | [operation cancel]; 119 | [_operationCache removeObjectForKey:url]; 120 | } 121 | UnLock(); 122 | } 123 | 124 | - (BOOL)canPreload { 125 | BOOL retValue = YES; 126 | Lock(); 127 | for (ShortMediaDownloadOperation *operation in self.operationCache.allValues) { 128 | if(!operation.isPreloading) { 129 | retValue = NO; 130 | break; 131 | } 132 | } 133 | UnLock(); 134 | return retValue; 135 | } 136 | 137 | - (void)dealloc { 138 | [_session invalidateAndCancel]; 139 | } 140 | 141 | #pragma mark NSURLSessionTaskDelegate 142 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { 143 | Lock(); 144 | ShortMediaDownloadOperation *operation = [_operationCache objectForKey:task.originalRequest.URL]; 145 | UnLock(); 146 | [operation URLSession:session task:task didCompleteWithError:error]; 147 | } 148 | 149 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { 150 | ShortMediaDownloadOperation *operation = [_operationCache objectForKey:task.originalRequest.URL]; 151 | [operation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler]; 152 | } 153 | 154 | #pragma mark - NSURLSessionDataDelegate 155 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 156 | didReceiveResponse:(NSURLResponse *)response 157 | completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { 158 | Lock(); 159 | ShortMediaDownloadOperation *operation = [_operationCache objectForKey:dataTask.originalRequest.URL]; 160 | UnLock(); 161 | [operation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler]; 162 | } 163 | 164 | 165 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { 166 | Lock(); 167 | ShortMediaDownloadOperation *operation = [_operationCache objectForKey:dataTask.originalRequest.URL]; 168 | UnLock(); 169 | [operation URLSession:session dataTask:dataTask didReceiveData:data]; 170 | } 171 | 172 | @end 173 | -------------------------------------------------------------------------------- /ShortMediaCacheDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaDownloadOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaDownloadOperation.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/30. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "ShortMediaDownloadOperation.h" 10 | #import 11 | 12 | @interface ShortMediaDownloadOperation() 13 | @property (readwrite, getter=isExecuting) BOOL executing; 14 | @property (readwrite, getter=isFinished) BOOL finished; 15 | @property (readwrite, getter=isCancelled) BOOL cancelled; 16 | @property (readwrite) BOOL started; 17 | 18 | @property (nonatomic, assign) NSInteger expectedSize; 19 | @property (nonatomic, assign) NSInteger receivedSize; 20 | 21 | @property (nonatomic, strong) NSRecursiveLock *lock; 22 | @property (nonatomic, weak) NSURLSession *session; 23 | @property (nonatomic, strong) NSURLSessionDataTask *dataTask; 24 | @property (nonatomic, strong) NSURLRequest *request; 25 | 26 | @property (nonatomic, copy) ShortMediaProgressBlock progress; 27 | @property (nonatomic, copy) ShortMediaCompletionBlock completion; 28 | 29 | @end 30 | 31 | @implementation ShortMediaDownloadOperation 32 | @synthesize executing = _executing; 33 | @synthesize finished = _finished; 34 | @synthesize cancelled = _cancelled; 35 | 36 | - (instancetype)initWithRequest:(NSURLRequest *)request 37 | session:(NSURLSession *)session 38 | options:(ShortMediaOptions)options 39 | progress:(ShortMediaProgressBlock)progress 40 | completion:(ShortMediaCompletionBlock)completion { 41 | self = [super init]; 42 | if (!self) return nil; 43 | _request = request; 44 | _session = session; 45 | _options = options; 46 | _progress = progress; 47 | _completion = completion; 48 | _executing = NO; 49 | _finished = NO; 50 | _cancelled = NO; 51 | _started = NO; 52 | return self; 53 | } 54 | 55 | - (void)dealloc { 56 | [self finish]; 57 | } 58 | 59 | #pragma mark - NSURLSessionTaskDelegate 60 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { 61 | 62 | NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; 63 | __block NSURLCredential *credential = nil; 64 | 65 | if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { 66 | if (!(_options & ShortMediaOptionsOptionAllowInvalidSSLCertificates)) { 67 | disposition = NSURLSessionAuthChallengePerformDefaultHandling; 68 | } else { 69 | credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; 70 | disposition = NSURLSessionAuthChallengeUseCredential; 71 | } 72 | } else { 73 | if ([challenge previousFailureCount] == 0) { 74 | if (_credential) { 75 | credential = _credential; 76 | disposition = NSURLSessionAuthChallengeUseCredential; 77 | } else { 78 | disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; 79 | } 80 | } else { 81 | disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; 82 | } 83 | } 84 | 85 | if (completionHandler) { 86 | completionHandler(disposition, credential); 87 | } 88 | } 89 | 90 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { 91 | if(!error) { 92 | [[ShortMediaCache shareCache] cacheCompletedWithUrl:_request.URL]; 93 | NSLog(@"ShortMediaDownloadOperation donwload complete: %@", _request.URL.absoluteString); 94 | } else { 95 | NSLog(@"ShortMediaDownloadOperation donwload error: %@", _request.URL.absoluteString); 96 | } 97 | if (_completion) _completion(error); 98 | 99 | [self finish]; 100 | } 101 | 102 | #pragma mark - NSURLSessionDataDelegate 103 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 104 | didReceiveResponse:(NSURLResponse *)response 105 | completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { 106 | if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) { 107 | NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0; 108 | NSString *rangeHead = [_request valueForHTTPHeaderField:@"Range"]; 109 | NSInteger startOffset = 0; 110 | if(rangeHead && rangeHead.length >= 7) { 111 | NSString *startOffsetStr = [rangeHead substringWithRange:NSMakeRange(6, rangeHead.length - 7)]; 112 | startOffset = [startOffsetStr integerValue]; 113 | } 114 | _expectedSize = startOffset + expected; 115 | _receivedSize = startOffset; 116 | 117 | [[ShortMediaCache shareCache] createCacheFileWithUrl:_request.URL]; 118 | if(_progress) _progress(startOffset, _expectedSize); 119 | } 120 | else { 121 | NSUInteger code = [((NSHTTPURLResponse *)response) statusCode]; 122 | NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:code userInfo:nil]; 123 | if(_completion) _completion(error); 124 | [self finish]; 125 | } 126 | 127 | if (completionHandler) { 128 | completionHandler(NSURLSessionResponseAllow); 129 | } 130 | } 131 | 132 | 133 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { 134 | [[ShortMediaCache shareCache] appendWithData:data url:_request.URL]; 135 | [_lock lock]; 136 | _receivedSize += data.length; 137 | [_lock unlock]; 138 | if(_progress) _progress(_receivedSize, _expectedSize); 139 | } 140 | 141 | #pragma mark - Methods 142 | - (void)start { 143 | [_lock lock]; 144 | self.started = YES; 145 | if([self isCancelled]) { 146 | [self cancelOperation]; 147 | NSError *cancelError = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:nil]; 148 | if(_completion) _completion(cancelError); 149 | } else if([self isReady] && ![self isFinished] && ![self isExecuting]) { 150 | if(!_request) { 151 | NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]; 152 | if(_completion) _completion(error); 153 | [self finish]; 154 | } else { 155 | self.executing = YES; 156 | _dataTask = [_session dataTaskWithRequest:_request]; 157 | [_dataTask resume]; 158 | } 159 | } 160 | [_lock unlock]; 161 | } 162 | 163 | - (void)cancel { 164 | [_lock lock]; 165 | if(![self isCancelled]) { 166 | [super cancel]; 167 | self.cancelled = YES; 168 | if ([self isExecuting]) { 169 | self.executing = NO; 170 | [self cancelOperation]; 171 | } 172 | if(self.started) { 173 | self.finished = YES; 174 | } 175 | } 176 | [_lock unlock]; 177 | } 178 | 179 | - (void)cancelOperation { 180 | if(_dataTask) { 181 | [_dataTask cancel]; 182 | _dataTask = nil; 183 | [self finish]; 184 | } 185 | } 186 | 187 | - (void)finish { 188 | self.finished = YES; 189 | self.executing = NO; 190 | } 191 | 192 | - (void)setExecuting:(BOOL)executing { 193 | [_lock lock]; 194 | if(_executing != executing) { 195 | [self willChangeValueForKey:@"isExecuting"]; 196 | _executing = executing; 197 | [self didChangeValueForKey:@"isExecuting"]; 198 | } 199 | [_lock unlock]; 200 | } 201 | 202 | - (BOOL)isExecuting { 203 | [_lock lock]; 204 | BOOL executing = _executing; 205 | [_lock unlock]; 206 | return executing; 207 | } 208 | 209 | - (void)setFinished:(BOOL)finished { 210 | [_lock lock]; 211 | if(_finished != finished) { 212 | [self willChangeValueForKey:@"isFinished"]; 213 | _finished = finished; 214 | [self didChangeValueForKey:@"isFinished"]; 215 | } 216 | [_lock unlock]; 217 | } 218 | 219 | - (BOOL)isFinished { 220 | [_lock lock]; 221 | BOOL finished = _finished; 222 | [_lock unlock]; 223 | return finished; 224 | } 225 | 226 | - (void)setCancelled:(BOOL)cancelled { 227 | [_lock lock]; 228 | if(_cancelled != cancelled) { 229 | [self willChangeValueForKey:@"isCancelled"]; 230 | _cancelled = cancelled; 231 | [self didChangeValueForKey:@"isCancelled"]; 232 | } 233 | [_lock unlock]; 234 | } 235 | 236 | - (BOOL)isCancelled { 237 | [_lock lock]; 238 | BOOL cancelled = _cancelled; 239 | [_lock unlock]; 240 | return cancelled; 241 | } 242 | 243 | - (BOOL)isConcurrent { 244 | return YES; 245 | } 246 | 247 | - (BOOL)isAsynchronous { 248 | return YES; 249 | } 250 | 251 | + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { 252 | if([key isEqualToString:@"isExecuting"] || 253 | [key isEqualToString:@"isFinished"] || 254 | [key isEqualToString:@"isCancelled"]) { 255 | return NO; 256 | } 257 | return YES; 258 | } 259 | 260 | @end 261 | -------------------------------------------------------------------------------- /ShortMediaCache/ShortMediaDiskCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // ShortMediaDiskCache.m 3 | // ShortMediaCacheDemo 4 | // 5 | // Created by dangercheng on 2018/7/31. 6 | // Copyright © 2018年 DandJ. All rights reserved. 7 | // 8 | 9 | #import "ShortMediaDiskCache.h" 10 | #import "ShortMediaCacheConfig.h" 11 | 12 | static NSString *const kMediaFinalDirectoryName = @"media"; 13 | static NSString *const kMediaTempDirectoryName = @"temp"; 14 | static NSString *const kMediaTrashDirectoryName = @"trash"; 15 | 16 | @implementation ShortMediaDiskCache { 17 | NSString *_finalPath; 18 | NSString *_tempPath; 19 | NSString *_trashPath; 20 | CFMutableDictionaryRef _fileHandleCache; 21 | } 22 | 23 | 24 | - (instancetype)initWithPath:(NSString *)path { 25 | self = [super init]; 26 | if(!self) return nil; 27 | _path = path; 28 | _finalPath = [path stringByAppendingPathComponent:kMediaFinalDirectoryName]; 29 | _tempPath = [path stringByAppendingPathComponent:kMediaTempDirectoryName]; 30 | _trashPath = [path stringByAppendingPathComponent:kMediaTrashDirectoryName]; 31 | NSFileManager *fileManager = [NSFileManager defaultManager]; 32 | 33 | _fileHandleCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 34 | 35 | NSError *error = nil; 36 | if(![fileManager createDirectoryAtPath:_finalPath withIntermediateDirectories:YES attributes:nil error:&error] || 37 | ![fileManager createDirectoryAtPath:_tempPath withIntermediateDirectories:YES attributes:nil error:&error] || 38 | ![fileManager createDirectoryAtPath:_trashPath withIntermediateDirectories:YES attributes:nil error:&error]) { 39 | return nil; 40 | } 41 | 42 | NSLog(@"ShortMediaCache tempPath:%@", _tempPath); 43 | NSLog(@"ShortMediaCache finalPath:%@", _finalPath); 44 | NSLog(@"ShortMediaCache trashPath:%@",_trashPath); 45 | return self; 46 | } 47 | 48 | - (void)dealloc { 49 | if (_fileHandleCache) CFRelease(_fileHandleCache); 50 | _fileHandleCache = NULL; 51 | } 52 | 53 | - (NSString *)createTempFileWithName:(NSString *)fileName { 54 | NSString *filePath = [_tempPath stringByAppendingPathComponent:fileName]; 55 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 56 | if(![fileManager fileExistsAtPath:filePath]) { 57 | [fileManager createFileAtPath:filePath contents:nil attributes:nil]; 58 | } 59 | return filePath; 60 | } 61 | 62 | - (void)appendData:(NSData *)data tempFileWithName:(NSString *)fileName { 63 | if(!data) { 64 | return; 65 | } 66 | if(!(fileName.length > 0)) { 67 | return; 68 | } 69 | NSString *filePath = [_tempPath stringByAppendingPathComponent:fileName]; 70 | NSFileHandle *fileHandle = CFDictionaryGetValue(_fileHandleCache, (__bridge const void *)(fileName)); 71 | if(!fileHandle) { 72 | fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath]; 73 | CFDictionarySetValue(_fileHandleCache, (__bridge const void *)(fileName), (__bridge const void *)(fileHandle)); 74 | } 75 | [fileHandle seekToEndOfFile]; 76 | [fileHandle writeData:data]; 77 | } 78 | 79 | - (void)moveTempFileToFinalWithName:(NSString *)fileName { 80 | if(!(fileName.length > 0)) { 81 | return; 82 | } 83 | NSString *tempFilePath = [_tempPath stringByAppendingPathComponent:fileName]; 84 | NSString *finalFilePath = [_finalPath stringByAppendingPathComponent:fileName]; 85 | NSError *error = nil; 86 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 87 | [fileManager moveItemAtPath:tempFilePath toPath:finalFilePath error:&error]; 88 | if(!error) { 89 | CFDictionaryRemoveValue(_fileHandleCache, (__bridge const void *)(fileName)); 90 | } 91 | } 92 | 93 | - (NSData *)dataFromOffset:(NSUInteger)offset 94 | length:(NSUInteger)length 95 | withName:(NSString *)fileName { 96 | if(!(fileName.length > 0)) { 97 | return nil; 98 | } 99 | 100 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 101 | NSString *tempFileFath = [_tempPath stringByAppendingPathComponent:fileName]; 102 | NSString *finalFilePath = [_finalPath stringByAppendingPathComponent:fileName]; 103 | 104 | NSString *targetPath = tempFileFath; 105 | BOOL fileExist = [fileManager fileExistsAtPath:finalFilePath]; 106 | if(fileExist) { 107 | targetPath = finalFilePath; 108 | } else { 109 | fileExist = [fileManager fileExistsAtPath:tempFileFath]; 110 | if(!fileExist) { 111 | return nil; 112 | } 113 | } 114 | 115 | NSError *error; 116 | NSData *tempVideoData = [NSData dataWithContentsOfFile:targetPath options:NSDataReadingMappedIfSafe error:&error]; 117 | if (!error && !(tempVideoData.length < (offset + length))) { 118 | NSRange respondRange = NSMakeRange(offset, length); 119 | return [tempVideoData subdataWithRange:respondRange]; 120 | } else { 121 | return nil; 122 | } 123 | } 124 | 125 | - (NSInteger)finalCachedSizeWithName:(NSString *)fileName { 126 | NSString *path = [_finalPath stringByAppendingPathComponent:fileName]; 127 | return [self fileSizeWithPath:path]; 128 | } 129 | 130 | - (NSInteger)tempCachedSizehWithName:(NSString *)fileName { 131 | NSString *path = [_tempPath stringByAppendingPathComponent:fileName]; 132 | return [self fileSizeWithPath:path]; 133 | } 134 | 135 | - (NSInteger)fileSizeWithPath:(NSString *)filePath { 136 | if(!(filePath.length > 0)) { 137 | return 0; 138 | } 139 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 140 | if(![fileManager fileExistsAtPath:filePath]) { 141 | return 0; 142 | } 143 | NSError *error = nil; 144 | NSInteger fileSize = 0; 145 | NSDictionary *fileDict = [fileManager attributesOfItemAtPath:filePath error:&error]; 146 | if (!error && fileDict) { 147 | fileSize = (NSInteger)[fileDict fileSize]; 148 | } 149 | 150 | return fileSize; 151 | } 152 | 153 | - (BOOL)fileExistAtFinalWithName:(NSString *)fileName { 154 | if(!(fileName.length > 0)) { 155 | return NO; 156 | } 157 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 158 | NSString *filePath = [_finalPath stringByAppendingPathComponent:fileName]; 159 | return [fileManager fileExistsAtPath:filePath]; 160 | } 161 | 162 | - (NSInteger)totalCachedSize { 163 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 164 | NSDirectoryEnumerator *fileEnumerator = [fileManager enumeratorAtPath:_tempPath]; 165 | NSInteger totalFileSize = 0; 166 | for (NSString *fileName in fileEnumerator) { 167 | NSString *filePath = [_tempPath stringByAppendingPathComponent:fileName]; 168 | NSInteger fileSize = [self fileSizeWithPath:filePath]; 169 | totalFileSize += fileSize; 170 | } 171 | fileEnumerator = [fileManager enumeratorAtPath:_finalPath]; 172 | for (NSString *fileName in fileEnumerator) { 173 | NSString *filePath = [_finalPath stringByAppendingPathComponent:fileName]; 174 | NSInteger fileSize = [self fileSizeWithPath:filePath]; 175 | totalFileSize += fileSize; 176 | } 177 | return totalFileSize; 178 | } 179 | 180 | - (void)moveAllCacheToTrash { 181 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 182 | NSDirectoryEnumerator *fileEnumerator_temp = [fileManager enumeratorAtPath:_tempPath]; 183 | for (NSString *fileName in fileEnumerator_temp) { 184 | NSError *error; 185 | NSString *filePath = [_tempPath stringByAppendingPathComponent:fileName]; 186 | NSString *toPath = [_trashPath stringByAppendingPathComponent:fileName]; 187 | [fileManager moveItemAtPath:filePath toPath:toPath error:&error]; 188 | if(!error) { 189 | CFDictionaryRemoveValue(_fileHandleCache, (__bridge const void *)(fileName)); 190 | } 191 | } 192 | 193 | NSDirectoryEnumerator *fileEnumerator_full = [fileManager enumeratorAtPath:_finalPath]; 194 | for (NSString *fileName in fileEnumerator_full) { 195 | NSError *error; 196 | NSString *filePath = [_finalPath stringByAppendingPathComponent:fileName]; 197 | NSString *toPath = [_trashPath stringByAppendingPathComponent:fileName]; 198 | [fileManager moveItemAtPath:filePath toPath:toPath error:nil]; 199 | if(!error) { 200 | CFDictionaryRemoveValue(_fileHandleCache, (__bridge const void *)(fileName)); 201 | } 202 | } 203 | } 204 | 205 | - (void)cleanTrash { 206 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 207 | [fileManager removeItemAtPath:_trashPath error:nil]; 208 | [fileManager createDirectoryAtPath:_trashPath withIntermediateDirectories:YES attributes:nil error:nil]; 209 | } 210 | 211 | - (void)resetCacheWithConfig:(ShortMediaCacheConfig *)config { 212 | [self resetTempCacheWitchConfig:config]; 213 | [self resetFinalCacheWitchConfig:config]; 214 | } 215 | 216 | - (void)resetTempCacheWitchConfig:(ShortMediaCacheConfig *)config { 217 | [self resetCacheWithPath:_tempPath maxTimeInterval:config.maxTempCacheTimeInterval maxSize:config.maxTempCacheSize]; 218 | } 219 | 220 | - (void)resetFinalCacheWitchConfig:(ShortMediaCacheConfig *)config { 221 | [self resetCacheWithPath:_finalPath maxTimeInterval:config.maxFinalCacheTimeInterval maxSize:config.maxFinalCacheSize]; 222 | } 223 | 224 | - (void)resetCacheWithPath:(NSString *)path 225 | maxTimeInterval:(NSInteger)timeInterval 226 | maxSize:(NSInteger)maxSize { 227 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 228 | NSURL *cacheUrl = [NSURL fileURLWithPath:path isDirectory:YES]; 229 | NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; 230 | NSDirectoryEnumerator *fileEnumerator = [fileManager enumeratorAtURL:cacheUrl 231 | includingPropertiesForKeys:resourceKeys 232 | options:NSDirectoryEnumerationSkipsHiddenFiles 233 | errorHandler:NULL]; 234 | 235 | NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-timeInterval]; 236 | NSMutableDictionary *> *remainningFiles = [NSMutableDictionary dictionary]; 237 | NSUInteger remainingSize = 0; 238 | 239 | NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init]; 240 | 241 | for (NSURL *fileURL in fileEnumerator) { 242 | @autoreleasepool { 243 | NSError *error; 244 | NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error]; 245 | if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) { 246 | continue; 247 | } 248 | NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey]; 249 | if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) { 250 | [urlsToDelete addObject:fileURL]; 251 | continue; 252 | } 253 | NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; 254 | remainingSize += totalAllocatedSize.unsignedIntegerValue; 255 | remainningFiles[fileURL] = resourceValues; 256 | } 257 | } 258 | 259 | for (NSURL *fileURL in urlsToDelete) { 260 | NSString *trashPath = [_trashPath stringByAppendingPathComponent:fileURL.lastPathComponent]; 261 | [fileManager moveItemAtPath:fileURL.path toPath:trashPath error:nil]; 262 | } 263 | 264 | if (maxSize > 0 && remainingSize > maxSize) { 265 | const NSUInteger targteCacheSize = maxSize / 2; 266 | NSArray *sortedFiles = [remainningFiles keysSortedByValueWithOptions:NSSortConcurrent 267 | usingComparator:^NSComparisonResult(id obj1, id obj2) { 268 | return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; 269 | }]; 270 | 271 | for (NSURL *fileURL in sortedFiles) { 272 | NSString *trashPath = [_trashPath stringByAppendingPathComponent:fileURL.lastPathComponent]; 273 | if([fileManager moveItemAtPath:fileURL.path toPath:trashPath error:nil]) { 274 | NSDictionary *resourceValues = remainningFiles[fileURL]; 275 | NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; 276 | remainingSize -= totalAllocatedSize.unsignedIntegerValue; 277 | 278 | if (remainingSize < targteCacheSize) { 279 | break; 280 | } 281 | } 282 | } 283 | } 284 | [self cleanTrash]; 285 | } 286 | 287 | @end 288 | -------------------------------------------------------------------------------- /ShortMediaCacheDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 53140E26212570EF0086B96A /* ShortMediaCacheConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 53140E25212570EF0086B96A /* ShortMediaCacheConfig.m */; }; 11 | 5382708F2119870D0049A100 /* ShortMediaWeakProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 5382708E2119870C0049A100 /* ShortMediaWeakProxy.m */; }; 12 | 5382709221199A8D0049A100 /* ShortMediaDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 5382709121199A8D0049A100 /* ShortMediaDownloader.m */; }; 13 | 53827095211BE1AD0049A100 /* PlayerCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 53827094211BE1AD0049A100 /* PlayerCell.m */; }; 14 | 53827098211C673F0049A100 /* AVAssetResourceLoadingDataRequest+ShortMediaCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 53827097211C673F0049A100 /* AVAssetResourceLoadingDataRequest+ShortMediaCache.m */; }; 15 | 53BF338E210F0A4B00DB23DD /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 53BF338D210F0A4B00DB23DD /* AppDelegate.m */; }; 16 | 53BF3391210F0A4C00DB23DD /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 53BF3390210F0A4C00DB23DD /* ViewController.m */; }; 17 | 53BF3394210F0A4C00DB23DD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53BF3392210F0A4C00DB23DD /* Main.storyboard */; }; 18 | 53BF3396210F0A4D00DB23DD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53BF3395210F0A4D00DB23DD /* Assets.xcassets */; }; 19 | 53BF3399210F0A4D00DB23DD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53BF3397210F0A4D00DB23DD /* LaunchScreen.storyboard */; }; 20 | 53BF339C210F0A4D00DB23DD /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 53BF339B210F0A4D00DB23DD /* main.m */; }; 21 | 53BF33A5210F0FD200DB23DD /* ShortMediaResourceLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 53BF33A4210F0FD200DB23DD /* ShortMediaResourceLoader.m */; }; 22 | 53BF33A8210F1A4C00DB23DD /* ShortMediaDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 53BF33A7210F1A4C00DB23DD /* ShortMediaDownloadOperation.m */; }; 23 | 53BF33AB210F1E0600DB23DD /* ShortMediaCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 53BF33AA210F1E0600DB23DD /* ShortMediaCache.m */; }; 24 | 53BF33AE210FF77A00DB23DD /* ShortMediaManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 53BF33AD210FF77A00DB23DD /* ShortMediaManager.m */; }; 25 | 53BF33B12110024B00DB23DD /* ShortMediaDiskCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 53BF33B02110024B00DB23DD /* ShortMediaDiskCache.m */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | 53140E24212570EF0086B96A /* ShortMediaCacheConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShortMediaCacheConfig.h; sourceTree = ""; }; 30 | 53140E25212570EF0086B96A /* ShortMediaCacheConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShortMediaCacheConfig.m; sourceTree = ""; }; 31 | 5382708D2119870C0049A100 /* ShortMediaWeakProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShortMediaWeakProxy.h; sourceTree = ""; }; 32 | 5382708E2119870C0049A100 /* ShortMediaWeakProxy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShortMediaWeakProxy.m; sourceTree = ""; }; 33 | 5382709021199A8D0049A100 /* ShortMediaDownloader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShortMediaDownloader.h; sourceTree = ""; }; 34 | 5382709121199A8D0049A100 /* ShortMediaDownloader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShortMediaDownloader.m; sourceTree = ""; }; 35 | 53827093211BE1AD0049A100 /* PlayerCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlayerCell.h; sourceTree = ""; }; 36 | 53827094211BE1AD0049A100 /* PlayerCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PlayerCell.m; sourceTree = ""; }; 37 | 53827096211C673F0049A100 /* AVAssetResourceLoadingDataRequest+ShortMediaCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AVAssetResourceLoadingDataRequest+ShortMediaCache.h"; sourceTree = ""; }; 38 | 53827097211C673F0049A100 /* AVAssetResourceLoadingDataRequest+ShortMediaCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "AVAssetResourceLoadingDataRequest+ShortMediaCache.m"; sourceTree = ""; }; 39 | 53BF3389210F0A4B00DB23DD /* ShortMediaCacheDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ShortMediaCacheDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | 53BF338C210F0A4B00DB23DD /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 41 | 53BF338D210F0A4B00DB23DD /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 42 | 53BF338F210F0A4B00DB23DD /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 43 | 53BF3390210F0A4C00DB23DD /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 44 | 53BF3393210F0A4C00DB23DD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | 53BF3395210F0A4D00DB23DD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | 53BF3398210F0A4D00DB23DD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | 53BF339A210F0A4D00DB23DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | 53BF339B210F0A4D00DB23DD /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 49 | 53BF33A3210F0FD200DB23DD /* ShortMediaResourceLoader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShortMediaResourceLoader.h; sourceTree = ""; }; 50 | 53BF33A4210F0FD200DB23DD /* ShortMediaResourceLoader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShortMediaResourceLoader.m; sourceTree = ""; }; 51 | 53BF33A6210F1A4C00DB23DD /* ShortMediaDownloadOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShortMediaDownloadOperation.h; sourceTree = ""; }; 52 | 53BF33A7210F1A4C00DB23DD /* ShortMediaDownloadOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShortMediaDownloadOperation.m; sourceTree = ""; }; 53 | 53BF33A9210F1E0600DB23DD /* ShortMediaCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShortMediaCache.h; sourceTree = ""; }; 54 | 53BF33AA210F1E0600DB23DD /* ShortMediaCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShortMediaCache.m; sourceTree = ""; }; 55 | 53BF33AC210FF77A00DB23DD /* ShortMediaManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShortMediaManager.h; sourceTree = ""; }; 56 | 53BF33AD210FF77A00DB23DD /* ShortMediaManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShortMediaManager.m; sourceTree = ""; }; 57 | 53BF33AF2110024B00DB23DD /* ShortMediaDiskCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShortMediaDiskCache.h; sourceTree = ""; }; 58 | 53BF33B02110024B00DB23DD /* ShortMediaDiskCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShortMediaDiskCache.m; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | 53BF3386210F0A4B00DB23DD /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | 53BF3380210F0A4B00DB23DD = { 73 | isa = PBXGroup; 74 | children = ( 75 | 53BF33A2210F0D2A00DB23DD /* ShortMediaCache */, 76 | 53BF338B210F0A4B00DB23DD /* ShortMediaCacheDemo */, 77 | 53BF338A210F0A4B00DB23DD /* Products */, 78 | ); 79 | sourceTree = ""; 80 | }; 81 | 53BF338A210F0A4B00DB23DD /* Products */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 53BF3389210F0A4B00DB23DD /* ShortMediaCacheDemo.app */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | 53BF338B210F0A4B00DB23DD /* ShortMediaCacheDemo */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 53BF338C210F0A4B00DB23DD /* AppDelegate.h */, 93 | 53BF338D210F0A4B00DB23DD /* AppDelegate.m */, 94 | 53BF338F210F0A4B00DB23DD /* ViewController.h */, 95 | 53BF3390210F0A4C00DB23DD /* ViewController.m */, 96 | 53827093211BE1AD0049A100 /* PlayerCell.h */, 97 | 53827094211BE1AD0049A100 /* PlayerCell.m */, 98 | 53BF3392210F0A4C00DB23DD /* Main.storyboard */, 99 | 53BF3395210F0A4D00DB23DD /* Assets.xcassets */, 100 | 53BF3397210F0A4D00DB23DD /* LaunchScreen.storyboard */, 101 | 53BF339A210F0A4D00DB23DD /* Info.plist */, 102 | 53BF339B210F0A4D00DB23DD /* main.m */, 103 | ); 104 | path = ShortMediaCacheDemo; 105 | sourceTree = ""; 106 | }; 107 | 53BF33A2210F0D2A00DB23DD /* ShortMediaCache */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 53BF33A3210F0FD200DB23DD /* ShortMediaResourceLoader.h */, 111 | 53BF33A4210F0FD200DB23DD /* ShortMediaResourceLoader.m */, 112 | 53BF33AC210FF77A00DB23DD /* ShortMediaManager.h */, 113 | 53BF33AD210FF77A00DB23DD /* ShortMediaManager.m */, 114 | 53BF33A9210F1E0600DB23DD /* ShortMediaCache.h */, 115 | 53BF33AA210F1E0600DB23DD /* ShortMediaCache.m */, 116 | 53BF33AF2110024B00DB23DD /* ShortMediaDiskCache.h */, 117 | 53BF33B02110024B00DB23DD /* ShortMediaDiskCache.m */, 118 | 5382709021199A8D0049A100 /* ShortMediaDownloader.h */, 119 | 5382709121199A8D0049A100 /* ShortMediaDownloader.m */, 120 | 53BF33A6210F1A4C00DB23DD /* ShortMediaDownloadOperation.h */, 121 | 53BF33A7210F1A4C00DB23DD /* ShortMediaDownloadOperation.m */, 122 | 5382708D2119870C0049A100 /* ShortMediaWeakProxy.h */, 123 | 5382708E2119870C0049A100 /* ShortMediaWeakProxy.m */, 124 | 53827096211C673F0049A100 /* AVAssetResourceLoadingDataRequest+ShortMediaCache.h */, 125 | 53827097211C673F0049A100 /* AVAssetResourceLoadingDataRequest+ShortMediaCache.m */, 126 | 53140E24212570EF0086B96A /* ShortMediaCacheConfig.h */, 127 | 53140E25212570EF0086B96A /* ShortMediaCacheConfig.m */, 128 | ); 129 | path = ShortMediaCache; 130 | sourceTree = ""; 131 | }; 132 | /* End PBXGroup section */ 133 | 134 | /* Begin PBXNativeTarget section */ 135 | 53BF3388210F0A4B00DB23DD /* ShortMediaCacheDemo */ = { 136 | isa = PBXNativeTarget; 137 | buildConfigurationList = 53BF339F210F0A4D00DB23DD /* Build configuration list for PBXNativeTarget "ShortMediaCacheDemo" */; 138 | buildPhases = ( 139 | 53BF3385210F0A4B00DB23DD /* Sources */, 140 | 53BF3386210F0A4B00DB23DD /* Frameworks */, 141 | 53BF3387210F0A4B00DB23DD /* Resources */, 142 | ); 143 | buildRules = ( 144 | ); 145 | dependencies = ( 146 | ); 147 | name = ShortMediaCacheDemo; 148 | productName = ShortMediaCacheDemo; 149 | productReference = 53BF3389210F0A4B00DB23DD /* ShortMediaCacheDemo.app */; 150 | productType = "com.apple.product-type.application"; 151 | }; 152 | /* End PBXNativeTarget section */ 153 | 154 | /* Begin PBXProject section */ 155 | 53BF3381210F0A4B00DB23DD /* Project object */ = { 156 | isa = PBXProject; 157 | attributes = { 158 | LastUpgradeCheck = 0930; 159 | ORGANIZATIONNAME = DandJ; 160 | TargetAttributes = { 161 | 53BF3388210F0A4B00DB23DD = { 162 | CreatedOnToolsVersion = 9.3; 163 | }; 164 | }; 165 | }; 166 | buildConfigurationList = 53BF3384210F0A4B00DB23DD /* Build configuration list for PBXProject "ShortMediaCacheDemo" */; 167 | compatibilityVersion = "Xcode 9.3"; 168 | developmentRegion = en; 169 | hasScannedForEncodings = 0; 170 | knownRegions = ( 171 | en, 172 | Base, 173 | ); 174 | mainGroup = 53BF3380210F0A4B00DB23DD; 175 | productRefGroup = 53BF338A210F0A4B00DB23DD /* Products */; 176 | projectDirPath = ""; 177 | projectRoot = ""; 178 | targets = ( 179 | 53BF3388210F0A4B00DB23DD /* ShortMediaCacheDemo */, 180 | ); 181 | }; 182 | /* End PBXProject section */ 183 | 184 | /* Begin PBXResourcesBuildPhase section */ 185 | 53BF3387210F0A4B00DB23DD /* Resources */ = { 186 | isa = PBXResourcesBuildPhase; 187 | buildActionMask = 2147483647; 188 | files = ( 189 | 53BF3399210F0A4D00DB23DD /* LaunchScreen.storyboard in Resources */, 190 | 53BF3396210F0A4D00DB23DD /* Assets.xcassets in Resources */, 191 | 53BF3394210F0A4C00DB23DD /* Main.storyboard in Resources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXResourcesBuildPhase section */ 196 | 197 | /* Begin PBXSourcesBuildPhase section */ 198 | 53BF3385210F0A4B00DB23DD /* Sources */ = { 199 | isa = PBXSourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | 53827095211BE1AD0049A100 /* PlayerCell.m in Sources */, 203 | 53BF33AB210F1E0600DB23DD /* ShortMediaCache.m in Sources */, 204 | 53BF33A5210F0FD200DB23DD /* ShortMediaResourceLoader.m in Sources */, 205 | 5382709221199A8D0049A100 /* ShortMediaDownloader.m in Sources */, 206 | 53140E26212570EF0086B96A /* ShortMediaCacheConfig.m in Sources */, 207 | 53BF33AE210FF77A00DB23DD /* ShortMediaManager.m in Sources */, 208 | 53BF3391210F0A4C00DB23DD /* ViewController.m in Sources */, 209 | 53BF33A8210F1A4C00DB23DD /* ShortMediaDownloadOperation.m in Sources */, 210 | 53BF339C210F0A4D00DB23DD /* main.m in Sources */, 211 | 53BF338E210F0A4B00DB23DD /* AppDelegate.m in Sources */, 212 | 53827098211C673F0049A100 /* AVAssetResourceLoadingDataRequest+ShortMediaCache.m in Sources */, 213 | 53BF33B12110024B00DB23DD /* ShortMediaDiskCache.m in Sources */, 214 | 5382708F2119870D0049A100 /* ShortMediaWeakProxy.m in Sources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXSourcesBuildPhase section */ 219 | 220 | /* Begin PBXVariantGroup section */ 221 | 53BF3392210F0A4C00DB23DD /* Main.storyboard */ = { 222 | isa = PBXVariantGroup; 223 | children = ( 224 | 53BF3393210F0A4C00DB23DD /* Base */, 225 | ); 226 | name = Main.storyboard; 227 | sourceTree = ""; 228 | }; 229 | 53BF3397210F0A4D00DB23DD /* LaunchScreen.storyboard */ = { 230 | isa = PBXVariantGroup; 231 | children = ( 232 | 53BF3398210F0A4D00DB23DD /* Base */, 233 | ); 234 | name = LaunchScreen.storyboard; 235 | sourceTree = ""; 236 | }; 237 | /* End PBXVariantGroup section */ 238 | 239 | /* Begin XCBuildConfiguration section */ 240 | 53BF339D210F0A4D00DB23DD /* Debug */ = { 241 | isa = XCBuildConfiguration; 242 | buildSettings = { 243 | ALWAYS_SEARCH_USER_PATHS = NO; 244 | CLANG_ANALYZER_NONNULL = YES; 245 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 247 | CLANG_CXX_LIBRARY = "libc++"; 248 | CLANG_ENABLE_MODULES = YES; 249 | CLANG_ENABLE_OBJC_ARC = YES; 250 | CLANG_ENABLE_OBJC_WEAK = YES; 251 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 252 | CLANG_WARN_BOOL_CONVERSION = YES; 253 | CLANG_WARN_COMMA = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 257 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 258 | CLANG_WARN_EMPTY_BODY = YES; 259 | CLANG_WARN_ENUM_CONVERSION = YES; 260 | CLANG_WARN_INFINITE_RECURSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 267 | CLANG_WARN_STRICT_PROTOTYPES = YES; 268 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 269 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 270 | CLANG_WARN_UNREACHABLE_CODE = YES; 271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 272 | CODE_SIGN_IDENTITY = "iPhone Developer"; 273 | COPY_PHASE_STRIP = NO; 274 | DEBUG_INFORMATION_FORMAT = dwarf; 275 | ENABLE_STRICT_OBJC_MSGSEND = YES; 276 | ENABLE_TESTABILITY = YES; 277 | GCC_C_LANGUAGE_STANDARD = gnu11; 278 | GCC_DYNAMIC_NO_PIC = NO; 279 | GCC_NO_COMMON_BLOCKS = YES; 280 | GCC_OPTIMIZATION_LEVEL = 0; 281 | GCC_PREPROCESSOR_DEFINITIONS = ( 282 | "DEBUG=1", 283 | "$(inherited)", 284 | ); 285 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 286 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 287 | GCC_WARN_UNDECLARED_SELECTOR = YES; 288 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 289 | GCC_WARN_UNUSED_FUNCTION = YES; 290 | GCC_WARN_UNUSED_VARIABLE = YES; 291 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 292 | MTL_ENABLE_DEBUG_INFO = YES; 293 | ONLY_ACTIVE_ARCH = YES; 294 | SDKROOT = iphoneos; 295 | }; 296 | name = Debug; 297 | }; 298 | 53BF339E210F0A4D00DB23DD /* Release */ = { 299 | isa = XCBuildConfiguration; 300 | buildSettings = { 301 | ALWAYS_SEARCH_USER_PATHS = NO; 302 | CLANG_ANALYZER_NONNULL = YES; 303 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 304 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 305 | CLANG_CXX_LIBRARY = "libc++"; 306 | CLANG_ENABLE_MODULES = YES; 307 | CLANG_ENABLE_OBJC_ARC = YES; 308 | CLANG_ENABLE_OBJC_WEAK = YES; 309 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 310 | CLANG_WARN_BOOL_CONVERSION = YES; 311 | CLANG_WARN_COMMA = YES; 312 | CLANG_WARN_CONSTANT_CONVERSION = YES; 313 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 314 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 315 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 316 | CLANG_WARN_EMPTY_BODY = YES; 317 | CLANG_WARN_ENUM_CONVERSION = YES; 318 | CLANG_WARN_INFINITE_RECURSION = YES; 319 | CLANG_WARN_INT_CONVERSION = YES; 320 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 322 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 323 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 324 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 325 | CLANG_WARN_STRICT_PROTOTYPES = YES; 326 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 327 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | CODE_SIGN_IDENTITY = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 333 | ENABLE_NS_ASSERTIONS = NO; 334 | ENABLE_STRICT_OBJC_MSGSEND = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu11; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | IPHONEOS_DEPLOYMENT_TARGET = 11.3; 344 | MTL_ENABLE_DEBUG_INFO = NO; 345 | SDKROOT = iphoneos; 346 | VALIDATE_PRODUCT = YES; 347 | }; 348 | name = Release; 349 | }; 350 | 53BF33A0210F0A4D00DB23DD /* Debug */ = { 351 | isa = XCBuildConfiguration; 352 | buildSettings = { 353 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 354 | CODE_SIGN_STYLE = Automatic; 355 | DEVELOPMENT_TEAM = DTCHC66PTA; 356 | INFOPLIST_FILE = ShortMediaCacheDemo/Info.plist; 357 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 358 | LD_RUNPATH_SEARCH_PATHS = ( 359 | "$(inherited)", 360 | "@executable_path/Frameworks", 361 | ); 362 | PRODUCT_BUNDLE_IDENTIFIER = com.DandJ.ShortMediaCacheDemo; 363 | PRODUCT_NAME = "$(TARGET_NAME)"; 364 | TARGETED_DEVICE_FAMILY = "1,2"; 365 | }; 366 | name = Debug; 367 | }; 368 | 53BF33A1210F0A4D00DB23DD /* Release */ = { 369 | isa = XCBuildConfiguration; 370 | buildSettings = { 371 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 372 | CODE_SIGN_STYLE = Automatic; 373 | DEVELOPMENT_TEAM = DTCHC66PTA; 374 | INFOPLIST_FILE = ShortMediaCacheDemo/Info.plist; 375 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 376 | LD_RUNPATH_SEARCH_PATHS = ( 377 | "$(inherited)", 378 | "@executable_path/Frameworks", 379 | ); 380 | PRODUCT_BUNDLE_IDENTIFIER = com.DandJ.ShortMediaCacheDemo; 381 | PRODUCT_NAME = "$(TARGET_NAME)"; 382 | TARGETED_DEVICE_FAMILY = "1,2"; 383 | }; 384 | name = Release; 385 | }; 386 | /* End XCBuildConfiguration section */ 387 | 388 | /* Begin XCConfigurationList section */ 389 | 53BF3384210F0A4B00DB23DD /* Build configuration list for PBXProject "ShortMediaCacheDemo" */ = { 390 | isa = XCConfigurationList; 391 | buildConfigurations = ( 392 | 53BF339D210F0A4D00DB23DD /* Debug */, 393 | 53BF339E210F0A4D00DB23DD /* Release */, 394 | ); 395 | defaultConfigurationIsVisible = 0; 396 | defaultConfigurationName = Release; 397 | }; 398 | 53BF339F210F0A4D00DB23DD /* Build configuration list for PBXNativeTarget "ShortMediaCacheDemo" */ = { 399 | isa = XCConfigurationList; 400 | buildConfigurations = ( 401 | 53BF33A0210F0A4D00DB23DD /* Debug */, 402 | 53BF33A1210F0A4D00DB23DD /* Release */, 403 | ); 404 | defaultConfigurationIsVisible = 0; 405 | defaultConfigurationName = Release; 406 | }; 407 | /* End XCConfigurationList section */ 408 | }; 409 | rootObject = 53BF3381210F0A4B00DB23DD /* Project object */; 410 | } 411 | --------------------------------------------------------------------------------