├── .swift-version ├── Docs ├── logo.png ├── classes.jpg └── iconcache.jpg ├── Examples ├── Default-568h@2x.png ├── FlyImageView │ ├── ProgressImageView.h │ ├── Cells │ │ ├── FlyImageTableViewCell.h │ │ ├── SDWebImageTableViewCell.h │ │ ├── TriditionTableViewCell.h │ │ ├── FlyImageLayerTableViewCell.h │ │ ├── FlyImageIconLayerTableViewCell.h │ │ ├── FlyImageIconViewTableViewCell.h │ │ ├── BaseTableViewCell.h │ │ ├── FlyImageLayerTableViewCell.m │ │ ├── SDWebImageTableViewCell.m │ │ ├── FlyImageIconViewTableViewCell.m │ │ ├── FlyImageIconLayerTableViewCell.m │ │ ├── FlyImageTableViewCell.m │ │ ├── BaseTableViewCell.m │ │ └── TriditionTableViewCell.m │ ├── AppDelegate.h │ ├── main.m │ ├── RootViewController.h │ ├── ProgressImageView.m │ ├── Info.plist │ ├── Launch Screen.storyboard │ ├── AppDelegate.m │ └── RootViewController.m ├── SingleView │ ├── SingleViewController.h │ ├── AppDelegate.h │ ├── main.m │ ├── Info.plist │ ├── AppDelegate.m │ └── SingleViewController.m ├── FlyImageView.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ ├── WebP.xcscheme │ │ ├── SingleView.xcscheme │ │ ├── FlyImageView.xcscheme │ │ └── FlyImageIconView.xcscheme ├── WebP │ ├── AppDelegate.h │ ├── main.m │ ├── Info.plist │ └── AppDelegate.m ├── FlyImageIconView │ ├── AppDelegate.h │ ├── main.m │ ├── Info.plist │ └── AppDelegate.m └── Podfile ├── Podfile ├── FlyImage.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── FlyImageTests.xcscheme │ └── FlyImage.xcscheme ├── FlyImage ├── UI │ ├── CALayer+FlyImageCache.h │ ├── UIImageView+FlyImageCache.h │ ├── CALayer+FlyImageIconCache.h │ ├── UIImageView+FlyImageIconCache.h │ ├── FlyImageIconCacheUIProtocol.h │ ├── FlyImageIconRenderer.h │ ├── FlyImageCacheUIProtocol.h │ ├── FlyImageRenderer.h │ ├── UIImageView+FlyImageCache.m │ ├── CALayer+FlyImageCache.m │ ├── UIImageView+FlyImageIconCache.m │ ├── CALayer+FlyImageIconCache.m │ ├── FlyImageIconRenderer.m │ └── FlyImageRenderer.m ├── Info.plist ├── FlyImage.h └── Core │ ├── FlyImageRetrieveOperation.h │ ├── FlyImageEncoder.h │ ├── FlyImageRetrieveOperation.m │ ├── FlyImageDataFile.h │ ├── FlyImageDecoder.h │ ├── FlyImageCacheProtocol.h │ ├── FlyImageIconCache.h │ ├── FlyImageDataFileManager.h │ ├── FlyImageUtils.h │ ├── FlyImageEncoder.m │ ├── FlyImageCache.h │ ├── FlyImageDownloader.h │ ├── FlyImageDataFile.m │ ├── FlyImageUtils.m │ ├── FlyImageDataFileManager.m │ └── FlyImageDecoder.m ├── .gitignore ├── FlyImageTests ├── Info.plist ├── FlyImageDataFileManagerTests.m ├── FlyImageDataFileTests.m ├── FlyImageCacheTests.m ├── FlyImageDownloadManagerTests.m └── FlyImageIconCacheTests.m ├── LICENSE ├── .travis.yml ├── FlyImage.podspec └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 2.3 2 | -------------------------------------------------------------------------------- /Docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/northwind/FlyImage/HEAD/Docs/logo.png -------------------------------------------------------------------------------- /Docs/classes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/northwind/FlyImage/HEAD/Docs/classes.jpg -------------------------------------------------------------------------------- /Docs/iconcache.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/northwind/FlyImage/HEAD/Docs/iconcache.jpg -------------------------------------------------------------------------------- /Examples/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/northwind/FlyImage/HEAD/Examples/Default-568h@2x.png -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '8.0' 2 | 3 | # ignore all warnings from all pods 4 | inhibit_all_warnings! 5 | 6 | target "FlyImage" do 7 | pod 'AFNetworking', '~> 3.1' 8 | pod 'libwebp', '0.6' 9 | end 10 | -------------------------------------------------------------------------------- /Examples/FlyImageView/ProgressImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressImageView.h 3 | // FlyImageView 4 | // 5 | // Created by Ye Tong on 8/12/16. 6 | // Copyright © 2016 Augmn. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ProgressImageView : UIImageView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /FlyImage.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/SingleView/SingleViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // SingleView 4 | // 5 | // Created by Ye Tong on 5/9/16. 6 | // Copyright © 2016 Augmn. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SingleViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Examples/FlyImageView.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/FlyImageTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageTableViewCell.h 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/14/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "BaseTableViewCell.h" 10 | 11 | @interface FlyImageTableViewCell : BaseTableViewCell 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/SDWebImageTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDWebImageTableViewCell.h 3 | // Demo 4 | // 5 | // Created by Ye Tong on 4/18/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "BaseTableViewCell.h" 10 | 11 | @interface SDWebImageTableViewCell : BaseTableViewCell 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/TriditionTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TriditionTableViewCell.h 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/14/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "BaseTableViewCell.h" 10 | 11 | @interface TriditionTableViewCell : BaseTableViewCell 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/FlyImageLayerTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageLayerTableViewCell.h 3 | // Demo 4 | // 5 | // Created by Ye Tong on 4/18/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "BaseTableViewCell.h" 10 | 11 | @interface FlyImageLayerTableViewCell : BaseTableViewCell 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /FlyImage/UI/CALayer+FlyImageCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageLayer.h 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/17/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageCacheUIProtocol.h" 11 | 12 | @interface CALayer (FlyImageCache) 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/FlyImageIconLayerTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageIconLayerTableViewCell.h 3 | // FlyImageView 4 | // 5 | // Created by Ye Tong on 4/27/16. 6 | // Copyright © 2016 Augmn. All rights reserved. 7 | // 8 | 9 | #import "BaseTableViewCell.h" 10 | 11 | @interface FlyImageIconLayerTableViewCell : BaseTableViewCell 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/FlyImageIconViewTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageIconViewTableViewCell.h 3 | // FlyImageView 4 | // 5 | // Created by Ye Tong on 5/3/16. 6 | // Copyright © 2016 Augmn. All rights reserved. 7 | // 8 | 9 | #import "BaseTableViewCell.h" 10 | 11 | @interface FlyImageIconViewTableViewCell : BaseTableViewCell 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Examples/WebP/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // WebP 4 | // 5 | // Created by Ye Tong on 5/3/16. 6 | // Copyright © 2016 Augmn. 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 | -------------------------------------------------------------------------------- /FlyImage/UI/UIImageView+FlyImageCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+FlyImageCache.h 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/17/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageCacheUIProtocol.h" 11 | 12 | @interface UIImageView (FlyImageCache) 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Examples/SingleView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SingleView 4 | // 5 | // Created by Ye Tong on 5/9/16. 6 | // Copyright © 2016 Augmn. 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 | -------------------------------------------------------------------------------- /Examples/FlyImageView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // FlyImageView 4 | // 5 | // Created by Ye Tong on 4/18/16. 6 | // Copyright © 2016 Augmn. 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 | -------------------------------------------------------------------------------- /FlyImage/UI/CALayer+FlyImageIconCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+FlyImageIconCache.h 3 | // FlyImage 4 | // 5 | // Created by Ye Tong on 4/27/16. 6 | // Copyright © 2016 Ye Tong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageIconCacheUIProtocol.h" 11 | 12 | @interface CALayer (FlyImageIconCache) 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Examples/FlyImageIconView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // FlyImageIconView 4 | // 5 | // Created by Ye Tong on 4/27/16. 6 | // Copyright © 2016 Augmn. 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 | -------------------------------------------------------------------------------- /FlyImage/UI/UIImageView+FlyImageIconCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+FlyImageIconCache.h 3 | // FlyImage 4 | // 5 | // Created by Ye Tong on 5/3/16. 6 | // Copyright © 2016 Ye Tong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageIconCacheUIProtocol.h" 11 | 12 | @interface UIImageView (FlyImageIconCache) 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Examples/WebP/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // WebP 4 | // 5 | // Created by Ye Tong on 5/3/16. 6 | // Copyright © 2016 Augmn. 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 | -------------------------------------------------------------------------------- /Examples/SingleView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SingleView 4 | // 5 | // Created by Ye Tong on 5/9/16. 6 | // Copyright © 2016 Augmn. 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 | -------------------------------------------------------------------------------- /Examples/FlyImageView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // FlyImageView 4 | // 5 | // Created by Ye Tong on 4/18/16. 6 | // Copyright © 2016 Augmn. 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 | -------------------------------------------------------------------------------- /Examples/FlyImageIconView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // FlyImageIconView 4 | // 5 | // Created by Ye Tong on 4/27/16. 6 | // Copyright © 2016 Augmn. 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 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/BaseTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTableViewCell.h 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/15/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BaseTableViewCell : UITableViewCell 12 | 13 | - (void)displayImageWithPhotos:(NSArray *)photos; 14 | 15 | - (id)imageViewWithFrame:(CGRect)frame; 16 | 17 | - (void)renderImageView:(id)imageView url:(NSURL *)url; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Examples/FlyImageView/RootViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RootViewController.h 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/24/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RootViewController : UIViewController 12 | 13 | @property (nonatomic, strong) NSArray *cells; 14 | @property (nonatomic, assign) CGFloat heightOfCell; 15 | @property (nonatomic, assign) NSInteger cellsPerRow; 16 | @property (nonatomic, assign) NSInteger activeIndex; 17 | @property (nonatomic, copy) NSString *suffix; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Examples/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '8.0' 2 | 3 | # ignore all warnings from all pods 4 | inhibit_all_warnings! 5 | 6 | target 'FlyImageView' do 7 | pod 'SDWebImage', '~> 3.7' 8 | pod 'FlyImage', :path => '../FlyImage.podspec' 9 | end 10 | 11 | target 'FlyImageIconView' do 12 | pod 'SDWebImage', '~> 3.7' 13 | pod 'FlyImage', :path => '../FlyImage.podspec' 14 | end 15 | 16 | target 'WebP' do 17 | pod 'SDWebImage/WebP', '~> 3.7' 18 | pod 'FlyImage/WebP', :path => '../FlyImage.podspec' 19 | end 20 | 21 | target 'SingleView' do 22 | pod 'FlyImage', :path => '../FlyImage.podspec' 23 | end 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | project.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | *.xccheckout 16 | *.moved-aside 17 | DerivedData 18 | *.hmap 19 | *.ipa 20 | *.xcuserstate 21 | *.xcscmblueprint 22 | 23 | # CocoaPods 24 | # 25 | # We recommend against adding the Pods directory to your .gitignore. However 26 | # you should judge for yourself, the pros and cons are mentioned at: 27 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 28 | # 29 | Pods/ 30 | Podfile.lock 31 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/FlyImageLayerTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageLayerTableViewCell.m 3 | // Demo 4 | // 5 | // Created by Ye Tong on 4/18/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "FlyImageLayerTableViewCell.h" 10 | #import "FlyImage.h" 11 | 12 | @implementation FlyImageLayerTableViewCell 13 | 14 | - (id)imageViewWithFrame:(CGRect)frame { 15 | CALayer *imageView = [[CALayer alloc] init]; 16 | imageView.frame = frame; 17 | imageView.contentsGravity = kCAGravityResizeAspectFill; 18 | imageView.cornerRadius = 10; 19 | [self.layer addSublayer:imageView]; 20 | 21 | return imageView; 22 | } 23 | 24 | - (void)renderImageView:(id)imageView url:(NSURL *)url { 25 | ((CALayer *)imageView).imageURL = url; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/SDWebImageTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDWebImageTableViewCell.m 3 | // Demo 4 | // 5 | // Created by Ye Tong on 4/18/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "SDWebImageTableViewCell.h" 10 | #import "UIImageView+WebCache.h" 11 | 12 | @implementation SDWebImageTableViewCell 13 | 14 | - (id)imageViewWithFrame:(CGRect)frame { 15 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame]; 16 | imageView.contentMode = UIViewContentModeScaleAspectFill; 17 | imageView.layer.cornerRadius = 10; 18 | [self addSubview:imageView]; 19 | 20 | return imageView; 21 | } 22 | 23 | - (void)renderImageView:(id)imageView url:(NSURL *)url { 24 | [((UIImageView *)imageView) sd_setImageWithURL:url]; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/FlyImageIconViewTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageIconViewTableViewCell.m 3 | // FlyImageView 4 | // 5 | // Created by Ye Tong on 5/3/16. 6 | // Copyright © 2016 Augmn. All rights reserved. 7 | // 8 | 9 | #import "FlyImageIconViewTableViewCell.h" 10 | #import "FlyImage.h" 11 | 12 | @implementation FlyImageIconViewTableViewCell 13 | 14 | - (id)imageViewWithFrame:(CGRect)frame { 15 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame]; 16 | imageView.contentMode = UIViewContentModeScaleAspectFill; 17 | imageView.layer.cornerRadius = 10; 18 | [self addSubview:imageView]; 19 | 20 | return imageView; 21 | } 22 | 23 | - (void)renderImageView:(id)imageView url:(NSURL *)url { 24 | ((UIImageView *)imageView).iconURL = url; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/FlyImageIconLayerTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageIconLayerTableViewCell.m 3 | // FlyImageView 4 | // 5 | // Created by Ye Tong on 4/27/16. 6 | // Copyright © 2016 Augmn. All rights reserved. 7 | // 8 | 9 | #import "FlyImageIconLayerTableViewCell.h" 10 | #import "FlyImage.h" 11 | 12 | @implementation FlyImageIconLayerTableViewCell 13 | 14 | - (id)imageViewWithFrame:(CGRect)frame { 15 | CALayer *imageLayer = [[CALayer alloc] init]; 16 | imageLayer.frame = frame; 17 | imageLayer.contentsGravity = kCAGravityResizeAspectFill; 18 | imageLayer.cornerRadius = 10; 19 | [self.layer addSublayer:imageLayer]; 20 | 21 | return imageLayer; 22 | } 23 | 24 | - (void)renderImageView:(id)imageView url:(NSURL *)url { 25 | ((CALayer *)imageView).iconURL = url; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/FlyImageTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageTableViewCell.m 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/14/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "FlyImageTableViewCell.h" 10 | #import "FlyImage.h" 11 | #import "ProgressImageView.h" 12 | 13 | @implementation FlyImageTableViewCell 14 | 15 | - (id)imageViewWithFrame:(CGRect)frame { 16 | ProgressImageView *imageView = [[ProgressImageView alloc] initWithFrame:frame]; 17 | imageView.contentMode = UIViewContentModeScaleAspectFill; 18 | imageView.layer.cornerRadius = 10; 19 | [self addSubview:imageView]; 20 | 21 | return imageView; 22 | } 23 | 24 | - (void)renderImageView:(id)imageView url:(NSURL *)url { 25 | [imageView setImageURL:url]; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /FlyImageTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /FlyImage/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.flyimage.image 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /FlyImage/FlyImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImage.h 3 | // FlyImage 4 | // 5 | // Created by Ye Tong on 4/18/16. 6 | // Copyright © 2016 Ye Tong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for FlyImage. 12 | FOUNDATION_EXPORT double FlyImageVersionNumber; 13 | 14 | //! Project version string for FlyImage. 15 | FOUNDATION_EXPORT const unsigned char FlyImageVersionString[]; 16 | 17 | #import 18 | 19 | #import 20 | #import 21 | #import 22 | 23 | #import 24 | #import 25 | #import 26 | 27 | #import 28 | #import 29 | #import 30 | -------------------------------------------------------------------------------- /Examples/FlyImageView/ProgressImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressImageView.m 3 | // FlyImageView 4 | // 5 | // Created by Ye Tong on 8/12/16. 6 | // Copyright © 2016 Augmn. All rights reserved. 7 | // 8 | 9 | #import "ProgressImageView.h" 10 | #import "FlyImage.h" 11 | 12 | @implementation ProgressImageView 13 | 14 | - (instancetype)initWithFrame:(CGRect)frame { 15 | if ( self = [super initWithFrame:frame] ) { 16 | [self addObserver:self forKeyPath:@"downloadingPercentage" options:NSKeyValueObservingOptionNew context:nil]; 17 | } 18 | 19 | return self; 20 | } 21 | 22 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 23 | if ([keyPath isEqualToString:@"downloadingPercentage"]) { 24 | NSLog(@"downloadingURL : %@", self.downloadingURL ); 25 | NSLog(@"downloadingPercentage : %f", self.downloadingPercentage ); 26 | } 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageRetrieveOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageRetrieveOperation.h 3 | // FlyImage 4 | // 5 | // Created by Ye Tong on 8/11/16. 6 | // Copyright © 2016 Ye Tong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageCacheProtocol.h" 11 | 12 | typedef UIImage* (^RetrieveOperationBlock)(void); 13 | 14 | /** 15 | * Internal class. In charge of retrieving and sending UIImage. 16 | */ 17 | @interface FlyImageRetrieveOperation : NSOperation 18 | 19 | /** 20 | * When the operation start running, the block will be executed, 21 | * and require an uncompressed UIImage. 22 | */ 23 | - (instancetype)initWithRetrieveBlock:(RetrieveOperationBlock)block; 24 | 25 | /** 26 | * Allow to add multiple blocks 27 | * 28 | * @param block 29 | */ 30 | - (void)addBlock:(FlyImageCacheRetrieveBlock)block; 31 | 32 | /** 33 | * Callback with result image, which can be nil. 34 | */ 35 | - (void)executeWithImage:(UIImage*)image; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageEncoder.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageEncoder.h 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/22/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void (^FFlyImageEncoderDrawingBlock)(CGContextRef context, CGRect contextBounds); 12 | 13 | /** 14 | * Convert an image to bitmap format. 15 | */ 16 | @interface FlyImageEncoder : NSObject 17 | 18 | /** 19 | * Draw an image, and save the bitmap data into memory buffer. 20 | * 21 | * @param size image size 22 | * @param bytes memory buffer 23 | * @param drawingBlock drawing function 24 | */ 25 | - (void)encodeWithImageSize:(CGSize)size 26 | bytes:(void*)bytes 27 | drawingBlock:(FFlyImageEncoderDrawingBlock)drawingBlock; 28 | 29 | /** 30 | * Calculate buffer size with image size. 31 | * 32 | * @param size image size 33 | */ 34 | + (size_t)dataLengthWithImageSize:(CGSize)size; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /FlyImage/UI/FlyImageIconCacheUIProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageIconCacheUIProtocol.h 3 | // FlyImage 4 | // 5 | // Created by Ye Tong on 8/9/16. 6 | // Copyright © 2016 Ye Tong. All rights reserved. 7 | // 8 | #import 9 | 10 | /** 11 | * Draw an image in the context with specific bounds. 12 | */ 13 | typedef void (^FlyImageIconDrawingBlock)(UIImage* image, CGContextRef context, CGRect contextBounds); 14 | 15 | @protocol FlyImageIconCacheUIProtocol 16 | 17 | /** 18 | * Convenient method of setPlaceHolderImageName:iconURL. 19 | */ 20 | - (void)setIconURL:(NSURL*)iconURL; 21 | 22 | /** 23 | * Download an icon, and save it using [FlyImageIconCache shareInstance]. 24 | */ 25 | - (void)setPlaceHolderImageName:(NSString*)imageName 26 | iconURL:(NSURL*)iconURL; 27 | 28 | /** 29 | * Set a customize drawing block. If not, it will use the default drawing method. 30 | */ 31 | - (void)setIconDrawingBlock:(FlyImageIconDrawingBlock)block; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /FlyImage/UI/FlyImageIconRenderer.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageIconRenderer.h 3 | // FlyImage 4 | // 5 | // Created by Ye Tong on 4/27/16. 6 | // Copyright © 2016 Ye Tong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageIconCache.h" 11 | 12 | @class FlyImageIconRenderer; 13 | @protocol FlyImageIconRendererDelegate 14 | 15 | - (void)flyImageIconRenderer:(FlyImageIconRenderer*)render willRenderImage:(UIImage*)image; 16 | 17 | - (void)flyImageIconRenderer:(FlyImageIconRenderer*)render 18 | drawImage:(UIImage*)image 19 | context:(CGContextRef)context 20 | bounds:(CGRect)contextBounds; 21 | 22 | @end 23 | 24 | /** 25 | * Internal class to download, draw, and retrieve icons. 26 | */ 27 | @interface FlyImageIconRenderer : NSObject 28 | 29 | @property (nonatomic, weak) id delegate; 30 | 31 | - (void)setPlaceHolderImageName:(NSString*)imageName 32 | iconURL:(NSURL*)iconURL 33 | drawSize:(CGSize)drawSize; 34 | 35 | @end -------------------------------------------------------------------------------- /FlyImage/UI/FlyImageCacheUIProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageCacheUIProtocol.h 3 | // FlyImage 4 | // 5 | // Created by Ye Tong on 8/9/16. 6 | // Copyright © 2016 Ye Tong. All rights reserved. 7 | // 8 | #import 9 | 10 | @protocol FlyImageCacheUIProtocol 11 | 12 | @property (nonatomic, strong) NSURL* downloadingURL; // may be nil/thumbnailURL/originalURL. 13 | @property (nonatomic, assign) float downloadingPercentage; // 0-1, downloading progress per downloading URL. 14 | 15 | /** 16 | * Convenient method of setPlaceHolderImageName:thumbnailURL:originalURL 17 | * 18 | * @param url originalURL 19 | */ 20 | - (void)setImageURL:(NSURL*)url; 21 | 22 | /** 23 | * Download images and render them with the below order: 24 | * 1. PlaceHolder 25 | * 2. Thumbnail Image 26 | * 3. Original Image 27 | * 28 | * These images will be saved into [FlyImageCache shareInstance] 29 | */ 30 | - (void)setPlaceHolderImageName:(NSString*)imageName 31 | thumbnailURL:(NSURL*)thumbnailURL 32 | originalURL:(NSURL*)originalURL; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Olivier Poitrey rs@dailymotion.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /FlyImage/UI/FlyImageRenderer.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageRenderer.h 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/11/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FlyImageRenderer; 12 | @protocol FlyImageRendererDelegate 13 | 14 | /** 15 | * Callback before download image. 16 | */ 17 | - (void)flyImageRenderer:(FlyImageRenderer*)render willRenderImage:(UIImage*)image; 18 | 19 | @optional 20 | /** 21 | * Callback after download image. 22 | * 23 | * @param render 24 | * @param url 25 | * @param progress 0...1 26 | */ 27 | - (void)flyImageRenderer:(FlyImageRenderer*)render didDownloadImageURL:(NSURL*)url progress:(float)progress; 28 | 29 | @end 30 | 31 | /** 32 | * Internal class to download, draw, and retrieve images. 33 | */ 34 | @interface FlyImageRenderer : NSObject 35 | 36 | @property (nonatomic, weak) id delegate; 37 | 38 | - (void)setPlaceHolderImageName:(NSString*)imageName 39 | thumbnailURL:(NSURL*)thumbnailURL 40 | originalURL:(NSURL*)originalURL 41 | drawSize:(CGSize)drawSize 42 | contentsGravity:(NSString* const)contentsGravity 43 | cornerRadius:(CGFloat)cornerRadius; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: objective-c 3 | 4 | cache: 5 | - bundler 6 | - cocoapods 7 | 8 | osx_image: xcode7.1 9 | 10 | before_install: 11 | 12 | - export LANG=en_US.UTF-8 13 | - env 14 | - locale 15 | - gem install cocoapods --no-rdoc --no-ri --no-document --quiet 16 | - pod --version 17 | - pod setup --silent > /dev/null 18 | - pod repo update --silent 19 | 20 | script: 21 | 22 | - pod lib lint --allow-warnings 23 | - xctool -workspace FlyImage.xcworkspace -scheme 'FlyImage' -sdk iphonesimulator -arch i386 build 24 | 25 | - pod install --project-directory=Examples 26 | - xctool -workspace './Examples/FlyImageView.xcworkspace' -scheme 'FlyImageView' -sdk iphonesimulator -arch i386 build 27 | - xctool -workspace './Examples/FlyImageView.xcworkspace' -scheme 'FlyImageIconView' -sdk iphonesimulator -arch i386 build 28 | - xctool -workspace './Examples/FlyImageView.xcworkspace' -scheme 'WebP' -sdk iphonesimulator -arch i386 build 29 | - xctool -workspace './Examples/FlyImageView.xcworkspace' -scheme 'SingleView' -sdk iphonesimulator -arch i386 build 30 | 31 | - xcodebuild -workspace FlyImage.xcworkspace -scheme 'FlyImageTests' -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6,OS=latest' test 32 | 33 | 34 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageRetrieveOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageRetrieveOperation.m 3 | // FlyImage 4 | // 5 | // Created by Ye Tong on 8/11/16. 6 | // Copyright © 2016 Ye Tong. All rights reserved. 7 | // 8 | 9 | #import "FlyImageRetrieveOperation.h" 10 | 11 | @implementation FlyImageRetrieveOperation { 12 | NSMutableArray* _blocks; 13 | RetrieveOperationBlock _retrieveBlock; 14 | } 15 | 16 | - (instancetype)initWithRetrieveBlock:(RetrieveOperationBlock)block 17 | { 18 | if (self = [self init]) { 19 | _retrieveBlock = block; 20 | } 21 | return self; 22 | } 23 | 24 | - (void)addBlock:(FlyImageCacheRetrieveBlock)block 25 | { 26 | if (_blocks == nil) { 27 | _blocks = [NSMutableArray new]; 28 | } 29 | 30 | [_blocks addObject:block]; 31 | } 32 | 33 | - (void)executeWithImage:(UIImage*)image 34 | { 35 | for (FlyImageCacheRetrieveBlock block in _blocks) { 36 | block(self.name, image); 37 | } 38 | [_blocks removeAllObjects]; 39 | } 40 | 41 | - (void)main 42 | { 43 | if (self.isCancelled) { 44 | return; 45 | } 46 | 47 | UIImage* image = _retrieveBlock(); 48 | [self executeWithImage:image]; 49 | } 50 | 51 | - (void)cancel 52 | { 53 | if (self.isFinished) 54 | return; 55 | [super cancel]; 56 | 57 | [self executeWithImage:nil]; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageDataFile.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageDataFile.h 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/18/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | * Wrapper of data file, map the disk file to the memory. 13 | * 14 | * Only support `append` operation, we can move the pointer to replace the data. 15 | * 16 | */ 17 | @interface FlyImageDataFile : NSObject 18 | 19 | @property (nonatomic, assign, readonly) void* address; 20 | @property (nonatomic, assign, readonly) off_t fileLength; // total length of the file. 21 | @property (nonatomic, assign, readonly) off_t pointer; // append the data after the pointer. Default is 0. 22 | @property (nonatomic, assign) size_t step; // Change the step value to increase the file length. Deafult is 1 byte. 23 | 24 | - (instancetype)initWithPath:(NSString*)path; 25 | 26 | - (BOOL)open; 27 | 28 | - (void)close; 29 | 30 | /** 31 | * Check the file length, if it is not big enough, then increase the file length with step. 32 | * 33 | * @param offset start position 34 | * @param length data length 35 | * 36 | * @return success or failed 37 | */ 38 | - (BOOL)prepareAppendDataWithOffset:(size_t)offset length:(size_t)length; 39 | 40 | /** 41 | * Append the data after pointer. 42 | * 43 | * Must execute `prepareAppendDataWithOffset:length` first. 44 | * 45 | * @param offset start position 46 | * @param length data length 47 | * 48 | * @return success or failed 49 | */ 50 | - (BOOL)appendDataWithOffset:(size_t)offset length:(size_t)length; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/BaseTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTableViewCell.m 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/15/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "BaseTableViewCell.h" 10 | 11 | @implementation BaseTableViewCell { 12 | NSMutableArray *_imageViews; 13 | NSArray *_photos; 14 | } 15 | 16 | - (void)displayImageWithPhotos:(NSArray *)photos { 17 | 18 | if ( _imageViews == nil ) { 19 | NSInteger photoCount = [photos count]; 20 | _imageViews = [[NSMutableArray alloc] initWithCapacity:photoCount]; 21 | 22 | CGRect frame = self.frame; 23 | CGFloat itemWidth = floor(frame.size.width / photoCount); 24 | CGFloat padding = 2; 25 | for (int i=0; i 10 | #import "FlyImageUtils.h" 11 | 12 | /** 13 | * Decode image file. 14 | */ 15 | @interface FlyImageDecoder : NSObject 16 | 17 | /** 18 | * Convert memory buffer to icon. 19 | * 20 | * @param bytes memory file 21 | * @param offset offset position at the memory file 22 | * @param length size of memory buffer 23 | * @param drawSize render size 24 | */ 25 | - (UIImage*)iconImageWithBytes:(void*)bytes 26 | offset:(size_t)offset 27 | length:(size_t)length 28 | drawSize:(CGSize)drawSize; 29 | 30 | /** 31 | * Decode a single image file. 32 | * 33 | * @param file FlyImageDataFile instance 34 | * @param contentType only support PNG/JPEG 35 | * @param bytes address of the memory 36 | * @param length file size 37 | * @param drawSize drawing size, not image size 38 | * @param contentsGravity contentsGravity of the image 39 | * @param cornerRadius cornerRadius of the image 40 | */ 41 | - (UIImage*)imageWithFile:(void*)file 42 | contentType:(ImageContentType)contentType 43 | bytes:(void*)bytes 44 | length:(size_t)length 45 | drawSize:(CGSize)drawSize 46 | contentsGravity:(NSString* const)contentsGravity 47 | cornerRadius:(CGFloat)cornerRadius; 48 | 49 | #ifdef FLYIMAGE_WEBP 50 | - (UIImage*)imageWithWebPData:(NSData*)imageData hasAlpha:(BOOL*)hasAlpha; 51 | #endif 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageCacheProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageCacheProtocol.h 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/2/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #ifndef FlyImageCacheProtocol_h 10 | #define FlyImageCacheProtocol_h 11 | 12 | typedef void (^FlyImageCacheRetrieveBlock)(NSString* key, UIImage* image); 13 | 14 | /** 15 | * Common API for FlyImageCache and FlyImageIconCache. 16 | */ 17 | @protocol FlyImageCacheProtocol 18 | 19 | /** 20 | * Create an image cache with default meta path. 21 | */ 22 | + (instancetype)sharedInstance; 23 | 24 | /** 25 | * Create an image cache with a specific meta path. 26 | * 27 | * @param metaPath specific meta path, all the images will be saved into folder `metaPath/files` 28 | */ 29 | - (instancetype)initWithMetaPath:(NSString*)metaPath; 30 | 31 | /** 32 | * Get image from cache asynchronously. 33 | */ 34 | - (void)asyncGetImageWithKey:(NSString*)key 35 | completed:(FlyImageCacheRetrieveBlock)completed; 36 | 37 | /** 38 | * Cancel geting an image from cache if the image has not already got. 39 | */ 40 | - (void)cancelGetImageWithKey:(NSString*)key; 41 | 42 | /** 43 | * Check if image exists in cache synchronized. NO delay. 44 | */ 45 | - (BOOL)isImageExistWithKey:(NSString*)key; 46 | 47 | /** 48 | * Remove an image from cache. 49 | */ 50 | - (void)removeImageWithKey:(NSString*)key; 51 | 52 | /** 53 | * Change the old key with a new key 54 | */ 55 | - (void)changeImageKey:(NSString*)oldKey 56 | newKey:(NSString*)newKey; 57 | 58 | /** 59 | * Remove all the images from the cache. 60 | */ 61 | - (void)purge; 62 | 63 | @end 64 | 65 | #endif /* FlyImageCacheProtocol_h */ 66 | -------------------------------------------------------------------------------- /Examples/WebP/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | Launch Screen 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UIRequiresFullScreen 37 | 38 | UIStatusBarHidden 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | Launch Screen 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UIRequiresFullScreen 37 | 38 | UIStatusBarHidden 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Examples/SingleView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | Launch Screen 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UIRequiresFullScreen 37 | 38 | UIStatusBarHidden 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageIconCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageIconCache.h 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/2/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageCacheProtocol.h" 11 | 12 | /** 13 | * Draw the icon in a background thread. 14 | * 15 | * @param context drawing context 16 | * @param contextBounds image size 17 | */ 18 | typedef void (^FlyImageCacheDrawingBlock)(CGContextRef context, CGRect contextBounds); 19 | 20 | /** 21 | * FlyImageIconCache will draw icons into one big file, 22 | * and will get great performace when try to render multiple icons in one screen. 23 | */ 24 | @interface FlyImageIconCache : NSObject 25 | 26 | /** 27 | * Add an icon into the FlyImageIconCache. 28 | * 29 | * @param key unique key 30 | * @param size image size 31 | * @param drawingBlock 32 | * @param completed callback after add, can be nil 33 | */ 34 | - (void)addImageWithKey:(NSString*)key 35 | size:(CGSize)size 36 | drawingBlock:(FlyImageCacheDrawingBlock)drawingBlock 37 | completed:(FlyImageCacheRetrieveBlock)completed; 38 | 39 | /** 40 | * FlyImageIconCache not support remove an icon from the cache, but you can replace an icon with the same key. 41 | * But the new image must has the same size with the previous one. 42 | * 43 | * @param key unique key 44 | * @param drawingBlock 45 | * @param completed callback after replace, can be nil 46 | */ 47 | - (void)replaceImageWithKey:(NSString*)key 48 | drawingBlock:(FlyImageCacheDrawingBlock)drawingBlock 49 | completed:(FlyImageCacheRetrieveBlock)completed; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Launch Screen.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 | -------------------------------------------------------------------------------- /Examples/FlyImageIconView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | Launch Screen 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UIRequiresFullScreen 37 | 38 | UIStatusBarHidden 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /FlyImage.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "FlyImage" 3 | s.version = "1.1" 4 | s.summary = "Download, cache, render small images with UIImageView category" 5 | s.description = 'FlyImage takes the advantages of SDWebImage, FastImageCache and AFNetworking, ' \ 6 | 'is a simple and high performance image library.Features: ' \ 7 | 'High Performance, reduce memory operations while rendering, avoid Memory warning caused by image; ' \ 8 | 'Store and retrieve different size of small images in one memory file, smooth scrolling; ' \ 9 | 'Simple, support UIImageView, CALayer category; ' \ 10 | 'An asynchronous image downloader; ' \ 11 | 'Support WebP format; ' \ 12 | 'Support mmap to improve I/O performace;' 13 | 14 | s.homepage = "https://github.com/northwind/FlyImage" 15 | s.license = "MIT" 16 | s.author = { "norristong" => "norristong_x@qq.com" } 17 | 18 | s.platform = :ios, "8.0" 19 | s.source = { :git => 'https://github.com/northwind/FlyImage.git', :tag => s.version.to_s } 20 | s.source_files = "FlyImage", "FlyImage/**/*.{h,m}" 21 | 22 | s.frameworks = "ImageIO", 'UIKit' 23 | s.requires_arc = true 24 | s.dependency 'AFNetworking', '~> 3.1' 25 | 26 | s.default_subspec = 'Core' 27 | 28 | s.subspec 'Core' do |core| 29 | core.source_files = "FlyImage", 'FlyImage/**/*.{h,m}' 30 | end 31 | 32 | s.subspec 'WebP' do |webp| 33 | webp.xcconfig = { 34 | 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) FLYIMAGE_WEBP=1', 35 | 'USER_HEADER_SEARCH_PATHS' => '$(inherited) $(SRCROOT)/libwebp/src' 36 | } 37 | webp.watchos.xcconfig = { 38 | 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) FLYIMAGE_WEBP=1', 39 | 'USER_HEADER_SEARCH_PATHS' => '$(inherited) $(SRCROOT)/libwebp/src' 40 | } 41 | webp.dependency 'FlyImage/Core' 42 | webp.dependency 'libwebp' 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageDataFileManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageDataFileManager.h 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/22/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageDataFile.h" 11 | 12 | /** 13 | * Manager of FlyImageDataFile. In charge of creating and deleting data file. 14 | */ 15 | @interface FlyImageDataFileManager : NSObject 16 | 17 | @property (nonatomic, strong, readonly) NSString* folderPath; // folder saved data files. 18 | @property (nonatomic, assign, readonly) BOOL isDiskFull; // Default is NO. 19 | 20 | - (instancetype)initWithFolderPath:(NSString*)folderPath; 21 | 22 | /** 23 | * Create a `FlyImageDataFile` if it doesn't exist. 24 | */ 25 | - (FlyImageDataFile*)createFileWithName:(NSString*)name; 26 | 27 | /** 28 | * Add an exist file. 29 | */ 30 | - (void)addExistFileName:(NSString*)name; 31 | 32 | /** 33 | * Check the file whether exist or not, no delay. 34 | */ 35 | - (BOOL)isFileExistWithName:(NSString*)name; 36 | 37 | /** 38 | * Get a `FlyImageDataFile` if it exists. 39 | */ 40 | - (FlyImageDataFile*)retrieveFileWithName:(NSString*)name; 41 | 42 | /** 43 | * Remove data file 44 | */ 45 | - (void)removeFileWithName:(NSString*)name; 46 | 47 | /** 48 | * Create a `FlyImageDataFile` async. 49 | */ 50 | - (void)asyncCreateFileWithName:(NSString*)name completed:(void (^)(FlyImageDataFile* dataFile))completed; 51 | 52 | /** 53 | * Remove all the data files, except some special files. 54 | * 55 | * @param names except files' names 56 | * @param toSize expected size of the folder 57 | * @param completed callback 58 | */ 59 | - (void)purgeWithExceptions:(NSArray*)names 60 | toSize:(NSUInteger)toSize 61 | completed:(void (^)(NSUInteger fileCount, NSUInteger totalSize))completed; 62 | 63 | /** 64 | * Calculate the folder size. 65 | */ 66 | - (void)calculateSizeWithCompletionBlock:(void (^)(NSUInteger fileCount, NSUInteger totalSize))block; 67 | 68 | /** 69 | * Free space left in the system space. 70 | */ 71 | - (void)freeDiskSpaceWithCompletionBlock:(void (^)(NSUInteger freeSize))block; 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /FlyImage.xcworkspace/xcshareddata/xcschemes/FlyImageTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageUtils.h 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/18/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | #import 9 | 10 | typedef NS_ENUM(NSInteger, ImageContentType) { ImageContentTypeUnknown, 11 | ImageContentTypeJPEG, 12 | ImageContentTypePNG, 13 | ImageContentTypeWebP, 14 | ImageContentTypeGif, 15 | ImageContentTypeTiff }; 16 | 17 | @interface FlyImageUtils : NSObject 18 | 19 | + (NSString*)directoryPath; 20 | 21 | + (CGFloat)contentsScale; 22 | 23 | + (NSString*)clientVersion; 24 | 25 | /** 26 | * Memory page size, default is 4096 27 | */ 28 | + (int)pageSize; 29 | 30 | /** 31 | * Compute the content type for an image data 32 | * 33 | * @param data image data 34 | * 35 | */ 36 | + (ImageContentType)contentTypeForImageData:(NSData*)data; 37 | 38 | @end 39 | 40 | /** 41 | * Copy from FastImageCache. 42 | * 43 | * @param rect draw area 44 | * @param cornerRadius 45 | * 46 | */ 47 | CGMutablePathRef _FICDCreateRoundedRectPath(CGRect rect, CGFloat cornerRadius); 48 | 49 | /** 50 | * calculate drawing bounds with original image size, target size and contentsGravity of layer. 51 | * 52 | * @param imageSize 53 | * @param targetSize 54 | * @param contentsGravity layer's attribute 55 | */ 56 | CGRect _FlyImageCalcDrawBounds(CGSize imageSize, CGSize targetSize, NSString* const contentsGravity); 57 | 58 | #define FlyImageErrorLog(fmt, ...) NSLog((@"FlyImage Error: " fmt), ##__VA_ARGS__) 59 | 60 | #define dispatch_main_sync_safe(block) \ 61 | if ([NSThread isMainThread]) { \ 62 | block(); \ 63 | } else { \ 64 | dispatch_sync(dispatch_get_main_queue(), block); \ 65 | } 66 | 67 | #define dispatch_main_async_safe(block) \ 68 | if ([NSThread isMainThread]) { \ 69 | block(); \ 70 | } else { \ 71 | dispatch_async(dispatch_get_main_queue(), block); \ 72 | } 73 | -------------------------------------------------------------------------------- /Examples/SingleView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SingleView 4 | // 5 | // Created by Ye Tong on 5/9/16. 6 | // Copyright © 2016 Augmn. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "SingleViewController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 22 | self.window.backgroundColor = [UIColor blackColor]; 23 | 24 | SingleViewController *rootViewController = [[SingleViewController alloc] init]; 25 | self.window.rootViewController = rootViewController; 26 | [self.window makeKeyAndVisible]; 27 | 28 | return YES; 29 | } 30 | 31 | - (void)applicationWillResignActive:(UIApplication *)application { 32 | // 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. 33 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 34 | } 35 | 36 | - (void)applicationDidEnterBackground:(UIApplication *)application { 37 | // 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. 38 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 39 | } 40 | 41 | - (void)applicationWillEnterForeground:(UIApplication *)application { 42 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 43 | } 44 | 45 | - (void)applicationDidBecomeActive:(UIApplication *)application { 46 | // 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. 47 | } 48 | 49 | - (void)applicationWillTerminate:(UIApplication *)application { 50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageEncoder.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageEncoder.m 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/22/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "FlyImageEncoder.h" 10 | #import "FlyImageUtils.h" 11 | 12 | @implementation FlyImageEncoder 13 | 14 | static NSInteger __bytesPerPixel = 4; 15 | static NSInteger __bitsPerComponent = 8; 16 | static float __alignmentSize = 64; 17 | 18 | - (void)encodeWithImageSize:(CGSize)size 19 | bytes:(void*)bytes 20 | drawingBlock:(FFlyImageEncoderDrawingBlock)drawingBlock 21 | { 22 | 23 | CGFloat screenScale = [FlyImageUtils contentsScale]; 24 | CGSize pixelSize = CGSizeMake(size.width * screenScale, size.height * screenScale); 25 | 26 | // It calculates the bytes-per-row based on the __bitsPerComponent and width arguments. 27 | size_t bytesPerRow = ceil((pixelSize.width * __bytesPerPixel) / __alignmentSize) * __alignmentSize; 28 | 29 | CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; 30 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 31 | 32 | CGContextRef context = CGBitmapContextCreate(bytes, 33 | pixelSize.width, 34 | pixelSize.height, 35 | __bitsPerComponent, 36 | bytesPerRow, 37 | colorSpace, 38 | bitmapInfo); 39 | CGColorSpaceRelease(colorSpace); 40 | 41 | CGContextTranslateCTM(context, 0, pixelSize.height); 42 | CGContextScaleCTM(context, 1, -1); 43 | 44 | // Call drawing block to allow client to draw into the context 45 | CGRect contextBounds = CGRectZero; 46 | contextBounds.size = pixelSize; 47 | CGContextClearRect(context, contextBounds); 48 | 49 | drawingBlock(context, contextBounds); 50 | CGContextRelease(context); 51 | } 52 | 53 | + (size_t)dataLengthWithImageSize:(CGSize)size 54 | { 55 | 56 | CGFloat screenScale = [FlyImageUtils contentsScale]; 57 | CGSize pixelSize = CGSizeMake(size.width * screenScale, size.height * screenScale); 58 | 59 | size_t bytesPerRow = ceil((pixelSize.width * __bytesPerPixel) / __alignmentSize) * __alignmentSize; 60 | CGFloat imageLength = bytesPerRow * (NSInteger)pixelSize.height; 61 | 62 | int pageSize = [FlyImageUtils pageSize]; 63 | size_t bytesToAppend = ceil(imageLength / pageSize) * pageSize; 64 | 65 | return bytesToAppend; 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /FlyImage/UI/UIImageView+FlyImageCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+FlyImageCache.m 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/17/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "UIImageView+FlyImageCache.h" 10 | #import "FlyImageRenderer.h" 11 | #import "objc/runtime.h" 12 | 13 | @interface UIImageView (__FlyImageCache) 14 | @end 15 | 16 | @implementation UIImageView (FlyImageCache) 17 | 18 | static char kRendererKey; 19 | 20 | - (void)setImageURL:(NSURL*)url 21 | { 22 | [self setPlaceHolderImageName:nil thumbnailURL:nil originalURL:url]; 23 | } 24 | 25 | - (void)setPlaceHolderImageName:(NSString*)imageName 26 | thumbnailURL:(NSURL*)thumbnailURL 27 | originalURL:(NSURL*)originalURL 28 | { 29 | FlyImageRenderer* renderer = objc_getAssociatedObject(self, &kRendererKey); 30 | 31 | if (renderer == nil) { 32 | renderer = [[FlyImageRenderer alloc] init]; 33 | renderer.delegate = self; 34 | 35 | objc_setAssociatedObject(self, &kRendererKey, renderer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 36 | } 37 | 38 | [renderer setPlaceHolderImageName:imageName 39 | thumbnailURL:thumbnailURL 40 | originalURL:originalURL 41 | drawSize:self.bounds.size 42 | contentsGravity:self.layer.contentsGravity 43 | cornerRadius:self.layer.cornerRadius]; 44 | } 45 | 46 | - (NSURL*)downloadingURL 47 | { 48 | return objc_getAssociatedObject(self, @selector(downloadingURL)); 49 | } 50 | 51 | - (void)setDownloadingURL:(NSURL*)url 52 | { 53 | objc_setAssociatedObject(self, @selector(downloadingURL), url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 54 | } 55 | 56 | - (float)downloadingPercentage 57 | { 58 | return [objc_getAssociatedObject(self, @selector(downloadingPercentage)) floatValue]; 59 | } 60 | 61 | - (void)setDownloadingPercentage:(float)progress 62 | { 63 | objc_setAssociatedObject(self, @selector(downloadingPercentage), @(progress), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 64 | } 65 | 66 | #pragma mark - FlyImageRendererDelegate 67 | - (void)flyImageRenderer:(FlyImageRenderer*)render willRenderImage:(UIImage*)image 68 | { 69 | if (image == nil && self.image == nil) { 70 | return; 71 | } 72 | 73 | self.image = image; 74 | [self setNeedsDisplay]; 75 | } 76 | 77 | - (void)flyImageRenderer:(FlyImageRenderer*)render didDownloadImageURL:(NSURL*)url progress:(float)progress 78 | { 79 | self.downloadingURL = url; 80 | self.downloadingPercentage = progress; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageCache.h 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/17/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageCacheProtocol.h" 11 | @class FlyImageDataFileManager; 12 | 13 | /** 14 | * Manage image files in one folder. 15 | */ 16 | @interface FlyImageCache : NSObject 17 | 18 | @property (nonatomic, assign) CGFloat maxCachedBytes; // Default is 512Mb. 19 | @property (nonatomic, assign) BOOL autoDismissImage; // If you want to reduce memory when the app enter background, set this flag as YES. Default is NO. 20 | @property (nonatomic, strong) FlyImageDataFileManager* dataFileManager; 21 | 22 | #ifdef FLYIMAGE_WEBP 23 | @property (nonatomic, assign) BOOL autoConvertWebP; // Should convert WebP file to JPEG file automaticlly. Default is NO. If yes, it will speed up retrieving operation for the next time. 24 | @property (nonatomic, assign) CGFloat compressionQualityForWebP; // Default is 0.8. 25 | #endif 26 | 27 | - (void)addImageWithKey:(NSString*)key 28 | filename:(NSString*)filename 29 | completed:(FlyImageCacheRetrieveBlock)completed; 30 | 31 | - (void)addImageWithKey:(NSString*)key 32 | filename:(NSString*)filename 33 | drawSize:(CGSize)drawSize 34 | contentsGravity:(NSString* const)contentsGravity 35 | cornerRadius:(CGFloat)cornerRadius 36 | completed:(FlyImageCacheRetrieveBlock)completed; 37 | 38 | /** 39 | * Get image with customize parameters from cache asynchronously. 40 | * Avoid executing `CGDataProviderCreateWithCopyOfData`. 41 | * 42 | * @param key image key 43 | * @param drawSize render size 44 | * @param contentsGravity contentMode of render view 45 | * @param cornerRadius cornerRadius of render view 46 | * @param completed callback 47 | */ 48 | - (void)asyncGetImageWithKey:(NSString*)key 49 | drawSize:(CGSize)drawSize 50 | contentsGravity:(NSString* const)contentsGravity 51 | cornerRadius:(CGFloat)cornerRadius 52 | completed:(FlyImageCacheRetrieveBlock)completed; 53 | 54 | /** 55 | * Get the image path saved in the disk. 56 | */ 57 | - (NSString*)imagePathWithKey:(NSString*)key; 58 | 59 | /** 60 | * Protect the file, which can't be removed. 61 | * 62 | * @param key image key 63 | */ 64 | - (void)protectFileWithKey:(NSString*)key; 65 | 66 | /** 67 | * Don't protect the file, which can be removed. 68 | * 69 | * @param key image key 70 | */ 71 | - (void)unProtectFileWithKey:(NSString*)key; 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageDownloader.h 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/22/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void (^FlyImageDownloadProgressBlock)(float progress); 12 | typedef void (^FlyImageDownloadSuccessBlock)(NSURLRequest* request, NSURL* filePath); 13 | typedef void (^FlyImageDownloadFailedBlock)(NSURLRequest* request, NSError* error); 14 | typedef NSUUID FlyImageDownloadHandlerId; // Unique ID of handler 15 | 16 | @class FlyImageDownloader; 17 | @protocol FlyImageDownloaderDelegate 18 | 19 | @optional 20 | /** 21 | * Callback before sending request. 22 | */ 23 | - (void)FlyImageDownloader:(FlyImageDownloader*)manager 24 | willSendRequest:(NSURLRequest*)request; 25 | 26 | /** 27 | * Callback after complete download. 28 | */ 29 | - (void)FlyImageDownloader:(FlyImageDownloader*)manager 30 | didReceiveResponse:(NSURLResponse*)response 31 | filePath:(NSURL*)filePath 32 | error:(NSError*)error 33 | request:(NSURLRequest*)request; 34 | 35 | /** 36 | * Callback after cancel some request. 37 | */ 38 | - (void)FlyImageDownloader:(FlyImageDownloader*)manager 39 | willCancelRequest:(NSURLRequest*)request; 40 | 41 | @end 42 | 43 | @interface FlyImageDownloader : NSObject 44 | 45 | @property (nonatomic, weak) id delegate; 46 | @property (nonatomic, copy) NSString* destinationPath; 47 | @property (nonatomic, assign) NSInteger maxDownloadingCount; // Default is 5; 48 | 49 | /** 50 | * Create a FlyImageDownloader with a default destination path. 51 | */ 52 | + (instancetype)sharedInstance; 53 | 54 | /** 55 | * Create a FlyImageDownloader with a specific destination path. 56 | */ 57 | - (instancetype)initWithDestinationPath:(NSString*)destinationPath; 58 | 59 | - (FlyImageDownloadHandlerId*)downloadImageForURLRequest:(NSURLRequest*)request; 60 | 61 | - (FlyImageDownloadHandlerId*)downloadImageForURLRequest:(NSURLRequest*)request 62 | success:(FlyImageDownloadSuccessBlock)success 63 | failed:(FlyImageDownloadFailedBlock)failed; 64 | 65 | /** 66 | * Send a download request with callbacks 67 | */ 68 | - (FlyImageDownloadHandlerId*)downloadImageForURLRequest:(NSURLRequest*)request 69 | progress:(FlyImageDownloadProgressBlock)progress 70 | success:(FlyImageDownloadSuccessBlock)success 71 | failed:(FlyImageDownloadFailedBlock)failed; 72 | 73 | /** 74 | * Cancel a downloading request. 75 | * 76 | * @param handlerId can't be nil 77 | */ 78 | - (void)cancelDownloadHandler:(FlyImageDownloadHandlerId*)handlerId; 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /FlyImage/UI/CALayer+FlyImageCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+FlyImageCache.m 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/17/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "CALayer+FlyImageCache.h" 10 | #import "FlyImageRenderer.h" 11 | #import "FlyImageUtils.h" 12 | #import "objc/runtime.h" 13 | 14 | @interface CALayer (__FlyImageCache) 15 | @end 16 | 17 | @implementation CALayer (FlyImageCache) 18 | 19 | static char kRendererKey; 20 | 21 | - (void)setImageURL:(NSURL*)url 22 | { 23 | [self setPlaceHolderImageName:nil thumbnailURL:nil originalURL:url]; 24 | } 25 | 26 | - (void)setPlaceHolderImageName:(NSString*)imageName 27 | thumbnailURL:(NSURL*)thumbnailURL 28 | originalURL:(NSURL*)originalURL 29 | { 30 | FlyImageRenderer* renderer = objc_getAssociatedObject(self, &kRendererKey); 31 | 32 | if (renderer == nil) { 33 | renderer = [[FlyImageRenderer alloc] init]; 34 | renderer.delegate = self; 35 | 36 | self.contentsScale = [FlyImageUtils contentsScale]; 37 | self.drawsAsynchronously = YES; 38 | 39 | objc_setAssociatedObject(self, &kRendererKey, renderer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 40 | } 41 | 42 | [renderer setPlaceHolderImageName:imageName 43 | thumbnailURL:thumbnailURL 44 | originalURL:originalURL 45 | drawSize:self.bounds.size 46 | contentsGravity:self.contentsGravity 47 | cornerRadius:self.cornerRadius]; 48 | } 49 | 50 | - (NSURL*)downloadingURL 51 | { 52 | return objc_getAssociatedObject(self, @selector(downloadingURL)); 53 | } 54 | 55 | - (void)setDownloadingURL:(NSURL*)url 56 | { 57 | objc_setAssociatedObject(self, @selector(downloadingURL), url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 58 | } 59 | 60 | - (float)downloadingPercentage 61 | { 62 | return [objc_getAssociatedObject(self, @selector(downloadingPercentage)) floatValue]; 63 | } 64 | 65 | - (void)setDownloadingPercentage:(float)progress 66 | { 67 | objc_setAssociatedObject(self, @selector(downloadingPercentage), @(progress), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 68 | } 69 | 70 | #pragma mark - FlyImageRendererDelegate 71 | - (void)flyImageRenderer:(FlyImageRenderer*)render willRenderImage:(UIImage*)image 72 | { 73 | if (image == nil && self.contents == nil) { 74 | return; 75 | } 76 | 77 | [CATransaction begin]; 78 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 79 | self.contents = (__bridge id _Nullable)(image.CGImage); 80 | [CATransaction commit]; 81 | 82 | [self setNeedsLayout]; 83 | } 84 | 85 | - (void)flyImageRenderer:(FlyImageRenderer*)render didDownloadImageURL:(NSURL*)url progress:(float)progress 86 | { 87 | self.downloadingURL = url; 88 | self.downloadingPercentage = progress; 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /FlyImage/UI/UIImageView+FlyImageIconCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+FlyImageIconCache.m 3 | // FlyImage 4 | // 5 | // Created by Ye Tong on 5/3/16. 6 | // Copyright © 2016 Ye Tong. All rights reserved. 7 | // 8 | 9 | #import "UIImageView+FlyImageIconCache.h" 10 | #import "FlyImageIconRenderer.h" 11 | #import "FlyImageUtils.h" 12 | #import "objc/runtime.h" 13 | 14 | @interface UIImageView (__FlyImageIconCache) 15 | @end 16 | 17 | @implementation UIImageView (FlyImageIconCache) 18 | 19 | static char kRendererKey; 20 | static char kDrawingBlockKey; 21 | 22 | - (void)setIconURL:(NSURL*)iconURL 23 | { 24 | [self setPlaceHolderImageName:nil iconURL:iconURL]; 25 | } 26 | 27 | - (void)setPlaceHolderImageName:(NSString*)imageName 28 | iconURL:(NSURL*)iconURL 29 | { 30 | 31 | FlyImageIconRenderer* renderer = objc_getAssociatedObject(self, &kRendererKey); 32 | 33 | if (renderer == nil) { 34 | renderer = [[FlyImageIconRenderer alloc] init]; 35 | renderer.delegate = self; 36 | 37 | objc_setAssociatedObject(self, &kRendererKey, renderer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 38 | } 39 | 40 | [renderer setPlaceHolderImageName:imageName 41 | iconURL:iconURL 42 | drawSize:self.bounds.size]; 43 | } 44 | 45 | - (void)setIconDrawingBlock:(FlyImageIconDrawingBlock)block 46 | { 47 | objc_setAssociatedObject(self, &kDrawingBlockKey, block, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 48 | } 49 | 50 | - (void)drawImage:(UIImage*)image inContext:(CGContextRef)context bounds:(CGRect)contextBounds 51 | { 52 | FlyImageIconDrawingBlock block = objc_getAssociatedObject(self, &kDrawingBlockKey); 53 | if (block != nil) { 54 | block(image, context, contextBounds); 55 | return; 56 | } 57 | 58 | // Clip to a rounded rect 59 | if (self.layer.cornerRadius > 0) { 60 | CGPathRef path = _FICDCreateRoundedRectPath(contextBounds, self.layer.cornerRadius * [FlyImageUtils contentsScale]); 61 | CGContextAddPath(context, path); 62 | CFRelease(path); 63 | CGContextEOClip(context); 64 | } 65 | 66 | UIGraphicsPushContext(context); 67 | CGRect drawRect = _FlyImageCalcDrawBounds(image.size, contextBounds.size, self.layer.contentsGravity); 68 | [image drawInRect:drawRect]; 69 | UIGraphicsPopContext(); 70 | } 71 | 72 | #pragma mark - FlyImageIconRendererDelegate 73 | - (void)flyImageIconRenderer:(FlyImageIconRenderer*)render 74 | drawImage:(UIImage*)image 75 | context:(CGContextRef)context 76 | bounds:(CGRect)contextBounds 77 | { 78 | [self drawImage:image inContext:context bounds:contextBounds]; 79 | } 80 | 81 | - (void)flyImageIconRenderer:(FlyImageIconRenderer*)render willRenderImage:(UIImage*)image 82 | { 83 | if (image == nil && self.image == nil) { 84 | return; 85 | } 86 | 87 | self.image = image; 88 | [self setNeedsLayout]; 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /Examples/FlyImageView/Cells/TriditionTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TriditionTableViewCell.m 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/14/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "TriditionTableViewCell.h" 10 | #import "FlyImageDownloader.h" 11 | #import "FlyImageDataFileManager.h" 12 | #import "FlyImageCache.h" 13 | #import 14 | #import 15 | 16 | @interface NSString (Extension) 17 | - (NSString *)md5; 18 | @end 19 | 20 | @implementation NSString (Extension) 21 | - (NSString *)md5 { 22 | const char *cStr = [self UTF8String]; 23 | unsigned char result[CC_MD5_DIGEST_LENGTH]; 24 | CC_MD5(cStr, (CC_LONG)strlen(cStr), result); // This is the md5 call 25 | return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 26 | result[0], 27 | result[1], 28 | result[2], 29 | result[3], 30 | result[4], 31 | result[5], 32 | result[6], 33 | result[7], 34 | result[8], 35 | result[9], 36 | result[10], 37 | result[11], 38 | result[12], 39 | result[13], 40 | result[14], 41 | result[15]]; 42 | } 43 | @end 44 | 45 | @implementation TriditionTableViewCell 46 | 47 | - (id)imageViewWithFrame:(CGRect)frame { 48 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame]; 49 | imageView.contentMode = UIViewContentModeScaleAspectFill; 50 | imageView.layer.masksToBounds = YES; 51 | imageView.layer.cornerRadius = 10; 52 | [self addSubview:imageView]; 53 | 54 | return imageView; 55 | } 56 | 57 | - (void)renderImageView:(id)imageView url:(NSURL *)url { 58 | 59 | NSString *key = url.absoluteString; 60 | if ( [[FlyImageCache sharedInstance] isImageExistWithKey:key] ) { 61 | 62 | NSString *path = [[FlyImageCache sharedInstance] imagePathWithKey:key]; 63 | NSURL *url = [NSURL fileURLWithPath:path]; 64 | [self doRenderImageView:imageView url:url]; 65 | 66 | }else{ 67 | NSURLRequest *request = [NSURLRequest requestWithURL:url]; 68 | [[FlyImageDownloader sharedInstance] downloadImageForURLRequest:request progress:nil success:^(NSURLRequest *request, NSURL *filePath) { 69 | 70 | [[FlyImageCache sharedInstance] addImageWithKey:key filename:[filePath lastPathComponent] completed:^(NSString *key, UIImage *image) { 71 | ((UIImageView *)imageView).image = image; 72 | }]; 73 | 74 | } failed:^(NSURLRequest *request, NSError *error) { 75 | NSLog(@"occur error = %@", error ); 76 | }]; 77 | 78 | ((UIImageView *)imageView).image = nil; 79 | } 80 | } 81 | 82 | - (void)doRenderImageView:(UIImageView *)imageView url:(NSURL *)url { 83 | NSData *data = [NSData dataWithContentsOfURL:url]; 84 | UIImage *image = [UIImage imageWithData:data]; 85 | imageView.image = image; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /Examples/WebP/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // WebP 4 | // 5 | // Created by Ye Tong on 5/3/16. 6 | // Copyright © 2016 Augmn. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "RootViewController.h" 11 | #import "FlyImageTableViewCell.h" 12 | #import "FlyImageLayerTableViewCell.h" 13 | #import "SDWebImageTableViewCell.h" 14 | #import "FlyImage.h" 15 | 16 | @interface AppDelegate () 17 | 18 | @end 19 | 20 | @implementation AppDelegate 21 | 22 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 23 | 24 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 25 | self.window.backgroundColor = [UIColor whiteColor]; 26 | 27 | [FlyImageCache sharedInstance].autoDismissImage = YES; 28 | 29 | RootViewController *rootViewController = [[RootViewController alloc] init]; 30 | rootViewController.suffix = @".webp"; 31 | rootViewController.heightOfCell = 150; 32 | rootViewController.cellsPerRow = 1; 33 | rootViewController.cells = @[ @{ 34 | @"class": [SDWebImageTableViewCell class], 35 | @"title": @"SDWebImage" 36 | } ,@{ 37 | @"class": [FlyImageTableViewCell class], 38 | @"title": @"FlyImageView" 39 | } ,@{ 40 | @"class": [FlyImageLayerTableViewCell class], 41 | @"title": @"FlyImageLayer" 42 | }]; 43 | 44 | self.window.rootViewController = rootViewController; 45 | [self.window makeKeyAndVisible]; 46 | 47 | return YES; 48 | } 49 | 50 | - (void)applicationWillResignActive:(UIApplication *)application { 51 | // 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. 52 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 53 | } 54 | 55 | - (void)applicationDidEnterBackground:(UIApplication *)application { 56 | // 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. 57 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 58 | } 59 | 60 | - (void)applicationWillEnterForeground:(UIApplication *)application { 61 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 62 | } 63 | 64 | - (void)applicationDidBecomeActive:(UIApplication *)application { 65 | // 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. 66 | } 67 | 68 | - (void)applicationWillTerminate:(UIApplication *)application { 69 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /FlyImage/UI/CALayer+FlyImageIconCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+FlyImageIconCache.m 3 | // FlyImage 4 | // 5 | // Created by Ye Tong on 4/27/16. 6 | // Copyright © 2016 Ye Tong. All rights reserved. 7 | // 8 | 9 | #import "CALayer+FlyImageIconCache.h" 10 | #import "FlyImageIconRenderer.h" 11 | #import "FlyImageUtils.h" 12 | #import "objc/runtime.h" 13 | 14 | @interface CALayer (__FlyImageIconCache) 15 | @end 16 | 17 | @implementation CALayer (FlyImageIconCache) 18 | 19 | static char kRendererKey; 20 | static char kDrawingBlockKey; 21 | 22 | - (void)setIconURL:(NSURL*)iconURL 23 | { 24 | [self setPlaceHolderImageName:nil iconURL:iconURL]; 25 | } 26 | 27 | - (void)setPlaceHolderImageName:(NSString*)imageName 28 | iconURL:(NSURL*)iconURL 29 | { 30 | FlyImageIconRenderer* renderer = objc_getAssociatedObject(self, &kRendererKey); 31 | 32 | if (renderer == nil) { 33 | renderer = [[FlyImageIconRenderer alloc] init]; 34 | renderer.delegate = self; 35 | 36 | self.contentsScale = [FlyImageUtils contentsScale]; 37 | self.drawsAsynchronously = YES; 38 | 39 | objc_setAssociatedObject(self, &kRendererKey, renderer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 40 | } 41 | 42 | [renderer setPlaceHolderImageName:imageName 43 | iconURL:iconURL 44 | drawSize:self.bounds.size]; 45 | } 46 | 47 | - (void)setIconDrawingBlock:(FlyImageIconDrawingBlock)block 48 | { 49 | objc_setAssociatedObject(self, &kDrawingBlockKey, block, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 50 | } 51 | 52 | - (void)drawImage:(UIImage*)image inContext:(CGContextRef)context bounds:(CGRect)contextBounds 53 | { 54 | FlyImageIconDrawingBlock block = objc_getAssociatedObject(self, &kDrawingBlockKey); 55 | if (block != nil) { 56 | block(image, context, contextBounds); 57 | return; 58 | } 59 | 60 | // Clip to a rounded rect 61 | if (self.cornerRadius > 0) { 62 | CGPathRef path = _FICDCreateRoundedRectPath(contextBounds, self.cornerRadius * [FlyImageUtils contentsScale]); 63 | CGContextAddPath(context, path); 64 | CFRelease(path); 65 | CGContextEOClip(context); 66 | } 67 | 68 | UIGraphicsPushContext(context); 69 | CGRect drawRect = _FlyImageCalcDrawBounds(image.size, contextBounds.size, self.contentsGravity); 70 | [image drawInRect:drawRect]; 71 | UIGraphicsPopContext(); 72 | } 73 | 74 | #pragma mark - FlyImageIconRendererDelegate 75 | - (void)flyImageIconRenderer:(FlyImageIconRenderer*)render 76 | drawImage:(UIImage*)image 77 | context:(CGContextRef)context 78 | bounds:(CGRect)contextBounds 79 | { 80 | [self drawImage:image inContext:context bounds:contextBounds]; 81 | } 82 | 83 | - (void)flyImageIconRenderer:(FlyImageIconRenderer*)render willRenderImage:(UIImage*)image 84 | { 85 | if (image == nil && self.contents == nil) { 86 | return; 87 | } 88 | 89 | [CATransaction begin]; 90 | [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; 91 | self.contents = (__bridge id _Nullable)(image.CGImage); 92 | [CATransaction commit]; 93 | 94 | [self setNeedsLayout]; 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /Examples/FlyImageIconView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // FlyImageIconView 4 | // 5 | // Created by Ye Tong on 4/27/16. 6 | // Copyright © 2016 Augmn. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "RootViewController.h" 11 | #import "TriditionTableViewCell.h" 12 | #import "SDWebImageTableViewCell.h" 13 | #import "FlyImageIconLayerTableViewCell.h" 14 | #import "FlyImageIconViewTableViewCell.h" 15 | #import "FlyImage.h" 16 | 17 | @interface AppDelegate () 18 | 19 | @end 20 | 21 | @implementation AppDelegate 22 | 23 | 24 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 25 | 26 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 27 | self.window.backgroundColor = [UIColor whiteColor]; 28 | 29 | RootViewController *rootViewController = [[RootViewController alloc] init]; 30 | rootViewController.suffix = @"_tn.jpg"; 31 | rootViewController.cellsPerRow = 10; 32 | rootViewController.heightOfCell = self.window.bounds.size.width / rootViewController.cellsPerRow; 33 | rootViewController.activeIndex = 3; 34 | rootViewController.cells = @[ @{ 35 | @"class": [TriditionTableViewCell class], 36 | @"title": @"UIKit" 37 | },@{ 38 | @"class": [SDWebImageTableViewCell class], 39 | @"title": @"SDWebImage" 40 | }, @{ 41 | @"class": [FlyImageIconViewTableViewCell class], 42 | @"title": @"FlyImageIconView" 43 | }, @{ 44 | @"class": [FlyImageIconLayerTableViewCell class], 45 | @"title": @"FlyImageIconLayer" 46 | }]; 47 | 48 | self.window.rootViewController = rootViewController; 49 | [self.window makeKeyAndVisible]; 50 | 51 | return YES; 52 | } 53 | 54 | - (void)applicationWillResignActive:(UIApplication *)application { 55 | // 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. 56 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 57 | } 58 | 59 | - (void)applicationDidEnterBackground:(UIApplication *)application { 60 | // 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. 61 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 62 | } 63 | 64 | - (void)applicationWillEnterForeground:(UIApplication *)application { 65 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 66 | } 67 | 68 | - (void)applicationDidBecomeActive:(UIApplication *)application { 69 | // 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. 70 | } 71 | 72 | - (void)applicationWillTerminate:(UIApplication *)application { 73 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /Examples/FlyImageView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // FlyImageView 4 | // 5 | // Created by Ye Tong on 4/18/16. 6 | // Copyright © 2016 Augmn. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "RootViewController.h" 11 | #import "TriditionTableViewCell.h" 12 | #import "FlyImageTableViewCell.h" 13 | #import "FlyImageLayerTableViewCell.h" 14 | #import "SDWebImageTableViewCell.h" 15 | #import "FlyImage.h" 16 | 17 | @interface AppDelegate () 18 | 19 | @end 20 | 21 | @implementation AppDelegate 22 | 23 | 24 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 25 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 26 | self.window.backgroundColor = [UIColor whiteColor]; 27 | 28 | [FlyImageCache sharedInstance].autoDismissImage = YES; 29 | 30 | RootViewController *rootViewController = [[RootViewController alloc] init]; 31 | rootViewController.suffix = @".jpg"; 32 | rootViewController.heightOfCell = 150; 33 | rootViewController.cellsPerRow = 1; 34 | rootViewController.activeIndex = 3; 35 | rootViewController.cells = @[ @{ 36 | @"class": [TriditionTableViewCell class], 37 | @"title": @"UIKit" 38 | },@{ 39 | @"class": [SDWebImageTableViewCell class], 40 | @"title": @"SDWebImage" 41 | } ,@{ 42 | @"class": [FlyImageTableViewCell class], 43 | @"title": @"FlyImageView" 44 | } ,@{ 45 | @"class": [FlyImageLayerTableViewCell class], 46 | @"title": @"FlyImageLayer" 47 | }]; 48 | 49 | self.window.rootViewController = rootViewController; 50 | [self.window makeKeyAndVisible]; 51 | 52 | return YES; 53 | } 54 | 55 | - (void)applicationWillResignActive:(UIApplication *)application { 56 | // 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. 57 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 58 | } 59 | 60 | - (void)applicationDidEnterBackground:(UIApplication *)application { 61 | // 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. 62 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 63 | } 64 | 65 | - (void)applicationWillEnterForeground:(UIApplication *)application { 66 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 67 | } 68 | 69 | - (void)applicationDidBecomeActive:(UIApplication *)application { 70 | // 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. 71 | } 72 | 73 | - (void)applicationWillTerminate:(UIApplication *)application { 74 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 75 | } 76 | 77 | - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { 78 | NSLog(@"Memory Warning"); 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /Examples/FlyImageView.xcworkspace/xcshareddata/xcschemes/WebP.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Examples/FlyImageView.xcworkspace/xcshareddata/xcschemes/SingleView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Examples/FlyImageView.xcworkspace/xcshareddata/xcschemes/FlyImageView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Examples/FlyImageView.xcworkspace/xcshareddata/xcschemes/FlyImageIconView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageDataFile.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageDataFile.m 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/18/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "FlyImageDataFile.h" 10 | #import "FlyImageCache.h" 11 | #import "FlyImageUtils.h" 12 | #import 13 | 14 | @implementation FlyImageDataFile { 15 | NSString* _filePath; 16 | int _fileDescriptor; 17 | size_t _maxLength; // default is 100Mb. 18 | 19 | NSRecursiveLock* _lock; 20 | } 21 | 22 | - (instancetype)initWithPath:(NSString*)path 23 | { 24 | if (self = [self init]) { 25 | _filePath = [path copy]; 26 | _maxLength = 1024 * 1024 * 100; 27 | _step = 1; 28 | _pointer = 0; 29 | _lock = [[NSRecursiveLock alloc] init]; 30 | _fileDescriptor = -1; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)dealloc 36 | { 37 | // should close the file if it's not be used again. 38 | [self close]; 39 | } 40 | 41 | - (BOOL)open 42 | { 43 | _fileDescriptor = open([_filePath fileSystemRepresentation], O_RDWR | O_CREAT, 0666); 44 | if (_fileDescriptor < 0) { 45 | FlyImageErrorLog(@"can't file at %@", _filePath); 46 | return NO; 47 | } 48 | 49 | _fileLength = lseek(_fileDescriptor, 0, SEEK_END); 50 | if (_fileLength == 0) { 51 | [self increaseFileLength:_step]; 52 | } else { 53 | [self mmap]; 54 | } 55 | 56 | return YES; 57 | } 58 | 59 | - (void)close 60 | { 61 | if (_fileDescriptor < 0) { 62 | return; 63 | } 64 | 65 | [_lock lock]; 66 | 67 | close(_fileDescriptor); 68 | _fileDescriptor = -1; 69 | 70 | // 取消内存映射 71 | [self munmap]; 72 | 73 | [_lock unlock]; 74 | } 75 | 76 | - (void)munmap 77 | { 78 | if (_address == NULL) { 79 | return; 80 | } 81 | 82 | munmap(_address, (size_t)_fileLength); 83 | _address = NULL; 84 | } 85 | 86 | - (void)mmap 87 | { 88 | _address = mmap(NULL, (size_t)_fileLength, (PROT_READ | PROT_WRITE), (MAP_FILE | MAP_SHARED), _fileDescriptor, 0); 89 | } 90 | 91 | - (BOOL)prepareAppendDataWithOffset:(size_t)offset length:(size_t)length 92 | { 93 | NSAssert(_fileDescriptor > -1, @"open this file first."); 94 | 95 | [_lock lock]; 96 | 97 | // can't exceed maxLength 98 | if (offset + length > _maxLength) { 99 | [_lock unlock]; 100 | return NO; 101 | } 102 | 103 | // Check the file length, if it is not big enough, then increase the file length with step. 104 | if (offset + length > _fileLength) { 105 | size_t correctLength = ceill((length * 1.0 / _step)) * _step; 106 | if (![self increaseFileLength:correctLength]) { 107 | [_lock unlock]; 108 | return NO; 109 | } 110 | } 111 | 112 | [_lock unlock]; 113 | return YES; 114 | } 115 | 116 | - (BOOL)appendDataWithOffset:(size_t)offset length:(size_t)length 117 | { 118 | NSAssert(_fileDescriptor > -1, @"open this file first."); 119 | 120 | [_lock lock]; 121 | 122 | int result = msync(_address + offset, length, MS_SYNC); 123 | if (result < 0) { 124 | FlyImageErrorLog(@"append data failed"); 125 | [_lock unlock]; 126 | return NO; 127 | } 128 | 129 | // move the pointer 130 | if (offset + length > _pointer) { 131 | _pointer = offset + length; 132 | } 133 | 134 | [_lock unlock]; 135 | 136 | return YES; 137 | } 138 | 139 | - (BOOL)increaseFileLength:(size_t)length 140 | { 141 | [_lock lock]; 142 | 143 | // cancel map first 144 | [self munmap]; 145 | 146 | // change file length 147 | int result = ftruncate(_fileDescriptor, _fileLength + length); 148 | if (result < 0) { 149 | FlyImageErrorLog(@"can't truncate data file"); 150 | [_lock unlock]; 151 | return NO; 152 | } 153 | 154 | // remap 155 | _fileLength = lseek(_fileDescriptor, 0, SEEK_END); 156 | [self mmap]; 157 | 158 | [_lock unlock]; 159 | 160 | return YES; 161 | } 162 | 163 | @end 164 | -------------------------------------------------------------------------------- /FlyImage.xcworkspace/xcshareddata/xcschemes/FlyImage.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![FlyImage Logo](Docs/logo.png) FlyImage 2 | ========= 3 | [![Build Status](https://travis-ci.org/northwind/FlyImage.svg?branch=master)](https://travis-ci.org/northwind/FlyImage) 4 | [![Pod Version](http://img.shields.io/cocoapods/v/FlyImage.svg?style=flat)](http://cocoadocs.org/docsets/FlyImage/) 5 | [![Pod Platform](http://img.shields.io/cocoapods/p/FlyImage.svg?style=flat)](http://cocoadocs.org/docsets/FlyImage/) 6 | [![Pod License](http://img.shields.io/cocoapods/l/FlyImage.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0.html) 7 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/northwind/FlyImage) 8 | 9 | FlyImage takes the advantages of [SDWebImage](https://github.com/rs/SDWebImage), [FastImageCache](https://github.com/path/FastImageCache) and [AFNetworking](https://github.com/AFNetworking/AFNetworking), is a simple and high performance image library. 10 | 11 | Features: 12 | 13 | - High Performance, reduce memory operations while rendering, avoid `Memory warning` caused by image 14 | - Store and retrieve different size of small images in one memory file, smooth scrolling 15 | - Simple, support `UIImageView`, `CALayer` category 16 | - An asynchronous image downloader 17 | - Support `WebP` format 18 | - Support `mmap` to improve I/O performace 19 | 20 | ## Installation with CocoaPods 21 | 22 | Set the Podfile like this: 23 | 24 | ``` 25 | platform :ios, '8.0' 26 | pod 'FlyImage', '~>1.0' 27 | ``` 28 | 29 | 30 | If you are using Swift, be sure to add `use_frameworks!`: 31 | 32 | ``` 33 | platform :ios, '8.0' 34 | use_frameworks! 35 | pod 'FlyImage', '~>1.0' 36 | ``` 37 | 38 | If you want to support `WebP`, just change the Podfile: 39 | 40 | ``` 41 | platform :ios, '8.0' 42 | pod 'FlyImage/WebP', '~>1.0' 43 | ``` 44 | 45 | ## How To Use 46 | 47 | Using UIImageView and CALayer Category 48 | 49 | ```objective-c 50 | #import "FlyImage.h" 51 | ... 52 | 53 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame]; 54 | [imageView setPlaceHolderImageName:@"default" 55 | thumbnailURL:[NSURL urlWithString:@"http://thumbnail"] 56 | originalURL:[NSURL urlWithString:@"http://original"]]; 57 | []self.view addSubview:imageView]; 58 | ... 59 | 60 | UIImageView *iconView = [[UIImageView alloc] initWithFrame:frame]; 61 | [iconView setIconURL:[NSURL urlWithString:@"http://original"]]; 62 | []self.view addSubview:iconView]; 63 | ... 64 | 65 | ``` 66 | 67 | Using FlyImageCache 68 | 69 | ```objective-c 70 | // retrieve a specific key, and get callback 71 | [[FlyImageCache sharedInstance] asyncGetImageWithKey:key 72 | completed:^(NSString *key, UIImage *image) { 73 | imageView.image = image; 74 | }]; 75 | 76 | // remove a image from the cache 77 | [[FlyImageCache sharedInstance] removeImageWithKey:key]; 78 | 79 | // delete all images 80 | [[FlyImageCache sharedInstance] purge]; 81 | 82 | ``` 83 | 84 | Using FlyImageIconCache 85 | 86 | ```objective-c 87 | // add a new icon with a specific key 88 | [[FlyImageIconCache sharedInstance] addImageWithKey:key 89 | size:drawSize 90 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 91 | UIImage *image = [UIImage imageWithName:@"imageName"]; 92 | 93 | UIGraphicsPushContext(context); 94 | [image drawInRect:contextBounds]; 95 | UIGraphicsPopContext(); 96 | } 97 | completed:nil]; 98 | 99 | // retrieve a specific key, and get callback 100 | [[FlyImageCache sharedInstance] asyncGetImageWithKey:key 101 | completed:^(NSString *key, UIImage *image) { 102 | imageView.image = image; 103 | }]; 104 | 105 | ``` 106 | 107 | More demos in folder [Examples](https://github.com/northwind/FlyImage/tree/master/Examples) 108 | 109 | 110 | ## Performance Mesure 111 | 112 | 113 | #### Memory when scrolling images 114 | 115 | > Demo Target: FlyImageView / Device: iPhone6 Plus 116 | 117 | Memory | FlyImage | SDWebImage | UIKit 118 | ------------ | ------------ | ------------- | ------------ 119 | All Heap Allocations | 2~7M | 2~4M | 2~5M 120 | All Anonymous VM | 17~30M | 310M | 17~30M 121 | 122 | 123 | 124 | #### FPS when more than 170 small images in the same screen 125 | 126 | > Demo Target: FlyImageIconView / Device: iPhone6 Plus 127 | 128 | FlyImage | SDWebImage | UIKit 129 | ------------ | ------------- | ------------ 130 | 58~60FPS | 6~7FPS | 6~7FPS 131 | 132 | ![FPS](Docs/iconcache.jpg) 133 | 134 | ## Architecture 135 | ![FlyImage Struct](Docs/classes.jpg) 136 | 137 | 138 | ## License 139 | 140 | FlyImage is made available under the [MIT license](http://opensource.org/licenses/MIT) 141 | 142 | -------------------------------------------------------------------------------- /Examples/SingleView/SingleViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SingleViewController.m 3 | // SingleView 4 | // 5 | // Created by Ye Tong on 5/9/16. 6 | // Copyright © 2016 Augmn. All rights reserved. 7 | // 8 | 9 | #import "SingleViewController.h" 10 | #import "FlyImage.h" 11 | 12 | @implementation SingleViewController { 13 | NSMutableArray *_imageViews; 14 | NSMutableArray *_iconViews; 15 | } 16 | 17 | - (BOOL)prefersStatusBarHidden { 18 | return YES; 19 | } 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | [FlyImageCache sharedInstance].autoDismissImage = YES; 24 | 25 | _imageViews = [NSMutableArray new]; 26 | _iconViews = [NSMutableArray new]; 27 | 28 | // add // remove // clear 29 | CGFloat fromY = self.view.bounds.size.height - 200; 30 | [self insertButtonWithTitle:@"AddImageView" selector:@selector(onAddImageView) point:CGPointMake(10, fromY)]; 31 | [self insertButtonWithTitle:@"RemoveImageView" selector:@selector(onRemoveImageView) point:CGPointMake(self.view.bounds.size.width/2 - 40, fromY)]; 32 | [self insertButtonWithTitle:@"ClearImageViews" selector:@selector(onClearImageViews) point:CGPointMake(self.view.bounds.size.width - 90, fromY)]; 33 | 34 | [self insertButtonWithTitle:@"AddIconView" selector:@selector(onAddIconView) point:CGPointMake(10, fromY + 100)]; 35 | [self insertButtonWithTitle:@"RemoveIconView" selector:@selector(onRemoveIconView) point:CGPointMake(self.view.bounds.size.width/2 - 40, fromY + 100)]; 36 | [self insertButtonWithTitle:@"ClearIconViews" selector:@selector(onClearIconViews) point:CGPointMake(self.view.bounds.size.width - 90, fromY + 100)]; 37 | 38 | } 39 | 40 | - (void)insertButtonWithTitle:(NSString *)title selector:(SEL)selector point:(CGPoint)point { 41 | UIButton *addButton = [UIButton buttonWithType:UIButtonTypeSystem]; 42 | addButton.frame = CGRectMake(point.x, point.y, 80, 44); 43 | addButton.backgroundColor = [UIColor orangeColor]; 44 | [addButton setTitle:title forState:UIControlStateNormal]; 45 | [addButton addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside]; 46 | addButton.titleLabel.adjustsFontSizeToFitWidth = YES; 47 | [self.view addSubview:addButton]; 48 | } 49 | 50 | - (void)onAddImageView { 51 | static NSInteger kCount = 0; 52 | 53 | NSMutableArray *imagesInRow = [NSMutableArray new]; 54 | CGFloat size = self.view.bounds.size.width / 5; 55 | NSInteger index = kCount % 100; 56 | 57 | for (NSInteger i=0; i<4; i++) { 58 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(i * (size + 10), [_imageViews count] * (size + 10), size, size)]; 59 | NSURL *url = [NSURL URLWithString: [NSString stringWithFormat:@"http://liuliantv.oss-cn-beijing.aliyuncs.com/flyimage/%ld.jpg", (long)index]]; 60 | imageView.imageURL = url; 61 | [self.view insertSubview:imageView atIndex:0]; 62 | 63 | [imagesInRow addObject:imageView]; 64 | } 65 | 66 | [_imageViews addObject:imagesInRow]; 67 | kCount++; 68 | } 69 | 70 | - (void)onRemoveImageView { 71 | NSArray *imagesInRow = [_imageViews lastObject]; 72 | for (UIImageView *imageView in imagesInRow) { 73 | [imageView removeFromSuperview]; 74 | } 75 | 76 | [_imageViews removeLastObject]; 77 | } 78 | 79 | - (void)onClearImageViews { 80 | for (NSArray *imagesInRow in _imageViews) { 81 | for (UIImageView *imageView in imagesInRow) { 82 | [imageView removeFromSuperview]; 83 | } 84 | } 85 | 86 | [_imageViews removeAllObjects]; 87 | } 88 | 89 | - (void)onAddIconView { 90 | static NSInteger kCount = 0; 91 | 92 | NSMutableArray *imagesInRow = [NSMutableArray new]; 93 | CGFloat size = self.view.bounds.size.width / 5; 94 | NSInteger index = kCount % 100; 95 | 96 | for (NSInteger i=0; i<4; i++) { 97 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(i * (size + 10), [_iconViews count] * (size + 10), size, size)]; 98 | NSURL *url = [NSURL URLWithString: [NSString stringWithFormat:@"http://liuliantv.oss-cn-beijing.aliyuncs.com/flyimage/%ld_tn.jpg", (long)index]]; 99 | imageView.iconURL = url; 100 | [self.view insertSubview:imageView atIndex:0]; 101 | 102 | [imagesInRow addObject:imageView]; 103 | } 104 | 105 | [_iconViews addObject:imagesInRow]; 106 | kCount++; 107 | } 108 | 109 | - (void)onRemoveIconView { 110 | NSArray *imagesInRow = [_iconViews lastObject]; 111 | for (UIImageView *imageView in imagesInRow) { 112 | [imageView removeFromSuperview]; 113 | } 114 | 115 | [_iconViews removeLastObject]; 116 | } 117 | 118 | - (void)onClearIconViews { 119 | for (NSArray *imagesInRow in _iconViews) { 120 | for (UIImageView *imageView in imagesInRow) { 121 | [imageView removeFromSuperview]; 122 | } 123 | } 124 | 125 | [_iconViews removeAllObjects]; 126 | } 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /FlyImage/UI/FlyImageIconRenderer.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageIconRenderer.m 3 | // FlyImage 4 | // 5 | // Created by Ye Tong on 4/27/16. 6 | // Copyright © 2016 Ye Tong. All rights reserved. 7 | // 8 | 9 | #import "FlyImageIconRenderer.h" 10 | #import "FlyImageUtils.h" 11 | #import "FlyImageCache.h" 12 | #import "FlyImageDownloader.h" 13 | 14 | @interface FlyImageIconRenderer () 15 | @property (nonatomic, strong) NSURL* iconURL; 16 | @end 17 | 18 | @implementation FlyImageIconRenderer { 19 | CGSize _drawSize; 20 | 21 | FlyImageDownloadHandlerId* _downloadHandlerId; 22 | } 23 | 24 | - (void)dealloc 25 | { 26 | [self cancelDownload]; 27 | } 28 | 29 | - (void)cancelDownload 30 | { 31 | if (_downloadHandlerId != nil) { 32 | [[FlyImageDownloader sharedInstance] cancelDownloadHandler:_downloadHandlerId]; 33 | _downloadHandlerId = nil; 34 | } 35 | } 36 | 37 | - (void)setPlaceHolderImageName:(NSString*)imageName 38 | iconURL:(NSURL*)iconURL 39 | drawSize:(CGSize)drawSize 40 | { 41 | 42 | if (_iconURL != nil && [_iconURL.absoluteString isEqualToString:iconURL.absoluteString]) { 43 | return; 44 | } 45 | 46 | [self cancelDownload]; 47 | 48 | _iconURL = iconURL; 49 | _drawSize = CGSizeMake(round(drawSize.width), round(drawSize.height)); 50 | 51 | [self renderWithPlaceHolderImageName:imageName]; 52 | } 53 | 54 | - (void)renderWithPlaceHolderImageName:(NSString*)imageName 55 | { 56 | NSString* key = _iconURL.absoluteString; 57 | 58 | // if has already downloaded image 59 | if (key != nil && [[FlyImageIconCache sharedInstance] isImageExistWithKey:key]) { 60 | __weak __typeof__(self) weakSelf = self; 61 | [[FlyImageIconCache sharedInstance] asyncGetImageWithKey:key 62 | completed:^(NSString* key, UIImage* image) { 63 | [weakSelf renderImage:image key:key ]; 64 | }]; 65 | 66 | return; 67 | } 68 | 69 | if (imageName != nil) { 70 | UIImage* placeHolderImage = [UIImage imageNamed:imageName]; 71 | [self doRenderImage:placeHolderImage]; 72 | } else if (key != nil) { 73 | // clear 74 | [self doRenderImage:nil]; 75 | } 76 | 77 | if (key == nil) { 78 | return; 79 | } 80 | 81 | if ([[FlyImageCache sharedInstance] isImageExistWithKey:key]) { 82 | NSString* imagePath = [[FlyImageCache sharedInstance] imagePathWithKey:key]; 83 | if (imagePath != nil) { 84 | NSURL* url = [NSURL fileURLWithPath:imagePath]; 85 | [self drawIconWithKey:key url:url]; 86 | return; 87 | } 88 | } 89 | 90 | [self downloadImage]; 91 | } 92 | 93 | - (void)downloadImage 94 | { 95 | __weak __typeof__(self) weakSelf = self; 96 | 97 | NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:_iconURL]; 98 | request.timeoutInterval = 30; // Default 30 seconds 99 | _downloadHandlerId = [[FlyImageDownloader sharedInstance] 100 | downloadImageForURLRequest:request 101 | success:^(NSURLRequest* request, NSURL* filePath) { 102 | 103 | NSString *downloadedKey = request.URL.absoluteString; 104 | [[FlyImageCache sharedInstance] addImageWithKey:downloadedKey 105 | filename:[filePath lastPathComponent] 106 | completed:nil]; 107 | 108 | // In case downloaded image is not equal with the new url 109 | if ( ![downloadedKey isEqualToString:weakSelf.iconURL.absoluteString] ) { 110 | return; 111 | } 112 | 113 | _downloadHandlerId = nil; 114 | [weakSelf drawIconWithKey:downloadedKey url:filePath]; 115 | 116 | } 117 | failed:^(NSURLRequest* request, NSError* error) { 118 | _downloadHandlerId = nil; 119 | }]; 120 | } 121 | 122 | - (void)drawIconWithKey:(NSString*)key url:(NSURL*)url 123 | { 124 | __weak __typeof__(self) weakSelf = self; 125 | [[FlyImageIconCache sharedInstance] addImageWithKey:key 126 | size:_drawSize 127 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 128 | 129 | NSData *data = [NSData dataWithContentsOfURL:url]; 130 | UIImage *image = [UIImage imageWithData:data]; 131 | 132 | [weakSelf.delegate flyImageIconRenderer:weakSelf 133 | drawImage:image 134 | context:context 135 | bounds:contextBounds]; 136 | 137 | } 138 | completed:^(NSString* key, UIImage* image) { 139 | [weakSelf renderImage:image key:key]; 140 | }]; 141 | } 142 | 143 | - (void)renderImage:(UIImage*)image key:(NSString*)key 144 | { 145 | dispatch_main_sync_safe(^{ 146 | if ( ![_iconURL.absoluteString isEqualToString:key] ) { 147 | return; 148 | } 149 | 150 | [self doRenderImage:image]; 151 | }); 152 | } 153 | 154 | - (void)doRenderImage:(UIImage*)image 155 | { 156 | [_delegate flyImageIconRenderer:self willRenderImage:image]; 157 | } 158 | 159 | @end 160 | -------------------------------------------------------------------------------- /Examples/FlyImageView/RootViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RootViewController.m 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/24/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "RootViewController.h" 10 | #import "BaseTableViewCell.h" 11 | #import "SDImageCache.h" 12 | #import 13 | 14 | @interface RootViewController () 15 | 16 | @property (nonatomic, assign) CGFloat itemWidth; 17 | @property (nonatomic, assign) CGFloat itemHeight; 18 | 19 | @end 20 | 21 | @implementation RootViewController { 22 | UITableView *_tableView; 23 | UISegmentedControl *_segment; 24 | 25 | NSMutableArray *_imageURLs; 26 | NSMutableArray *_cells; 27 | NSMutableArray *_indentifiers; 28 | } 29 | 30 | - (instancetype)init { 31 | if (self = [super init]) { 32 | _cells = [[NSMutableArray alloc] init]; 33 | _indentifiers = [[NSMutableArray alloc] init]; 34 | _activeIndex = 0; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)viewDidLoad { 40 | [super viewDidLoad]; 41 | self.view.backgroundColor = [UIColor whiteColor]; 42 | 43 | // setup image paths 44 | _imageURLs = [[NSMutableArray alloc] init]; 45 | for (int i=0; i<100; i++) { 46 | NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://flyimage.oss-us-west-1.aliyuncs.com/%d%@", i, self.suffix ]]; 47 | [_imageURLs addObject:url]; 48 | } 49 | 50 | CGFloat segmentHeight = 30; 51 | CGRect bounds = self.view.bounds; 52 | 53 | _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, bounds.size.width, bounds.size.height-segmentHeight) 54 | style:UITableViewStylePlain]; 55 | _tableView.opaque = YES; 56 | _tableView.directionalLockEnabled = YES; 57 | _tableView.backgroundColor = [UIColor clearColor]; 58 | _tableView.allowsSelection = NO; 59 | _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; 60 | _tableView.dataSource = self; 61 | _tableView.delegate = self; 62 | 63 | NSMutableArray *items = [NSMutableArray array]; 64 | for (NSDictionary *info in _cells) { 65 | [items addObject: [info objectForKey:@"title"]]; 66 | 67 | Class class = [info objectForKey:@"class"]; 68 | NSString *indentifier = NSStringFromClass(class); 69 | [_indentifiers addObject:indentifier]; 70 | [_tableView registerClass:class forCellReuseIdentifier:indentifier]; 71 | } 72 | [self.view addSubview:_tableView]; 73 | 74 | NSDictionary *textAttributes = [NSDictionary dictionaryWithObjectsAndKeys:[UIFont systemFontOfSize:10], NSFontAttributeName, nil]; 75 | [[UISegmentedControl appearance] setTitleTextAttributes:textAttributes forState:UIControlStateNormal]; 76 | 77 | _segment = [[UISegmentedControl alloc] initWithItems:items]; 78 | _segment.backgroundColor = [UIColor whiteColor]; 79 | _segment.frame = CGRectMake(0, bounds.size.height - segmentHeight, bounds.size.width, segmentHeight); 80 | [_segment setSelectedSegmentIndex:_activeIndex]; 81 | [_segment addTarget:self action:@selector(onTapSegment) forControlEvents:UIControlEventValueChanged]; 82 | [self.view addSubview:_segment]; 83 | 84 | SDWebImageManager *sdManager = [SDWebImageManager sharedManager]; 85 | sdManager.delegate = self; 86 | 87 | _itemWidth = floor(self.view.frame.size.width / _cellsPerRow) - 4; 88 | _itemHeight = _heightOfCell - 4; 89 | } 90 | 91 | #pragma mark - SDWebImageManagerDelegate 92 | 93 | - (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL{ 94 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(_itemWidth, _heightOfCell), NO, [UIScreen mainScreen].scale); 95 | 96 | CGRect box = CGRectMake(0, 0, _itemWidth, _itemHeight); 97 | [[UIBezierPath bezierPathWithRoundedRect:box cornerRadius:10.f] addClip]; 98 | [image drawInRect:box]; 99 | 100 | UIImage* ret = UIGraphicsGetImageFromCurrentImageContext(); 101 | 102 | UIGraphicsEndImageContext(); 103 | return ret; 104 | } 105 | 106 | - (BOOL)prefersStatusBarHidden { 107 | return YES; 108 | } 109 | 110 | - (void)onTapSegment { 111 | _activeIndex = _segment.selectedSegmentIndex; 112 | 113 | [_tableView reloadData]; 114 | } 115 | 116 | #pragma mark - UITableViewDataSource 117 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 118 | return 2000; 119 | } 120 | 121 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 122 | return self.heightOfCell; 123 | } 124 | 125 | // The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath: 126 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 127 | 128 | NSInteger startIndex = ([indexPath row] * self.cellsPerRow) % [_imageURLs count]; 129 | NSInteger count = MIN(self.cellsPerRow, [_imageURLs count] - startIndex); 130 | NSArray *photos = [_imageURLs subarrayWithRange:NSMakeRange(startIndex, count)]; 131 | 132 | BaseTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_indentifiers[_activeIndex] forIndexPath:indexPath]; 133 | [cell displayImageWithPhotos:photos]; 134 | 135 | return cell; 136 | } 137 | 138 | @end 139 | -------------------------------------------------------------------------------- /FlyImageTests/FlyImageDataFileManagerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageDataFileManagerTests.m 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/2/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageDataFileManager.h" 11 | 12 | @interface FlyImageDataFileManagerTests : XCTestCase 13 | 14 | @end 15 | 16 | static FlyImageDataFileManager* _fileManager; 17 | 18 | @implementation FlyImageDataFileManagerTests 19 | 20 | - (void)setUp 21 | { 22 | [super setUp]; 23 | // Put setup code here. This method is called before the invocation of each test method in the class. 24 | 25 | // Put setup code here. This method is called before the invocation of each test method in the class. 26 | if (_fileManager == nil) { 27 | NSString* directoryPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; 28 | NSString* folderPath = [directoryPath stringByAppendingPathComponent:@"flyimage2/files"]; 29 | 30 | _fileManager = [[FlyImageDataFileManager alloc] initWithFolderPath:folderPath]; 31 | } 32 | } 33 | 34 | - (void)tearDown 35 | { 36 | // Put teardown code here. This method is called after the invocation of each test method in the class. 37 | [super tearDown]; 38 | } 39 | 40 | - (void)test10Create 41 | { 42 | XCTestExpectation* expectation = [self expectationWithDescription:@"test10Create"]; 43 | 44 | [_fileManager asyncCreateFileWithName:@"10" completed:^(FlyImageDataFile* dataFile) { 45 | XCTAssert( dataFile != nil ); 46 | [expectation fulfill]; 47 | }]; 48 | 49 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 50 | } 51 | 52 | - (void)test11CreateMultipleTimes 53 | { 54 | XCTestExpectation* expectation = [self expectationWithDescription:@"test11CreateMultipleTimes"]; 55 | 56 | __block int sum = 0; 57 | for (int i = 0; i < 100; i++) { 58 | [_fileManager asyncCreateFileWithName:@"11" completed:^(FlyImageDataFile* dataFile) { 59 | XCTAssert( dataFile != nil ); 60 | 61 | sum++; 62 | if ( sum == 100 ){ 63 | [expectation fulfill]; 64 | } 65 | }]; 66 | } 67 | 68 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 69 | } 70 | 71 | - (void)test12CreateSameName 72 | { 73 | XCTestExpectation* expectation = [self expectationWithDescription:@"test12CreateSameName"]; 74 | 75 | [_fileManager asyncCreateFileWithName:@"10" completed:^(FlyImageDataFile* dataFile) { 76 | XCTAssert( dataFile != nil ); 77 | [expectation fulfill]; 78 | }]; 79 | 80 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 81 | } 82 | 83 | - (void)test13CreateMultipleNames 84 | { 85 | XCTestExpectation* expectation = [self expectationWithDescription:@"test13CreateMultipleNames"]; 86 | 87 | __block int sum = 0; 88 | for (int i = 1; i <= 100; i++) { 89 | [_fileManager asyncCreateFileWithName:[NSString stringWithFormat:@"%d", i] completed:^(FlyImageDataFile* dataFile) { 90 | XCTAssert( dataFile != nil ); 91 | 92 | sum++; 93 | if ( sum == 100 ){ 94 | [expectation fulfill]; 95 | } 96 | }]; 97 | } 98 | 99 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 100 | } 101 | 102 | - (void)test15SyncCreate 103 | { 104 | id dataFile = [_fileManager createFileWithName:@"100"]; 105 | XCTAssert(dataFile != nil); 106 | } 107 | 108 | - (void)test20IsExist 109 | { 110 | XCTAssert([_fileManager isFileExistWithName:@"10"]); 111 | XCTAssert([_fileManager isFileExistWithName:@"11"]); 112 | 113 | XCTAssert(![_fileManager isFileExistWithName:@"NotExist"]); 114 | } 115 | 116 | - (void)test30Retrieve 117 | { 118 | FlyImageDataFile* file10 = [_fileManager retrieveFileWithName:@"10"]; 119 | XCTAssert(file10 != nil); 120 | 121 | FlyImageDataFile* file11 = [_fileManager retrieveFileWithName:@"11"]; 122 | XCTAssert(file11 != nil); 123 | 124 | FlyImageDataFile* fileNotExist = [_fileManager retrieveFileWithName:@"NotExist"]; 125 | XCTAssert(fileNotExist == nil); 126 | } 127 | 128 | - (void)test50Remove 129 | { 130 | [_fileManager removeFileWithName:@"10"]; 131 | 132 | XCTAssert(![_fileManager isFileExistWithName:@"10"]); 133 | } 134 | 135 | - (void)test90Purge 136 | { 137 | XCTestExpectation* expectation = [self expectationWithDescription:@"test90Purge"]; 138 | 139 | [_fileManager purgeWithExceptions:@[ @"11", @"10" ] toSize:0 completed:^(NSUInteger fileCount, NSUInteger totalSize) { 140 | XCTAssert( fileCount > 0 ); 141 | XCTAssert( totalSize > 0 ); 142 | [expectation fulfill]; 143 | }]; 144 | 145 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 146 | } 147 | 148 | - (void)test99PurgeAll 149 | { 150 | XCTestExpectation* expectation = [self expectationWithDescription:@"test99PurgeAll"]; 151 | 152 | [_fileManager purgeWithExceptions:nil toSize:0 completed:^(NSUInteger fileCount, NSUInteger totalSize) { 153 | XCTAssert( fileCount == 0 ); 154 | XCTAssert( totalSize == 0 ); 155 | [expectation fulfill]; 156 | }]; 157 | 158 | [self waitForExpectationsWithTimeout:1000 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 159 | } 160 | 161 | @end 162 | -------------------------------------------------------------------------------- /FlyImageTests/FlyImageDataFileTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageDataFileTests.m 3 | // Demo 4 | // 5 | // Created by Norris Tong on 16/3/20. 6 | // Copyright © 2016年 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageDataFile.h" 11 | #import "FlyImageUtils.h" 12 | #import 13 | 14 | @interface FlyImageDataFileTests : XCTestCase 15 | 16 | @end 17 | 18 | static FlyImageDataFile* _dataFile; 19 | static size_t kTestLength = 4096 * 10; 20 | static int kTestCount = 50; 21 | static CGFloat imageWidth = 1920.0; 22 | static CGFloat imageHeight = 1200.0; 23 | static NSString* testDataFileName = @"testDataFile"; 24 | 25 | @implementation FlyImageDataFileTests 26 | 27 | - (void)setUp 28 | { 29 | [super setUp]; 30 | 31 | // Put setup code here. This method is called before the invocation of each test method in the class. 32 | if (_dataFile == nil) { 33 | 34 | NSString* filePath = [[FlyImageUtils directoryPath] stringByAppendingPathComponent:testDataFileName]; 35 | BOOL isFileExist = [[NSFileManager defaultManager] fileExistsAtPath:filePath]; 36 | if (!isFileExist) { 37 | [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]; 38 | } 39 | 40 | _dataFile = [[FlyImageDataFile alloc] initWithPath:filePath]; 41 | } 42 | } 43 | 44 | - (void)tearDown 45 | { 46 | // Put teardown code here. This method is called after the invocation of each test method in the class. 47 | [super tearDown]; 48 | } 49 | 50 | - (void)test1Open 51 | { 52 | XCTAssert([_dataFile open]); 53 | } 54 | 55 | - (void)test20PrepareAppendDataWrongLength 56 | { 57 | XCTAssert(![_dataFile prepareAppendDataWithOffset:0 length:200 * 1024 * 1024]); 58 | } 59 | 60 | - (void)test30AppendData 61 | { 62 | BOOL ret = YES; 63 | 64 | for (int i = 0; i < kTestCount; i++) { 65 | ret &= [_dataFile prepareAppendDataWithOffset:_dataFile.pointer length:kTestLength]; 66 | memset(_dataFile.address, 1, kTestLength); 67 | ret &= [_dataFile appendDataWithOffset:_dataFile.pointer length:kTestLength]; 68 | 69 | if (!ret) { 70 | break; 71 | } 72 | } 73 | 74 | XCTAssert(ret, @"Pass"); 75 | } 76 | 77 | - (void)createImageAtPath:(NSString*)path 78 | { 79 | // generate an image with special size 80 | CGRect rect = CGRectMake(0.0f, 0.0f, imageWidth, imageHeight); 81 | UIGraphicsBeginImageContext(rect.size); 82 | CGContextRef context = UIGraphicsGetCurrentContext(); 83 | CGContextSetFillColorWithColor(context, [[UIColor redColor] CGColor]); 84 | CGContextFillRect(context, rect); 85 | UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); 86 | UIGraphicsEndImageContext(); 87 | 88 | NSData* imageData = UIImagePNGRepresentation(image); 89 | [imageData writeToFile:path atomically:YES]; 90 | } 91 | 92 | - (void)test50Memcpy 93 | { 94 | XCTestExpectation* expectation = [self expectationWithDescription:@"memcpy"]; 95 | 96 | NSString* directoryPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; 97 | NSString* imagePath = [directoryPath stringByAppendingPathComponent:@"image"]; 98 | [self createImageAtPath:imagePath]; 99 | 100 | NSData* data = [NSData dataWithContentsOfFile:imagePath]; 101 | UIImage* image = [UIImage imageWithData:data]; 102 | CGSize imageSize = image.size; 103 | 104 | [data enumerateByteRangesUsingBlock:^(const void* _Nonnull bytes, NSRange byteRange, BOOL* _Nonnull stop) { 105 | 106 | size_t length = byteRange.length; 107 | ssize_t pageSize = [FlyImageUtils pageSize]; 108 | size_t correctLength = ceil((length / pageSize )) * pageSize; 109 | 110 | BOOL ret = YES; 111 | ret &= [_dataFile prepareAppendDataWithOffset:0 length:correctLength]; 112 | memcpy(_dataFile.address, bytes, correctLength); 113 | ret &= [_dataFile appendDataWithOffset:0 length:correctLength]; 114 | 115 | XCTAssert( ret, @"Pass" ); 116 | 117 | // Create CGImageRef whose backing store *is* the mapped image table entry. We avoid a memcpy this way. 118 | CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, _dataFile.address, correctLength, nil); 119 | 120 | CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; 121 | NSInteger bitsPerComponent = 8; 122 | NSInteger bitsPerPixel = 4 * 8; 123 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 124 | 125 | static NSInteger bytesPerPixel = 4; 126 | static float kAlignment = 64; 127 | size_t bytesPerRow = ceil((imageSize.width * bytesPerPixel) / kAlignment) * kAlignment; 128 | 129 | CGImageRef imageRef = CGImageCreate(imageSize.width, 130 | imageSize.height, 131 | bitsPerComponent, 132 | bitsPerPixel, 133 | bytesPerRow, 134 | colorSpace, 135 | bitmapInfo, 136 | dataProvider, 137 | NULL, 138 | false, 139 | (CGColorRenderingIntent)0); 140 | 141 | CGDataProviderRelease(dataProvider); 142 | CGColorSpaceRelease(colorSpace); 143 | 144 | UIImage *revertImage = [UIImage imageWithCGImage:imageRef]; 145 | XCTAssert( revertImage != nil, @"Pass" ); 146 | 147 | CGSize revertSize = revertImage.size; 148 | XCTAssert( imageSize.width == revertSize.width && imageSize.height == imageSize.height, @"Pass" ); 149 | 150 | [expectation fulfill]; 151 | }]; 152 | 153 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { 154 | [[NSFileManager defaultManager] removeItemAtPath:imagePath error:nil]; 155 | 156 | XCTAssert(YES, @"Pass"); 157 | }]; 158 | } 159 | 160 | - (void)test99Remove 161 | { 162 | [_dataFile close]; 163 | _dataFile = nil; 164 | 165 | NSString* filePath = [[FlyImageUtils directoryPath] stringByAppendingPathComponent:testDataFileName]; 166 | [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; 167 | 168 | XCTAssert(YES, @"Pass"); 169 | } 170 | 171 | @end 172 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageUtils.m 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/18/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "FlyImageUtils.h" 10 | 11 | @implementation FlyImageUtils 12 | 13 | + (NSString*)directoryPath 14 | { 15 | 16 | static NSString* __directoryPath = nil; 17 | 18 | static dispatch_once_t onceToken; 19 | dispatch_once(&onceToken, ^{ 20 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 21 | __directoryPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"flyImage"]; 22 | 23 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 24 | BOOL directoryExists = [fileManager fileExistsAtPath:__directoryPath]; 25 | if (directoryExists == NO) { 26 | [fileManager createDirectoryAtPath:__directoryPath withIntermediateDirectories:YES attributes:nil error:nil]; 27 | } 28 | }); 29 | 30 | return __directoryPath; 31 | } 32 | 33 | + (CGFloat)contentsScale 34 | { 35 | 36 | static CGFloat __contentsScale = 1; 37 | static dispatch_once_t onceToken; 38 | dispatch_once(&onceToken, ^{ 39 | __contentsScale = [UIScreen mainScreen].scale; 40 | }); 41 | 42 | return __contentsScale; 43 | } 44 | 45 | + (NSString*)clientVersion 46 | { 47 | 48 | static NSString* __clientVersion = nil; 49 | static dispatch_once_t onceToken; 50 | dispatch_once(&onceToken, ^{ 51 | NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; 52 | NSString *build = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"]; 53 | 54 | __clientVersion = [version stringByAppendingString:build]; 55 | }); 56 | 57 | return __clientVersion; 58 | } 59 | 60 | + (int)pageSize 61 | { 62 | static int __pageSize = 0; 63 | 64 | static dispatch_once_t onceToken; 65 | dispatch_once(&onceToken, ^{ 66 | __pageSize = getpagesize(); 67 | }); 68 | 69 | return __pageSize; 70 | } 71 | 72 | + (ImageContentType)contentTypeForImageData:(NSData*)data 73 | { 74 | uint8_t c; 75 | [data getBytes:&c length:1]; 76 | switch (c) { 77 | case 0xFF: 78 | return ImageContentTypeJPEG; 79 | case 0x89: 80 | return ImageContentTypePNG; 81 | case 0x47: 82 | return ImageContentTypeGif; 83 | case 0x49: 84 | case 0x4D: 85 | return ImageContentTypeTiff; 86 | case 0x52: 87 | // R as RIFF for WEBP 88 | if ([data length] < 12) { 89 | return ImageContentTypeUnknown; 90 | } 91 | 92 | NSString* testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding]; 93 | if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) { 94 | return ImageContentTypeWebP; 95 | } 96 | 97 | return ImageContentTypeUnknown; 98 | } 99 | return ImageContentTypeUnknown; 100 | } 101 | 102 | // from FastImageCache 103 | CGMutablePathRef _FICDCreateRoundedRectPath(CGRect rect, CGFloat cornerRadius) 104 | { 105 | CGMutablePathRef path = CGPathCreateMutable(); 106 | 107 | CGFloat minX = CGRectGetMinX(rect); 108 | CGFloat midX = CGRectGetMidX(rect); 109 | CGFloat maxX = CGRectGetMaxX(rect); 110 | CGFloat minY = CGRectGetMinY(rect); 111 | CGFloat midY = CGRectGetMidY(rect); 112 | CGFloat maxY = CGRectGetMaxY(rect); 113 | 114 | CGPathMoveToPoint(path, NULL, minX, midY); 115 | CGPathAddArcToPoint(path, NULL, minX, maxY, midX, maxY, cornerRadius); 116 | CGPathAddArcToPoint(path, NULL, maxX, maxY, maxX, midY, cornerRadius); 117 | CGPathAddArcToPoint(path, NULL, maxX, minY, midX, minY, cornerRadius); 118 | CGPathAddArcToPoint(path, NULL, minX, minY, minX, midY, cornerRadius); 119 | 120 | return path; 121 | } 122 | 123 | CGRect _FlyImageCalcDrawBounds(CGSize imageSize, CGSize targetSize, NSString* const contentsGravity) 124 | { 125 | 126 | CGFloat x, y, width, height; 127 | if ([contentsGravity isEqualToString:kCAGravityCenter]) { 128 | 129 | x = (targetSize.width - imageSize.width) / 2; 130 | y = (targetSize.height - imageSize.height) / 2; 131 | width = imageSize.width; 132 | height = imageSize.height; 133 | 134 | } else if ([contentsGravity isEqualToString:kCAGravityTop]) { 135 | 136 | x = (targetSize.width - imageSize.width) / 2; 137 | y = targetSize.height - imageSize.height; 138 | width = imageSize.width; 139 | height = imageSize.height; 140 | 141 | } else if ([contentsGravity isEqualToString:kCAGravityBottom]) { 142 | 143 | x = (targetSize.width - imageSize.width) / 2; 144 | y = 0; 145 | width = imageSize.width; 146 | height = imageSize.height; 147 | 148 | } else if ([contentsGravity isEqualToString:kCAGravityLeft]) { 149 | 150 | x = 0; 151 | y = (targetSize.height - imageSize.height) / 2; 152 | width = imageSize.width; 153 | height = imageSize.height; 154 | 155 | } else if ([contentsGravity isEqualToString:kCAGravityRight]) { 156 | 157 | x = targetSize.width - imageSize.width; 158 | y = (targetSize.height - imageSize.height) / 2; 159 | width = imageSize.width; 160 | height = imageSize.height; 161 | 162 | } else if ([contentsGravity isEqualToString:kCAGravityTopLeft]) { 163 | 164 | x = 0; 165 | y = targetSize.height - imageSize.height; 166 | width = imageSize.width; 167 | height = imageSize.height; 168 | 169 | } else if ([contentsGravity isEqualToString:kCAGravityTopRight]) { 170 | 171 | x = targetSize.width - imageSize.width; 172 | y = targetSize.height - imageSize.height; 173 | width = imageSize.width; 174 | height = imageSize.height; 175 | 176 | } else if ([contentsGravity isEqualToString:kCAGravityBottomLeft]) { 177 | 178 | x = 0; 179 | y = 0; 180 | width = imageSize.width; 181 | height = imageSize.height; 182 | 183 | } else if ([contentsGravity isEqualToString:kCAGravityBottomRight]) { 184 | 185 | x = targetSize.width - imageSize.width; 186 | y = 0; 187 | width = imageSize.width; 188 | height = imageSize.height; 189 | 190 | } else if ([contentsGravity isEqualToString:kCAGravityResizeAspectFill]) { 191 | 192 | CGFloat scaleWidth = targetSize.width / imageSize.width; 193 | CGFloat scaleHeight = targetSize.height / imageSize.height; 194 | 195 | if (scaleWidth < scaleHeight) { 196 | y = 0; 197 | height = targetSize.height; 198 | width = scaleHeight * imageSize.width; 199 | x = (targetSize.width - width) / 2; 200 | } else { 201 | x = 0; 202 | width = targetSize.width; 203 | height = scaleWidth * imageSize.height; 204 | y = (targetSize.height - height) / 2; 205 | } 206 | } else if ([contentsGravity isEqualToString:kCAGravityResize]) { 207 | 208 | x = y = 0; 209 | width = targetSize.width; 210 | height = targetSize.height; 211 | 212 | } else { 213 | 214 | // kCAGravityResizeAspect 215 | CGFloat scaleWidth = targetSize.width / imageSize.width; 216 | CGFloat scaleHeight = targetSize.height / imageSize.height; 217 | 218 | if (scaleWidth > scaleHeight) { 219 | y = 0; 220 | height = targetSize.height; 221 | width = scaleHeight * imageSize.width; 222 | x = (targetSize.width - width) / 2; 223 | } else { 224 | x = 0; 225 | width = targetSize.width; 226 | height = scaleWidth * imageSize.height; 227 | y = (targetSize.height - height) / 2; 228 | } 229 | } 230 | 231 | return CGRectMake(x, y, width, height); 232 | } 233 | 234 | @end 235 | -------------------------------------------------------------------------------- /FlyImageTests/FlyImageCacheTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageCacheTests.m 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/3/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageCache.h" 11 | #import "FlyImageDataFIleManager.h" 12 | 13 | @interface FlyImageCacheTests : XCTestCase 14 | 15 | @end 16 | 17 | static FlyImageCache* _imageCache; 18 | static CGFloat imageWidth = 1920.0; 19 | static CGFloat imageHeight = 1200.0; 20 | static FlyImageDataFileManager* _fileManager; 21 | static int kMultipleTimes = 15; 22 | 23 | @implementation FlyImageCacheTests 24 | 25 | - (void)setUp 26 | { 27 | [super setUp]; 28 | // Put setup code here. This method is called before the invocation of each test method in the class. 29 | if (_imageCache == nil) { 30 | _imageCache = [FlyImageCache sharedInstance]; 31 | _fileManager = [_imageCache valueForKey:@"dataFileManager"]; 32 | } 33 | } 34 | 35 | - (void)tearDown 36 | { 37 | // Put teardown code here. This method is called after the invocation of each test method in the class. 38 | [super tearDown]; 39 | } 40 | 41 | - (void)addImageFile:(NSString*)name 42 | { 43 | // generate an image with special size 44 | CGRect rect = CGRectMake(0.0f, 0.0f, imageWidth, imageHeight); 45 | UIGraphicsBeginImageContext(rect.size); 46 | CGContextRef context = UIGraphicsGetCurrentContext(); 47 | CGContextSetFillColorWithColor(context, [[UIColor redColor] CGColor]); 48 | CGContextFillRect(context, rect); 49 | UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); 50 | UIGraphicsEndImageContext(); 51 | 52 | NSData* imageData = UIImagePNGRepresentation(image); 53 | NSString* directoryPath = [_fileManager folderPath]; 54 | NSString* imagePath = [directoryPath stringByAppendingPathComponent:name]; 55 | [imageData writeToFile:imagePath atomically:YES]; 56 | 57 | [_fileManager addExistFileName:name]; 58 | } 59 | 60 | - (void)drawALineInContext:(CGContextRef)context rect:(CGRect)rect 61 | { 62 | UIGraphicsPushContext(context); 63 | 64 | CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor); 65 | CGContextSetLineWidth(context, 10.0); 66 | CGContextMoveToPoint(context, 0.0, 0.0); 67 | CGContextAddLineToPoint(context, rect.size.width, rect.size.height); 68 | 69 | UIGraphicsPopContext(); 70 | } 71 | 72 | - (void)test10AddImage 73 | { 74 | XCTestExpectation* expectation = [self expectationWithDescription:@"test10AddImage"]; 75 | 76 | NSString* filename = @"10"; 77 | [self addImageFile:filename]; 78 | 79 | [_imageCache addImageWithKey:filename 80 | filename:filename 81 | completed:^(NSString* key, UIImage* image) { 82 | XCTAssert( image.size.width == imageWidth ); 83 | XCTAssert( image.size.height == imageHeight ); 84 | 85 | [expectation fulfill]; 86 | }]; 87 | 88 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 89 | } 90 | 91 | - (void)test11AddMultipleTimes 92 | { 93 | XCTestExpectation* expectation = [self expectationWithDescription:@"test11AddMultipleTimes"]; 94 | 95 | NSString* filename = @"11"; 96 | [self addImageFile:filename]; 97 | 98 | __block int sum = 0; 99 | for (int i = 0; i < kMultipleTimes; i++) { 100 | 101 | [_imageCache addImageWithKey:filename 102 | filename:filename 103 | completed:^(NSString* key, UIImage* image) { 104 | XCTAssert( image.size.width == imageWidth ); 105 | XCTAssert( image.size.height == imageHeight ); 106 | 107 | sum++; 108 | if ( sum == kMultipleTimes ){ 109 | [expectation fulfill]; 110 | } 111 | }]; 112 | } 113 | 114 | [self waitForExpectationsWithTimeout:30 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 115 | } 116 | 117 | - (void)test13AddMultipleKeys 118 | { 119 | XCTestExpectation* expectation = [self expectationWithDescription:@"test13AddMultipleKeys"]; 120 | 121 | __block int sum = 0; 122 | for (int i = 1; i <= kMultipleTimes; i++) { 123 | 124 | NSString* filename = [NSString stringWithFormat:@"%d", i]; 125 | [self addImageFile:filename]; 126 | 127 | [_imageCache addImageWithKey:filename 128 | filename:filename 129 | completed:^(NSString* key, UIImage* image) { 130 | XCTAssert( image.size.width == imageWidth ); 131 | XCTAssert( image.size.height == imageHeight ); 132 | 133 | sum++; 134 | if ( sum == kMultipleTimes ){ 135 | [expectation fulfill]; 136 | } 137 | }]; 138 | } 139 | 140 | [self waitForExpectationsWithTimeout:100 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 141 | } 142 | 143 | - (void)test30AsyncGetImage 144 | { 145 | XCTestExpectation* expectation = [self expectationWithDescription:@"test30AsyncGetImage"]; 146 | 147 | [_imageCache asyncGetImageWithKey:@"10" 148 | completed:^(NSString* key, UIImage* image) { 149 | XCTAssert( image.size.width == imageWidth ); 150 | XCTAssert( image.size.height == imageHeight ); 151 | 152 | [expectation fulfill]; 153 | }]; 154 | 155 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 156 | } 157 | 158 | - (void)test30AsyncGetImageMultipleTimes 159 | { 160 | XCTestExpectation* expectation = [self expectationWithDescription:@"test30AsyncGetImageMultipleTimes"]; 161 | 162 | NSString* filename = @"10"; 163 | 164 | __block int sum = 0; 165 | for (int i = 0; i < kMultipleTimes; i++) { 166 | [_imageCache asyncGetImageWithKey:filename 167 | drawSize:CGSizeMake(500, 800) 168 | contentsGravity:kCAGravityResizeAspect 169 | cornerRadius:0 170 | completed:^(NSString* key, UIImage* image) { 171 | XCTAssert( image.size.width == 500 ); 172 | XCTAssert( image.size.height == 800 ); 173 | 174 | sum++; 175 | if ( sum == kMultipleTimes ){ 176 | [expectation fulfill]; 177 | } 178 | }]; 179 | } 180 | 181 | [self waitForExpectationsWithTimeout:30 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 182 | } 183 | 184 | - (void)test50RemoveImage 185 | { 186 | NSString* imageKey = @"11"; 187 | [_imageCache removeImageWithKey:imageKey]; 188 | XCTAssert(![_imageCache isImageExistWithKey:imageKey]); 189 | } 190 | 191 | - (void)test60ImagePath 192 | { 193 | XCTAssert([_imageCache imagePathWithKey:@"10"] != nil); 194 | XCTAssert([_imageCache imagePathWithKey:@"11"] == nil); 195 | } 196 | 197 | - (void)test80ChangeImageKey 198 | { 199 | XCTestExpectation* expectation = [self expectationWithDescription:@"test80ChangeImageKey"]; 200 | 201 | [_imageCache changeImageKey:@"10" newKey:@"newKey"]; 202 | XCTAssert(![_imageCache isImageExistWithKey:@"10"]); 203 | XCTAssert([_imageCache isImageExistWithKey:@"newKey"]); 204 | 205 | [_imageCache asyncGetImageWithKey:@"newKey" completed:^(NSString* key, UIImage* image) { 206 | XCTAssert( image.size.width == imageWidth ); 207 | XCTAssert( image.size.height == imageHeight ); 208 | 209 | [expectation fulfill]; 210 | }]; 211 | 212 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 213 | } 214 | 215 | - (void)test90Purge 216 | { 217 | [_imageCache purge]; 218 | XCTAssert(![_imageCache isImageExistWithKey:@"10"]); 219 | } 220 | 221 | @end 222 | -------------------------------------------------------------------------------- /FlyImageTests/FlyImageDownloadManagerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageDownloaderTests.m 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/4/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageDownloader.h" 11 | 12 | @interface DownloadManagerClient : NSObject 13 | @property (nonatomic, strong) XCTestExpectation* expectation; 14 | @end 15 | 16 | static const NSString* kAssetHost = @"https://flyimage.oss-us-west-1.aliyuncs.com/"; 17 | static int kMultipleTimes = 15; 18 | 19 | @implementation DownloadManagerClient 20 | - (void)FlyImageDownloader:(FlyImageDownloader*)manager 21 | willSendRequest:(NSURLRequest*)request 22 | { 23 | NSAssert(request != nil, nil); 24 | } 25 | 26 | - (void)FlyImageDownloader:(FlyImageDownloader*)manager 27 | didReceiveResponse:(NSURLResponse*)response 28 | filePath:(NSURL*)filePath 29 | error:(NSError*)error 30 | request:(NSURLRequest*)request 31 | { 32 | NSAssert(request != nil, nil); 33 | 34 | [self.expectation fulfill]; 35 | } 36 | 37 | - (void)FlyImageDownloader:(FlyImageDownloader*)manager 38 | willCancelRequest:(NSURLRequest*)request 39 | { 40 | NSAssert(request != nil, nil); 41 | 42 | [self.expectation fulfill]; 43 | } 44 | @end 45 | 46 | @interface FlyImageDownloaderTests : XCTestCase 47 | 48 | @end 49 | 50 | static FlyImageDownloader* _downloadManager; 51 | 52 | @implementation FlyImageDownloaderTests 53 | 54 | - (void)setUp 55 | { 56 | [super setUp]; 57 | // Put setup code here. This method is called before the invocation of each test method in the class. 58 | if (_downloadManager == nil) { 59 | _downloadManager = [FlyImageDownloader sharedInstance]; 60 | } 61 | } 62 | 63 | - (void)tearDown 64 | { 65 | // Put teardown code here. This method is called after the invocation of each test method in the class. 66 | [super tearDown]; 67 | 68 | _downloadManager.delegate = nil; 69 | } 70 | 71 | - (void)test10Success 72 | { 73 | XCTestExpectation* expectation = [self expectationWithDescription:@"test10AddImage"]; 74 | NSString* imagePath = [kAssetHost stringByAppendingPathComponent:@"10.jpg"]; 75 | 76 | NSURL* url = [NSURL URLWithString:imagePath]; 77 | NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url]; 78 | request.timeoutInterval = 30; 79 | [_downloadManager downloadImageForURLRequest:request progress:^(float percentage) { 80 | XCTAssert( percentage >= 0 && percentage <= 1 ); 81 | } success:^(NSURLRequest* request, NSURL* filePath) { 82 | 83 | NSData *data = [[NSData alloc] initWithContentsOfURL:filePath]; 84 | UIImage *image = [UIImage imageWithData:data]; 85 | XCTAssert( image.size.width == 1024 ); 86 | 87 | [expectation fulfill]; 88 | 89 | } failed:^(NSURLRequest* request, NSError* error) { 90 | XCTAssert( NO ); 91 | 92 | [expectation fulfill]; 93 | }]; 94 | 95 | [self waitForExpectationsWithTimeout:60 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 96 | } 97 | 98 | - (void)test30Failed 99 | { 100 | XCTestExpectation* expectation = [self expectationWithDescription:@"test30Failed"]; 101 | NSString* imagePath = [kAssetHost stringByAppendingPathComponent:@"xxx"]; 102 | 103 | NSURL* url = [NSURL URLWithString:imagePath]; 104 | NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url]; 105 | request.timeoutInterval = 30; 106 | [_downloadManager downloadImageForURLRequest:request progress:nil success:^(NSURLRequest* request, NSURL* filePath) { 107 | XCTAssert( NO ); 108 | 109 | [expectation fulfill]; 110 | 111 | } failed:^(NSURLRequest* request, NSError* error) { 112 | XCTAssert( error != nil ); 113 | 114 | [expectation fulfill]; 115 | }]; 116 | 117 | [self waitForExpectationsWithTimeout:60 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 118 | } 119 | 120 | - (void)test31FailedMultiple 121 | { 122 | XCTestExpectation* expectation = [self expectationWithDescription:@"test31FailedMultiple"]; 123 | NSString* imagePath = [kAssetHost stringByAppendingPathComponent:@"xxx"]; 124 | 125 | __block int sum = 0; 126 | for (int i = 0; i < kMultipleTimes; i++) { 127 | NSURL* url = [NSURL URLWithString:imagePath]; 128 | NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url]; 129 | request.timeoutInterval = 5; 130 | [_downloadManager downloadImageForURLRequest:request progress:nil success:^(NSURLRequest* request, NSURL* filePath) { 131 | XCTAssert( NO ); 132 | } failed:^(NSURLRequest* request, NSError* error) { 133 | XCTAssert( error != nil ); 134 | 135 | sum++; 136 | if ( sum == kMultipleTimes ){ 137 | [expectation fulfill]; 138 | } 139 | }]; 140 | } 141 | 142 | [self waitForExpectationsWithTimeout:60 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 143 | } 144 | 145 | - (void)test50CancelTask 146 | { 147 | 148 | XCTestExpectation* expectation = [self expectationWithDescription:@"test90Cancel"]; 149 | NSString* imagePath = [kAssetHost stringByAppendingPathComponent:@"11.jpg"]; 150 | 151 | NSURL* url = [NSURL URLWithString:imagePath]; 152 | NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url]; 153 | FlyImageDownloadHandlerId* handlerId = [_downloadManager downloadImageForURLRequest:request 154 | progress:nil 155 | success:^(NSURLRequest* request, NSURL* filePath) { 156 | XCTAssert( NO ); 157 | [expectation fulfill]; 158 | } 159 | failed:^(NSURLRequest* request, NSError* error) { 160 | XCTAssert( error != nil ); 161 | [expectation fulfill]; 162 | }]; 163 | 164 | [_downloadManager cancelDownloadHandler:handlerId]; 165 | 166 | [self waitForExpectationsWithTimeout:60 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 167 | } 168 | 169 | - (void)test51CancelHandler 170 | { 171 | 172 | XCTestExpectation* expectation = [self expectationWithDescription:@"test91CancelMultipleTimes"]; 173 | NSString* imagePath = [kAssetHost stringByAppendingPathComponent:@"12.jpg"]; 174 | 175 | NSURL* url = [NSURL URLWithString:imagePath]; 176 | NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url]; 177 | FlyImageDownloadHandlerId* handlerId = [_downloadManager downloadImageForURLRequest:request 178 | progress:nil 179 | success:^(NSURLRequest* request, NSURL* filePath) { 180 | XCTAssert( NO ); 181 | [expectation fulfill]; 182 | } 183 | failed:^(NSURLRequest* request, NSError* error) { 184 | XCTAssert( error != nil ); 185 | [expectation fulfill]; 186 | }]; 187 | 188 | for (int i = 0; i < kMultipleTimes; i++) { 189 | [_downloadManager downloadImageForURLRequest:request 190 | progress:nil 191 | success:^(NSURLRequest* request, NSURL* filePath) { 192 | XCTAssert( NO ); 193 | [expectation fulfill]; 194 | } 195 | failed:^(NSURLRequest* request, NSError* error) { 196 | XCTAssert( NO ); 197 | [expectation fulfill]; 198 | }]; 199 | } 200 | 201 | [_downloadManager cancelDownloadHandler:handlerId]; 202 | 203 | [self waitForExpectationsWithTimeout:60 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 204 | } 205 | 206 | - (void)test90DelegateSend 207 | { 208 | XCTestExpectation* expectation = [self expectationWithDescription:@"test50Delegate"]; 209 | 210 | DownloadManagerClient* client = [[DownloadManagerClient alloc] init]; 211 | client.expectation = expectation; 212 | _downloadManager.delegate = client; 213 | 214 | NSString* imagePath = [kAssetHost stringByAppendingPathComponent:@"13.jpg"]; 215 | NSURL* url = [NSURL URLWithString:imagePath]; 216 | NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url]; 217 | [_downloadManager downloadImageForURLRequest:request]; 218 | 219 | [self waitForExpectationsWithTimeout:60 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 220 | } 221 | 222 | - (void)test91DelegateCancel 223 | { 224 | XCTestExpectation* expectation = [self expectationWithDescription:@"test50Delegate"]; 225 | 226 | DownloadManagerClient* client = [[DownloadManagerClient alloc] init]; 227 | client.expectation = expectation; 228 | _downloadManager.delegate = client; 229 | 230 | NSString* imagePath = [kAssetHost stringByAppendingPathComponent:@"14.jpg"]; 231 | NSURL* url = [NSURL URLWithString:imagePath]; 232 | NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url]; 233 | FlyImageDownloadHandlerId* handlerId = [_downloadManager downloadImageForURLRequest:request]; 234 | [_downloadManager cancelDownloadHandler:handlerId]; 235 | 236 | [self waitForExpectationsWithTimeout:60 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 237 | } 238 | 239 | @end 240 | -------------------------------------------------------------------------------- /FlyImage/UI/FlyImageRenderer.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageRenderer.m 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/11/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "FlyImageRenderer.h" 10 | #import "FlyImageCache.h" 11 | #import "FlyImageUtils.h" 12 | #import "FlyImageDownloader.h" 13 | 14 | @implementation FlyImageRenderer { 15 | NSString* _placeHolderImageName; 16 | NSURL* _thumbnailURL; 17 | NSURL* _originalURL; 18 | 19 | CGSize _drawSize; 20 | NSString* _contentsGravity; 21 | CGFloat _cornerRadius; 22 | 23 | FlyImageDownloadHandlerId* _downloadHandlerId; 24 | } 25 | 26 | - (instancetype)init 27 | { 28 | if (self = [super init]) { 29 | // event 30 | if ([FlyImageCache sharedInstance].autoDismissImage) { 31 | [[NSNotificationCenter defaultCenter] addObserver:self 32 | selector:@selector(applicationWillEnterForeground:) 33 | name:UIApplicationWillEnterForegroundNotification 34 | object:nil]; 35 | 36 | [[NSNotificationCenter defaultCenter] addObserver:self 37 | selector:@selector(applicationDidEnterBackground:) 38 | name:UIApplicationDidEnterBackgroundNotification 39 | object:nil]; 40 | } 41 | } 42 | return self; 43 | } 44 | 45 | - (void)dealloc 46 | { 47 | [self cancelDownload]; 48 | 49 | if ([FlyImageCache sharedInstance].autoDismissImage) { 50 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; 51 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; 52 | } 53 | } 54 | 55 | - (void)applicationDidEnterBackground:(UIApplication*)application 56 | { 57 | [self cancelDownload]; 58 | 59 | // clear image data to reduce memory 60 | [self renderImage:nil]; 61 | } 62 | 63 | - (void)applicationWillEnterForeground:(UIApplication*)application 64 | { 65 | // repaint 66 | [self render]; 67 | } 68 | 69 | - (void)cancelDownload 70 | { 71 | if (_downloadHandlerId != nil) { 72 | [[FlyImageDownloader sharedInstance] cancelDownloadHandler:_downloadHandlerId]; 73 | _downloadHandlerId = nil; 74 | } 75 | 76 | // try to cancel getting image operation. 77 | if (_originalURL) { 78 | [[FlyImageCache sharedInstance] cancelGetImageWithKey:_originalURL.absoluteString]; 79 | } 80 | 81 | if (_thumbnailURL) { 82 | [[FlyImageCache sharedInstance] cancelGetImageWithKey:_thumbnailURL.absoluteString]; 83 | } 84 | } 85 | 86 | - (void)setPlaceHolderImageName:(NSString*)imageName 87 | thumbnailURL:(NSURL*)thumbnailURL 88 | originalURL:(NSURL*)originalURL 89 | drawSize:(CGSize)drawSize 90 | contentsGravity:(NSString* const)contentsGravity 91 | cornerRadius:(CGFloat)cornerRadius 92 | { 93 | 94 | if (_originalURL != nil && [_originalURL.absoluteString isEqualToString:originalURL.absoluteString]) { 95 | return; 96 | } 97 | 98 | [self cancelDownload]; 99 | 100 | _placeHolderImageName = imageName; 101 | _thumbnailURL = thumbnailURL; 102 | _originalURL = originalURL; 103 | _drawSize = drawSize; 104 | _contentsGravity = contentsGravity; 105 | _cornerRadius = cornerRadius; 106 | 107 | [self render]; 108 | } 109 | 110 | - (void)render 111 | { 112 | // 0. clear 113 | [self renderImage:nil]; 114 | 115 | // if has already downloaded original image 116 | NSString* originalKey = _originalURL.absoluteString; 117 | if (originalKey != nil && [[FlyImageCache sharedInstance] isImageExistWithKey:originalKey]) { 118 | __weak __typeof__(self) weakSelf = self; 119 | [[FlyImageCache sharedInstance] asyncGetImageWithKey:originalKey 120 | drawSize:_drawSize 121 | contentsGravity:_contentsGravity 122 | cornerRadius:_cornerRadius 123 | completed:^(NSString* key, UIImage* image) { 124 | [weakSelf renderOriginalImage:image key:key]; 125 | }]; 126 | return; 127 | } 128 | 129 | // if there is no thumbnail, then render original image 130 | NSString* thumbnailKey = _thumbnailURL.absoluteString; 131 | if (thumbnailKey != nil && [[FlyImageCache sharedInstance] isImageExistWithKey:thumbnailKey]) { 132 | __weak __typeof__(self) weakSelf = self; 133 | [[FlyImageCache sharedInstance] asyncGetImageWithKey:thumbnailKey 134 | drawSize:_drawSize 135 | contentsGravity:_contentsGravity 136 | cornerRadius:_cornerRadius 137 | completed:^(NSString* key, UIImage* image) { 138 | [weakSelf renderThumbnailImage:image key:key]; 139 | }]; 140 | return; 141 | } 142 | 143 | if (_placeHolderImageName != nil) { 144 | UIImage* placeHolderImage = [UIImage imageNamed:_placeHolderImageName]; 145 | [self renderImage:placeHolderImage]; 146 | } 147 | 148 | if (_thumbnailURL == nil && _originalURL != nil) { 149 | [self downloadOriginal]; 150 | return; 151 | } 152 | 153 | if (_thumbnailURL == nil) { 154 | return; 155 | } 156 | 157 | [self downloadThumbnail]; 158 | } 159 | 160 | - (void)downloadThumbnail 161 | { 162 | 163 | __weak __typeof__(self) weakSelf = self; 164 | __block NSURL* downloadingURL = _thumbnailURL; 165 | __block NSString* downloadingKey = downloadingURL.absoluteString; 166 | 167 | NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:downloadingURL]; 168 | request.timeoutInterval = 30; // Default 30 seconds 169 | _downloadHandlerId = [[FlyImageDownloader sharedInstance] 170 | downloadImageForURLRequest:request 171 | progress:^(float progress) { 172 | if ( [_delegate respondsToSelector:@selector(flyImageRenderer:didDownloadImageURL:progress:)] ){ 173 | [_delegate flyImageRenderer:weakSelf didDownloadImageURL:downloadingURL progress:progress]; 174 | } 175 | } 176 | success:^(NSURLRequest* request, NSURL* filePath) { 177 | _downloadHandlerId = nil; 178 | 179 | [[FlyImageCache sharedInstance] addImageWithKey:downloadingKey 180 | filename:filePath.lastPathComponent 181 | drawSize:_drawSize 182 | contentsGravity:_contentsGravity 183 | cornerRadius:_cornerRadius 184 | completed:^(NSString *key, UIImage *image) { 185 | [weakSelf renderThumbnailImage:image key:key]; 186 | }]; 187 | 188 | } 189 | failed:^(NSURLRequest* request, NSError* error) { 190 | _downloadHandlerId = nil; 191 | 192 | // if error code is cancelled, no need to download original image. 193 | if ( error.code != NSURLErrorCancelled && _originalURL != nil ){ 194 | [weakSelf downloadOriginal]; 195 | } 196 | }]; 197 | } 198 | 199 | - (void)downloadOriginal 200 | { 201 | 202 | __weak __typeof__(self) weakSelf = self; 203 | __block NSURL* downloadingURL = _originalURL; 204 | __block NSString* downloadingKey = downloadingURL.absoluteString; 205 | 206 | NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:downloadingURL]; 207 | request.timeoutInterval = 30; // Default 30 seconds 208 | _downloadHandlerId = [[FlyImageDownloader sharedInstance] 209 | downloadImageForURLRequest:request 210 | progress:^(float progress) { 211 | if ( [_delegate respondsToSelector:@selector(flyImageRenderer:didDownloadImageURL:progress:)] ){ 212 | [_delegate flyImageRenderer:weakSelf didDownloadImageURL:downloadingURL progress:progress]; 213 | } 214 | } 215 | success:^(NSURLRequest* request, NSURL* filePath) { 216 | _downloadHandlerId = nil; 217 | 218 | [[FlyImageCache sharedInstance] addImageWithKey:downloadingKey 219 | filename:filePath.lastPathComponent 220 | drawSize:_drawSize 221 | contentsGravity:_contentsGravity 222 | cornerRadius:_cornerRadius 223 | completed:^(NSString *key, UIImage *image) { 224 | [weakSelf renderOriginalImage:image key:key]; 225 | }]; 226 | 227 | } 228 | failed:^(NSURLRequest* request, NSError* error) { 229 | _downloadHandlerId = nil; 230 | }]; 231 | } 232 | 233 | - (void)renderThumbnailImage:(UIImage*)image key:(NSString*)key 234 | { 235 | dispatch_main_sync_safe(^{ 236 | if ( ![key isEqualToString:_thumbnailURL.absoluteString] ) { 237 | return; 238 | } 239 | 240 | [self renderImage:image]; 241 | 242 | if ( _originalURL != nil ){ 243 | [self downloadOriginal]; 244 | } 245 | }); 246 | } 247 | 248 | - (void)renderOriginalImage:(UIImage*)image key:(NSString*)key 249 | { 250 | dispatch_main_sync_safe(^{ 251 | if ( ![key isEqualToString:_originalURL.absoluteString] ) { 252 | return; 253 | } 254 | 255 | [self renderImage:image]; 256 | }); 257 | } 258 | 259 | - (void)renderImage:(UIImage*)image 260 | { 261 | [_delegate flyImageRenderer:self willRenderImage:image]; 262 | } 263 | 264 | @end 265 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageDataFileManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageDataFileManager.m 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/22/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "FlyImageDataFileManager.h" 10 | #import "FlyImageUtils.h" 11 | 12 | @implementation FlyImageDataFileManager { 13 | NSFileManager* _fileManager; 14 | dispatch_queue_t _fileQueue; 15 | NSMutableDictionary* _fileNames; 16 | NSMutableDictionary* _creatingFiles; 17 | } 18 | 19 | - (instancetype)initWithFolderPath:(NSString*)folderPath 20 | { 21 | if (self = [self init]) { 22 | _folderPath = [folderPath copy]; 23 | 24 | // create a unique queue 25 | NSString* queueName = [@"com.flyimage.filemanager." stringByAppendingString:[[NSUUID UUID] UUIDString]]; 26 | _fileQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL); 27 | 28 | dispatch_sync(_fileQueue, ^{ 29 | _fileManager = [NSFileManager new]; 30 | 31 | [self makeDirectory]; 32 | [self listDirectory]; 33 | [self checkDiskStatus]; 34 | }); 35 | } 36 | return self; 37 | } 38 | 39 | - (void)makeDirectory 40 | { 41 | BOOL isFolderExist = [_fileManager fileExistsAtPath:_folderPath]; 42 | if (!isFolderExist) { 43 | [_fileManager createDirectoryAtPath:_folderPath withIntermediateDirectories:YES attributes:nil error:nil]; 44 | } 45 | } 46 | 47 | - (void)listDirectory 48 | { 49 | _creatingFiles = [[NSMutableDictionary alloc] init]; 50 | 51 | NSArray* filenames = [_fileManager contentsOfDirectoryAtPath:_folderPath error:nil]; 52 | _fileNames = [[NSMutableDictionary alloc] initWithCapacity:[filenames count]]; 53 | for (NSString* filename in filenames) { 54 | [_fileNames setObject:@(1) forKey:filename]; 55 | } 56 | } 57 | 58 | // Execute in the _fileQueue 59 | - (void)checkDiskStatus 60 | { 61 | NSDictionary* fileAttributes = [_fileManager attributesOfFileSystemForPath:@"/" error:nil]; 62 | unsigned long long freeSize = [[fileAttributes objectForKey:NSFileSystemFreeSize] unsignedLongLongValue]; 63 | 64 | // set disk is full when free size is less than 20Mb 65 | _isDiskFull = freeSize < 1024 * 1024 * 20; 66 | } 67 | 68 | - (void)asyncCreateFileWithName:(NSString*)name completed:(void (^)(FlyImageDataFile* dataFile))completed 69 | { 70 | NSParameterAssert(name); 71 | NSParameterAssert(completed); 72 | 73 | // already exist 74 | NSString* filePath = [_folderPath stringByAppendingPathComponent:name]; 75 | if ([self isFileExistWithName:name]) { 76 | FlyImageDataFile* file = [[FlyImageDataFile alloc] initWithPath:filePath]; 77 | completed(file); 78 | return; 79 | } 80 | 81 | // can't add more 82 | if (_isDiskFull) { 83 | completed(nil); 84 | return; 85 | } 86 | 87 | // save all the blocks into _creatingFiles, waiting for callback 88 | @synchronized(_creatingFiles) 89 | { 90 | if ([_creatingFiles objectForKey:name] == nil) { 91 | [_creatingFiles setObject:[NSMutableArray arrayWithObject:completed] forKey:name]; 92 | } else { 93 | NSMutableArray* blocks = [_creatingFiles objectForKey:name]; 94 | [blocks addObject:completed]; 95 | } 96 | } 97 | 98 | dispatch_async(_fileQueue, ^{ 99 | 100 | NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; 101 | [attributes setValue:NSFileProtectionCompleteUnlessOpen forKeyPath:NSFileProtectionKey]; 102 | 103 | BOOL success = [_fileManager createFileAtPath:filePath contents:nil attributes:attributes]; 104 | if ( !success ) { 105 | FlyImageErrorLog(@"can't create file at path %@", filePath); 106 | 107 | // check if the disk is full 108 | [self checkDiskStatus]; 109 | 110 | [self afterCreateFile:nil name:name]; 111 | return; 112 | } 113 | 114 | // update index 115 | @synchronized (_fileNames) { 116 | [_fileNames setObject:@(1) forKey:name]; 117 | } 118 | 119 | FlyImageDataFile *file = [[FlyImageDataFile alloc] initWithPath:filePath]; 120 | [self afterCreateFile:file name:name]; 121 | }); 122 | } 123 | 124 | - (void)afterCreateFile:(FlyImageDataFile*)file name:(NSString*)name 125 | { 126 | 127 | NSArray* blocks = nil; 128 | @synchronized(_creatingFiles) 129 | { 130 | blocks = [[_creatingFiles objectForKey:name] copy]; 131 | [_creatingFiles removeObjectForKey:name]; 132 | } 133 | 134 | dispatch_main_sync_safe(^{ 135 | for ( void (^block)(FlyImageDataFile *dataFile) in blocks) { 136 | block( file ); 137 | } 138 | }); 139 | } 140 | 141 | - (void)addExistFileName:(NSString*)name 142 | { 143 | NSParameterAssert(name); 144 | 145 | @synchronized(_fileNames) 146 | { 147 | [_fileNames setObject:@(1) forKey:name]; 148 | } 149 | } 150 | 151 | - (FlyImageDataFile*)createFileWithName:(NSString*)name 152 | { 153 | NSParameterAssert(name); 154 | 155 | // already exist 156 | NSString* filePath = [_folderPath stringByAppendingPathComponent:name]; 157 | if ([self isFileExistWithName:name]) { 158 | return [[FlyImageDataFile alloc] initWithPath:filePath]; 159 | } 160 | 161 | // can't add more 162 | if (_isDiskFull) { 163 | return nil; 164 | } 165 | 166 | NSMutableDictionary* attributes = [NSMutableDictionary dictionary]; 167 | [attributes setValue:NSFileProtectionCompleteUnlessOpen forKeyPath:NSFileProtectionKey]; 168 | 169 | BOOL success = [_fileManager createFileAtPath:filePath contents:nil attributes:attributes]; 170 | if (!success) { 171 | FlyImageErrorLog(@"can't create file at path %@", filePath); 172 | 173 | // check if the disk is full 174 | [self checkDiskStatus]; 175 | 176 | return nil; 177 | } 178 | 179 | // update index 180 | @synchronized(_fileNames) 181 | { 182 | [_fileNames setObject:@(1) forKey:name]; 183 | } 184 | 185 | return [[FlyImageDataFile alloc] initWithPath:filePath]; 186 | } 187 | 188 | - (BOOL)isFileExistWithName:(NSString*)name 189 | { 190 | NSParameterAssert(name); 191 | 192 | return [_fileNames objectForKey:name] != nil; 193 | } 194 | 195 | - (FlyImageDataFile*)retrieveFileWithName:(NSString*)name 196 | { 197 | NSParameterAssert(name); 198 | 199 | if (![self isFileExistWithName:name]) { 200 | return nil; 201 | } 202 | 203 | NSString* filePath = [_folderPath stringByAppendingPathComponent:name]; 204 | FlyImageDataFile* file = [[FlyImageDataFile alloc] initWithPath:filePath]; 205 | 206 | return file; 207 | } 208 | 209 | - (void)removeFileWithName:(NSString*)name 210 | { 211 | NSParameterAssert(name); 212 | 213 | if (![self isFileExistWithName:name]) { 214 | return; 215 | } 216 | 217 | // remove from the indexes first 218 | @synchronized(_fileNames) 219 | { 220 | [_fileNames removeObjectForKey:name]; 221 | } 222 | 223 | // delete file 224 | dispatch_async(_fileQueue, ^{ 225 | [_fileManager removeItemAtPath:[_folderPath stringByAppendingPathComponent:name] error:nil]; 226 | 227 | [self checkDiskStatus]; 228 | }); 229 | } 230 | 231 | - (void)purgeWithExceptions:(NSArray*)names 232 | toSize:(NSUInteger)toSize 233 | completed:(void (^)(NSUInteger fileCount, NSUInteger totalSize))completed 234 | { 235 | dispatch_async(_fileQueue, ^{ 236 | 237 | // from array to dictionary 238 | NSMutableDictionary *exceptions = [[NSMutableDictionary alloc] initWithCapacity:[names count]]; 239 | for (NSString *name in names) { 240 | [exceptions setObject:@(1) forKey:name]; 241 | } 242 | 243 | NSUInteger totalSize = (NSUInteger)[[_fileManager attributesOfItemAtPath:_folderPath error:nil] fileSize]; 244 | 245 | NSURL *folderURL = [NSURL fileURLWithPath:_folderPath isDirectory:YES]; 246 | NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLNameKey, NSURLTotalFileAllocatedSizeKey]; 247 | 248 | // This enumerator prefetches useful properties for our cache files. 249 | NSDirectoryEnumerator *enumerator = [_fileManager enumeratorAtURL:folderURL 250 | includingPropertiesForKeys:resourceKeys 251 | options:NSDirectoryEnumerationSkipsHiddenFiles 252 | errorHandler:NULL]; 253 | 254 | // TODO SORT 255 | NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init]; 256 | NSMutableArray *namesToDelete = [[NSMutableArray alloc] init]; 257 | for (NSURL *fileURL in enumerator) { 258 | NSNumber *isDirectory; 259 | [fileURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil]; 260 | 261 | if ([isDirectory boolValue]) { 262 | continue; 263 | } 264 | 265 | NSString *fileName; 266 | [fileURL getResourceValue:&fileName forKey:NSURLNameKey error:nil]; 267 | 268 | // dont remove file in exceptions 269 | if ( [exceptions objectForKey:fileName] != nil ) { 270 | continue; 271 | } 272 | 273 | NSNumber *fileSize; 274 | [fileURL getResourceValue:&fileSize forKey:NSURLTotalFileAllocatedSizeKey error:nil]; 275 | 276 | // dont remove more files 277 | totalSize -= [fileSize unsignedLongValue]; 278 | if ( totalSize <= toSize ) { 279 | break; 280 | } 281 | 282 | [urlsToDelete addObject:fileURL]; 283 | [namesToDelete addObject:fileName]; 284 | } 285 | 286 | // remove file and index 287 | for (NSURL *fileURL in urlsToDelete) { 288 | [_fileManager removeItemAtURL:fileURL error:nil]; 289 | } 290 | @synchronized (_fileNames) { 291 | for (NSString *fileName in namesToDelete) { 292 | [_fileNames removeObjectForKey:fileName]; 293 | } 294 | } 295 | 296 | [self checkDiskStatus]; 297 | 298 | if ( completed != nil ) { 299 | NSUInteger fileCount = [_fileNames count]; 300 | completed( fileCount, fileCount == 0 ? 0 : totalSize ); 301 | } 302 | }); 303 | } 304 | 305 | - (void)calculateSizeWithCompletionBlock:(void (^)(NSUInteger fileCount, NSUInteger totalSize))block 306 | { 307 | NSParameterAssert(block); 308 | 309 | dispatch_async(_fileQueue, ^{ 310 | // dont count self folder 311 | NSUInteger fileCount = MAX(0, [[[_fileManager enumeratorAtPath:_folderPath] allObjects] count] - 1); 312 | NSUInteger totalSize = (NSUInteger)[[_fileManager attributesOfItemAtPath:_folderPath error:nil] fileSize]; 313 | 314 | dispatch_main_async_safe(^{ 315 | block( fileCount, fileCount == 0 ? 0 : totalSize ); 316 | }); 317 | }); 318 | } 319 | 320 | - (void)freeDiskSpaceWithCompletionBlock:(void (^)(NSUInteger freeSize))block 321 | { 322 | NSParameterAssert(block); 323 | 324 | dispatch_async(_fileQueue, ^{ 325 | NSDictionary *fileAttributes = [_fileManager attributesOfFileSystemForPath:@"/" error:nil]; 326 | NSUInteger freeSize = (NSUInteger)[[fileAttributes objectForKey:NSFileSystemFreeSize] unsignedLongLongValue]; 327 | 328 | dispatch_main_async_safe(^{ 329 | block( freeSize ); 330 | }); 331 | }); 332 | } 333 | 334 | @end 335 | -------------------------------------------------------------------------------- /FlyImage/Core/FlyImageDecoder.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageDecoder.m 3 | // Demo 4 | // 5 | // Created by Ye Tong on 3/22/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import "FlyImageDecoder.h" 10 | #ifdef FLYIMAGE_WEBP 11 | #import "webp/decode.h" 12 | #endif 13 | 14 | static void __ReleaseAsset(void* info, const void* data, size_t size) 15 | { 16 | if (info != NULL) { 17 | CFRelease(info); // will cause dealloc of FlyImageDataFile 18 | } 19 | } 20 | 21 | #ifdef FLYIMAGE_WEBP 22 | // This gets called when the UIImage gets collected and frees the underlying image. 23 | static void free_image_data(void* info, const void* data, size_t size) 24 | { 25 | if (info != NULL) { 26 | WebPFreeDecBuffer(&(((WebPDecoderConfig*)info)->output)); 27 | free(info); 28 | } 29 | 30 | if (data != NULL) { 31 | free((void*)data); 32 | } 33 | } 34 | #endif 35 | 36 | @implementation FlyImageDecoder 37 | 38 | - (UIImage*)iconImageWithBytes:(void*)bytes 39 | offset:(size_t)offset 40 | length:(size_t)length 41 | drawSize:(CGSize)drawSize 42 | { 43 | 44 | // Create CGImageRef whose backing store *is* the mapped image table entry. We avoid a memcpy this way. 45 | CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, bytes + offset, length, __ReleaseAsset); 46 | 47 | CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; 48 | NSInteger bitsPerComponent = 8; 49 | NSInteger bitsPerPixel = 4 * 8; 50 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 51 | 52 | static NSInteger bytesPerPixel = 4; 53 | static float kAlignment = 64; 54 | CGFloat screenScale = [FlyImageUtils contentsScale]; 55 | size_t bytesPerRow = ceil((drawSize.width * screenScale * bytesPerPixel) / kAlignment) * kAlignment; 56 | 57 | CGImageRef imageRef = CGImageCreate(drawSize.width * screenScale, 58 | drawSize.height * screenScale, 59 | bitsPerComponent, 60 | bitsPerPixel, 61 | bytesPerRow, 62 | colorSpace, 63 | bitmapInfo, 64 | dataProvider, 65 | NULL, 66 | false, 67 | kCGRenderingIntentDefault); 68 | 69 | CGDataProviderRelease(dataProvider); 70 | CGColorSpaceRelease(colorSpace); 71 | 72 | if (imageRef == nil) { 73 | return nil; 74 | } 75 | 76 | UIImage* image = [[UIImage alloc] initWithCGImage:imageRef 77 | scale:screenScale 78 | orientation:UIImageOrientationUp]; 79 | CGImageRelease(imageRef); 80 | 81 | return image; 82 | } 83 | 84 | - (CGImageRef)imageRefWithFile:(void*)file 85 | contentType:(ImageContentType)contentType 86 | bytes:(void*)bytes 87 | length:(size_t)length 88 | { 89 | if (contentType == ImageContentTypeUnknown || contentType == ImageContentTypeGif || contentType == ImageContentTypeTiff) { 90 | return nil; 91 | } 92 | 93 | // Create CGImageRef whose backing store *is* the mapped image table entry. We avoid a memcpy this way. 94 | CGDataProviderRef dataProvider = nil; 95 | CGImageRef imageRef = nil; 96 | if (contentType == ImageContentTypeJPEG) { 97 | CFRetain(file); 98 | dataProvider = CGDataProviderCreateWithData(file, bytes, length, __ReleaseAsset); 99 | imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, YES, kCGRenderingIntentDefault); 100 | 101 | } else if (contentType == ImageContentTypePNG) { 102 | CFRetain(file); 103 | dataProvider = CGDataProviderCreateWithData(file, bytes, length, __ReleaseAsset); 104 | imageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, YES, kCGRenderingIntentDefault); 105 | 106 | } else if (contentType == ImageContentTypeWebP) { 107 | #ifdef FLYIMAGE_WEBP 108 | // `WebPGetInfo` weill return image width and height 109 | int width = 0, height = 0; 110 | if (!WebPGetInfo(bytes, length, &width, &height)) { 111 | return nil; 112 | } 113 | 114 | WebPDecoderConfig* config = malloc(sizeof(WebPDecoderConfig)); 115 | if (!WebPInitDecoderConfig(config)) { 116 | return nil; 117 | } 118 | 119 | config->options.no_fancy_upsampling = 1; 120 | config->options.bypass_filtering = 1; 121 | config->options.use_threads = 1; 122 | config->output.colorspace = MODE_RGBA; 123 | 124 | // Decode the WebP image data into a RGBA value array 125 | VP8StatusCode decodeStatus = WebPDecode(bytes, length, config); 126 | if (decodeStatus != VP8_STATUS_OK) { 127 | return nil; 128 | } 129 | 130 | // Construct UIImage from the decoded RGBA value array 131 | uint8_t* data = WebPDecodeRGBA(bytes, length, &width, &height); 132 | dataProvider = CGDataProviderCreateWithData(config, data, config->options.scaled_width * config->options.scaled_height * 4, free_image_data); 133 | 134 | CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); 135 | CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast; 136 | CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; 137 | 138 | imageRef = CGImageCreate(width, height, 8, 32, 4 * width, colorSpaceRef, bitmapInfo, dataProvider, NULL, YES, renderingIntent); 139 | #endif 140 | } 141 | 142 | if (dataProvider != nil) { 143 | CGDataProviderRelease(dataProvider); 144 | } 145 | 146 | return imageRef; 147 | } 148 | 149 | - (UIImage*)imageWithFile:(void*)file 150 | contentType:(ImageContentType)contentType 151 | bytes:(void*)bytes 152 | length:(size_t)length 153 | drawSize:(CGSize)drawSize 154 | contentsGravity:(NSString* const)contentsGravity 155 | cornerRadius:(CGFloat)cornerRadius 156 | { 157 | 158 | CGImageRef imageRef = [self imageRefWithFile:file contentType:contentType bytes:bytes length:length]; 159 | if (imageRef == nil) { 160 | return nil; 161 | } 162 | 163 | CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)); 164 | CGFloat contentsScale = 1; 165 | if (drawSize.width < imageSize.width && drawSize.height < imageSize.height) { 166 | contentsScale = [FlyImageUtils contentsScale]; 167 | } 168 | CGSize contextSize = CGSizeMake(drawSize.width * contentsScale, drawSize.height * contentsScale); 169 | 170 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 171 | CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); 172 | 173 | int infoMask = (bitmapInfo & kCGBitmapAlphaInfoMask); 174 | BOOL anyNonAlpha = (infoMask == kCGImageAlphaNone || infoMask == kCGImageAlphaNoneSkipFirst || infoMask == kCGImageAlphaNoneSkipLast); 175 | 176 | // CGBitmapContextCreate doesn't support kCGImageAlphaNone with RGB. 177 | // https://developer.apple.com/library/mac/#qa/qa1037/_index.html 178 | if (cornerRadius > 0) { 179 | bitmapInfo &= kCGImageAlphaPremultipliedLast; 180 | } else if (infoMask == kCGImageAlphaNone && CGColorSpaceGetNumberOfComponents(colorSpace) > 1) { 181 | // Unset the old alpha info. 182 | bitmapInfo &= ~kCGBitmapAlphaInfoMask; 183 | 184 | // Set noneSkipFirst. 185 | bitmapInfo |= kCGImageAlphaNoneSkipFirst; 186 | } 187 | // Some PNGs tell us they have alpha but only 3 components. Odd. 188 | else if (!anyNonAlpha && CGColorSpaceGetNumberOfComponents(colorSpace) == 3) { 189 | // Unset the old alpha info. 190 | bitmapInfo &= ~kCGBitmapAlphaInfoMask; 191 | bitmapInfo |= kCGImageAlphaPremultipliedFirst; 192 | } 193 | 194 | // It calculates the bytes-per-row based on the bitsPerComponent and width arguments. 195 | static NSInteger bytesPerPixel = 4; 196 | static float kAlignment = 64; 197 | size_t bytesPerRow = ceil((contextSize.width * bytesPerPixel) / kAlignment) * kAlignment; 198 | 199 | CGContextRef context = CGBitmapContextCreate(NULL, contextSize.width, contextSize.height, CGImageGetBitsPerComponent(imageRef), bytesPerRow, colorSpace, bitmapInfo); 200 | CGColorSpaceRelease(colorSpace); 201 | 202 | // If failed, return undecompressed image 203 | if (!context) { 204 | UIImage* image = [[UIImage alloc] initWithCGImage:imageRef 205 | scale:contentsScale 206 | orientation:UIImageOrientationUp]; 207 | CGImageRelease(imageRef); 208 | return image; 209 | } 210 | 211 | CGContextScaleCTM(context, contentsScale, contentsScale); 212 | CGContextSetInterpolationQuality(context, kCGInterpolationHigh); 213 | 214 | CGRect contextBounds = CGRectMake(0, 0, drawSize.width, drawSize.height); 215 | 216 | // Clip to a rounded rect 217 | if (cornerRadius > 0) { 218 | CGPathRef path = _FICDCreateRoundedRectPath(contextBounds, cornerRadius); 219 | CGContextAddPath(context, path); 220 | CFRelease(path); 221 | CGContextEOClip(context); 222 | } 223 | 224 | CGContextDrawImage(context, _FlyImageCalcDrawBounds(imageSize, 225 | drawSize, 226 | contentsGravity), 227 | imageRef); 228 | 229 | CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); 230 | CGContextRelease(context); 231 | 232 | UIImage* decompressedImage = [UIImage imageWithCGImage:decompressedImageRef 233 | scale:contentsScale 234 | orientation:UIImageOrientationUp]; 235 | 236 | CGImageRelease(decompressedImageRef); 237 | CGImageRelease(imageRef); 238 | 239 | return decompressedImage; 240 | } 241 | 242 | #ifdef FLYIMAGE_WEBP 243 | - (UIImage*)imageWithWebPData:(NSData*)imageData hasAlpha:(BOOL*)hasAlpha 244 | { 245 | 246 | // `WebPGetInfo` weill return image width and height 247 | int width = 0, height = 0; 248 | if (!WebPGetInfo(imageData.bytes, imageData.length, &width, &height)) { 249 | return nil; 250 | } 251 | 252 | WebPDecoderConfig* config = malloc(sizeof(WebPDecoderConfig)); 253 | if (!WebPInitDecoderConfig(config)) { 254 | return nil; 255 | } 256 | 257 | config->options.no_fancy_upsampling = 1; 258 | config->options.bypass_filtering = 1; 259 | config->options.use_threads = 1; 260 | config->output.colorspace = MODE_RGBA; 261 | 262 | // Decode the WebP image data into a RGBA value array 263 | VP8StatusCode decodeStatus = WebPDecode(imageData.bytes, imageData.length, config); 264 | if (decodeStatus != VP8_STATUS_OK) { 265 | return nil; 266 | } 267 | 268 | // set alpha value 269 | if (hasAlpha != nil) { 270 | *hasAlpha = config->input.has_alpha; 271 | } 272 | 273 | // Construct UIImage from the decoded RGBA value array 274 | uint8_t* data = WebPDecodeRGBA(imageData.bytes, imageData.length, &width, &height); 275 | CGDataProviderRef dataProvider = CGDataProviderCreateWithData(config, data, config->options.scaled_width * config->options.scaled_height * 4, free_image_data); 276 | 277 | CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); 278 | CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast; 279 | CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; 280 | 281 | CGImageRef imageRef = CGImageCreate(width, height, 8, 32, 4 * width, colorSpaceRef, bitmapInfo, dataProvider, NULL, YES, renderingIntent); 282 | UIImage* decodeImage = [UIImage imageWithCGImage:imageRef]; 283 | 284 | UIGraphicsBeginImageContextWithOptions(decodeImage.size, !config->input.has_alpha, 1); 285 | [decodeImage drawInRect:CGRectMake(0, 0, decodeImage.size.width, decodeImage.size.height)]; 286 | UIImage* decompressedImage = UIGraphicsGetImageFromCurrentImageContext(); 287 | UIGraphicsEndImageContext(); 288 | 289 | return decompressedImage; 290 | } 291 | #endif 292 | 293 | @end 294 | -------------------------------------------------------------------------------- /FlyImageTests/FlyImageIconCacheTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlyImageIconCacheTests.m 3 | // Demo 4 | // 5 | // Created by Norris Tong on 4/3/16. 6 | // Copyright © 2016 NorrisTong. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FlyImageIconCache.h" 11 | 12 | @interface FlyImageIconCacheTests : XCTestCase 13 | 14 | @end 15 | 16 | #define kImageWidth 30 17 | #define kImageHeight 40 18 | 19 | @implementation FlyImageIconCacheTests 20 | 21 | - (void)setUp 22 | { 23 | [super setUp]; 24 | // Put setup code here. This method is called before the invocation of each test method in the class. 25 | } 26 | 27 | - (void)tearDown 28 | { 29 | // Put teardown code here. This method is called after the invocation of each test method in the class. 30 | [super tearDown]; 31 | } 32 | 33 | - (void)drawALineInContext:(CGContextRef)context rect:(CGRect)rect 34 | { 35 | UIGraphicsPushContext(context); 36 | 37 | CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor); 38 | CGContextSetLineWidth(context, 10.0); 39 | CGContextMoveToPoint(context, 0.0, 0.0); 40 | CGContextAddLineToPoint(context, rect.size.width, rect.size.height); 41 | 42 | UIGraphicsPopContext(); 43 | } 44 | 45 | - (void)test10AddImage 46 | { 47 | XCTestExpectation* expectation = [self expectationWithDescription:@"test10AddImage"]; 48 | 49 | [[FlyImageIconCache sharedInstance] addImageWithKey:@"10" 50 | size:CGSizeMake(kImageWidth, kImageHeight) 51 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 52 | [self drawALineInContext:context rect:contextBounds]; 53 | } 54 | completed:^(NSString* key, UIImage* image) { 55 | XCTAssert( image.size.width == kImageWidth ); 56 | XCTAssert( image.size.height == kImageHeight ); 57 | 58 | [expectation fulfill]; 59 | }]; 60 | 61 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 62 | } 63 | 64 | - (void)test11AddMultipleTimes 65 | { 66 | XCTestExpectation* expectation = [self expectationWithDescription:@"test11AddMultipleTimes"]; 67 | 68 | __block int sum = 0; 69 | for (int i = 0; i < 100; i++) { 70 | [[FlyImageIconCache sharedInstance] addImageWithKey:@"11" 71 | size:CGSizeMake(kImageWidth, kImageHeight) 72 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 73 | [self drawALineInContext:context rect:contextBounds]; 74 | } 75 | completed:^(NSString* key, UIImage* image) { 76 | XCTAssert( image.size.width == kImageWidth ); 77 | XCTAssert( image.size.height == kImageHeight ); 78 | 79 | sum++; 80 | if ( sum == 100 ){ 81 | [expectation fulfill]; 82 | } 83 | }]; 84 | } 85 | 86 | [self waitForExpectationsWithTimeout:30 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 87 | } 88 | 89 | - (void)test12AddSameImage 90 | { 91 | XCTestExpectation* expectation = [self expectationWithDescription:@"test12AddSameImage"]; 92 | 93 | [[FlyImageIconCache sharedInstance] addImageWithKey:@"10" 94 | size:CGSizeMake(kImageWidth, kImageHeight) 95 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 96 | [self drawALineInContext:context rect:contextBounds]; 97 | } 98 | completed:^(NSString* key, UIImage* image) { 99 | 100 | 101 | [[FlyImageIconCache sharedInstance] addImageWithKey:@"10" 102 | size:CGSizeMake(kImageWidth, kImageHeight) 103 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 104 | [self drawALineInContext:context rect:contextBounds]; 105 | } 106 | completed:^(NSString* key, UIImage* image) { 107 | 108 | XCTAssert( image.size.width == kImageWidth ); 109 | XCTAssert( image.size.height == kImageHeight ); 110 | 111 | [expectation fulfill]; 112 | 113 | }]; 114 | }]; 115 | 116 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 117 | } 118 | 119 | - (void)test13AddMultipleKeys 120 | { 121 | XCTestExpectation* expectation = [self expectationWithDescription:@"test13AddMultipleKeys"]; 122 | 123 | __block int sum = 0; 124 | for (int i = 1; i <= 100; i++) { 125 | [[FlyImageIconCache sharedInstance] addImageWithKey:[NSString stringWithFormat:@"%d", i] 126 | size:CGSizeMake(kImageWidth, kImageHeight) 127 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 128 | [self drawALineInContext:context rect:contextBounds]; 129 | } 130 | completed:^(NSString* key, UIImage* image) { 131 | XCTAssert( image.size.width == kImageWidth ); 132 | XCTAssert( image.size.height == kImageHeight ); 133 | 134 | sum++; 135 | if ( sum == 100 ){ 136 | [expectation fulfill]; 137 | } 138 | }]; 139 | } 140 | 141 | [self waitForExpectationsWithTimeout:30 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 142 | } 143 | 144 | - (void)test20ReplaceImage 145 | { 146 | XCTestExpectation* expectation = [self expectationWithDescription:@"test20ReplaceImage"]; 147 | 148 | [[FlyImageIconCache sharedInstance] addImageWithKey:@"20" 149 | size:CGSizeMake(kImageWidth, kImageHeight) 150 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 151 | [self drawALineInContext:context rect:contextBounds]; 152 | } 153 | completed:^(NSString* key, UIImage* image) { 154 | 155 | [[FlyImageIconCache sharedInstance] replaceImageWithKey:@"20" 156 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 157 | [self drawALineInContext:context rect:contextBounds]; 158 | } 159 | completed:^(NSString* key, UIImage* image) { 160 | XCTAssert( image.size.width == kImageWidth ); 161 | XCTAssert( image.size.height == kImageHeight ); 162 | 163 | [expectation fulfill]; 164 | }]; 165 | }]; 166 | 167 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 168 | } 169 | 170 | - (void)test30AsyncGetImage 171 | { 172 | XCTestExpectation* expectation = [self expectationWithDescription:@"test30AsyncGetImage"]; 173 | 174 | [[FlyImageIconCache sharedInstance] addImageWithKey:@"30" 175 | size:CGSizeMake(kImageWidth, kImageHeight) 176 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 177 | [self drawALineInContext:context rect:contextBounds]; 178 | } 179 | completed:^(NSString* key, UIImage* image) { 180 | 181 | [[FlyImageIconCache sharedInstance] asyncGetImageWithKey:@"30" completed:^(NSString* key, UIImage* image) { 182 | XCTAssert( image.size.width == kImageWidth ); 183 | XCTAssert( image.size.height == kImageHeight ); 184 | 185 | [expectation fulfill]; 186 | }]; 187 | 188 | }]; 189 | 190 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 191 | } 192 | 193 | - (void)test31AsyncGetImageMultipleTimes 194 | { 195 | XCTestExpectation* expectation = [self expectationWithDescription:@"test31AsyncGetImageMultipleTimes"]; 196 | 197 | [[FlyImageIconCache sharedInstance] addImageWithKey:@"31" 198 | size:CGSizeMake(kImageWidth, kImageHeight) 199 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 200 | [self drawALineInContext:context rect:contextBounds]; 201 | } 202 | completed:^(NSString* key, UIImage* image) { 203 | 204 | __block int sum = 0; 205 | for (int i = 0; i < 100; i++) { 206 | [[FlyImageIconCache sharedInstance] asyncGetImageWithKey:@"31" 207 | completed:^(NSString* key, UIImage* image) { 208 | XCTAssert( image.size.width == kImageWidth ); 209 | XCTAssert( image.size.height == kImageHeight ); 210 | 211 | sum++; 212 | if ( sum == 100 ){ 213 | [expectation fulfill]; 214 | } 215 | }]; 216 | } 217 | 218 | }]; 219 | 220 | [self waitForExpectationsWithTimeout:30 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 221 | } 222 | 223 | - (void)test40DifferentSize 224 | { 225 | XCTestExpectation* expectation = [self expectationWithDescription:@"test40DifferentSize"]; 226 | 227 | [[FlyImageIconCache sharedInstance] addImageWithKey:@"400" 228 | size:CGSizeMake(27, 57) 229 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 230 | [self drawALineInContext:context rect:contextBounds]; 231 | } 232 | completed:^(NSString* key, UIImage* image) { 233 | XCTAssert( image.size.width == 27 ); 234 | XCTAssert( image.size.height == 57 ); 235 | 236 | [[FlyImageIconCache sharedInstance] addImageWithKey:@"401" 237 | size:CGSizeMake(88, 99) 238 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 239 | [self drawALineInContext:context rect:contextBounds]; 240 | } 241 | completed:^(NSString* key, UIImage* image) { 242 | XCTAssert( image.size.width == 88 ); 243 | XCTAssert( image.size.height == 99 ); 244 | 245 | [expectation fulfill]; 246 | }]; 247 | }]; 248 | 249 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 250 | } 251 | 252 | - (void)test70RemoveImage 253 | { 254 | XCTestExpectation* expectation = [self expectationWithDescription:@"test70RemoveImage"]; 255 | 256 | [[FlyImageIconCache sharedInstance] addImageWithKey:@"70" 257 | size:CGSizeMake(kImageWidth, kImageHeight) 258 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 259 | [self drawALineInContext:context rect:contextBounds]; 260 | } 261 | completed:^(NSString* key, UIImage* image) { 262 | 263 | [[FlyImageIconCache sharedInstance] removeImageWithKey:@"70"]; 264 | XCTAssert(![[FlyImageIconCache sharedInstance] isImageExistWithKey:@"70"]); 265 | 266 | [expectation fulfill]; 267 | }]; 268 | 269 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 270 | } 271 | 272 | - (void)test80ChangeImageKey 273 | { 274 | XCTestExpectation* expectation = [self expectationWithDescription:@"test80ChangeImageKey"]; 275 | 276 | [[FlyImageIconCache sharedInstance] addImageWithKey:@"70" 277 | size:CGSizeMake(kImageWidth, kImageHeight) 278 | drawingBlock:^(CGContextRef context, CGRect contextBounds) { 279 | [self drawALineInContext:context rect:contextBounds]; 280 | } 281 | completed:^(NSString* key, UIImage* image) { 282 | 283 | [[FlyImageIconCache sharedInstance] changeImageKey:@"80" newKey:@"newKey"]; 284 | XCTAssert(![[FlyImageIconCache sharedInstance] isImageExistWithKey:@"80"]); 285 | XCTAssert([[FlyImageIconCache sharedInstance] isImageExistWithKey:@"newKey"]); 286 | 287 | [[FlyImageIconCache sharedInstance] asyncGetImageWithKey:@"newKey" completed:^(NSString* key, UIImage* image) { 288 | XCTAssert( image.size.width == kImageWidth ); 289 | XCTAssert( image.size.height == kImageHeight ); 290 | 291 | [expectation fulfill]; 292 | }]; 293 | 294 | }]; 295 | 296 | [self waitForExpectationsWithTimeout:10 handler:^(NSError* error) { XCTAssert(YES, @"Pass"); }]; 297 | } 298 | 299 | - (void)test90Purge 300 | { 301 | [[FlyImageIconCache sharedInstance] purge]; 302 | XCTAssert(![[FlyImageIconCache sharedInstance] isImageExistWithKey:@"10"]); 303 | } 304 | 305 | @end 306 | --------------------------------------------------------------------------------