1

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
  • It looks like the deployment target needs to be iOS 3 (pre GCD) ;-) – vadian May 23 '16 at 12:37
  • Thanks @vadian for your response, but as I'm using XCode 7.3, I cannot set the deployment target to iOS 3. Moreover, I am unable to understand why the app is crashing on the actual device even though it is running absolutely nice on the simulator. Also, the issue is while loading the images on the screen. Otherwise the app is running perfectly fine. – Adnan Farooqui May 24 '16 at 05:53
  • I was kidding. What I wanted to say is that the code is very old (no ARC, explicit synthesized properties, outdated API). Consider to use modern API this could also solve the problem. – vadian May 24 '16 at 06:31
  • @vadian, I can still not understand the reason for the crash. I tested it on iPhone 4S with iOS 9.3.2 and it ran absolutely fine but when I ran it on iPhone 6 (iOS 9.3.2), it crashed again. I even tried allowing zombie objects so as to find which released object was being accessed but could not find an error message on the console. can you please suggest something apart from using modern API? – Adnan Farooqui May 25 '16 at 05:00

0 Answers0