From 1ec69221e39dbcbfe8f79e1ebb33c3e65a05fb59 Mon Sep 17 00:00:00 2001 From: appleguy Date: Thu, 9 Feb 2017 13:52:36 -0800 Subject: [PATCH] [UIImage+ASConvenience] Reorder header and add more documentation. (#3004) Order the methods so the most valuable one, as_imageNamed, is more immediately noticeable. Also added a block comment describing its purpose and usage suggestions. Separated @implementation for fairly unrelated methods by categories. --- AsyncDisplayKit/UIImage+ASConvenience.h | 73 +++++++++++-------- AsyncDisplayKit/UIImage+ASConvenience.m | 94 +++++++++++++------------ 2 files changed, 95 insertions(+), 72 deletions(-) diff --git a/AsyncDisplayKit/UIImage+ASConvenience.h b/AsyncDisplayKit/UIImage+ASConvenience.h index 8c89d83907..1c89ccf4cd 100644 --- a/AsyncDisplayKit/UIImage+ASConvenience.h +++ b/AsyncDisplayKit/UIImage+ASConvenience.h @@ -15,14 +15,52 @@ NS_ASSUME_NONNULL_BEGIN -// High-performance flat-colored, rounded-corner resizable images -// -// For "Baked-in Opaque" corners, set cornerColor equal to the color behind the rounded image object, i.e. the background color. -// For "Baked-in Alpha" corners, set cornerColor = [UIColor clearColor] -// -// See http://asyncdisplaykit.org/docs/corner-rounding.html for an explanation. +/** + * Dramatically faster version of +[UIImage imageNamed:]. Although it is believed that imageNamed: + * has a cache and is fast, it actually performs expensive asset catalog lookups and is often a + * performance bottleneck (verified on iOS 7 through iOS 10). + * + * Use [UIImage as_imageNamed:] anywhere in your app, even if you aren't using other parts of ASDK. + * Although not the best choice for extremely large assets that are only used once, it is the ideal + * choice for any assets used in tab bars, nav bars, buttons, table or collection cells, etc. + */ -@interface UIImage (ASDKAdditions) +@interface UIImage (ASDKFastImageNamed) + +/** + * A version of imageNamed that caches results because loading an image is expensive. + * Calling with the same name value will usually return the same object. A UIImage, + * after creation, is immutable and thread-safe so it's fine to share these objects across multiple threads. + * + * @param imageName The name of the image to load + * @return The loaded image or nil + */ ++ (UIImage *)as_imageNamed:(NSString *)imageName; + +/** + * A version of imageNamed that caches results because loading an image is expensive. + * Calling with the same name value will usually return the same object. A UIImage, + * after creation, is immutable and thread-safe so it's fine to share these objects across multiple threads. + * + * @param imageName The name of the image to load + * @param traitCollection The traits associated with the intended environment for the image. + * @return The loaded image or nil + */ ++ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection; + +@end + +/** + * High-performance flat-colored, rounded-corner resizable images + * + * For "Baked-in Opaque" corners, set cornerColor equal to the color behind the rounded image object, + * i.e. the background color. + * For "Baked-in Alpha" corners, set cornerColor = [UIColor clearColor] + * + * See http://asyncdisplaykit.org/docs/corner-rounding.html for an explanation. + */ + +@interface UIImage (ASDKResizableRoundedRects) /** * This generates a flat-color, rounded-corner resizeable image @@ -69,27 +107,6 @@ NS_ASSUME_NONNULL_BEGIN roundedCorners:(UIRectCorner)roundedCorners scale:(CGFloat)scale AS_WARN_UNUSED_RESULT; -/** - * A version of imageNamed that caches results because loading an image is expensive. - * Calling with the same name value will usually return the same object. A UIImage, - * after creation, is immutable and thread-safe so it's fine to share these objects across multiple threads. - * - * @param imageName The name of the image to load - * @return The loaded image or nil - */ -+ (UIImage *)as_imageNamed:(NSString *)imageName; - -/** - * A version of imageNamed that caches results because loading an image is expensive. - * Calling with the same name value will usually return the same object. A UIImage, - * after creation, is immutable and thread-safe so it's fine to share these objects across multiple threads. - * - * @param imageName The name of the image to load - * @param traitCollection The traits associated with the intended environment for the image. - * @return The loaded image or nil - */ -+ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection; - @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/UIImage+ASConvenience.m b/AsyncDisplayKit/UIImage+ASConvenience.m index 3fad3a5f6d..4d1b5985c9 100644 --- a/AsyncDisplayKit/UIImage+ASConvenience.m +++ b/AsyncDisplayKit/UIImage+ASConvenience.m @@ -14,7 +14,56 @@ #import #import -@implementation UIImage (ASDKAdditions) +#pragma mark - ASDKFastImageNamed + +@implementation UIImage (ASDKFastImageNamed) + +UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollection) +{ + static NSCache *imageCache = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // Because NSCache responds to memory warnings, we do not need an explicit limit. + // all of these objects contain compressed image data and are relatively small + // compared to the backing stores of text and image views. + imageCache = [[NSCache alloc] init]; + }); + + UIImage *image = nil; + if ([imageName length] > 0) { + NSString *imageKey = imageName; + if (traitCollection) { + char imageKeyBuffer[256]; + snprintf(imageKeyBuffer, sizeof(imageKeyBuffer), "%s|%ld|%ld", imageName.UTF8String, (long)traitCollection.horizontalSizeClass, (long)traitCollection.verticalSizeClass); + imageKey = [NSString stringWithUTF8String:imageKeyBuffer]; + } + + image = [imageCache objectForKey:imageKey]; + if (!image) { + image = [UIImage imageNamed:imageName inBundle:nil compatibleWithTraitCollection:traitCollection]; + if (image) { + [imageCache setObject:image forKey:imageKey]; + } + } + } + return image; +} + ++ (UIImage *)as_imageNamed:(NSString *)imageName +{ + return cachedImageNamed(imageName, nil); +} + ++ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(UITraitCollection *)traitCollection +{ + return cachedImageNamed(imageName, traitCollection); +} + +@end + +#pragma mark - ASDKResizableRoundedRects + +@implementation UIImage (ASDKResizableRoundedRects) + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius cornerColor:(UIColor *)cornerColor @@ -123,47 +172,4 @@ return result; } -#pragma mark - as_imageNamed - -UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollection) -{ - static NSCache *imageCache = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // Because NSCache responds to memory warnings, we do not need an explicit limit. - // all of these objects contain compressed image data and are relatively small - // compared to the backing stores of text and image views. - imageCache = [[NSCache alloc] init]; - }); - - UIImage *image = nil; - if ([imageName length] > 0) { - NSString *imageKey = imageName; - if (traitCollection) { - char imageKeyBuffer[256]; - snprintf(imageKeyBuffer, sizeof(imageKeyBuffer), "%s|%ld|%ld", imageName.UTF8String, (long)traitCollection.horizontalSizeClass, (long)traitCollection.verticalSizeClass); - imageKey = [NSString stringWithUTF8String:imageKeyBuffer]; - } - - image = [imageCache objectForKey:imageKey]; - if (!image) { - image = [UIImage imageNamed:imageName inBundle:nil compatibleWithTraitCollection:traitCollection]; - if (image) { - [imageCache setObject:image forKey:imageKey]; - } - } - } - return image; -} - -+ (UIImage *)as_imageNamed:(NSString *)imageName -{ - return cachedImageNamed(imageName, nil); -} - -+ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(UITraitCollection *)traitCollection -{ - return cachedImageNamed(imageName, traitCollection); -} - @end