Skip to content

Instantly share code, notes, and snippets.

@ajjames
Last active June 14, 2020 08:12
Show Gist options
  • Select an option

  • Save ajjames/3863a5bf516175f8d33f to your computer and use it in GitHub Desktop.

Select an option

Save ajjames/3863a5bf516175f8d33f to your computer and use it in GitHub Desktop.
Cancelable UIImageView async download extension (with simple NSCache-backed ImageManager class, and cancelable Downloader class)
//
// Downloader.swift
// Copyright (c) 2015 Andrew James. All rights reserved.
//
import Foundation
import UIKit
public class Downloader : NSObject
{
private let operation: NSOperation
public init(operation:NSOperation)
{
self.operation = operation
}
public convenience init(imageAtUrl imageUrl:NSURL, completion: ((image:UIImage?,error:NSError?)->())?)
{
var newOperation = NSBlockOperation()
newOperation.addExecutionBlock { () -> Void in
ImageManager.getImageSynchronously(imageUrl, cancelableOperation:newOperation) { (image, error) -> () in
if let callback = completion
{
callback(image: image, error: error)
}
}
}
self.init(operation: newOperation)
}
public func cancel()
{
if !operation.cancelled
{
operation.cancel()
}
}
public func start()
{
var blockOperation = operation
if blockOperation.ready
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
blockOperation.start()
}
}
}
}
//
// ImageManager.swift
// Copyright (c) 2015 Andrew James. All rights reserved.
//
import Foundation
import UIKit
public class ImageManager
{
private class var imageCache: NSCache
{
struct Static
{
static var instance: NSCache?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = NSCache()
}
return Static.instance!
}
public class func getImageAsynchronously(url:NSURL, completionHandler:(image:UIImage?, error: NSError?)->())
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
ImageManager.getImageSynchronously(url, cancelableOperation: nil, completionHandler: completionHandler)
})
}
public class func getImageSynchronously(url:NSURL, cancelableOperation:NSOperation?, completionHandler:(image:UIImage?, error: NSError?)->())
{
let isCancelled = cancelableOperation?.cancelled ?? false
if isCancelled
{
self.hideNetworkActivity()
dispatch_async(dispatch_get_main_queue()) {
completionHandler(image: nil, error: nil)
}
}
showNetworkActivity()
var image:UIImage?
var returnError:NSError?
if let cachedImage = self.imageCache.objectForKey(url) as? UIImage
{
image = cachedImage
}
else if let data = self.fetchDataSyncronously(url)
{
image = UIImage(data: data)
if image != nil
{
self.imageCache.setObject(image!, forKey: url)
}
}
if image == nil
{
returnError = NSError(domain:"Error: unable to load image", code:0, userInfo:nil)
}
self.hideNetworkActivity()
dispatch_async(dispatch_get_main_queue()) {
completionHandler(image:image,error:returnError)
}
}
public class func fetchDataSyncronously(url:NSURL) -> NSData?
{
var request: NSURLRequest = NSURLRequest(URL: url)
var urlConnection: NSURLConnection = NSURLConnection(request: request, delegate: self)!
var response: NSURLResponse?
var error:NSError?
let data = NSURLConnection.sendSynchronousRequest(request, returningResponse:&response, error:&error)
if (error != nil)
{
println(error!.description)
}
return data
}
private class func showNetworkActivity()
{
dispatch_async(dispatch_get_main_queue()) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
}
}
private class func hideNetworkActivity()
{
dispatch_async(dispatch_get_main_queue()) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
}
}
//
// UIImageView-Extension.swift
// Copyright (c) 2015 Andrew James. All rights reserved.
//
import Foundation
import UIKit
import ObjectiveC
var AssociatedObjectHandle_downloader: UInt8 = 0
var AssociatedObjectHandle_downloaderUrl: UInt8 = 0
public extension UIImageView
{
public func downloadImageAsync(url:NSURL, completion:(imageView:UIImageView)->())
{
downloaderUrl = url
cancelDownload()
var imageView = self
downloader = Downloader(imageAtUrl: url, completion: { (image, error) -> () in
if error != nil
{
println(error!.description)
}
if imageView.downloaderUrl == url
{
imageView.downloader = nil
dispatch_async(dispatch_get_main_queue(), { () -> Void in
imageView.image = image
completion(imageView: imageView)
})
}
else
{
completion(imageView: imageView)
}
})
self.downloader?.start()
}
public func cancelDownload()
{
self.downloader?.cancel()
}
//--------------------------------------------------------------------------------------------------------
// BELOW:
// Contains objective-C voodoo until Swift offers first-class support for stored properties in extensions
//--------------------------------------------------------------------------------------------------------
private var downloader: Downloader?
{
get
{
if let downloaderObject = objc_getAssociatedObject(self, &AssociatedObjectHandle_downloader) as? Downloader
{
return downloaderObject
}
else
{
return nil
}
}
set
{
objc_setAssociatedObject(self, &AssociatedObjectHandle_downloader, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
}
private var downloaderUrl: NSURL?
{
get
{
if let downloaderUrlObject = objc_getAssociatedObject(self, &AssociatedObjectHandle_downloaderUrl) as? NSURL
{
return downloaderUrlObject
}
else
{
return nil
}
}
set
{
objc_setAssociatedObject(self, &AssociatedObjectHandle_downloaderUrl, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment