├── HXImage.podspec ├── HXImage ├── Private │ ├── HXImageManager.h │ ├── HXImageManager.m │ ├── NSString+HXImage.h │ └── NSString+HXImage.m └── Public │ ├── HXImage.h │ ├── UIImage+HXImage.h │ └── UIImage+HXImage.m ├── HXImageDemo ├── HXImageDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── shuang.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── shuang.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── HXImageDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── NSObject+Dealloc.h │ ├── NSObject+Dealloc.m │ ├── ViewController.h │ ├── ViewController.m │ ├── main.m │ └── tabbar_style@3x.png ├── LICENSE ├── Plan A ├── KBImageManager.h ├── KBImageManager.m ├── NSBundle+Scale.h ├── NSBundle+Scale.m ├── NSObject+MRC.h ├── NSObject+MRC.m ├── NSString+Scale.h ├── NSString+Scale.m ├── UIImage+HXImage.h └── UIImage+HXImage.m ├── Plan B ├── KBImageManager.h ├── KBImageManager.m ├── NSBundle+KBSCaleArray.h ├── NSBundle+KBSCaleArray.m ├── NSMutableDictionary+WeakReference.h ├── NSMutableDictionary+WeakReference.m ├── NSString+AddScale.h ├── NSString+AddScale.m ├── UIImage+HXImage.h ├── UIImage+HXImage.m ├── WeakReference.h └── WeakReference.m ├── Plan C ├── README.md └── description.md /HXImage.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint HXImage.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "HXImage" 19 | s.version = "0.0.1" 20 | s.summary = "A automatically reference manager for icon image." 21 | 22 | # This description is used to generate tags and improve search results. 23 | # * Think: What does it do? Why did you write it? What is the focus? 24 | # * Try to keep it short, snappy and to the point. 25 | # * Write the description between the DESC delimiters below. 26 | # * Finally, don't worry about the indent, CocoaPods strips it! 27 | s.description = <<-DESC 28 | 1. Dealloc image atomically 29 | 2. Reuse image if repeat create. 30 | DESC 31 | 32 | s.homepage = "https://github.com/Magic-Unique/HXImage" 33 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 34 | 35 | 36 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 37 | # 38 | # Licensing your code is important. See http://choosealicense.com for more info. 39 | # CocoaPods will detect a license file if there is a named LICENSE* 40 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 41 | # 42 | 43 | s.license = "MIT" 44 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 45 | 46 | 47 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 48 | # 49 | # Specify the authors of the library, with email addresses. Email addresses 50 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 51 | # accepts just a name if you'd rather not provide an email address. 52 | # 53 | # Specify a social_media_url where others can refer to, for example a twitter 54 | # profile URL. 55 | # 56 | 57 | s.author = { "冷秋" => "516563564@qq.com" } 58 | # Or just: s.author = "" 59 | # s.authors = { "" => "" } 60 | # s.social_media_url = "http://twitter.com/" 61 | 62 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 63 | # 64 | # If this Pod runs only on iOS or OS X, then specify the platform and 65 | # the deployment target. You can optionally include the target after the platform. 66 | # 67 | 68 | # s.platform = :ios 69 | s.platform = :ios, "6.0" 70 | 71 | # When using multiple platforms 72 | # s.ios.deployment_target = "5.0" 73 | # s.osx.deployment_target = "10.7" 74 | # s.watchos.deployment_target = "2.0" 75 | # s.tvos.deployment_target = "9.0" 76 | 77 | 78 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 79 | # 80 | # Specify the location from where the source should be retrieved. 81 | # Supports git, hg, bzr, svn and HTTP. 82 | # 83 | 84 | s.source = { :git => "https://github.com/Magic-Unique/HXImage.git", :tag => "#{s.version}" } 85 | 86 | 87 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 88 | # 89 | # CocoaPods is smart about how it includes source code. For source files 90 | # giving a folder will include any swift, h, m, mm, c & cpp files. 91 | # For header files it will include any header in the folder. 92 | # Not including the public_header_files will make all headers public. 93 | # 94 | 95 | s.source_files = "HXImage/**/*.{h,m}" 96 | # s.exclude_files = "Classes/Exclude" 97 | 98 | s.public_header_files = "HXImage/Public/*.h" 99 | 100 | 101 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 102 | # 103 | # A list of resources included with the Pod. These are copied into the 104 | # target bundle with a build phase script. Anything else will be cleaned. 105 | # You can preserve files from being cleaned, please don't preserve 106 | # non-essential files like tests, examples and documentation. 107 | # 108 | 109 | # s.resource = "icon.png" 110 | # s.resources = "Resources/*.png" 111 | 112 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave" 113 | 114 | 115 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 116 | # 117 | # Link your library with frameworks, or libraries. Libraries do not include 118 | # the lib prefix of their name. 119 | # 120 | 121 | # s.framework = "SomeFramework" 122 | # s.frameworks = "SomeFramework", "AnotherFramework" 123 | 124 | # s.library = "iconv" 125 | # s.libraries = "iconv", "xml2" 126 | 127 | 128 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 129 | # 130 | # If your library depends on compiler flags you can set them in the xcconfig hash 131 | # where they will only apply to your library. If you depend on other Podspecs 132 | # you can include multiple dependencies to ensure it works. 133 | 134 | # s.requires_arc = true 135 | 136 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 137 | # s.dependency "JSONKit", "~> 1.4" 138 | 139 | end 140 | -------------------------------------------------------------------------------- /HXImage/Private/HXImageManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // HXImageManager.h 3 | // HXImage 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HXImageManager : NSObject 12 | 13 | + (instancetype)sharedManager; 14 | 15 | - (UIImage *)imageNamed:(NSString *)name inDirectory:(NSString *)directory; 16 | 17 | - (UIImage *)imageWithContentsOfFile:(NSString *)path; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /HXImage/Private/HXImageManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // HXImageManager.m 3 | // HXImage 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. All rights reserved. 7 | // 8 | 9 | #import "HXImageManager.h" 10 | #import "NSString+HXImage.h" 11 | 12 | @interface HXImageManager () 13 | 14 | @property (nonatomic, strong, readonly) NSMapTable *caches; 15 | 16 | @end 17 | 18 | @implementation HXImageManager 19 | 20 | + (instancetype)sharedManager { 21 | static HXImageManager *_manager = nil; 22 | static dispatch_once_t onceToken; 23 | dispatch_once(&onceToken, ^{ 24 | _manager = [self new]; 25 | }); 26 | return _manager; 27 | } 28 | 29 | - (instancetype)init { 30 | self = [super init]; 31 | if (self) { 32 | _caches = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsCopyIn 33 | valueOptions:NSPointerFunctionsWeakMemory]; 34 | } 35 | return self; 36 | } 37 | 38 | - (UIImage *)imageNamed:(NSString *)name inDirectory:(NSString *)directory { 39 | NSString *MD5 = [directory stringByAppendingPathComponent:name].hx_MD5; 40 | UIImage *image = [self.caches objectForKey:MD5]; 41 | if (image == nil) { 42 | NSString *keyNamed = name; 43 | NSUInteger scale = 0; 44 | NSString *type = nil; 45 | if (keyNamed.hx_containsType) { 46 | type = keyNamed.hx_type; 47 | keyNamed = keyNamed.hx_removeType; 48 | } 49 | if (keyNamed.hx_containsScale) { 50 | scale = keyNamed.hx_scale; 51 | keyNamed = keyNamed.hx_removeScale; 52 | } 53 | image = [self __searchImageNamed:keyNamed scale:scale type:type inDirectory:directory]; 54 | if (image) { 55 | [self.caches setObject:image forKey:MD5]; 56 | } 57 | } 58 | return image; 59 | } 60 | 61 | - (UIImage *)imageWithContentsOfFile:(NSString *)path { 62 | NSString *MD5 = path.hx_MD5; 63 | UIImage *image = [self.caches objectForKey:MD5]; 64 | if (image == nil) { 65 | image = [UIImage imageWithContentsOfFile:path]; 66 | if (image) { 67 | [self.caches setObject:image forKey:MD5]; 68 | } 69 | } 70 | return image; 71 | } 72 | 73 | - (UIImage *)__searchImageNamed:(NSString *)name scale:(NSUInteger)scale type:(NSString *)type inDirectory:(NSString *)directory { 74 | NSArray *scales = [self __scaleSuffixWithScale:scale]; 75 | NSArray *types = [self __typeSuffixWithType:type]; 76 | NSFileManager *fmgr = [NSFileManager defaultManager]; 77 | for (NSString *scale in scales) { 78 | for (NSString *type in types) { 79 | NSString *fileName = [NSString stringWithFormat:@"%@%@.%@", name, scale, type]; 80 | NSString *path = [directory stringByAppendingPathComponent:fileName]; 81 | BOOL isDirecotry = NO; 82 | if ([fmgr fileExistsAtPath:path isDirectory:&isDirecotry]) { 83 | if (!isDirecotry) { 84 | UIImage *image = [UIImage imageWithContentsOfFile:path]; 85 | if (image) { 86 | return image; 87 | } 88 | } 89 | } 90 | } 91 | } 92 | return nil; 93 | } 94 | 95 | - (NSArray *)__scaleSuffixWithScale:(NSUInteger)scale { 96 | if (scale != 0) { 97 | return @[[NSString stringWithFormat:@"@%@x", @(scale)]]; 98 | } else { 99 | static NSArray *_scales = nil; 100 | static dispatch_once_t onceToken; 101 | dispatch_once(&onceToken, ^{ 102 | NSUInteger screenScale = (NSUInteger)UIScreen.mainScreen.scale; 103 | switch (screenScale) { 104 | case 1: 105 | _scales = @[@"", @"@2x", @"@3x"]; 106 | break; 107 | case 2: 108 | _scales = @[@"@2x", @"@3x", @""]; 109 | break; 110 | case 3: 111 | _scales = @[@"@3x", @"@2x", @""]; 112 | break; 113 | default: 114 | _scales = @[@"@3x", @"@2x", @""]; 115 | break; 116 | } 117 | }); 118 | return _scales; 119 | } 120 | } 121 | 122 | - (NSArray *)__typeSuffixWithType:(NSString *)type { 123 | if (type) { 124 | return @[type]; 125 | } else { 126 | return @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"]; 127 | } 128 | } 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /HXImage/Private/NSString+HXImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+HXImage.h 3 | // HXImage 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSString (HXImage) 12 | 13 | @property (nonatomic, strong, readonly) NSString *hx_MD5; 14 | 15 | @property (nonatomic, assign, readonly) BOOL hx_containsScale; 16 | @property (nonatomic, assign, readonly) NSUInteger hx_scale; 17 | @property (nonatomic, strong, readonly) NSString *hx_removeScale; 18 | 19 | @property (nonatomic, assign, readonly) BOOL hx_containsType; 20 | @property (nonatomic, strong, readonly) NSString *hx_type; 21 | @property (nonatomic, strong, readonly) NSString *hx_removeType; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /HXImage/Private/NSString+HXImage.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+HXImage.m 3 | // HXImage 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. All rights reserved. 7 | // 8 | 9 | #import "NSString+HXImage.h" 10 | #import 11 | 12 | @implementation NSString (HXImage) 13 | 14 | - (NSString *)hx_MD5 { 15 | const char *string = self.UTF8String; 16 | int length = (int)strlen(string); 17 | unsigned char bytes[CC_MD5_DIGEST_LENGTH]; 18 | CC_MD5(string, length, bytes); 19 | return [self hx_stringFromBytes:bytes length:CC_MD5_DIGEST_LENGTH]; 20 | } 21 | 22 | - (NSString *)hx_stringFromBytes:(unsigned char *)bytes length:(NSUInteger)length { 23 | NSMutableString *mutableString = @"".mutableCopy; 24 | for (int i = 0; i < length; i++) 25 | [mutableString appendFormat:@"%02x", bytes[i]]; 26 | return [NSString stringWithString:mutableString]; 27 | } 28 | 29 | - (NSString *)hx_type { 30 | NSArray *item = [self componentsSeparatedByString:@"."]; 31 | if (item.count > 1) { 32 | return item.lastObject; 33 | } else { 34 | return nil; 35 | } 36 | } 37 | 38 | - (BOOL)hx_containsType { 39 | return [self componentsSeparatedByString:@"."].count > 1; 40 | } 41 | 42 | - (NSString *)hx_removeType { 43 | return [self stringByDeletingPathExtension]; 44 | } 45 | 46 | - (NSUInteger)hx_scale { 47 | NSString *name = self.stringByDeletingPathExtension; 48 | if ([name hasSuffix:@"@2x"]) { 49 | return 2; 50 | } else if ([name hasSuffix:@"@3x"]) { 51 | return 3; 52 | } else { 53 | return 0; 54 | } 55 | } 56 | 57 | - (BOOL)hx_containsScale { 58 | NSString *name = self.stringByDeletingPathExtension; 59 | return [name hasSuffix:@"@2x"] || [name hasSuffix:@"@3x"]; 60 | } 61 | 62 | - (NSString *)hx_removeScale { 63 | NSString *type = self.pathExtension; 64 | NSString *name = self.stringByDeletingPathExtension; 65 | NSMutableArray *item = [[name componentsSeparatedByString:@"@"] mutableCopy]; 66 | [item removeLastObject]; 67 | name = [item componentsJoinedByString:@"@"]; 68 | return [name stringByAppendingPathExtension:type]; 69 | } 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /HXImage/Public/HXImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // HXImage.h 3 | // HXImage 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. All rights reserved. 7 | // 8 | 9 | #ifndef HXImage_h 10 | #define HXImage_h 11 | 12 | #import "UIImage+HXImage.h" 13 | 14 | #endif /* HXImage_h */ 15 | -------------------------------------------------------------------------------- /HXImage/Public/UIImage+HXImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+HXImage.h 3 | // HXImage 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage (HXImage) 12 | 13 | + (instancetype)hx_imageNamed:(NSString *)name; 14 | 15 | + (instancetype)hx_imageNamed:(NSString *)name inDirectory:(NSString *)directory; 16 | 17 | + (instancetype)hx_imageNamed:(NSString *)name inBundle:(NSBundle *)bundle; 18 | 19 | + (instancetype)hx_imageWithContentsOfFile:(NSString *)path; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /HXImage/Public/UIImage+HXImage.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+HXImage.m 3 | // HXImage 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. All rights reserved. 7 | // 8 | 9 | #import "UIImage+HXImage.h" 10 | #import "HXImageManager.h" 11 | 12 | @implementation UIImage (HXImage) 13 | 14 | + (instancetype)hx_imageNamed:(NSString *)name { 15 | return [self hx_imageNamed:name inBundle:NSBundle.mainBundle]; 16 | } 17 | 18 | + (instancetype)hx_imageNamed:(NSString *)name inBundle:(NSBundle *)bundle { 19 | return [self hx_imageNamed:name inDirectory:bundle.bundlePath]; 20 | } 21 | 22 | + (instancetype)hx_imageNamed:(NSString *)name inDirectory:(NSString *)directory { 23 | return [[HXImageManager sharedManager] imageNamed:name inDirectory:directory]; 24 | } 25 | 26 | + (instancetype)hx_imageWithContentsOfFile:(NSString *)path { 27 | return [[HXImageManager sharedManager] imageWithContentsOfFile:path]; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7E526AAE20EB11170003F0E0 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E526AAD20EB11170003F0E0 /* AppDelegate.m */; }; 11 | 7E526AB120EB11180003F0E0 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E526AB020EB11180003F0E0 /* ViewController.m */; }; 12 | 7E526AB420EB11180003F0E0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7E526AB220EB11180003F0E0 /* Main.storyboard */; }; 13 | 7E526AB620EB11190003F0E0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7E526AB520EB11190003F0E0 /* Assets.xcassets */; }; 14 | 7E526AB920EB11190003F0E0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7E526AB720EB11190003F0E0 /* LaunchScreen.storyboard */; }; 15 | 7E526ABC20EB11190003F0E0 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E526ABB20EB11190003F0E0 /* main.m */; }; 16 | 7E526AD020EB1DB20003F0E0 /* tabbar_style@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 7E526ACF20EB1DB20003F0E0 /* tabbar_style@3x.png */; }; 17 | 7E526AD320EB1EAC0003F0E0 /* NSObject+Dealloc.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E526AD220EB1EAC0003F0E0 /* NSObject+Dealloc.m */; }; 18 | 7E526ADF20EB21070003F0E0 /* UIImage+HXImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E526AD920EB21070003F0E0 /* UIImage+HXImage.m */; }; 19 | 7E526AE020EB21070003F0E0 /* NSString+HXImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E526ADD20EB21070003F0E0 /* NSString+HXImage.m */; }; 20 | 7E526AE120EB21070003F0E0 /* HXImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E526ADE20EB21070003F0E0 /* HXImageManager.m */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 7E526AA920EB11170003F0E0 /* HXImageDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HXImageDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | 7E526AAC20EB11170003F0E0 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 26 | 7E526AAD20EB11170003F0E0 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 27 | 7E526AAF20EB11180003F0E0 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 28 | 7E526AB020EB11180003F0E0 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 29 | 7E526AB320EB11180003F0E0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 30 | 7E526AB520EB11190003F0E0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | 7E526AB820EB11190003F0E0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 32 | 7E526ABA20EB11190003F0E0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | 7E526ABB20EB11190003F0E0 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 34 | 7E526ACF20EB1DB20003F0E0 /* tabbar_style@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tabbar_style@3x.png"; sourceTree = ""; }; 35 | 7E526AD120EB1EAC0003F0E0 /* NSObject+Dealloc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+Dealloc.h"; sourceTree = ""; }; 36 | 7E526AD220EB1EAC0003F0E0 /* NSObject+Dealloc.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Dealloc.m"; sourceTree = ""; }; 37 | 7E526AD720EB21070003F0E0 /* UIImage+HXImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+HXImage.h"; sourceTree = ""; }; 38 | 7E526AD820EB21070003F0E0 /* HXImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HXImage.h; sourceTree = ""; }; 39 | 7E526AD920EB21070003F0E0 /* UIImage+HXImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+HXImage.m"; sourceTree = ""; }; 40 | 7E526ADB20EB21070003F0E0 /* HXImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HXImageManager.h; sourceTree = ""; }; 41 | 7E526ADC20EB21070003F0E0 /* NSString+HXImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+HXImage.h"; sourceTree = ""; }; 42 | 7E526ADD20EB21070003F0E0 /* NSString+HXImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+HXImage.m"; sourceTree = ""; }; 43 | 7E526ADE20EB21070003F0E0 /* HXImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HXImageManager.m; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | 7E526AA620EB11170003F0E0 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 7E526AA020EB11170003F0E0 = { 58 | isa = PBXGroup; 59 | children = ( 60 | 7E526AD520EB21070003F0E0 /* HXImage */, 61 | 7E526AAB20EB11170003F0E0 /* HXImageDemo */, 62 | 7E526AAA20EB11170003F0E0 /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | 7E526AAA20EB11170003F0E0 /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 7E526AA920EB11170003F0E0 /* HXImageDemo.app */, 70 | ); 71 | name = Products; 72 | sourceTree = ""; 73 | }; 74 | 7E526AAB20EB11170003F0E0 /* HXImageDemo */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 7E526ACF20EB1DB20003F0E0 /* tabbar_style@3x.png */, 78 | 7E526AAC20EB11170003F0E0 /* AppDelegate.h */, 79 | 7E526AAD20EB11170003F0E0 /* AppDelegate.m */, 80 | 7E526AAF20EB11180003F0E0 /* ViewController.h */, 81 | 7E526AB020EB11180003F0E0 /* ViewController.m */, 82 | 7E526AD120EB1EAC0003F0E0 /* NSObject+Dealloc.h */, 83 | 7E526AD220EB1EAC0003F0E0 /* NSObject+Dealloc.m */, 84 | 7E526AB220EB11180003F0E0 /* Main.storyboard */, 85 | 7E526AB520EB11190003F0E0 /* Assets.xcassets */, 86 | 7E526AB720EB11190003F0E0 /* LaunchScreen.storyboard */, 87 | 7E526ABA20EB11190003F0E0 /* Info.plist */, 88 | 7E526ABB20EB11190003F0E0 /* main.m */, 89 | ); 90 | path = HXImageDemo; 91 | sourceTree = ""; 92 | }; 93 | 7E526AD520EB21070003F0E0 /* HXImage */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 7E526AD620EB21070003F0E0 /* Public */, 97 | 7E526ADA20EB21070003F0E0 /* Private */, 98 | ); 99 | name = HXImage; 100 | path = ../HXImage; 101 | sourceTree = ""; 102 | }; 103 | 7E526AD620EB21070003F0E0 /* Public */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 7E526AD820EB21070003F0E0 /* HXImage.h */, 107 | 7E526AD720EB21070003F0E0 /* UIImage+HXImage.h */, 108 | 7E526AD920EB21070003F0E0 /* UIImage+HXImage.m */, 109 | ); 110 | path = Public; 111 | sourceTree = ""; 112 | }; 113 | 7E526ADA20EB21070003F0E0 /* Private */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 7E526ADC20EB21070003F0E0 /* NSString+HXImage.h */, 117 | 7E526ADD20EB21070003F0E0 /* NSString+HXImage.m */, 118 | 7E526ADB20EB21070003F0E0 /* HXImageManager.h */, 119 | 7E526ADE20EB21070003F0E0 /* HXImageManager.m */, 120 | ); 121 | path = Private; 122 | sourceTree = ""; 123 | }; 124 | /* End PBXGroup section */ 125 | 126 | /* Begin PBXNativeTarget section */ 127 | 7E526AA820EB11170003F0E0 /* HXImageDemo */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = 7E526ABF20EB11190003F0E0 /* Build configuration list for PBXNativeTarget "HXImageDemo" */; 130 | buildPhases = ( 131 | 7E526AA520EB11170003F0E0 /* Sources */, 132 | 7E526AA620EB11170003F0E0 /* Frameworks */, 133 | 7E526AA720EB11170003F0E0 /* Resources */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | ); 139 | name = HXImageDemo; 140 | productName = HXImageDemo; 141 | productReference = 7E526AA920EB11170003F0E0 /* HXImageDemo.app */; 142 | productType = "com.apple.product-type.application"; 143 | }; 144 | /* End PBXNativeTarget section */ 145 | 146 | /* Begin PBXProject section */ 147 | 7E526AA120EB11170003F0E0 /* Project object */ = { 148 | isa = PBXProject; 149 | attributes = { 150 | LastUpgradeCheck = 0940; 151 | ORGANIZATIONNAME = unique; 152 | TargetAttributes = { 153 | 7E526AA820EB11170003F0E0 = { 154 | CreatedOnToolsVersion = 9.4.1; 155 | }; 156 | }; 157 | }; 158 | buildConfigurationList = 7E526AA420EB11170003F0E0 /* Build configuration list for PBXProject "HXImageDemo" */; 159 | compatibilityVersion = "Xcode 9.3"; 160 | developmentRegion = en; 161 | hasScannedForEncodings = 0; 162 | knownRegions = ( 163 | en, 164 | Base, 165 | ); 166 | mainGroup = 7E526AA020EB11170003F0E0; 167 | productRefGroup = 7E526AAA20EB11170003F0E0 /* Products */; 168 | projectDirPath = ""; 169 | projectRoot = ""; 170 | targets = ( 171 | 7E526AA820EB11170003F0E0 /* HXImageDemo */, 172 | ); 173 | }; 174 | /* End PBXProject section */ 175 | 176 | /* Begin PBXResourcesBuildPhase section */ 177 | 7E526AA720EB11170003F0E0 /* Resources */ = { 178 | isa = PBXResourcesBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | 7E526AB920EB11190003F0E0 /* LaunchScreen.storyboard in Resources */, 182 | 7E526AB620EB11190003F0E0 /* Assets.xcassets in Resources */, 183 | 7E526AB420EB11180003F0E0 /* Main.storyboard in Resources */, 184 | 7E526AD020EB1DB20003F0E0 /* tabbar_style@3x.png in Resources */, 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | }; 188 | /* End PBXResourcesBuildPhase section */ 189 | 190 | /* Begin PBXSourcesBuildPhase section */ 191 | 7E526AA520EB11170003F0E0 /* Sources */ = { 192 | isa = PBXSourcesBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | 7E526AB120EB11180003F0E0 /* ViewController.m in Sources */, 196 | 7E526AE020EB21070003F0E0 /* NSString+HXImage.m in Sources */, 197 | 7E526ABC20EB11190003F0E0 /* main.m in Sources */, 198 | 7E526ADF20EB21070003F0E0 /* UIImage+HXImage.m in Sources */, 199 | 7E526AAE20EB11170003F0E0 /* AppDelegate.m in Sources */, 200 | 7E526AE120EB21070003F0E0 /* HXImageManager.m in Sources */, 201 | 7E526AD320EB1EAC0003F0E0 /* NSObject+Dealloc.m in Sources */, 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | /* End PBXSourcesBuildPhase section */ 206 | 207 | /* Begin PBXVariantGroup section */ 208 | 7E526AB220EB11180003F0E0 /* Main.storyboard */ = { 209 | isa = PBXVariantGroup; 210 | children = ( 211 | 7E526AB320EB11180003F0E0 /* Base */, 212 | ); 213 | name = Main.storyboard; 214 | sourceTree = ""; 215 | }; 216 | 7E526AB720EB11190003F0E0 /* LaunchScreen.storyboard */ = { 217 | isa = PBXVariantGroup; 218 | children = ( 219 | 7E526AB820EB11190003F0E0 /* Base */, 220 | ); 221 | name = LaunchScreen.storyboard; 222 | sourceTree = ""; 223 | }; 224 | /* End PBXVariantGroup section */ 225 | 226 | /* Begin XCBuildConfiguration section */ 227 | 7E526ABD20EB11190003F0E0 /* Debug */ = { 228 | isa = XCBuildConfiguration; 229 | buildSettings = { 230 | ALWAYS_SEARCH_USER_PATHS = NO; 231 | CLANG_ANALYZER_NONNULL = YES; 232 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 234 | CLANG_CXX_LIBRARY = "libc++"; 235 | CLANG_ENABLE_MODULES = YES; 236 | CLANG_ENABLE_OBJC_ARC = YES; 237 | CLANG_ENABLE_OBJC_WEAK = YES; 238 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 239 | CLANG_WARN_BOOL_CONVERSION = YES; 240 | CLANG_WARN_COMMA = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 243 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 244 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 245 | CLANG_WARN_EMPTY_BODY = YES; 246 | CLANG_WARN_ENUM_CONVERSION = YES; 247 | CLANG_WARN_INFINITE_RECURSION = YES; 248 | CLANG_WARN_INT_CONVERSION = YES; 249 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 250 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 251 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 252 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 253 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 254 | CLANG_WARN_STRICT_PROTOTYPES = YES; 255 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 256 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 257 | CLANG_WARN_UNREACHABLE_CODE = YES; 258 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 259 | CODE_SIGN_IDENTITY = "iPhone Developer"; 260 | COPY_PHASE_STRIP = NO; 261 | DEBUG_INFORMATION_FORMAT = dwarf; 262 | ENABLE_STRICT_OBJC_MSGSEND = YES; 263 | ENABLE_TESTABILITY = YES; 264 | GCC_C_LANGUAGE_STANDARD = gnu11; 265 | GCC_DYNAMIC_NO_PIC = NO; 266 | GCC_NO_COMMON_BLOCKS = YES; 267 | GCC_OPTIMIZATION_LEVEL = 0; 268 | GCC_PREPROCESSOR_DEFINITIONS = ( 269 | "DEBUG=1", 270 | "$(inherited)", 271 | ); 272 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 273 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 274 | GCC_WARN_UNDECLARED_SELECTOR = YES; 275 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 276 | GCC_WARN_UNUSED_FUNCTION = YES; 277 | GCC_WARN_UNUSED_VARIABLE = YES; 278 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 279 | MTL_ENABLE_DEBUG_INFO = YES; 280 | ONLY_ACTIVE_ARCH = YES; 281 | SDKROOT = iphoneos; 282 | }; 283 | name = Debug; 284 | }; 285 | 7E526ABE20EB11190003F0E0 /* Release */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | ALWAYS_SEARCH_USER_PATHS = NO; 289 | CLANG_ANALYZER_NONNULL = YES; 290 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 291 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 292 | CLANG_CXX_LIBRARY = "libc++"; 293 | CLANG_ENABLE_MODULES = YES; 294 | CLANG_ENABLE_OBJC_ARC = YES; 295 | CLANG_ENABLE_OBJC_WEAK = YES; 296 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 297 | CLANG_WARN_BOOL_CONVERSION = YES; 298 | CLANG_WARN_COMMA = YES; 299 | CLANG_WARN_CONSTANT_CONVERSION = YES; 300 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 301 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 302 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 303 | CLANG_WARN_EMPTY_BODY = YES; 304 | CLANG_WARN_ENUM_CONVERSION = YES; 305 | CLANG_WARN_INFINITE_RECURSION = YES; 306 | CLANG_WARN_INT_CONVERSION = YES; 307 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 308 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 309 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 310 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 311 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 312 | CLANG_WARN_STRICT_PROTOTYPES = YES; 313 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 314 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 315 | CLANG_WARN_UNREACHABLE_CODE = YES; 316 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 317 | CODE_SIGN_IDENTITY = "iPhone Developer"; 318 | COPY_PHASE_STRIP = NO; 319 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 320 | ENABLE_NS_ASSERTIONS = NO; 321 | ENABLE_STRICT_OBJC_MSGSEND = YES; 322 | GCC_C_LANGUAGE_STANDARD = gnu11; 323 | GCC_NO_COMMON_BLOCKS = YES; 324 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 325 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 326 | GCC_WARN_UNDECLARED_SELECTOR = YES; 327 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 328 | GCC_WARN_UNUSED_FUNCTION = YES; 329 | GCC_WARN_UNUSED_VARIABLE = YES; 330 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 331 | MTL_ENABLE_DEBUG_INFO = NO; 332 | SDKROOT = iphoneos; 333 | VALIDATE_PRODUCT = YES; 334 | }; 335 | name = Release; 336 | }; 337 | 7E526AC020EB11190003F0E0 /* Debug */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 341 | CODE_SIGN_STYLE = Automatic; 342 | INFOPLIST_FILE = HXImageDemo/Info.plist; 343 | LD_RUNPATH_SEARCH_PATHS = ( 344 | "$(inherited)", 345 | "@executable_path/Frameworks", 346 | ); 347 | PRODUCT_BUNDLE_IDENTIFIER = com.unique.HXImageDemo; 348 | PRODUCT_NAME = "$(TARGET_NAME)"; 349 | TARGETED_DEVICE_FAMILY = "1,2"; 350 | }; 351 | name = Debug; 352 | }; 353 | 7E526AC120EB11190003F0E0 /* Release */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 357 | CODE_SIGN_STYLE = Automatic; 358 | INFOPLIST_FILE = HXImageDemo/Info.plist; 359 | LD_RUNPATH_SEARCH_PATHS = ( 360 | "$(inherited)", 361 | "@executable_path/Frameworks", 362 | ); 363 | PRODUCT_BUNDLE_IDENTIFIER = com.unique.HXImageDemo; 364 | PRODUCT_NAME = "$(TARGET_NAME)"; 365 | TARGETED_DEVICE_FAMILY = "1,2"; 366 | }; 367 | name = Release; 368 | }; 369 | /* End XCBuildConfiguration section */ 370 | 371 | /* Begin XCConfigurationList section */ 372 | 7E526AA420EB11170003F0E0 /* Build configuration list for PBXProject "HXImageDemo" */ = { 373 | isa = XCConfigurationList; 374 | buildConfigurations = ( 375 | 7E526ABD20EB11190003F0E0 /* Debug */, 376 | 7E526ABE20EB11190003F0E0 /* Release */, 377 | ); 378 | defaultConfigurationIsVisible = 0; 379 | defaultConfigurationName = Release; 380 | }; 381 | 7E526ABF20EB11190003F0E0 /* Build configuration list for PBXNativeTarget "HXImageDemo" */ = { 382 | isa = XCConfigurationList; 383 | buildConfigurations = ( 384 | 7E526AC020EB11190003F0E0 /* Debug */, 385 | 7E526AC120EB11190003F0E0 /* Release */, 386 | ); 387 | defaultConfigurationIsVisible = 0; 388 | defaultConfigurationName = Release; 389 | }; 390 | /* End XCConfigurationList section */ 391 | }; 392 | rootObject = 7E526AA120EB11170003F0E0 /* Project object */; 393 | } 394 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo.xcodeproj/project.xcworkspace/xcuserdata/shuang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Magic-Unique/HXImage/0b7451fb3b657050fc19063a6e5494b76f88070f/HXImageDemo/HXImageDemo.xcodeproj/project.xcworkspace/xcuserdata/shuang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo.xcodeproj/xcuserdata/shuang.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo.xcodeproj/xcuserdata/shuang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | HXImageDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // HXImageDemo 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. 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 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // HXImageDemo 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/NSObject+Dealloc.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Dealloc.h 3 | // HXImageDemo 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSObject (Dealloc) 12 | 13 | - (void)onDealloc:(void(^)(void))handler; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/NSObject+Dealloc.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Dealloc.m 3 | // HXImageDemo 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. All rights reserved. 7 | // 8 | 9 | #import "NSObject+Dealloc.h" 10 | #import 11 | 12 | @interface HXDeallocTask : NSObject 13 | 14 | @property (nonatomic, copy) void (^task)(void); 15 | 16 | + (instancetype)task:(void(^)(void))task; 17 | 18 | @end 19 | 20 | @implementation NSObject (Dealloc) 21 | 22 | - (void)onDealloc:(void (^)(void))handler { 23 | objc_setAssociatedObject(self, "dealloc", [HXDeallocTask task:handler], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 24 | } 25 | 26 | @end 27 | 28 | @implementation HXDeallocTask 29 | 30 | + (instancetype)task:(void (^)(void))task { 31 | HXDeallocTask *_task = [self new]; 32 | _task.task = task; 33 | return _task; 34 | } 35 | 36 | - (void)dealloc { 37 | !_task?:_task(); 38 | } 39 | 40 | @end 41 | 42 | 43 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // HXImageDemo 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // HXImageDemo 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "UIImage+HXImage.h" 11 | #import "NSObject+Dealloc.h" 12 | 13 | @interface ViewController () 14 | 15 | @property (nonatomic, strong, readonly) UIImageView *imageView; 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | _imageView = [[UIImageView alloc] init]; 24 | [self.view addSubview:_imageView]; 25 | UIImage *image = [UIImage hx_imageNamed:@"tabbar_style@3x"]; 26 | [image onDealloc:^{ 27 | NSLog(@"UIImage has dealloc."); 28 | }]; 29 | _imageView.image = image; 30 | [_imageView sizeToFit]; 31 | _imageView.center = self.view.center; 32 | [_imageView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onClick:)]]; 33 | _imageView.userInteractionEnabled = YES; 34 | } 35 | 36 | - (void)onClick:(id)sender { 37 | _imageView.image = nil; 38 | } 39 | 40 | - (void)didReceiveMemoryWarning { 41 | [super didReceiveMemoryWarning]; 42 | // Dispose of any resources that can be recreated. 43 | } 44 | 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // HXImageDemo 4 | // 5 | // Created by 冷秋 on 2018/7/3. 6 | // Copyright © 2018年 unique. 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 | -------------------------------------------------------------------------------- /HXImageDemo/HXImageDemo/tabbar_style@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Magic-Unique/HXImage/0b7451fb3b657050fc19063a6e5494b76f88070f/HXImageDemo/HXImageDemo/tabbar_style@3x.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 冷秋 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Plan A/KBImageManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // KBImageManager.h 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface KBImageManager : NSObject 13 | 14 | + (instancetype)defaultManager; 15 | 16 | - (UIImage *)imageWithName:(NSString *)name; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Plan A/KBImageManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // KBImageManager.m 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import "KBImageManager.h" 10 | #import "NSBundle+Scale.h" 11 | #import "NSString+Scale.h" 12 | #import "NSObject+MRC.h" 13 | 14 | 15 | @interface KBImageManager () 16 | 17 | @property (nonatomic, strong) NSMutableDictionary *imageDic; 18 | @property (nonatomic, assign) BOOL isEnum; 19 | @property (nonatomic, strong) NSLock *lock; 20 | 21 | @property (nonatomic, strong) dispatch_queue_t queue; 22 | 23 | @end 24 | 25 | @implementation KBImageManager 26 | 27 | + (instancetype)defaultManager { 28 | static KBImageManager *manager = nil; 29 | static dispatch_once_t onceToken; 30 | dispatch_once(&onceToken, ^{ 31 | manager = [[KBImageManager alloc]init]; 32 | [manager addRunLoopObserver]; 33 | manager.isEnum = NO; 34 | }); 35 | return manager; 36 | } 37 | 38 | 39 | - (UIImage *)imageWithName:(NSString *)name { 40 | // read from RAM 41 | UIImage *image = [self.imageDic valueForKey:name]; 42 | 43 | if(image) { 44 | return image; 45 | } 46 | 47 | // read ROM 48 | NSString *res = name.stringByDeletingPathExtension; 49 | NSString *ext = name.pathExtension; 50 | NSString *path = nil; 51 | CGFloat scale = 1; 52 | 53 | // If no extension, guess by system supported (same as UIImage). 54 | NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"]; 55 | NSArray *scales = [NSBundle scaleArray]; 56 | for (int s = 0; s < scales.count; s++) { 57 | scale = ((NSNumber *)scales[s]).floatValue; 58 | NSString *scaledName = [res stringByAppendingScale:scale]; 59 | for (NSString *e in exts) { 60 | path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e]; 61 | if (path) break; 62 | } 63 | if (path) break; 64 | } 65 | if (path.length == 0) return nil; 66 | 67 | NSData *data = [NSData dataWithContentsOfFile:path]; 68 | if (data.length == 0) return nil; 69 | UIImage *storeimage = [[UIImage alloc] initWithData:data scale:scale]; 70 | 71 | // save to RAM 72 | [self.imageDic setObject:storeimage forKey:name]; 73 | 74 | return storeimage; 75 | } 76 | 77 | #pragma mark - Run loop 78 | 79 | - (void)addRunLoopObserver { 80 | CFRunLoopObserverRef oberver= CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { 81 | switch (activity) { 82 | case kCFRunLoopEntry: 83 | break; 84 | case kCFRunLoopBeforeTimers: 85 | break; 86 | case kCFRunLoopBeforeSources: 87 | break; 88 | case kCFRunLoopBeforeWaiting: 89 | [self selfRemoveRetainCountIsOne]; 90 | break; 91 | case kCFRunLoopAfterWaiting: 92 | break; 93 | case kCFRunLoopExit: 94 | break; 95 | default: 96 | break; 97 | } 98 | }); 99 | 100 | 101 | CFRunLoopAddObserver(CFRunLoopGetMain(), oberver, kCFRunLoopCommonModes); 102 | } 103 | 104 | - (void)selfRemoveRetainCountIsOne { 105 | dispatch_async(self.queue, ^{ 106 | [self.lock lock]; 107 | if(self.isEnum) return; 108 | NSMutableArray *keyArr = @[].mutableCopy; 109 | self.isEnum = YES; 110 | [self.imageDic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, NSObject * _Nonnull obj, BOOL * _Nonnull stop) { 111 | NSInteger count = obj.obj_retainCount; 112 | if(count == 2) { 113 | [keyArr addObject:key]; 114 | } 115 | }]; 116 | [keyArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 117 | [self.imageDic removeObjectForKey:obj]; 118 | }]; 119 | self.isEnum = NO; 120 | [self.lock unlock]; 121 | }); 122 | } 123 | 124 | 125 | 126 | - (NSLock *)lock { 127 | if(!_lock) { 128 | _lock = [NSLock new]; 129 | } 130 | return _lock; 131 | } 132 | 133 | 134 | - (NSMutableDictionary *)imageDic { 135 | if (!_imageDic) { 136 | _imageDic = [NSMutableDictionary dictionary]; 137 | } 138 | return _imageDic; 139 | } 140 | 141 | - (dispatch_queue_t)queue { 142 | if (_queue == NULL) { 143 | _queue = dispatch_queue_create("com.huaxu.image", DISPATCH_QUEUE_CONCURRENT); 144 | } 145 | return _queue; 146 | } 147 | 148 | @end 149 | -------------------------------------------------------------------------------- /Plan A/NSBundle+Scale.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+Scale.h 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSBundle (Scale) 12 | 13 | + (NSArray *)scaleArray; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Plan A/NSBundle+Scale.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+Scale.m 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import "NSBundle+Scale.h" 10 | #import 11 | 12 | @implementation NSBundle (Scale) 13 | 14 | + (NSArray *)scaleArray { 15 | static NSArray *scales; 16 | static dispatch_once_t onceToken; 17 | dispatch_once(&onceToken, ^{ 18 | CGFloat screenScale = [UIScreen mainScreen].scale; 19 | if (screenScale <= 1) { 20 | scales = @[@1,@2,@3]; 21 | } else if (screenScale <= 2) { 22 | scales = @[@2,@3,@1]; 23 | } else { 24 | scales = @[@3,@2,@1]; 25 | } 26 | }); 27 | return scales; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Plan A/NSObject+MRC.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+MRC.h 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSObject (MRC) 12 | 13 | @property (nonatomic, assign, readonly) NSUInteger obj_retainCount; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Plan A/NSObject+MRC.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+MRC.m 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import "NSObject+MRC.h" 10 | 11 | @implementation NSObject (MRC) 12 | 13 | - (NSUInteger)obj_retainCount { 14 | return [[self valueForKey:@"retainCount"] unsignedLongValue]; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Plan A/NSString+Scale.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+AddScale.h 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface NSString (Scale) 13 | 14 | - (NSString *)stringByAppendingScale:(CGFloat)scale; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Plan A/NSString+Scale.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+AddScale.m 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import "NSString+Scale.h" 10 | 11 | @implementation NSString (Scale) 12 | 13 | - (NSString *)stringByAppendingScale:(CGFloat)scale { 14 | if (fabs(scale - 1) <= __FLT_EPSILON__ || self.length == 0 || [self hasSuffix:@"/"]) { 15 | return self.copy; 16 | } 17 | return [self stringByAppendingFormat:@"@%@x", @(scale)]; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Plan A/UIImage+HXImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+HXImage.h 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage (HXImage) 12 | 13 | + (instancetype)hx_imageNamed:(NSString *)name; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Plan A/UIImage+HXImage.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+HXImage.m 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import "UIImage+HXImage.h" 10 | #import "KBImageManager.h" 11 | 12 | 13 | @implementation UIImage (HXImage) 14 | 15 | + (instancetype)hx_imageNamed:(NSString *)name { 16 | if (name.length == 0) return nil; 17 | if ([name hasSuffix:@"/"]) return nil; 18 | 19 | KBImageManager *manager = [KBImageManager defaultManager]; 20 | 21 | return [manager imageWithName:name]; 22 | 23 | 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Plan B/KBImageManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // KBImageManager.h 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | @interface KBImageManager : NSObject 14 | 15 | + (instancetype)defaultManager; 16 | 17 | - (UIImage *)getImageWithName:(NSString *)name; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Plan B/KBImageManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // KBImageManager.m 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import "KBImageManager.h" 10 | #import "NSBundle+KBSCaleArray.h" 11 | #import "NSString+AddScale.h" 12 | #import "NSMutableDictionary+WeakReference.h" 13 | 14 | @interface KBImageManager() 15 | 16 | @property (nonatomic, strong) NSMutableDictionary *imageBuff; 17 | 18 | @end 19 | 20 | @implementation KBImageManager 21 | 22 | + (instancetype)defaultManager { 23 | static KBImageManager *manager = nil; 24 | static dispatch_once_t onceToken; 25 | dispatch_once(&onceToken, ^{ 26 | manager = [[self alloc] init]; 27 | }); 28 | return manager; 29 | } 30 | 31 | - (UIImage *)getImageWithName:(NSString *)name { 32 | 33 | UIImage *image = [self.imageBuff weak_getObjectForKey:name]; 34 | if(image) { 35 | return image; 36 | } 37 | 38 | NSString *res = name.stringByDeletingPathExtension; 39 | NSString *ext = name.pathExtension; 40 | NSString *path = nil; 41 | CGFloat scale = 1; 42 | 43 | // If no extension, guess by system supported (same as UIImage). 44 | NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"]; 45 | NSArray *scales = [NSBundle scaleArray]; 46 | for (int s = 0; s < scales.count; s++) { 47 | scale = ((NSNumber *)scales[s]).floatValue; 48 | NSString *scaledName = [res stringByAppendingScale:scale]; 49 | for (NSString *e in exts) { 50 | path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e]; 51 | if (path) break; 52 | } 53 | if (path) break; 54 | } 55 | if (path.length == 0) return nil; 56 | 57 | NSData *data = [NSData dataWithContentsOfFile:path]; 58 | if (data.length == 0) return nil; 59 | UIImage *storeImage = [[UIImage alloc] initWithData:data scale:scale]; 60 | [self.imageBuff weak_setObject:storeImage forKey:name]; 61 | 62 | return storeImage; 63 | 64 | } 65 | - (NSMutableDictionary *)imageBuff { 66 | if(!_imageBuff) { 67 | _imageBuff = [NSMutableDictionary dictionary]; 68 | } 69 | return _imageBuff; 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /Plan B/NSBundle+KBSCaleArray.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+KBSCaleArray.h 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSBundle (KBSCaleArray) 12 | 13 | + (NSArray *)scaleArray; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Plan B/NSBundle+KBSCaleArray.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+KBSCaleArray.m 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import "NSBundle+KBSCaleArray.h" 10 | #import 11 | 12 | @implementation NSBundle (KBSCaleArray) 13 | 14 | +(NSArray *)scaleArray { 15 | static NSArray *scales; 16 | static dispatch_once_t onceToken; 17 | dispatch_once(&onceToken, ^{ 18 | CGFloat screenScale = [UIScreen mainScreen].scale; 19 | if (screenScale <= 1) { 20 | scales = @[@1,@2,@3]; 21 | } else if (screenScale <= 2) { 22 | scales = @[@2,@3,@1]; 23 | } else { 24 | scales = @[@3,@2,@1]; 25 | } 26 | }); 27 | return scales; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Plan B/NSMutableDictionary+WeakReference.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableDictionary+WeakReference.h 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/4/29. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSMutableDictionary (WeakReference) 12 | 13 | - (void)weak_setObject:(id)anObject forKey:(NSString *)aKey; 14 | 15 | - (void)weak_setObjectWithDictionary:(NSDictionary *)dic; 16 | 17 | - (id)weak_getObjectForKey:(NSString *)key; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Plan B/NSMutableDictionary+WeakReference.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableDictionary+WeakReference.m 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/4/29. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import "NSMutableDictionary+WeakReference.h" 10 | #import "WeakReference.h" 11 | 12 | @implementation NSMutableDictionary (WeakReference) 13 | 14 | - (void)weak_setObject:(id)anObject forKey:(NSString *)aKey { 15 | [self setObject:makeWeakReference(anObject) forKey:aKey]; 16 | } 17 | 18 | - (void)weak_setObjectWithDictionary:(NSDictionary *)dictionary { 19 | for (NSString *key in dictionary.allKeys) { 20 | [self setObject:makeWeakReference(dictionary[key]) forKey:key]; 21 | } 22 | } 23 | 24 | - (id)weak_getObjectForKey:(NSString *)key { 25 | return weakReferenceNonretainedObjectValue(self[key]); 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Plan B/NSString+AddScale.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+AddScale.h 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface NSString (AddScale) 13 | 14 | - (NSString *)stringByAppendingScale:(CGFloat)scale; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Plan B/NSString+AddScale.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+AddScale.m 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import "NSString+AddScale.h" 10 | 11 | @implementation NSString (AddScale) 12 | 13 | - (NSString *)stringByAppendingScale:(CGFloat)scale { 14 | if (fabs(scale - 1) <= __FLT_EPSILON__ || self.length == 0 || [self hasSuffix:@"/"]) return self.copy; 15 | return [self stringByAppendingFormat:@"@%@x", @(scale)]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Plan B/UIImage+HXImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+HXImage.h 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage (HXImage) 12 | 13 | + (UIImage *)hx_ImageNamed:(NSString *)name; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Plan B/UIImage+HXImage.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+HXImage.m 3 | // Kuer 4 | // 5 | // Created by Kuber on 16/3/30. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import "UIImage+HXImage.h" 10 | #import "KBImageManager.h" 11 | 12 | 13 | @implementation UIImage (HXImage) 14 | 15 | + (UIImage *)hx_ImageNamed:(NSString *)name { 16 | if (name.length == 0) return nil; 17 | if ([name hasSuffix:@"/"]) return nil; 18 | 19 | KBImageManager *manager = [KBImageManager defaultManager]; 20 | 21 | return [manager getImageWithName:name]; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Plan B/WeakReference.h: -------------------------------------------------------------------------------- 1 | // 2 | // WeakReference.h 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/4/29. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef id (^WeakReference)(void); 12 | 13 | WeakReference makeWeakReference(id object); 14 | 15 | id weakReferenceNonretainedObjectValue(WeakReference ref); 16 | -------------------------------------------------------------------------------- /Plan B/WeakReference.m: -------------------------------------------------------------------------------- 1 | // 2 | // WeakReference.m 3 | // Kuber 4 | // 5 | // Created by Kuber on 16/4/29. 6 | // Copyright © 2016年 Huaxu Technology. All rights reserved. 7 | // 8 | 9 | #import "WeakReference.h" 10 | 11 | WeakReference makeWeakReference(id object) { 12 | __weak id weakref = object; 13 | return ^{ 14 | return weakref; 15 | }; 16 | } 17 | 18 | id weakReferenceNonretainedObjectValue(WeakReference ref) { 19 | return ref ? ref() : nil; 20 | } 21 | -------------------------------------------------------------------------------- /Plan C: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Magic-Unique/HXImage/0b7451fb3b657050fc19063a6e5494b76f88070f/Plan C -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HXImage 2 | 3 | A automatically reference manager for icon image. 4 | 5 | 自动创建销毁图片资源 6 | 7 | [中文说明](https://www.jianshu.com/p/5be5febe73ac) 8 | 9 | Author: [@冷秋](http://www.weibo.com/magicunique) [@Forever波波哥哥](http://weibo.com/u/2336714285) 10 | 11 | ## Usage 使用 12 | 13 | ### CocoaPods 14 | 15 | ```ruby 16 | pod 'HXImage' 17 | ``` 18 | 19 | ```objc 20 | #import 21 | ``` 22 | 23 | If you import HXImage with CocoaPods, the Plan-C will be used. 24 | 25 | 使用 CocoaPods 会自动导入 Plan C 目录 26 | 27 | ### Source 28 | 29 | Drag any *Plan* folder(Plan C for best) into your project. 30 | 31 | 选择任意 Plan 目录(推荐 Plan C), 导入到工程中: 32 | 33 | ```objc 34 | #import "UIImage+HXImage.h" 35 | ``` 36 | 37 | ### Release Your Image File 38 | 39 | HXImage is not supporting image in Assets.car. So you must release your image to the main bundle. 40 | 41 | HXImage 不支持 Assets.car 中的文件,所以你必须把图片文件放到 Bundle 中。 42 | 43 | ### Create UIImage Instance 44 | 45 | Create an UIImage instance. 46 | 47 | 创建 UIImage 对象 48 | 49 | ```objc 50 | // default 51 | UIImage *image = [UIImage hx_imageNamed:@"image"]; 52 | 53 | // in special folder 54 | UIImage *image = [UIImage hx_imageNamed:@"image" inDirectory:@"Documents"]; 55 | 56 | // contents of file 57 | UIImage *image = [UIImage hx_imageWithContentsOfFile:@"path/to/image"]; 58 | ``` 59 | 60 | * **The *image* will be dealloc atomically if there is not any reference to the instance.** 61 | * **当没有任何引用到 *image* 对象时,该对象会自动销毁** 62 | 63 | ```objc 64 | // default 65 | UIImage *image = [UIImage hx_imageNamed:@"image"]; 66 | image = nil; // call -[UIImage dealloc] 67 | ``` 68 | 69 | * **The same image will not be created 70 | before dealloc.** 71 | * **在旧的图片销毁之前,重复取相同名字的图片,不会用到多余的内存** 72 | 73 | ```objc 74 | // repeat creating 75 | UIImage *image = [UIImage hx_imageNamed:@"image"]; 76 | UIImage *newImage = [UIImage hx_imageNamed:@"image"]; // image == newImage 77 | image = nil; 78 | newImage = nil; // call -[UIImage dealloc] 79 | ``` -------------------------------------------------------------------------------- /description.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 这篇文章是笔者在开发App过程中发现的一些内存问题, 然后学习了YYKit框架时候也发现了图片的缓存处理的不够得当 (YYKit 作者联系了我, 说明了YYKit重写imageNamed:的目的不是为了内存管理, 而是增加兼容性, 同时也是为了YYKit中的动画服务). 以下内容是笔者在开发中做了一些实验以及总结. 如有错误望即时提出, 笔者会第一时间改正. 4 | 5 | 文章的前篇主要是对两种不同的`UIImage`工厂方法的分析, 以及对 YYKit 中的 YYImage 的分析. 罗列出这些工厂方法的内存管理的优缺点. 6 | 7 | 文章的后篇是本文要说明的重点, 如何结合两种工厂方法的优点做更进一步的节约内存的管理. 8 | 9 | > PS 10 | > 11 | > 本文所说的 Resource 是指使用`imageWithContentsOfFile:`创建图片的图片管理方式. 12 | > 13 | > ImageAssets 是指使用`imageNamed:`创建图片的图片管理方式. 14 | > 15 | > 如果你对这两个方法已经了如指掌, 可以直接看**UIImage 与 YYImage 的内存问题**和后面的内容 16 | 17 | [TOC] 18 | 19 | 20 | # UIImage 的内存处理 21 | 22 | 在实际的苹果App开发中, 将图片文件导入到工程中无非使用两种方式. 一种是 Resource `(我也不知道应该称呼什么,就这么叫吧)`,还有一种是 ImageAssets 形式存储在一个图片资源管理文件中. 这两种方式都可以存储任何形式的图片文件, 但是都有各自的优缺点在内. 接下来我们就来谈谈这两种图片数据管理方式的优缺点. 23 | 24 | ## Resource 与 "imageWithContentsOfFile:" 25 | 26 | ### Resource 的使用方式 27 | 28 | 将文件直接拖入到工程目录下, 并告诉Xcode打包项目时候把这些图片文件打包进去. 这样在应用的".app"文件夹中就有这些图片. 在项目中, 读取这些图片可以通过以下方式来获取图片文件并封装成`UIImge`对象: 29 | 30 | ```objc 31 | NSString *path = [NSBundle.mainBundle pathForResource:@"image@2x" type:@"png"]; 32 | UIImage *image = [UIImage imageWithContentsOfFile:path]; 33 | ``` 34 | 35 | 而底层的实现原理近似是: 36 | 37 | ```objc 38 | + (instancetype)imageWithContentsOfFile:(NSString *)fileName { 39 | NSUInteger scale = 0; 40 | { 41 | scale = 2;//这一部分是取 fileName 中"@"符号后面那个数字, 如果不存在则为1, 这一部分的逻辑省略 42 | } 43 | return [[self alloc] initWithData:[NSData dataWithContentsOfFile:fileName scale:scale]; 44 | } 45 | ``` 46 | 这种方式有一个局限性, 就是图片文件必须在`.ipa`的根目录下或者在沙盒中. 在`.ipa`的根目录下创建图片文件仅仅只有一种方式, 就是通过 Xcode 把图片文件直接拖入工程中. 还有一种情况也会创建图片文件, 就是当工程支持低版本的 iOS 系统时, 低版本的iOS系统并不支持 ImageAssets 打包文件的图片读取, 所以 Xcode 在编译时候会自动地将 ImageAssets 中的图片复制一份到根目录中. 此时也可以使用这个方法创建图片. 47 | 48 | ### Resource 的特性 49 | 50 | 在 Resource 的图片管理方式中, 所有的图片创建都是通过读取文件数据得到的, 读取一次文件数据就会产生一次`NSData`以及产生一个`UIImage`, 当图片创建好后销毁对应的`NSData`, 当`UIImage`的引用计数器变为0的时候自动销毁`UIImage`. 这样的话就可以保证图片不会长期地存在在内存中. 51 | 52 | ### Resource 的常用情景 53 | 54 | 55 | 由于这种方法的特性, 所以 Resource 的方法一般用在图片数据很大, 图片一般不需要多次使用的情况. 比如说**引导页背景**(图片全屏, 有时候运行APP会显示, 有时候根本就用不到). 56 | 57 | ### Resource 的优点 58 | 59 | 图片的生命周期可以得到管理无疑是 Resource 最大的优点, 当我们需要图片的时候就创建一个, 当我们不需要这个图片的时候就让他销毁. 图片不会长期的保存在内存当中, 所以不会有很多的内存浪费. 同时, 大图一般不会长期使用, 而且大图占用内存一般比小图多了好多倍, 所以在减少大图的内存占用中, Resource 做的非常好. 60 | 61 | ## ImageAssets 与 "imageNamed:" 62 | 63 | ImageAssets 的设计初衷主要是为了自动适配 Retina 屏幕和非 Retina 屏幕, 也就是解决 iPhone 4 和 iPhone 3GS 以及以前机型的屏幕适配问题. 现在 iPhone 3GS 以及之前的机型都已被淘汰, 非 Retina 屏幕已不再是开发考虑的范围. 但是 plus 机型的推出将 Retina 屏幕又提高了一个水平, ImageAssets 现在的主要功能则是区分 plus 屏幕和非 plus 屏幕, 也就是解决 2 倍 Retina 屏幕和 3 倍 Retina 屏幕的视屏问题. 64 | 65 | ### ImageAssets 的使用方式 66 | 67 | iOS 开发中一般在工程内导入两个到三个同内容不同像素的图片文件, 一般如下: 68 | 69 | 1. image.png (30 x 30) 70 | 2. image@2x.png (60 x 60) 71 | 3. image@3x.png (90 x 90) 72 | 73 | 这三张图片都是相同内容, 而且图片名称的前缀相同, 区别在与图片名以及图片的分辨率. 开发者将这三张图片拉入 ImageAssets 后, Xcode 会以图片前缀创建一个图片组(这里也就是 "image"). 然后在代码中写: 74 | 75 | ```objc 76 | UIImage *image = [UIImage imageNamed:@"image"]; 77 | ``` 78 | 79 | 就会根据不同屏幕来获取对应不同的图片数据来创建图片. 如果是 3GS 之前的机型就会读取 "image.png", 普通 Retina 会读取 "image@2x.png", plus Retina 会读取 "image@3x.png", 如果某一个文件不存在, 就会用另一个分辨率的图片代替之. 80 | 81 | ### ImageAssets 的特性 82 | 83 | 与 Resources 相似, ImageAssets 也是从图片文件中读取图片数据转为 UIImage, 只不过这些图片数据都打包在 ImageAssets 中. 还有一个最大的区别就是图片缓存. 相当于有一个字典, key 是图片名, value是图片对象. 调用`imageNamed:`方法时候先从这个字典里取, 如果取到就直接返回, 如果取不到再去文件中创建, 然后保存到这个字典后再返回. 由于字典的`key`和`value`都是强引用, 所以一旦创建后的图片永不销毁. 84 | 85 | 其内部代码相似于: 86 | 87 | ```objc 88 | 89 | + (NSMutableDictionary *)imageBuff { 90 | static NSMutableDictionary *_imageBuff; 91 | static dispatch_once_t onceToken; 92 | dispatch_once(&onceToken, ^{ 93 | _imageBuff = [[NSMutableDictionary alloc] init]; 94 | }); 95 | return _imageBuff; 96 | } 97 | 98 | + (instancetype)imageNamed:(NSString *)imageName { 99 | if (!imageName) { 100 | return nil; 101 | } 102 | UIImage *image = self.imageBuff[imageName]; 103 | if (image) { 104 | return image; 105 | } 106 | NSString *path = @"this is the image path"//这段逻辑忽略 107 | image = [self imageWithContentsOfFile:path]; 108 | if (image) { 109 | self.imageBuff[imageName] = image; 110 | } 111 | return image; 112 | } 113 | 114 | ``` 115 | 116 | ### ImageAssets 的使用场景 117 | 118 | ImageAssets 最主要的使用场景就是 icon 类的图片, 一般 icon 类的图片大小在 3kb 到 20 kb 不等, 都是一些小文件. 119 | 120 | ### ImageAssets 的优点 121 | 122 | 当一个 icon 在多个地方需要被显示的时候, 其对应的`UIImage`对象只会被创建一次, 而且多个地方的 icon 都将会共用一个 `UIImage` 对象. 减少沙盒的读取操作. 123 | 124 | # YYImage 的内存处理 125 | 126 | ***由于YYImage的目的并不是为了关闭缓存, 所以此段没有分析的意义, 现已删除.*** 127 | 128 | YYImage 的核心就是学习`imageWithContentsOfFile:`的方法原理去实现`imageNamed:`方法. 达到`imageNamed:`方法中没有缓存功能, 最终使得不需要图片的时候即可销毁图片对象. 129 | 130 | ## imageWithContentsOfFile 代替 imageNamed 131 | 132 | 首先看 YYImage 的代码: 133 | 134 | ```objc 135 | + (YYImage *)imageNamed:(NSString *)name { 136 | if (name.length == 0) return nil; 137 | if ([name hasSuffix:@"/"]) return nil; 138 | 139 | NSString *res = name.stringByDeletingPathExtension; 140 | NSString *ext = name.pathExtension; 141 | NSString *path = nil; 142 | CGFloat scale = 1; 143 | 144 | // If no extension, guess by system supported (same as UIImage). 145 | NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"]; 146 | NSArray *scales = [NSBundle preferredScales]; 147 | for (int s = 0; s < scales.count; s++) { 148 | scale = ((NSNumber *)scales[s]).floatValue; 149 | NSString *scaledName = [res stringByAppendingNameScale:scale]; 150 | for (NSString *e in exts) { 151 | path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e]; 152 | if (path) break; 153 | } 154 | if (path) break; 155 | } 156 | if (path.length == 0) return nil; 157 | 158 | NSData *data = [NSData dataWithContentsOfFile:path]; 159 | if (data.length == 0) return nil; 160 | 161 | return [[self alloc] initWithData:data scale:scale]; 162 | } 163 | ``` 164 | 165 | 166 | 从代码可以看出 `[YYImage imageNamed:]`这个方法底层是利用通过一定的计算获取到最佳尺寸, 然后枚举图片匹配图片文件名, 拼接成路径后利用`NSData`创建出`UIImage`. 本质上和`imageWithContentsOfFile:`没有啥区别. 167 | 168 | # UIImage 与 YYImage 的内存问题 169 | ### Resource 的缺点 170 | 171 | 当我们需要图片的时候就会去沙盒中读取这个图片文件, 转换成`UIImage`对象来使用. 现在假设一种场景: 172 | 173 | > 1. image@2x.png 图片占用 5kb 的内存 174 | > 2. image@2x.png 在多个界面都用到, 且有7处会同时显示这个图片 175 | 176 | 通过代码分析就可以知道 Resource 这个方式在这个情景下会占用 **5kb/个 X 7个 = 35kb** 内存. 然而, 在 ImageAssets 方式下, 全部取自字典缓存中的`UIImage`, 无论有几处显示图片, 都只会占用 **5kb/个 X 1个 = 5kb** 内存. 此时 Resource 占用内存将会更大. 177 | 178 | 由于 YYImage 的核心就是利用`imageWithContentsOfFile:`代替`imageNamed:`, 所以这也是 YYImage 的缺陷之处 179 | 180 | ### ImageAssets 的缺点 181 | 182 | 第一次读取的图片保存到缓冲区, 然后永不销毁. 如果这个图片过大, 占用几百 kb, 这一块的内存将不会释放, 必然导致内存的浪费, 而且这个浪费的周期与APP的生命周期同步. 183 | 184 | # 解决方案 185 | 186 | 为了解决 Resource 的多图共存问题, 可以学习 ImageAssets 中的字典来形成键值对, 当字典中`name`对应的`image`存在就不创建, 如果不存在就创建. 字典的存在必然导致 UIImage 永不销毁, 所以还要考虑字典不会影响到 UIImage 的自动销毁问题. 由此可以做出如下总结: 187 | 188 | 1. 需要一个字典存储已经创建的 Image 的 name-image 映射 189 | 2. 当除了这个字典外, 没有别的对象持有 image, 则从这个字典中删除对应 name-image 映射 190 | 191 | 第一个要求的实现方式很简单, 接下来探讨第二个要求. 192 | 193 | 首先可以考虑如何判断**除了字典外没有别的对象持有 image**? 字典是强引用 key 和 value 的, 当 image 放入字典的时候, image 的引用计数器就会 + 1. 我们可以判断字典中的 image 的引用计数器是否为 1, 如果为 1 则可以判断出目前只有字典持有这个 image, 因此可以从这个字典里删除这个 image. 194 | 195 | 这样即可提出一个方案 **MRC+字典** 196 | 197 | 我们还可以换一种思想, 字典是强引用容器, 字典存在必然导致内部value的引用计数器大于等于1. 如果字典是一个弱引用容器, 字典的存在并不会影响到内部value的引用计数器, 那么 image 的销毁就不会因为字典而受到影响. 198 | 199 | 于是又有一个方案 **弱引用字典** 200 | 201 | 接下来对这两个方案作深入的分析和实现: 202 | 203 | ## 方案一之 MRC+字典 204 | 205 | 该方案具体思路是: 找到一个合适的时机, 遍历所有 value 的 引用计数器, 当某个 value 的引用计数器为 1 时候(说明只有字典持有这个image), 则删除这个key-value对. 206 | 207 | ### 第一步, 在ARC下获取某个对象的引用计数器: 208 | 209 | 首先 ARC 下是不允许使用`retainCount`这个属性的, 但是由于 ARC 的原理是编译器自动为我们管理引用计数器, 所以就算是 ARC 环境下, 引用计数器也是 Enable 状态, 并且仍然是利用引用计数器来管理内存. 所以我们可以使用 KVC 来获取引用计数器: 210 | 211 | ```objc 212 | @implementation NSObject (MRC) 213 | 214 | // 无法直接重写 retainCount 的方法, 所以加了一个前缀 215 | - (NSUInteger)obj_retainCount { 216 | return [[self valueForKey:@"retainCount"] unsignedLongValue]; 217 | } 218 | 219 | @end 220 | 221 | ``` 222 | 223 | ### 第二步 遍历 value的引用计数器 224 | 225 | ```objc 226 | // 由于遍历键值对时候不能做添加和删除操作, 所以把要删除的key放到一个数组中 227 | NSMutableArray *keyArr = [NSMutableArray array]; 228 | [self.imageDic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, NSObject * _Nonnull obj, BOOL * _Nonnull stop) { 229 | NSInteger count = obj.obj_retainCount; 230 | if(count == 2) {// 字典持有 + obj参数持有 = 2 231 | [keyArr addObject:key]; 232 | } 233 | }]; 234 | [keyArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 235 | [self.imageDic removeObjectForKey:obj]; 236 | }]; 237 | ``` 238 | 239 | 然后处理遍历时机. 选择遍历时机是一个很困难的, 不能因为遍历而大量占有系统资源. 可以在每一次通过 name 创建(或者从字典中获取)时候遍历一次, 但这个方法有可能会长时间不调用(比如一个用户在某一个界面上呆很久). 所以我们可以在每一次 runloop 到来时候来做一次遍历, 同时我们还需要标记遍历状态, 防止第二次 runloop 到来时候第一次的遍历还没结束就开始新的遍历了(此时应该直接放弃第二次遍历).代码如下: 240 | 241 | ```objc 242 | CFRunLoopObserverRef oberver= CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { 243 | if (activity == kCFRunLoopBeforeWaiting) { 244 | static enuming = NO; 245 | if (!enuming) { 246 | enuming = YES; 247 | // 这里是遍历代码 248 | enuming = NO; 249 | } 250 | } 251 | }); 252 | 253 | CFRunLoopAddObserver(CFRunLoopGetMain(), oberver, kCFRunLoopCommonModes); 254 | ``` 255 | 256 | 具体实现请看代码. 257 | 258 | ## 方案二之 弱引用字典 259 | 260 | 在上面那个方案中, 会在每一次 runloop 到来之时开辟一个线程去遍历键值对. 通常来说, 每一个 APP 创建的图片个数很大, 所以遍历键值对虽然不会阻塞主线程, 但仍然是一个非常耗时耗资源的工作. 261 | 262 | **弱引用容器**是指基于`NSArray`, `NSDictionary`, `NSSet`的容器类, 该容器与这些类最大的区别在于, 将对象放入容器中并不会改变对象的引用计数器, 同时容器是以一个弱引用指针指向这个对象, 当对象销毁时自动从容器中删除, 无需额外的操作. 263 | 264 | 目前常用的弱引用容器的实现方式是**block封装解封** 265 | 266 | 利用block封装一个对象, 且block中对象的持有操作是一个弱引用指针. 而后将block当做对象放入容器中. 容器直接持有block, 而不直接持有对象. 取对象时解包block即可得到对应对象. 267 | 268 | ### 第一步 封装与解封 269 | 270 | ```objc 271 | 272 | typedef id (^WeakReference)(void); 273 | 274 | WeakReference makeWeakReference(id object) { 275 | __weak id weakref = object; 276 | return ^{ 277 | return weakref; 278 | }; 279 | } 280 | 281 | id weakReferenceNonretainedObjectValue(WeakReference ref) { 282 | return ref ? ref() : nil; 283 | } 284 | 285 | ``` 286 | ### 第二步 改造原容器 287 | 288 | ```objc 289 | - (void)weak_setObject:(id)anObject forKey:(NSString *)aKey { 290 | [self setObject:makeWeakReference(anObject) forKey:aKey]; 291 | } 292 | 293 | - (void)weak_setObjectWithDictionary:(NSDictionary *)dic { 294 | for (NSString *key in dic.allKeys) { 295 | [self setObject:makeWeakReference(dic[key]) forKey:key]; 296 | } 297 | } 298 | 299 | - (id)weak_getObjectForKey:(NSString *)key { 300 | return weakReferenceNonretainedObjectValue(self[key]); 301 | } 302 | ``` 303 | 304 | 这样就实现了一个**弱引用字典**, 之后用弱引用字典代替`imageNamed:`中的强引用字典即可. 305 | --------------------------------------------------------------------------------