Skip to content

Commit

Permalink
Swift 2.0 updates and optimizations. Xcode 7.0 compatibility updates.
Browse files Browse the repository at this point in the history
  • Loading branch information
kiavashfaisali authored and Kiavash Faisali committed Oct 12, 2015
1 parent 03e1e7f commit 9ed7060
Show file tree
Hide file tree
Showing 365 changed files with 2,534 additions and 2,582 deletions.
22 changes: 10 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Xcode
#
## Build generated
build/
DerivedData

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
Expand All @@ -10,17 +12,13 @@ build/
*.perspectivev3
!default.perspectivev3
xcuserdata

## Other
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
*.xcscmblueprint

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
#
# Pods/
## Obj-C/Swift specific
*.hmap
*.ipa
6 changes: 3 additions & 3 deletions KFSwiftImageLoader.podspec
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
Pod::Spec.new do |s|
s.name = 'KFSwiftImageLoader'
s.version = '1.2'
s.version = '2.0.0'
s.summary = 'High-performance, lightweight, and energy-efficient pure Swift async web image loader with memory and disk caching for iOS and  Watch.'
s.homepage = 'https://github.com/kiavashfaisali/KFSwiftImageLoader'
s.license = { :type => 'MIT',
:file => 'LICENSE' }
:file => 'LICENSE' }
s.authors = { 'kiavashfaisali' => '[email protected]' }

s.platform = :ios, '8.2'
s.requires_arc = true
s.ios.deployment_target = '8.2'
s.source = { :git => 'https://github.com/kiavashfaisali/KFSwiftImageLoader.git',
:tag => s.version.to_s }
:tag => s.version.to_s }
s.source_files = 'KFSwiftImageLoader/*.swift'
end
40 changes: 20 additions & 20 deletions KFSwiftImageLoader/KFImageCacheManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import UIKit
import MapKit
import WatchKit

