Browse Source

[Issue #2187][Issue #2245][Issue #2255] Adding image cache and response serializer properties to UIButton category, as well as cancelling image request operations correctly on a per-state basis

Changing signature of success blocks in UIButton category to be consistent with those of UIImageView category
Mattt Thompson 11 years ago
parent
commit
6507d59be6

+ 39 - 6
UIKit+AFNetworking/UIButton+AFNetworking.h

@@ -28,11 +28,40 @@
 
 #import <UIKit/UIKit.h>
 
+@protocol AFURLResponseSerialization, AFImageCache;
+
 /**
  This category adds methods to the UIKit framework's `UIButton` class. The methods in this category provide support for loading remote images and background images asynchronously from a URL.
  */
 @interface UIButton (AFNetworking)
 
+///----------------------------
+/// @name Accessing Image Cache
+///----------------------------
+
+/**
+ The image cache used to improve image loadiing performance on scroll views. By default, `UIButton` will use the `sharedImageCache` of `UIImageView`.
+ */
++ (id <AFImageCache>)sharedImageCache;
+
+/**
+ Set the cache used for image loading.
+
+ @param imageCache The image cache.
+ */
++ (void)setSharedImageCache:(id <AFImageCache>)imageCache;
+
+///------------------------------------
+/// @name Accessing Response Serializer
+///------------------------------------
+
+/**
+ The response serializer used to create an image representation from the server response and response data. By default, this is an instance of `AFImageResponseSerializer`.
+
+ @discussion Subclasses of `AFImageResponseSerializer` could be used to perform post-processing, such as color correction, face detection, or other effects. See https://github.com/AFNetworking/AFCoreImageSerializer
+ */
+@property (nonatomic, strong) id <AFURLResponseSerialization> imageResponseSerializer;
+
 ///--------------------
 /// @name Setting Image
 ///--------------------
@@ -77,7 +106,7 @@
 - (void)setImageForState:(UIControlState)state
           withURLRequest:(NSURLRequest *)urlRequest
         placeholderImage:(UIImage *)placeholderImage
-                 success:(void (^)(NSHTTPURLResponse *response, UIImage *image))success
+                 success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
                  failure:(void (^)(NSError *error))failure;
 
 
@@ -123,7 +152,7 @@
 - (void)setBackgroundImageForState:(UIControlState)state
                     withURLRequest:(NSURLRequest *)urlRequest
                   placeholderImage:(UIImage *)placeholderImage
-                           success:(void (^)(NSHTTPURLResponse *response, UIImage *image))success
+                           success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
                            failure:(void (^)(NSError *error))failure;
 
 
@@ -132,14 +161,18 @@
 ///------------------------------
 
 /**
- Cancels any executing image operation for the receiver, if one exists.
+ Cancels any executing image operation for the specified control state of the receiver, if one exists.
+ 
+ @param state The control state.
  */
-- (void)cancelImageRequestOperation;
+- (void)cancelImageRequestOperationForState:(UIControlState)state;
 
 /**
- Cancels any executing background image operation for the receiver, if one exists.
+ Cancels any executing background image operation for the specified control state of the receiver, if one exists.
+ 
+ @param state The control state.
  */
-- (void)cancelBackgroundImageRequestOperation;
+- (void)cancelBackgroundImageRequestOperationForState:(UIControlState)state;
 
 @end
 

+ 139 - 62
UIKit+AFNetworking/UIButton+AFNetworking.m

@@ -26,11 +26,12 @@
 
 #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
 
+#import "AFURLResponseSerialization.h"
 #import "AFHTTPRequestOperation.h"
 
+#import "UIImageView+AFNetworking.h"
+
 @interface UIButton (_AFNetworking)
-@property (readwrite, nonatomic, strong, setter = af_setImageRequestOperation:) AFHTTPRequestOperation *af_imageRequestOperation;
-@property (readwrite, nonatomic, strong, setter = af_setBackgroundImageRequestOperation:) AFHTTPRequestOperation *af_backgroundImageRequestOperation;
 @end
 
 @implementation UIButton (_AFNetworking)
@@ -46,20 +47,36 @@
     return _af_sharedImageRequestOperationQueue;
 }
 
-- (AFHTTPRequestOperation *)af_imageRequestOperation {
-    return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, @selector(af_imageRequestOperation));
+#pragma mark -
+
+static const char * af_imageRequestOperationKeyForState(UIControlState state) {
+    return [[NSString stringWithFormat:@"af_imageRequestOperationKeyForState_%lu", state] cStringUsingEncoding:NSASCIIStringEncoding];
+}
+
+- (AFHTTPRequestOperation *)af_imageRequestOperationForState:(UIControlState)state {
+    return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, af_imageRequestOperationKeyForState(state));
 }
 
