I'm trying to run my application on iPhone 6 (iOS 9.3.2) and using XCode 7.3 but the problem is that it is getting crashed everytime I move from one tab to another. The Exception is pointing to this class and the method marked as "error".
Can anyone please help me out with this?
I'm attaching the code for your reference.
Thanks.
//
// AsyncImageView.m //
#import "AsyncImageView.h"
#import <objc/message.h>
#define MAX_IMAGE_SIZE 1.5 * 1024 * 1024
#define RESIZE_IMG 0
NSString *const AsyncImageLoadDidFinish = @"AsyncImageLoadDidFinish";
NSString *const AsyncImageLoadDidFail = @"AsyncImageLoadDidFail";
NSString *const AsyncImageTargetReleased = @"AsyncImageTargetReleased";
NSString *const AsyncImageImageKey = @"image";
NSString *const AsyncImageURLKey = @"URL";
NSString *const AsyncImageCacheKey = @"cache";
NSString *const AsyncImageErrorKey = @"error";
@interface AsyncImageConnection : NSObject
@property (nonatomic, strong) NSURLConnection *connection;
@property (nonatomic, strong) NSMutableData *data;
@property (nonatomic, strong) NSURL *URL;
@property (nonatomic, strong) NSCache *cache;
@property (nonatomic, strong) id target;
@property (nonatomic, assign) SEL success;
@property (nonatomic, assign) SEL failure;
@property (nonatomic, readonly, getter = isLoading) BOOL loading;
@property (nonatomic, readonly) BOOL cancelled;
- (AsyncImageConnection *)initWithURL:(NSURL *)URL
cache:(NSCache *)cache
target:(id)target
success:(SEL)success
failure:(SEL)failure;
- (void)start;
- (void)cancel;
- (BOOL)isInCache;
@end
@implementation AsyncImageConnection
@synthesize connection = _connection;
@synthesize data = _data;
@synthesize URL = _URL;
@synthesize cache = _cache;
@synthesize target = _target;
@synthesize success = _success;
@synthesize failure = _failure;
@synthesize loading = _loading;
@synthesize cancelled = _cancelled;
- (AsyncImageConnection *)initWithURL:(NSURL *)URL
cache:(NSCache *)cache
target:(id)target
success:(SEL)success
failure:(SEL)failure
{
if ((self = [self init]))
{
self.URL = URL;
self.cache = cache;
self.target = target;
self.success = success;
self.failure = failure;
}
return self;
}
- (UIImage *)cachedImage
{
if ([_URL isFileURL])
{
NSString *path = [[_URL absoluteURL] path];
NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
if ([path hasPrefix:resourcePath])
{
return [UIImage imageNamed:[path substringFromIndex:[resourcePath length]]];
}
}
return [_cache objectForKey:_URL];
}
- (BOOL)isInCache
{
return [self cachedImage] != nil;
}
- (void)loadFailedWithError:(NSError *)error
{
_loading = NO;
_cancelled = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:AsyncImageLoadDidFail
object:_target
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
_URL, AsyncImageURLKey,
error, AsyncImageErrorKey,
nil]];
}
- (void)cacheImage:(UIImage *)image
{
if (!_cancelled)
{
if (image && _URL)
{
BOOL storeInCache = YES;
if ([_URL isFileURL])
{
if ([[[_URL absoluteURL] path] hasPrefix:[[NSBundle mainBundle] resourcePath]])
{
//do not store in cache
storeInCache = NO;
}
}
if (storeInCache)
{
#if RESIZE_IMG
// resize the image before storing
NSData *data = UIImageJPEGRepresentation(image, 1.0);
if (data.length > MAX_IMAGE_SIZE)
{
image = [HFUtils imageWithImage:image scaledBy:MAX_IMAGE_SIZE / (float)data.length];
}
#endif
[_cache setObject:image forKey:_URL];
}
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
image, AsyncImageImageKey,
_URL, AsyncImageURLKey,
nil];
if (_cache)
{
[userInfo setObject:_cache forKey:AsyncImageCacheKey];
}
_loading = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:AsyncImageLoadDidFinish
object:_target
userInfo:[[userInfo copy] autorelease]];
}
else
{
_loading = NO;
_cancelled = NO;
}
}
- (void)processDataInBackground:(NSData *)data
{
@synchronized ([self class])
{
if (!_cancelled)
{
UIImage *image = [[UIImage alloc] initWithData:data];
if (image)
{
//add to cache (may be cached already but it doesn't matter)
[self performSelectorOnMainThread:@selector(cacheImage:)
withObject:image
waitUntilDone:YES];
[image release];
}
else
{
@autoreleasepool
{
NSError *error = [NSError errorWithDomain:@"AsyncImageLoader" code:0 userInfo:[NSDictionary dictionaryWithObject:@"Invalid image data" forKey:NSLocalizedDescriptionKey]];
[self performSelectorOnMainThread:@selector(loadFailedWithError:) withObject:error waitUntilDone:YES];
}
}
}
else
{
//clean up
[self performSelectorOnMainThread:@selector(cacheImage:)
withObject:nil
waitUntilDone:YES];
}
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.data = [NSMutableData data];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
//add data
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self performSelectorInBackground:@selector(processDataInBackground:) withObject:_data];
self.connection = nil;
self.data = nil;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.connection = nil;
self.data = nil;
[self loadFailedWithError:error];
}
- (void)start
{
if (_loading && !_cancelled)
{
return;
}
//begin loading
_loading = YES;
_cancelled = NO;
//check for nil URL
if (_URL == nil)
{
[self cacheImage:nil];
return;
}
//check for cached image
UIImage *image = [self cachedImage];
if (image)
{
//add to cache (cached already but it doesn't matter)
[self performSelectorOnMainThread:@selector(cacheImage:)
withObject:image
waitUntilDone:NO];
return;
}
//begin load
NSURLRequest *request = [NSURLRequest requestWithURL:_URL
cachePolicy:NSURLCacheStorageNotAllowed
timeoutInterval:[AsyncImageLoader sharedLoader].loadingTimeout];
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[_connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[_connection start];
}
- (void)cancel
{
_cancelled = YES;
[_connection cancel];
self.connection = nil;
self.data = nil;
}
- (void)dealloc
{
[_connection release];
[_data release];
[_URL release];
[_target release];
[super ah_dealloc];
}
@end
@interface AsyncImageLoader ()
@property (nonatomic, strong) NSMutableArray *connections;
@end
@implementation AsyncImageLoader
@synthesize cache = _cache;
@synthesize connections = _connections;
@synthesize concurrentLoads = _concurrentLoads;
@synthesize loadingTimeout = _loadingTimeout;
+ (AsyncImageLoader *)sharedLoader
{
static AsyncImageLoader *sharedInstance = nil;
if (sharedInstance == nil)
{
sharedInstance = [[self alloc] init];
}
return sharedInstance;
}
+ (NSCache *)defaultCache
{
static NSCache *sharedInstance = nil;
if (sharedInstance == nil)
{
sharedInstance = [[NSCache alloc] init];
}
return sharedInstance;
}
- (AsyncImageLoader *)init
{
if ((self = [super init]))
{
self.cache = [[self class] defaultCache];
_concurrentLoads = 2;
_loadingTimeout = 60.0;
_connections = [[NSMutableArray alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(imageLoaded:)
name:AsyncImageLoadDidFinish
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(imageFailed:)
name:AsyncImageLoadDidFail
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(targetReleased:)
name:AsyncImageTargetReleased
object:nil];
}
return self;
}
- (void)updateQueue
{
//start connections
NSUInteger count = 0;
for (AsyncImageConnection *connection in _connections)
{
if (![connection isLoading])
{
if ([connection isInCache])
{
[connection start];
}
else if (count < _concurrentLoads)
{
count ++;
[connection start];
}
}
}
}
- (void)imageLoaded:(NSNotification *)notification
{
//complete connections for URL
NSURL *URL = [notification.userInfo objectForKey:AsyncImageURLKey];
for (int i = (int)[_connections count] - 1; i >= 0; i--)
{
AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
if (connection.URL == URL || [connection.URL isEqual:URL])
{
//cancel earlier connections for same target/action
for (int j = i - 1; j >= 0; j--)
{
AsyncImageConnection *earlier = [_connections objectAtIndex:(NSUInteger)j];
if (earlier.target == connection.target &&
earlier.success == connection.success)
{
[earlier cancel];
[_connections removeObjectAtIndex:(NSUInteger)j];
i--;
}
}
//cancel connection (in case it's a duplicate)
[connection cancel];
//perform action
UIImage *image = [notification.userInfo objectForKey:AsyncImageImageKey];
#if RESIZE_IMG
// resize image before sending it over
NSData *data = UIImageJPEGRepresentation(image, 1.0);
if (data.length > MAX_IMAGE_SIZE)
{
image = [HFUtils imageWithImage:image scaledBy:MAX_IMAGE_SIZE / (float)data.length];
}
#endif
objc_msgSend(connection.target, connection.success, image, connection.URL);
//remove from queue
[_connections removeObjectAtIndex:(NSUInteger)i];
}
}
//update the queue
[self updateQueue];
}
- (void)imageFailed:(NSNotification *)notification
{
//remove connections for URL
NSURL *URL = [notification.userInfo objectForKey:AsyncImageURLKey];
for (int i = (int)[_connections count] - 1; i >= 0; i--)
{
AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
if ([connection.URL isEqual:URL])
{
//cancel connection (in case it's a duplicate)
[connection cancel];
//perform failure action
if (connection.failure)
{
NSError *error = [notification.userInfo objectForKey:AsyncImageErrorKey];
objc_msgSend(connection.target, connection.failure, error, URL);
}
//remove from queue
[_connections removeObjectAtIndex:(NSUInteger)i];
}
}
//update the queue
[self updateQueue];
}
- (void)targetReleased:(NSNotification *)notification
{
//remove connections for URL
id target = [notification object];
for (int i = (int)[_connections count] - 1; i >= 0; i--)
{
AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
if (connection.target == target)
{
//cancel connection
[connection cancel];
[_connections removeObjectAtIndex:(NSUInteger)i];
}
}
//update the queue
[self updateQueue];
}
- (void)loadImageWithURL:(NSURL *)URL target:(id)target success:(SEL)success failure:(SEL)failure
{
if (URL == nil)
return;
//check cache
UIImage *image = [_cache objectForKey:URL];
if (image)
{
[self cancelLoadingImagesForTarget:self action:success];
if (success) [target performSelectorOnMainThread:success withObject:image waitUntilDone:NO];
return;
}
//create new connection
AsyncImageConnection *connection = [[AsyncImageConnection alloc] initWithURL:URL
cache:_cache
target:target
success:success
failure:failure];
BOOL added = NO;
for (NSUInteger i = 0; i < [_connections count]; i++)
{
AsyncImageConnection *existingConnection = [_connections objectAtIndex:i];
if (!existingConnection.loading)
{
[_connections insertObject:connection atIndex:i];
added = YES;
break;
}
}
if (!added)
{
[_connections addObject:connection];
}
[connection release];
[self updateQueue];
}
- (void)loadImageWithURL:(NSURL *)URL target:(id)target action:(SEL)action
{
[self loadImageWithURL:URL target:target success:action failure:NULL];
}
- (void)loadImageWithURL:(NSURL *)URL
{
[self loadImageWithURL:URL target:nil success:NULL failure:NULL];
}
- (void)cancelLoadingURL:(NSURL *)URL target:(id)target action:(SEL)action
{
for (int i = (int)[_connections count] - 1; i >= 0; i--)
{
AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
if ([connection.URL isEqual:URL] && connection.target == target && connection.success == action)
{
[connection cancel];
[_connections removeObjectAtIndex:(NSUInteger)i];
}
}
}
- (void)cancelLoadingURL:(NSURL *)URL target:(id)target
{
for (int i = (int)[_connections count] - 1; i >= 0; i--)
{
AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
if ([connection.URL isEqual:URL] && connection.target == target)
{
[connection cancel];
[_connections removeObjectAtIndex:(NSUInteger)i];
}
}
}
- (void)cancelLoadingURL:(NSURL *)URL
{
for (int i = (int)[_connections count] - 1; i >= 0; i--)
{
AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
if ([connection.URL isEqual:URL])
{
[connection cancel];
[_connections removeObjectAtIndex:(NSUInteger)i];
}
}
}
- (void)cancelLoadingImagesForTarget:(id)target action:(SEL)action
{
for (int i = (int)[_connections count] - 1; i >= 0; i--)
{
AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
if (connection.target == target && connection.success == action)
{
[connection cancel];
}
}
}
- (void)cancelLoadingImagesForTarget:(id)target
{
for (int i = (int)[_connections count] - 1; i >= 0; i--)
{
AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
if (connection.target == target)
{
[connection cancel];
}
}
}
- (NSURL *)URLForTarget:(id)target action:(SEL)action
{
//return the most recent image URL assigned to the target for the given action
//this is not neccesarily the next image that will be assigned
for (int i = (int)[_connections count] - 1; i >= 0; i--)
{
AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
if (connection.target == target && connection.success == action)
{
return [[connection.URL ah_retain] autorelease];
}
}
return nil;
}
- (NSURL *)URLForTarget:(id)target
{
//return the most recent image URL assigned to the target
//this is not neccesarily the next image that will be assigned
for (int i = (int)[_connections count] - 1; i >= 0; i--)
{
AsyncImageConnection *connection = [_connections objectAtIndex:(NSUInteger)i];
if (connection.target == target)
{
return [[connection.URL ah_retain] autorelease];
}
}
return nil;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_cache release];
[_connections release];
[super ah_dealloc];
}
@end
@implementation UIImageView(AsyncImageView)
- (void)setImageURL:(NSURL *)imageURL
{
[[AsyncImageLoader sharedLoader] loadImageWithURL:imageURL target:self action:@selector(setImage:)];
}
- (NSURL *)imageURL
{
return [[AsyncImageLoader sharedLoader] URLForTarget:self action:@selector(setImage:)];
}
@end
@interface AsyncImageView ()
@property (nonatomic, strong) UIActivityIndicatorView *activityView;
@end
@implementation AsyncImageView
@synthesize showActivityIndicator = _showActivityIndicator;
@synthesize activityIndicatorStyle = _activityIndicatorStyle;
@synthesize crossfadeImages = _crossfadeImages;
@synthesize crossfadeDuration = _crossfadeDuration;
@synthesize activityView = _activityView;
- (void)setUp
{
_showActivityIndicator = YES;
_activityIndicatorStyle = UIActivityIndicatorViewStyleGray;
_crossfadeImages = YES;
_crossfadeDuration = 0.4;
}
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
[self setUp];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super initWithCoder:aDecoder]))
{
[self setUp];
}
return self;
}
- (void)setImageURL:(NSURL *)imageURL
{
super.imageURL = imageURL;
if (_showActivityIndicator && !self.image && imageURL)
{
if (_activityView == nil)
{
_activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:_activityIndicatorStyle];
_activityView.hidesWhenStopped = YES;
_activityView.center = CGPointMake(self.bounds.size.width / 2.0f, self.bounds.size.height / 2.0f);
_activityView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
[self addSubview:_activityView];
}
[_activityView startAnimating];
}
}
- (void)setActivityIndicatorStyle:(UIActivityIndicatorViewStyle)style
{
_activityIndicatorStyle = style;
[_activityView removeFromSuperview];
self.activityView = nil;
}
- (void)setImage:(UIImage *)image //error
{
if (_crossfadeImages)
{
//implement crossfade transition without needing to import QuartzCore
id animation = objc_msgSend(NSClassFromString(@"CATransition"), @selector(animation));
objc_msgSend(animation, @selector(setType:), @"kCATransitionFade");
objc_msgSend(animation, @selector(setDuration:), _crossfadeDuration);
objc_msgSend(self.layer, @selector(addAnimation:forKey:), animation, nil);
}
super.image = image;
[_activityView stopAnimating];
}
- (void)dealloc
{
[[AsyncImageLoader sharedLoader] cancelLoadingURL:self.imageURL target:self];
[_activityView release];
[super ah_dealloc];
}
@end