// MARK: - Completion Holder Class
// MARK: - CompletionHolder Class
final internal class CompletionHolder {
var completion: ((finished: Bool, error: NSError!) -> Void)?
var completion: ((finished: Bool, error: NSError?) -> Void)?

init(completion: ((Bool, error: NSError!) -> Void)?) {
init(completion: ((finished: Bool, error: NSError?) -> Void)?) {
self.completion = completion
}
}
Expand All @@ -51,19 +51,19 @@ final public class KFImageCacheManager {
}()

/**
Sets the fade duration time (in seconds) for images when they are being loaded into their views.
A value of 0 implies no fade animation.
The default value is 0.1 seconds.

:returns: An NSTimeInterval value representing time in seconds.
Sets the fade duration time (in seconds) for images when they are being loaded into their views.
A value of 0 implies no fade animation.
The default value is 0.1 seconds.
- returns: An NSTimeInterval value representing time in seconds.
*/
public var fadeAnimationDuration: NSTimeInterval = 0.1

/**
Sets the maximum time (in seconds) that the disk cache will use to maintain a cached response.
The default value is 604800 seconds (1 week).

:returns: An unsigned integer value representing time in seconds.
Sets the maximum time (in seconds) that the disk cache will use to maintain a cached response.
The default value is 604800 seconds (1 week).
- returns: An unsigned integer value representing time in seconds.
*/
public var diskCacheMaxAge: UInt = 60 * 60 * 24 * 7 {
willSet {
Expand All @@ -74,10 +74,10 @@ final public class KFImageCacheManager {
}

/**
Sets the maximum time (in seconds) that the request should take before timing out.
The default value is 60 seconds.

:returns: An NSTimeInterval value representing time in seconds.
Sets the maximum time (in seconds) that the request should take before timing out.
The default value is 60 seconds.
- returns: An NSTimeInterval value representing time in seconds.
*/
public var timeoutIntervalForRequest: NSTimeInterval = 60.0 {
willSet {
Expand All @@ -86,10 +86,10 @@ final public class KFImageCacheManager {
}

/**
Sets the cache policy which the default requests and underlying session configuration use to determine caching behaviour.
The default value is ReturnCacheDataElseLoad.

:returns: An NSURLRequestCachePolicy value representing the cache policy.
Sets the cache policy which the default requests and underlying session configuration use to determine caching behaviour.
The default value is ReturnCacheDataElseLoad.
- returns: An NSURLRequestCachePolicy value representing the cache policy.
*/
public var requestCachePolicy: NSURLRequestCachePolicy = .ReturnCacheDataElseLoad {
willSet {
Expand Down
112 changes: 53 additions & 59 deletions KFSwiftImageLoader/MKAnnotationViewExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,56 +30,55 @@ public extension MKAnnotationView {
return objc_getAssociatedObject(self, &completionHolderAssociationKey) as? CompletionHolder
}
set {
objc_setAssociatedObject(self, &completionHolderAssociationKey, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN))
objc_setAssociatedObject(self, &completionHolderAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}

// MARK: - Image Loading Methods
/**
Asynchronously downloads an image and loads it into the view using a URL string.

:param: string The image URL in the form of a String.
:param: placeholderImage An optional UIImage representing a placeholder image that is loaded into the view while the asynchronous download takes place. The default value is nil.
:param: completion An optional closure that is called to indicate completion of the intended purpose of this method. It returns two values: the first is a Bool indicating whether everything was successful, and the second is an optional NSError which will be non-nil should an error occur. The default value is nil.
Asynchronously downloads an image and loads it into the view using a URL string.
- parameter string: The image URL in the form of a String.
- parameter placeholderImage: An optional UIImage representing a placeholder image that is loaded into the view while the asynchronous download takes place. The default value is nil.
- parameter completion: An optional closure that is called to indicate completion of the intended purpose of this method. It returns two values: the first is a Bool indicating whether everything was successful, and the second is an optional NSError which will be non-nil should an error occur. The default value is nil.
*/
final public func loadImageFromURLString(string: String, placeholderImage: UIImage? = nil, completion: ((finished: Bool, error: NSError!) -> Void)? = nil) {
final public func loadImageFromURLString(string: String, placeholderImage: UIImage? = nil, completion: ((finished: Bool, error: NSError?) -> Void)? = nil) {
if let url = NSURL(string: string) {
loadImageFromURL(url, placeholderImage: placeholderImage, completion: completion)
}
}

/**
Asynchronously downloads an image and loads it into the view using an NSURL object.

:param: url The image URL in the form of an NSURL object.
:param: placeholderImage An optional UIImage representing a placeholder image that is loaded into the view while the asynchronous download takes place. The default value is nil.
:param: completion An optional closure that is called to indicate completion of the intended purpose of this method. It returns two values: the first is a Bool indicating whether everything was successful, and the second is an optional NSError which will be non-nil should an error occur. The default value is nil.
Asynchronously downloads an image and loads it into the view using an NSURL object.
- parameter url: The image URL in the form of an NSURL object.
- parameter placeholderImage: An optional UIImage representing a placeholder image that is loaded into the view while the asynchronous download takes place. The default value is nil.
- parameter completion: An optional closure that is called to indicate completion of the intended purpose of this method. It returns two values: the first is a Bool indicating whether everything was successful, and the second is an optional NSError which will be non-nil should an error occur. The default value is nil.
*/
final public func loadImageFromURL(url: NSURL, placeholderImage: UIImage? = nil, completion: ((finished: Bool, error: NSError!) -> Void)? = nil) {
final public func loadImageFromURL(url: NSURL, placeholderImage: UIImage? = nil, completion: ((finished: Bool, error: NSError?) -> Void)? = nil) {
let cacheManager = KFImageCacheManager.sharedInstance
let request = NSMutableURLRequest(URL: url, cachePolicy: cacheManager.session.configuration.requestCachePolicy, timeoutInterval: cacheManager.session.configuration.timeoutIntervalForRequest)
request.addValue("image/*", forHTTPHeaderField: "Accept")
loadImageFromRequest(request, placeholderImage: placeholderImage, completion: completion)
}

/**
Asynchronously downloads an image and loads it into the view using an NSURLRequest object.

:param: request The image URL in the form of an NSURLRequest object.
:param: placeholderImage An optional UIImage representing a placeholder image that is loaded into the view while the asynchronous download takes place. The default value is nil.
:param: completion An optional closure that is called to indicate completion of the intended purpose of this method. It returns two values: the first is a Bool indicating whether everything was successful, and the second is an optional NSError which will be non-nil should an error occur. The default value is nil.
Asynchronously downloads an image and loads it into the view using an NSURLRequest object.
- parameter request: The image URL in the form of an NSURLRequest object.
- parameter placeholderImage: An optional UIImage representing a placeholder image that is loaded into the view while the asynchronous download takes place. The default value is nil.
- parameter completion: An optional closure that is called to indicate completion of the intended purpose of this method. It returns two values: the first is a Bool indicating whether everything was successful, and the second is an optional NSError which will be non-nil should an error occur. The default value is nil.
*/
final public func loadImageFromRequest(request: NSURLRequest, placeholderImage: UIImage? = nil, completion: ((finished: Bool, error: NSError!) -> Void)? = nil) {
final public func loadImageFromRequest(request: NSURLRequest, placeholderImage: UIImage? = nil, completion: ((finished: Bool, error: NSError?) -> Void)? = nil) {
self.completionHolder = CompletionHolder(completion: completion)

if request.URL?.absoluteString == nil {
guard let urlAbsoluteString = request.URL?.absoluteString else {
self.completionHolder.completion?(finished: false, error: nil)
return
}

let cacheManager = KFImageCacheManager.sharedInstance
let fadeAnimationDuration = cacheManager.fadeAnimationDuration
let urlAbsoluteString = request.URL!.absoluteString!

func loadImage(image: UIImage) -> Void {
UIView.transitionWithView(self, duration: fadeAnimationDuration, options: .TransitionCrossDissolve, animations: {
Expand All @@ -94,7 +93,7 @@ public extension MKAnnotationView {
loadImage(image)
}
// If there's already a cached response, load the image data into the image view.
else if let cachedResponse = NSURLCache.sharedURLCache().cachedResponseForRequest(request), image = UIImage(data: cachedResponse.data), creationTimestamp = cachedResponse.userInfo?["creationTimestamp"] as? CFTimeInterval where (CACurrentMediaTime() - creationTimestamp) < Double(cacheManager.diskCacheMaxAge) {
else if let cachedResponse = NSURLCache.sharedURLCache().cachedResponseForRequest(request), image = UIImage(data: cachedResponse.data), creationTimestamp = cachedResponse.userInfo?["creationTimestamp"] as? CFTimeInterval where (NSDate.timeIntervalSinceReferenceDate() - creationTimestamp) < Double(cacheManager.diskCacheMaxAge) {
loadImage(image)

cacheManager[urlAbsoluteString] = image
Expand All @@ -109,54 +108,49 @@ public extension MKAnnotationView {
self.image = image
}

let initialIndexIdentifier = -1

// If the image isn't already being downloaded, begin downloading the image.
if cacheManager.isDownloadingFromURL(urlAbsoluteString) == false {
cacheManager.setIsDownloadingFromURL(true, forURLString: urlAbsoluteString)

let dataTask = cacheManager.session.dataTaskWithRequest(request) {
(data: NSData!, response: NSURLResponse!, error: NSError!) in
(taskData: NSData?, taskResponse: NSURLResponse?, taskError: NSError?) in

guard let data = taskData, response = taskResponse, image = UIImage(data: data) where taskError == nil else {
dispatch_async(dispatch_get_main_queue()) {
cacheManager.setIsDownloadingFromURL(false, forURLString: urlAbsoluteString)
cacheManager.removeImageCacheObserversForKey(urlAbsoluteString)
self.completionHolder.completion?(finished: false, error: taskError)
}

return
}

dispatch_async(dispatch_get_main_queue()) {
var finished = false
UIView.transitionWithView(self, duration: fadeAnimationDuration, options: .TransitionCrossDissolve, animations: {
self.image = image
}, completion: nil)

cacheManager[urlAbsoluteString] = image

let responseDataIsCacheable = cacheManager.diskCacheMaxAge > 0 &&
Double(data.length) <= 0.05 * Double(NSURLCache.sharedURLCache().diskCapacity) &&
(cacheManager.session.configuration.requestCachePolicy == .ReturnCacheDataElseLoad ||
cacheManager.session.configuration.requestCachePolicy == .ReturnCacheDataDontLoad) &&
(request.cachePolicy == .ReturnCacheDataElseLoad ||
request.cachePolicy == .ReturnCacheDataDontLoad)

// If there is no error, load the image into the image view and cache it.
if error == nil {
if let image = UIImage(data: data) {
UIView.transitionWithView(self, duration: fadeAnimationDuration, options: .TransitionCrossDissolve, animations: {
self.image = image
}, completion: nil)

cacheManager[urlAbsoluteString] = image

let responseDataIsCacheable = cacheManager.diskCacheMaxAge > 0 &&
Double(data.length) <= 0.05 * Double(NSURLCache.sharedURLCache().diskCapacity) &&
(cacheManager.session.configuration.requestCachePolicy == .ReturnCacheDataElseLoad ||
cacheManager.session.configuration.requestCachePolicy == .ReturnCacheDataDontLoad) &&
(request.cachePolicy == .ReturnCacheDataElseLoad ||
request.cachePolicy == .ReturnCacheDataDontLoad)

if let httpResponse = response as? NSHTTPURLResponse, url = httpResponse.URL where responseDataIsCacheable {
var allHeaderFields = httpResponse.allHeaderFields
allHeaderFields["Cache-Control"] = "max-age=\(cacheManager.diskCacheMaxAge)"
if let cacheControlResponse = NSHTTPURLResponse(URL: url, statusCode: httpResponse.statusCode, HTTPVersion: "HTTP/1.1", headerFields: allHeaderFields) {
let cachedResponse = NSCachedURLResponse(response: cacheControlResponse, data: data, userInfo: ["creationTimestamp": CACurrentMediaTime()], storagePolicy: .Allowed)
NSURLCache.sharedURLCache().storeCachedResponse(cachedResponse, forRequest: request)
}
if let httpResponse = response as? NSHTTPURLResponse, url = httpResponse.URL where responseDataIsCacheable {
if var allHeaderFields = httpResponse.allHeaderFields as? [String: String] {
allHeaderFields["Cache-Control"] = "max-age=\(cacheManager.diskCacheMaxAge)"
NSDate.timeIntervalSinceReferenceDate()
if let cacheControlResponse = NSHTTPURLResponse(URL: url, statusCode: httpResponse.statusCode, HTTPVersion: "HTTP/1.1", headerFields: allHeaderFields) {
let cachedResponse = NSCachedURLResponse(response: cacheControlResponse, data: data, userInfo: ["creationTimestamp": NSDate.timeIntervalSinceReferenceDate()], storagePolicy: .Allowed)
NSURLCache.sharedURLCache().storeCachedResponse(cachedResponse, forRequest: request)
}

finished = true
}
}

// If there was an error or image data wasn't returned, remove the observers and set isDownloading to false.
if finished == false {
cacheManager.setIsDownloadingFromURL(false, forURLString: urlAbsoluteString)
cacheManager.removeImageCacheObserversForKey(urlAbsoluteString)
}

self.completionHolder.completion?(finished: finished, error: error)
self.completionHolder.completion?(finished: true, error: nil)
}
}

Expand All @@ -165,7 +159,7 @@ public extension MKAnnotationView {
// Since the image is already being downloaded and hasn't been cached, register the image view as a cache observer.
else {
weak var weakSelf = self
cacheManager.addImageCacheObserver(weakSelf!, withInitialIndexIdentifier: initialIndexIdentifier, forKey: urlAbsoluteString)
cacheManager.addImageCacheObserver(weakSelf!, withInitialIndexIdentifier: -1, forKey: urlAbsoluteString)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion KFSwiftImageLoader/Supporting Files/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.kiavashfaisali.$(PRODUCT_NAME:rfc1034identifier)</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
Expand Down
Loading

0 comments on commit 9ed7060

Please sign in to comment.