-- (void)af_setImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation {
-    objc_setAssociatedObject(self, @selector(af_imageRequestOperation), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+- (void)af_setImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation
+                           forState:(UIControlState)state
+{
+    objc_setAssociatedObject(self, af_imageRequestOperationKeyForState(state), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+#pragma mark -
+
+static const char * af_backgroundImageRequestOperationKeyForState(UIControlState state) {
+    return [[NSString stringWithFormat:@"af_backgroundImageRequestOperationKeyForState_%lu", state] cStringUsingEncoding:NSASCIIStringEncoding];
 }
 
-- (AFHTTPRequestOperation *)af_backgroundImageRequestOperation {
-    return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, @selector(af_backgroundImageRequestOperation));
+- (AFHTTPRequestOperation *)af_backgroundImageRequestOperationForState:(UIControlState)state {
+    return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, af_backgroundImageRequestOperationKeyForState(state));
 }
 
-- (void)af_setBackgroundImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation {
-    objc_setAssociatedObject(self, @selector(af_backgroundImageRequestOperation), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+- (void)af_setBackgroundImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation
+                                     forState:(UIControlState)state
+{
+    objc_setAssociatedObject(self, af_backgroundImageRequestOperationKeyForState(state), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 }
 
 @end
@@ -68,6 +85,38 @@
 
 @implementation UIButton (AFNetworking)
 
++ (id <AFImageCache>)sharedImageCache {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wgnu"
+    return objc_getAssociatedObject(self, @selector(sharedImageCache)) ?: [UIImageView sharedImageCache];
+#pragma clang diagnostic pop
+}
+
++ (void)setSharedImageCache:(id <AFImageCache>)imageCache {
+    objc_setAssociatedObject(self, @selector(sharedImageCache), imageCache, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+#pragma mark -
+
+- (id <AFURLResponseSerialization>)imageResponseSerializer {
+    static id <AFURLResponseSerialization> _af_defaultImageResponseSerializer = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        _af_defaultImageResponseSerializer = [AFImageResponseSerializer serializer];
+    });
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wgnu"
+    return objc_getAssociatedObject(self, @selector(imageResponseSerializer)) ?: _af_defaultImageResponseSerializer;
+#pragma clang diagnostic pop
+}
+
+- (void)setImageResponseSerializer:(id <AFURLResponseSerialization>)serializer {
+    objc_setAssociatedObject(self, @selector(imageResponseSerializer), serializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+#pragma mark -
+
 - (void)setImageForState:(UIControlState)state
                  withURL:(NSURL *)url
 {
@@ -87,34 +136,48 @@
 - (void)setImageForState:(UIControlState)state
           withURLRequest:(NSURLRequest *)urlRequest
         placeholderImage:(UIImage *)placeholderImage
-                 success:(void (^)(NSHTTPURLResponse *response, UIImage *image))success
+                 success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
                  failure:(void (^)(NSError *error))failure
 {
-    [self cancelImageRequestOperation];
-
-    [self setImage:placeholderImage forState:state];
-
-    __weak __typeof(self)weakSelf = self;
-    self.af_imageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
-    self.af_imageRequestOperation.responseSerializer = [AFImageResponseSerializer serializer];
-    [self.af_imageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
-        __strong __typeof(weakSelf)strongSelf = weakSelf;
-        if ([[urlRequest URL] isEqual:[operation.request URL]]) {
-            if (success) {
-                success(operation.response, responseObject);
-            } else if (responseObject) {
-                [strongSelf setImage:responseObject forState:state];
-            }
+    [self cancelImageRequestOperationForState:state];
+
+    UIImage *cachedImage = [[[self class] sharedImageCache] cachedImageForRequest:urlRequest];
+    if (cachedImage) {
+        if (success) {
+            success(nil, nil, cachedImage);
+        } else {
+            [self setImage:cachedImage forState:state];
         }
-    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
-        if ([[urlRequest URL] isEqual:[operation.response URL]]) {
-            if (failure) {
-                failure(error);
-            }
+
+        [self af_setImageRequestOperation:nil forState:state];
+    } else {
+        if (placeholderImage) {
+            [self setImage:placeholderImage forState:state];
         }
-    }];
 
-    [[[self class] af_sharedImageRequestOperationQueue] addOperation:self.af_imageRequestOperation];
+        __weak __typeof(self)weakSelf = self;
+        AFHTTPRequestOperation *imageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
+        imageRequestOperation.responseSerializer = self.imageResponseSerializer;
+        [imageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
+            __strong __typeof(weakSelf)strongSelf = weakSelf;
+            if ([[urlRequest URL] isEqual:[operation.request URL]]) {
+                if (success) {
+                    success(operation.request, operation.response, responseObject);
+                } else if (responseObject) {
+                    [strongSelf setImage:responseObject forState:state];
+                }
+            }
+        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
+            if ([[urlRequest URL] isEqual:[operation.response URL]]) {
+                if (failure) {
+                    failure(error);
+                }
+            }
+        }];
+
+        [self af_setImageRequestOperation:imageRequestOperation forState:state];
+        [[[self class] af_sharedImageRequestOperationQueue] addOperation:imageRequestOperation];
+    }
 }
 
 #pragma mark -
@@ -138,46 +201,60 @@
 - (void)setBackgroundImageForState:(UIControlState)state
                     withURLRequest:(NSURLRequest *)urlRequest
                   placeholderImage:(UIImage *)placeholderImage
-                           success:(void (^)(NSHTTPURLResponse *response, UIImage *image))success
+                           success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
                            failure:(void (^)(NSError *error))failure
 {
-    [self cancelBackgroundImageRequestOperation];
-
-    [self setBackgroundImage:placeholderImage forState:state];
-
-    __weak __typeof(self)weakSelf = self;
-    self.af_backgroundImageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
-    self.af_backgroundImageRequestOperation.responseSerializer = [AFImageResponseSerializer serializer];
-    [self.af_backgroundImageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
-        __strong __typeof(weakSelf)strongSelf = weakSelf;
-        if ([[urlRequest URL] isEqual:[operation.request URL]]) {
-            if (success) {
-                success(operation.response, responseObject);
-            } else if (responseObject) {
-                [strongSelf setBackgroundImage:responseObject forState:state];
-            }
+    [self cancelBackgroundImageRequestOperationForState:state];
+
+    UIImage *cachedImage = [[[self class] sharedImageCache] cachedImageForRequest:urlRequest];
+    if (cachedImage) {
+        if (success) {
+            success(nil, nil, cachedImage);
+        } else {
+            [self setBackgroundImage:cachedImage forState:state];
         }
-    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
-        if ([[urlRequest URL] isEqual:[operation.response URL]]) {
-            if (failure) {
-                failure(error);
-            }
+
+        [self af_setBackgroundImageRequestOperation:nil forState:state];
+    } else {
+        if (placeholderImage) {
+            [self setBackgroundImage:placeholderImage forState:state];
         }
-    }];
 
-    [[[self class] af_sharedImageRequestOperationQueue] addOperation:self.af_backgroundImageRequestOperation];
+        __weak __typeof(self)weakSelf = self;
+        AFHTTPRequestOperation *backgroundImageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
+        backgroundImageRequestOperation.responseSerializer = self.imageResponseSerializer;
+        [backgroundImageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
+            __strong __typeof(weakSelf)strongSelf = weakSelf;
+            if ([[urlRequest URL] isEqual:[operation.request URL]]) {
+                if (success) {
+                    success(operation.request, operation.response, responseObject);
+                } else if (responseObject) {
+                    [strongSelf setBackgroundImage:responseObject forState:state];
+                }
+            }
+        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
+            if ([[urlRequest URL] isEqual:[operation.response URL]]) {
+                if (failure) {
+                    failure(error);
+                }
+            }
+        }];
+
+        [self af_setBackgroundImageRequestOperation:backgroundImageRequestOperation forState:state];
+        [[[self class] af_sharedImageRequestOperationQueue] addOperation:backgroundImageRequestOperation];
+    }
 }
 
 #pragma mark -
 
-- (void)cancelImageRequestOperation {
-    [self.af_imageRequestOperation cancel];
-    self.af_imageRequestOperation = nil;
+- (void)cancelImageRequestOperationForState:(UIControlState)state {
+    [[self af_imageRequestOperationForState:state] cancel];
+    [self af_setImageRequestOperation:nil forState:state];
 }
 
-- (void)cancelBackgroundImageRequestOperation {
-    [self.af_backgroundImageRequestOperation cancel];
-    self.af_backgroundImageRequestOperation = nil;
+- (void)cancelBackgroundImageRequestOperationForState:(UIControlState)state {
+    [[self af_backgroundImageRequestOperationForState:state] cancel];
+    [self af_setBackgroundImageRequestOperation:nil forState:state];
 }
 
 @end

+ 3 - 1
UIKit+AFNetworking/UIImageView+AFNetworking.m

@@ -82,10 +82,12 @@
 #pragma clang diagnostic pop
 }
 
-+ (void)setSharedImageCache:(id<AFImageCache>)imageCache {
++ (void)setSharedImageCache:(id <AFImageCache>)imageCache {
     objc_setAssociatedObject(self, @selector(sharedImageCache), imageCache, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 }
 
+#pragma mark -
+
 - (id <AFURLResponseSerialization>)imageResponseSerializer {
     static id <AFURLResponseSerialization> _af_defaultImageResponseSerializer = nil;
     static dispatch_once_t onceToken;