Nitish
Nitish

Reputation: 14123

Received memory warning in dispatch_async

This is the code I am writing in cellForRowAtIndex for downloading the image :

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    @autoreleasepool {
        __block UIImage * img;
        __block NSData *data;
        if(![messageDocument.SmallImageURL isEqual:@""])
        {
            data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:messageDocument.SmallImageURL]];
            img = [UIImage imageWithData:data];
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            imgUser.image = img;
            img = nil;

            imgUser.contentMode = UIViewContentModeScaleAspectFill;
            CGSize size;
            if(imgUser.image.size.width > imageFrame.size.width || imgUser.image.size.height > rectImage.size.height)
            {
                if(imgUser.image.size.width < imageFrame.size.width)
                {
                    rectImage.size.width = imgUser.image.size.width;
                }

                if(imgUser.image.size.height < rectImage.size.height)
                {
                    rectImage.size.height = imgUser.image.size.height;
                }

                size = CGSizeAspectFit(imgUser.image.size, rectImage.size);
                imgUser.frame = CGRectMake(imgUser.frame.origin.x, rectImage.origin.y, size.width, size.height);

                height = imgUser.frame.size.height;
            }
            else
            {
                imgUser.frame = CGRectMake(imageFrame.origin.x, imageFrame.origin.y, imgUser.image.size.width, imgUser.image.size.height);
                height = imgUser.image.size.height;

            }

            CGPoint contentOffset = tableMessageDetail.contentOffset;
            [tableMessageDetail beginUpdates];
            [tableMessageDetail endUpdates];
            [tableMessageDetail setContentOffset:contentOffset];
        });

            messageDocument.Pic = data;
            data = nil;
        if(messageDocument.Pic != nil)
        {
            Attachment *attachment = [Attachment new];
            attachment.DocId = messageDocument.DocId;
            attachment.DocURL = messageDocument.DocURL;
            attachment.ImageId = messageDocument.ImageId;
            attachment.MessageId = messageDocument.MessageId;
            attachment.SmallImageURL = messageDocument.SmallImageURL;
            attachment.OriginalFileName = messageDocument.OriginalFileName;
            if([messageDocument.DocURL isEqual:@""])
            {
                NSArray *attachmentArray = [messageDocument.SmallImageURL componentsSeparatedByString:@"/"];
                NSString *attachmentName = [attachmentArray objectAtIndex:attachmentArray.count - 1];
                attachment.AttachmentName = attachmentName;
            }
            else
            {
                NSArray *attachmentArray = [messageDocument.DocURL componentsSeparatedByString:@"/"];
                NSString *attachmentName = [attachmentArray objectAtIndex:attachmentArray.count - 1];
                attachment.AttachmentName = attachmentName;
            }

            attachment.Pic = messageDocument.Pic;
            [[CommonModel shared]CreateAttachment:attachment];
            [[CommonModel shared]UpdateMessageDocumentPic:messageDocument];

            attachment = nil;
        }
    }
});  

But if there are more than 6 images, I am getting Memory exception with the following message on console :

Message from debugger: Terminated due to memory issue

Upvotes: 4

Views: 377

Answers (2)

Adela Toderici
Adela Toderici

Reputation: 1112

As @Marek R said you have to use block with simple logic. First, create a method which only download the image async from url.

var cache = NSCache()

func imageForUrl(urlString: String, completionHandler:(image: UIImage?, url: String) -> ()) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {()in
    var data: NSData? = self.cache.objectForKey(urlString) as? NSData

    if let goodData = data {
        let image = UIImage(data: goodData)
        dispatch_async(dispatch_get_main_queue(), {() in
            completionHandler(image: image, url: urlString)
        })
        return
    }

    var downloadTask: NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: urlString)!, completionHandler: {(data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
        if (error != nil) {
            completionHandler(image: nil, url: urlString)
            return
        }

        if data != nil {
            let image = UIImage(data: data)
            self.cache.setObject(data, forKey: urlString)
            dispatch_async(dispatch_get_main_queue(), {() in
                completionHandler(image: image, url: urlString)
            })
            return
        }

    })
    downloadTask.resume()
})

}

Second, call imageFromUrl method and then continue your image modifications out of the block:

imageForUrl("http://useYourLinkHere/image.png", completionHandler:{(image: UIImage?, url: String) in
    self.imgUser.image = image!
})

        imgUser.contentMode = UIViewContentModeScaleAspectFill;
        CGSize size;
        if(imgUser.image.size.width > imageFrame.size.width || imgUser.image.size.height > rectImage.size.height)
        {
            if(imgUser.image.size.width < imageFrame.size.width)
            {
                rectImage.size.width = imgUser.image.size.width;
            }

            if(imgUser.image.size.height < rectImage.size.height)
            {
                rectImage.size.height = imgUser.image.size.height;
            }

            size = CGSizeAspectFit(imgUser.image.size, rectImage.size);
            imgUser.frame = CGRectMake(imgUser.frame.origin.x, rectImage.origin.y, size.width, size.height);

            height = imgUser.frame.size.height;
        }
        else
        {
            imgUser.frame = CGRectMake(imageFrame.origin.x, imageFrame.origin.y, imgUser.image.size.width, imgUser.image.size.height);
            height = imgUser.image.size.height;

        }

        CGPoint contentOffset = tableMessageDetail.contentOffset;
        [tableMessageDetail beginUpdates];
        [tableMessageDetail endUpdates];
        [tableMessageDetail setContentOffset:contentOffset];

        messageDocument.Pic = data;
        data = nil;
    if(messageDocument.Pic != nil)
    {
        Attachment *attachment = [Attachment new];
        attachment.DocId = messageDocument.DocId;
        attachment.DocURL = messageDocument.DocURL;
        attachment.ImageId = messageDocument.ImageId;
        attachment.MessageId = messageDocument.MessageId;
        attachment.SmallImageURL = messageDocument.SmallImageURL;
        attachment.OriginalFileName = messageDocument.OriginalFileName;
        if([messageDocument.DocURL isEqual:@""])
        {
            NSArray *attachmentArray = [messageDocument.SmallImageURL componentsSeparatedByString:@"/"];
            NSString *attachmentName = [attachmentArray objectAtIndex:attachmentArray.count - 1];
            attachment.AttachmentName = attachmentName;
        }
        else
        {
            NSArray *attachmentArray = [messageDocument.DocURL componentsSeparatedByString:@"/"];
            NSString *attachmentName = [attachmentArray objectAtIndex:attachmentArray.count - 1];
            attachment.AttachmentName = attachmentName;
        }

        attachment.Pic = messageDocument.Pic;
        [[CommonModel shared]CreateAttachment:attachment];
        [[CommonModel shared]UpdateMessageDocumentPic:messageDocument];

        attachment = nil;
    }

The code you use out of the block should be break in many methods, one of them should be called: 'resizeImageFrame()' another one should be 'loadModel()', to have an ordered code.

Good luck!

Upvotes: 1

Rahul Mathur
Rahul Mathur

Reputation: 882

There is nothing wrong in your downloading logic. The issue is that you are trying to save the images in your array, which end up increasing your application memory. As the images that you are holding are compressed pngs, they do not appear to hold much size, but it drastically increase the memory of your application and can crash your app.

To solve your issue, you need to save each and every file to document directory as soon as it is downloaded and then save the name of the images (or thumbnail if you need to display it somewhere) instead of the whole full resolution images.

Update

You can analyse memory allocation for your application using instruments tool in Xcode. It can give you hints for the object that are consuming the most memory.

Upvotes: 4

Related Questions