The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .gitignore
├── .swift-version
├── .travis.yml
├── Assets
    └── github-header.png
├── Cartfile.private
├── Cartfile.resolved
├── Haneke.playground
    ├── contents.xcplayground
    ├── playground.xcworkspace
    │   └── contents.xcworkspacedata
    └── section-1.swift
├── Haneke.xcodeproj
    ├── project.pbxproj
    ├── project.xcworkspace
    │   └── contents.xcworkspacedata
    └── xcshareddata
    │   └── xcschemes
    │       ├── Haneke-iOS.xcscheme
    │       ├── Haneke-tvOS.xcscheme
    │       └── HanekeDemo.xcscheme
├── Haneke.xcworkspace
    ├── contents.xcworkspacedata
    └── xcshareddata
    │   ├── Haneke.xcscmblueprint
    │   └── IDEWorkspaceChecks.plist
├── Haneke
    ├── CGSize+Swift.swift
    ├── Cache.swift
    ├── CryptoSwiftMD5.swift
    ├── Data.swift
    ├── DiskCache.swift
    ├── DiskFetcher.swift
    ├── Fetch.swift
    ├── Fetcher.swift
    ├── Format.swift
    ├── Haneke.h
    ├── Haneke.swift
    ├── Info-iOS.plist
    ├── Info-tvOS.plist
    ├── Log.swift
    ├── NSFileManager+Haneke.swift
    ├── NSHTTPURLResponse+Haneke.swift
    ├── NSURLResponse+Haneke.swift
    ├── NetworkFetcher.swift
    ├── String+Haneke.swift
    ├── UIButton+Haneke.swift
    ├── UIImage+Haneke.swift
    ├── UIImageView+Haneke.swift
    └── UIView+Haneke.swift
├── HanekeDemo
    ├── AppDelegate.swift
    ├── Base.lproj
    │   ├── LaunchScreen.xib
    │   └── Main.storyboard
    ├── CollectionViewCell.swift
    ├── Images.xcassets
    │   └── AppIcon.appiconset
    │   │   ├── Contents.json
    │   │   ├── icon-60@2x.png
    │   │   ├── icon-60@3x.png
    │   │   ├── icon-76.png
    │   │   └── icon-76@2x.png
    ├── Info.plist
    └── ViewController.swift
├── HanekeSwift.podspec
├── HanekeTests
    ├── AsyncFetcher.swift
    ├── CGSize+HanekeTests.swift
    ├── CacheTests.swift
    ├── DataTests.swift
    ├── DiskCacheTests.swift
    ├── DiskFetcherTests.swift
    ├── DiskTestCase.swift
    ├── FetchTests.swift
    ├── FetcherTests.swift
    ├── FormatTests.swift
    ├── HanekeTests-Bridging-Header.h
    ├── HanekeTests.swift
    ├── Info.plist
    ├── NSData+Test.swift
    ├── NSFileManager+HanekeTests.swift
    ├── NSHTTPURLResponse+HanekeTests.swift
    ├── NSURLResponse+HanekeTests.swift
    ├── NetworkFetcherTests.swift
    ├── String+HanekeTests.swift
    ├── UIButton+HanekeTests.swift
    ├── UIImage+HanekeTests.swift
    ├── UIImage+Test.swift
    ├── UIImageView+HanekeTests.swift
    └── XCTestCase+Test.swift
├── LICENSE
├── Package.swift
└── README.md


/.gitignore:
--------------------------------------------------------------------------------
 1 | # Xcode
 2 | .DS_Store
 3 | */build/*
 4 | *.pbxuser
 5 | !default.pbxuser
 6 | *.mode1v3
 7 | !default.mode1v3
 8 | *.mode2v3
 9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | profile
14 | *.moved-aside
15 | DerivedData
16 | .idea/
17 | *.hmap
18 | *.xccheckout
19 | 
20 | #CocoaPods
21 | Pods
22 | 
23 | #Carthage
24 | Carthage
25 | 


--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.0
2 | 


--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
 1 | language: swift
 2 | osx_image: xcode10.2
 3 | before_install:
 4 | - brew update
 5 | - brew install carthage || brew outdated carthage || brew upgrade carthage
 6 | install:
 7 | - carthage bootstrap
 8 | branches:
 9 |   only:
10 |     - master
11 | script:
12 |   - set -o pipefail && xcodebuild build test -workspace Haneke.xcworkspace -scheme Haneke-iOS -destination 'platform=iOS Simulator,name=iPhone X,OS=12.0' | xcpretty --color
13 | 


--------------------------------------------------------------------------------
/Assets/github-header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haneke/HanekeSwift/a2e8e5b9a91eef90138a4f43c9a0044c4e90a6ef/Assets/github-header.png


--------------------------------------------------------------------------------
/Cartfile.private:
--------------------------------------------------------------------------------
1 | github "AliSoftware/OHHTTPStubs"
2 | 


--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "AliSoftware/OHHTTPStubs" "7.0.0"
2 | 


--------------------------------------------------------------------------------
/Haneke.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2 | <playground version='3.0' sdk='iphonesimulator' auto-termination-delay='100'>
3 |     <sections>
4 |         <code source-file-name='section-1.swift'/>
5 |     </sections>
6 |     <timeline fileName='timeline.xctimeline'/>
7 | </playground>


--------------------------------------------------------------------------------
/Haneke.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <Workspace
3 |    version = "1.0">
4 |    <FileRef
5 |       location = "self:">
6 |    </FileRef>
7 | </Workspace>
8 | 


--------------------------------------------------------------------------------
/Haneke.playground/section-1.swift:
--------------------------------------------------------------------------------
 1 | // Open this playground from the Haneke workspace after building the Haneke framework. See: http://stackoverflow.com/a/24049021/143378
 2 | import Haneke
 3 | 
 4 | /// Initialize a JSON cache and fetch/cache a JSON response.
 5 | func example1() {
 6 |     let cache = Cache<JSON>(name: "github")
 7 |     let url = URL(string: "https://api.github.com/users/haneke")!
 8 |     
 9 |     cache.fetch(URL: url).onSuccess { json in
10 |         let bio = json.dictionary?["bio"]
11 |         print(bio.map { String(describing: $0) } ?? "nil")
12 |     }
13 | }
14 | 
15 | /// Set a image view image from a url using the shared image cache and resizing.
16 | func example2() {
17 |     let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
18 |     let url = URL(string: "https://avatars.githubusercontent.com/u/8600207?v=2")!
19 | 
20 |     imageView.hnk_setImageFromURL(url)
21 | }
22 | 
23 | /// Set and fetch data from the shared data cache
24 | func example3() {
25 |     let cache = Shared.dataCache
26 |     let data = "SGVscCEgSSdtIHRyYXBwZWQgaW4gYSBCYXNlNjQgc3RyaW5nIQ==".asData()!
27 |     
28 |     cache.set(value: data, key: "secret")
29 |     
30 |     cache.fetch(key: "secret").onSuccess { fetchedData in
31 |         let fetchedString = String(data: fetchedData, encoding: .utf8)
32 |         print(fetchedString ?? "nil")
33 |     }
34 | }
35 | 


--------------------------------------------------------------------------------
/Haneke.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <Workspace
3 |    version = "1.0">
4 |    <FileRef
5 |       location = "self:Haneke.xcodeproj">
6 |    </FileRef>
7 | </Workspace>
8 | 


--------------------------------------------------------------------------------
/Haneke.xcodeproj/xcshareddata/xcschemes/Haneke-iOS.xcscheme:
--------------------------------------------------------------------------------
  1 | <?xml version="1.0" encoding="UTF-8"?>
  2 | <Scheme
  3 |    LastUpgradeVersion = "1020"
  4 |    version = "1.3">
  5 |    <BuildAction
  6 |       parallelizeBuildables = "YES"
  7 |       buildImplicitDependencies = "YES">
  8 |       <BuildActionEntries>
  9 |          <BuildActionEntry
 10 |             buildForTesting = "YES"
 11 |             buildForRunning = "YES"
 12 |             buildForProfiling = "YES"
 13 |             buildForArchiving = "YES"
 14 |             buildForAnalyzing = "YES">
 15 |             <BuildableReference
 16 |                BuildableIdentifier = "primary"
 17 |                BlueprintIdentifier = "A095C9551980418C00CD0F4C"
 18 |                BuildableName = "Haneke.framework"
 19 |                BlueprintName = "Haneke-iOS"
 20 |                ReferencedContainer = "container:Haneke.xcodeproj">
 21 |             </BuildableReference>
 22 |          </BuildActionEntry>
 23 |          <BuildActionEntry
 24 |             buildForTesting = "YES"
 25 |             buildForRunning = "NO"
 26 |             buildForProfiling = "NO"
 27 |             buildForArchiving = "NO"
 28 |             buildForAnalyzing = "NO">
 29 |             <BuildableReference
 30 |                BuildableIdentifier = "primary"
 31 |                BlueprintIdentifier = "A095C9601980418C00CD0F4C"
 32 |                BuildableName = "HanekeTests.xctest"
 33 |                BlueprintName = "HanekeTests"
 34 |                ReferencedContainer = "container:Haneke.xcodeproj">
 35 |             </BuildableReference>
 36 |          </BuildActionEntry>
 37 |       </BuildActionEntries>
 38 |    </BuildAction>
 39 |    <TestAction
 40 |       buildConfiguration = "Debug"
 41 |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 42 |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 43 |       codeCoverageEnabled = "YES"
 44 |       shouldUseLaunchSchemeArgsEnv = "YES">
 45 |       <Testables>
 46 |          <TestableReference
 47 |             skipped = "NO">
 48 |             <BuildableReference
 49 |                BuildableIdentifier = "primary"
 50 |                BlueprintIdentifier = "A095C9601980418C00CD0F4C"
 51 |                BuildableName = "HanekeTests.xctest"
 52 |                BlueprintName = "HanekeTests"
 53 |                ReferencedContainer = "container:Haneke.xcodeproj">
 54 |             </BuildableReference>
 55 |          </TestableReference>
 56 |       </Testables>
 57 |       <MacroExpansion>
 58 |          <BuildableReference
 59 |             BuildableIdentifier = "primary"
 60 |             BlueprintIdentifier = "A095C9551980418C00CD0F4C"
 61 |             BuildableName = "Haneke.framework"
 62 |             BlueprintName = "Haneke-iOS"
 63 |             ReferencedContainer = "container:Haneke.xcodeproj">
 64 |          </BuildableReference>
 65 |       </MacroExpansion>
 66 |       <AdditionalOptions>
 67 |       </AdditionalOptions>
 68 |    </TestAction>
 69 |    <LaunchAction
 70 |       buildConfiguration = "Debug"
 71 |       selectedDebuggerIdentifier = ""
 72 |       selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
 73 |       launchStyle = "0"
 74 |       useCustomWorkingDirectory = "NO"
 75 |       ignoresPersistentStateOnLaunch = "NO"
 76 |       debugDocumentVersioning = "YES"
 77 |       debugServiceExtension = "internal"
 78 |       allowLocationSimulation = "YES">
 79 |       <MacroExpansion>
 80 |          <BuildableReference
 81 |             BuildableIdentifier = "primary"
 82 |             BlueprintIdentifier = "A095C9551980418C00CD0F4C"
 83 |             BuildableName = "Haneke.framework"
 84 |             BlueprintName = "Haneke-iOS"
 85 |             ReferencedContainer = "container:Haneke.xcodeproj">
 86 |          </BuildableReference>
 87 |       </MacroExpansion>
 88 |       <AdditionalOptions>
 89 |       </AdditionalOptions>
 90 |    </LaunchAction>
 91 |    <ProfileAction
 92 |       buildConfiguration = "Release"
 93 |       shouldUseLaunchSchemeArgsEnv = "YES"
 94 |       savedToolIdentifier = ""
 95 |       useCustomWorkingDirectory = "NO"
 96 |       debugDocumentVersioning = "YES">
 97 |       <MacroExpansion>
 98 |          <BuildableReference
 99 |             BuildableIdentifier = "primary"
100 |             BlueprintIdentifier = "A095C9551980418C00CD0F4C"
101 |             BuildableName = "Haneke.framework"
102 |             BlueprintName = "Haneke-iOS"
103 |             ReferencedContainer = "container:Haneke.xcodeproj">
104 |          </BuildableReference>
105 |       </MacroExpansion>
106 |    </ProfileAction>
107 |    <AnalyzeAction
108 |       buildConfiguration = "Debug">
109 |    </AnalyzeAction>
110 |    <ArchiveAction
111 |       buildConfiguration = "Release"
112 |       revealArchiveInOrganizer = "YES">
113 |    </ArchiveAction>
114 | </Scheme>
115 | 


--------------------------------------------------------------------------------
/Haneke.xcodeproj/xcshareddata/xcschemes/Haneke-tvOS.xcscheme:
--------------------------------------------------------------------------------
  1 | <?xml version="1.0" encoding="UTF-8"?>
  2 | <Scheme
  3 |    LastUpgradeVersion = "1020"
  4 |    version = "1.3">
  5 |    <BuildAction
  6 |       parallelizeBuildables = "YES"
  7 |       buildImplicitDependencies = "YES">
  8 |       <BuildActionEntries>
  9 |          <BuildActionEntry
 10 |             buildForTesting = "YES"
 11 |             buildForRunning = "YES"
 12 |             buildForProfiling = "YES"
 13 |             buildForArchiving = "YES"
 14 |             buildForAnalyzing = "YES">
 15 |             <BuildableReference
 16 |                BuildableIdentifier = "primary"
 17 |                BlueprintIdentifier = "6393C5DA1C3B229200EB1FD8"
 18 |                BuildableName = "Haneke.framework"
 19 |                BlueprintName = "Haneke-tvOS"
 20 |                ReferencedContainer = "container:Haneke.xcodeproj">
 21 |             </BuildableReference>
 22 |          </BuildActionEntry>
 23 |       </BuildActionEntries>
 24 |    </BuildAction>
 25 |    <TestAction
 26 |       buildConfiguration = "Debug"
 27 |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 28 |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 29 |       shouldUseLaunchSchemeArgsEnv = "YES">
 30 |       <Testables>
 31 |          <TestableReference
 32 |             skipped = "NO">
 33 |             <BuildableReference
 34 |                BuildableIdentifier = "primary"
 35 |                BlueprintIdentifier = "A095C9601980418C00CD0F4C"
 36 |                BuildableName = "HanekeTests.xctest"
 37 |                BlueprintName = "HanekeTests"
 38 |                ReferencedContainer = "container:Haneke.xcodeproj">
 39 |             </BuildableReference>
 40 |          </TestableReference>
 41 |       </Testables>
 42 |       <MacroExpansion>
 43 |          <BuildableReference
 44 |             BuildableIdentifier = "primary"
 45 |             BlueprintIdentifier = "6393C5DA1C3B229200EB1FD8"
 46 |             BuildableName = "Haneke.framework"
 47 |             BlueprintName = "Haneke-tvOS"
 48 |             ReferencedContainer = "container:Haneke.xcodeproj">
 49 |          </BuildableReference>
 50 |       </MacroExpansion>
 51 |       <AdditionalOptions>
 52 |       </AdditionalOptions>
 53 |    </TestAction>
 54 |    <LaunchAction
 55 |       buildConfiguration = "Debug"
 56 |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 57 |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 58 |       launchStyle = "0"
 59 |       useCustomWorkingDirectory = "NO"
 60 |       ignoresPersistentStateOnLaunch = "NO"
 61 |       debugDocumentVersioning = "YES"
 62 |       debugServiceExtension = "internal"
 63 |       allowLocationSimulation = "YES">
 64 |       <MacroExpansion>
 65 |          <BuildableReference
 66 |             BuildableIdentifier = "primary"
 67 |             BlueprintIdentifier = "6393C5DA1C3B229200EB1FD8"
 68 |             BuildableName = "Haneke.framework"
 69 |             BlueprintName = "Haneke-tvOS"
 70 |             ReferencedContainer = "container:Haneke.xcodeproj">
 71 |          </BuildableReference>
 72 |       </MacroExpansion>
 73 |       <AdditionalOptions>
 74 |       </AdditionalOptions>
 75 |    </LaunchAction>
 76 |    <ProfileAction
 77 |       buildConfiguration = "Release"
 78 |       shouldUseLaunchSchemeArgsEnv = "YES"
 79 |       savedToolIdentifier = ""
 80 |       useCustomWorkingDirectory = "NO"
 81 |       debugDocumentVersioning = "YES">
 82 |       <MacroExpansion>
 83 |          <BuildableReference
 84 |             BuildableIdentifier = "primary"
 85 |             BlueprintIdentifier = "6393C5DA1C3B229200EB1FD8"
 86 |             BuildableName = "Haneke.framework"
 87 |             BlueprintName = "Haneke-tvOS"
 88 |             ReferencedContainer = "container:Haneke.xcodeproj">
 89 |          </BuildableReference>
 90 |       </MacroExpansion>
 91 |    </ProfileAction>
 92 |    <AnalyzeAction
 93 |       buildConfiguration = "Debug">
 94 |    </AnalyzeAction>
 95 |    <ArchiveAction
 96 |       buildConfiguration = "Release"
 97 |       revealArchiveInOrganizer = "YES">
 98 |    </ArchiveAction>
 99 | </Scheme>
100 | 


--------------------------------------------------------------------------------
/Haneke.xcodeproj/xcshareddata/xcschemes/HanekeDemo.xcscheme:
--------------------------------------------------------------------------------
  1 | <?xml version="1.0" encoding="UTF-8"?>
  2 | <Scheme
  3 |    LastUpgradeVersion = "1020"
  4 |    version = "1.3">
  5 |    <BuildAction
  6 |       parallelizeBuildables = "YES"
  7 |       buildImplicitDependencies = "YES">
  8 |       <BuildActionEntries>
  9 |          <BuildActionEntry
 10 |             buildForTesting = "YES"
 11 |             buildForRunning = "YES"
 12 |             buildForProfiling = "YES"
 13 |             buildForArchiving = "YES"
 14 |             buildForAnalyzing = "YES">
 15 |             <BuildableReference
 16 |                BuildableIdentifier = "primary"
 17 |                BlueprintIdentifier = "A0026E9919C9BFBC004DE0C6"
 18 |                BuildableName = "HanekeDemo.app"
 19 |                BlueprintName = "HanekeDemo"
 20 |                ReferencedContainer = "container:Haneke.xcodeproj">
 21 |             </BuildableReference>
 22 |          </BuildActionEntry>
 23 |          <BuildActionEntry
 24 |             buildForTesting = "YES"
 25 |             buildForRunning = "YES"
 26 |             buildForProfiling = "NO"
 27 |             buildForArchiving = "NO"
 28 |             buildForAnalyzing = "YES">
 29 |             <BuildableReference
 30 |                BuildableIdentifier = "primary"
 31 |                BlueprintIdentifier = "A0026EAD19C9BFBC004DE0C6"
 32 |                BuildableName = "HanekeDemoTests.xctest"
 33 |                BlueprintName = "HanekeDemoTests"
 34 |                ReferencedContainer = "container:Haneke.xcodeproj">
 35 |             </BuildableReference>
 36 |          </BuildActionEntry>
 37 |       </BuildActionEntries>
 38 |    </BuildAction>
 39 |    <TestAction
 40 |       buildConfiguration = "Debug"
 41 |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 42 |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 43 |       shouldUseLaunchSchemeArgsEnv = "YES">
 44 |       <Testables>
 45 |          <TestableReference
 46 |             skipped = "NO">
 47 |             <BuildableReference
 48 |                BuildableIdentifier = "primary"
 49 |                BlueprintIdentifier = "A0026EAD19C9BFBC004DE0C6"
 50 |                BuildableName = "HanekeDemoTests.xctest"
 51 |                BlueprintName = "HanekeDemoTests"
 52 |                ReferencedContainer = "container:Haneke.xcodeproj">
 53 |             </BuildableReference>
 54 |          </TestableReference>
 55 |       </Testables>
 56 |       <MacroExpansion>
 57 |          <BuildableReference
 58 |             BuildableIdentifier = "primary"
 59 |             BlueprintIdentifier = "A0026E9919C9BFBC004DE0C6"
 60 |             BuildableName = "HanekeDemo.app"
 61 |             BlueprintName = "HanekeDemo"
 62 |             ReferencedContainer = "container:Haneke.xcodeproj">
 63 |          </BuildableReference>
 64 |       </MacroExpansion>
 65 |       <AdditionalOptions>
 66 |       </AdditionalOptions>
 67 |    </TestAction>
 68 |    <LaunchAction
 69 |       buildConfiguration = "Debug"
 70 |       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 71 |       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 72 |       launchStyle = "0"
 73 |       useCustomWorkingDirectory = "NO"
 74 |       ignoresPersistentStateOnLaunch = "NO"
 75 |       debugDocumentVersioning = "YES"
 76 |       debugServiceExtension = "internal"
 77 |       allowLocationSimulation = "YES">
 78 |       <BuildableProductRunnable
 79 |          runnableDebuggingMode = "0">
 80 |          <BuildableReference
 81 |             BuildableIdentifier = "primary"
 82 |             BlueprintIdentifier = "A0026E9919C9BFBC004DE0C6"
 83 |             BuildableName = "HanekeDemo.app"
 84 |             BlueprintName = "HanekeDemo"
 85 |             ReferencedContainer = "container:Haneke.xcodeproj">
 86 |          </BuildableReference>
 87 |       </BuildableProductRunnable>
 88 |       <AdditionalOptions>
 89 |       </AdditionalOptions>
 90 |    </LaunchAction>
 91 |    <ProfileAction
 92 |       buildConfiguration = "Release"
 93 |       shouldUseLaunchSchemeArgsEnv = "YES"
 94 |       savedToolIdentifier = ""
 95 |       useCustomWorkingDirectory = "NO"
 96 |       debugDocumentVersioning = "YES">
 97 |       <BuildableProductRunnable
 98 |          runnableDebuggingMode = "0">
 99 |          <BuildableReference
100 |             BuildableIdentifier = "primary"
101 |             BlueprintIdentifier = "A0026E9919C9BFBC004DE0C6"
102 |             BuildableName = "HanekeDemo.app"
103 |             BlueprintName = "HanekeDemo"
104 |             ReferencedContainer = "container:Haneke.xcodeproj">
105 |          </BuildableReference>
106 |       </BuildableProductRunnable>
107 |    </ProfileAction>
108 |    <AnalyzeAction
109 |       buildConfiguration = "Debug">
110 |    </AnalyzeAction>
111 |    <ArchiveAction
112 |       buildConfiguration = "Release"
113 |       revealArchiveInOrganizer = "YES">
114 |    </ArchiveAction>
115 | </Scheme>
116 | 


--------------------------------------------------------------------------------
/Haneke.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <Workspace
3 |    version = "1.0">
4 |    <FileRef
5 |       location = "container:Haneke.xcodeproj">
6 |    </FileRef>
7 | </Workspace>
8 | 


--------------------------------------------------------------------------------
/Haneke.xcworkspace/xcshareddata/Haneke.xcscmblueprint:
--------------------------------------------------------------------------------
 1 | {
 2 |   "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "C93564C1AFB8B50FF58188F21182F5D0EEAD1C3F",
 3 |   "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
 4 | 
 5 |   },
 6 |   "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
 7 |     "38C2A0D4F62B675E8C16C8BC1437C7753846C8AC" : 0,
 8 |     "C93564C1AFB8B50FF58188F21182F5D0EEAD1C3F" : 0
 9 |   },
10 |   "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "D9D86AAE-952B-48A9-A2AB-D2832C98D4A1",
11 |   "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
12 |     "38C2A0D4F62B675E8C16C8BC1437C7753846C8AC" : "HanekeSwiftHanekeTests\/Submodules\/OHHTTPStubs",
13 |     "C93564C1AFB8B50FF58188F21182F5D0EEAD1C3F" : "HanekeSwift\/"
14 |   },
15 |   "DVTSourceControlWorkspaceBlueprintNameKey" : "Haneke",
16 |   "DVTSourceControlWorkspaceBlueprintVersion" : 204,
17 |   "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Haneke.xcworkspace",
18 |   "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
19 |     {
20 |       "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/AliSoftware\/OHHTTPStubs.git",
21 |       "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
22 |       "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "38C2A0D4F62B675E8C16C8BC1437C7753846C8AC"
23 |     },
24 |     {
25 |       "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Haneke\/HanekeSwift.git",
26 |       "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
27 |       "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "C93564C1AFB8B50FF58188F21182F5D0EEAD1C3F"
28 |     }
29 |   ]
30 | }


--------------------------------------------------------------------------------
/Haneke.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3 | <plist version="1.0">
4 | <dict>
5 | 	<key>IDEDidComputeMac32BitWarning</key>
6 | 	<true/>
7 | </dict>
8 | </plist>
9 | 


--------------------------------------------------------------------------------
/Haneke/CGSize+Swift.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  CGSize+Swift.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Oriol Blanc Gimeno on 09/09/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import UIKit
10 | 
11 | extension CGSize {
12 | 
13 |     func hnk_aspectFillSize(_ size: CGSize) -> CGSize {
14 |         let scaleWidth = size.width / self.width
15 |         let scaleHeight = size.height / self.height
16 |         let scale = max(scaleWidth, scaleHeight)
17 | 
18 |         let resultSize = CGSize(width: self.width * scale, height: self.height * scale)
19 |         return CGSize(width: ceil(resultSize.width), height: ceil(resultSize.height))
20 |     }
21 | 
22 |     func hnk_aspectFitSize(_ size: CGSize) -> CGSize {
23 |         let targetAspect = size.width / size.height
24 |         let sourceAspect = self.width / self.height
25 |         var resultSize = size
26 | 
27 |         if (targetAspect > sourceAspect) {
28 |             resultSize.width = size.height * sourceAspect
29 |         }
30 |         else {
31 |             resultSize.height = size.width / sourceAspect
32 |         }
33 |         return CGSize(width: ceil(resultSize.width), height: ceil(resultSize.height))
34 |     }
35 | }
36 | 


--------------------------------------------------------------------------------
/Haneke/Cache.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  Cache.swift
  3 | //  Haneke
  4 | //
  5 | //  Created by Luis Ascorbe on 23/07/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import UIKit
 10 | 
 11 | // Used to add T to NSCache
 12 | class ObjectWrapper : NSObject {
 13 |     let hnk_value: Any
 14 |     
 15 |     init(value: Any) {
 16 |         self.hnk_value = value
 17 |     }
 18 | }
 19 | 
 20 | extension HanekeGlobals {
 21 |     
 22 |     // It'd be better to define this in the Cache class but Swift doesn't allow statics in a generic type
 23 |     public struct Cache {
 24 |         
 25 |         public static let OriginalFormatName = "original"
 26 | 
 27 |         public enum ErrorCode : Int {
 28 |             case objectNotFound = -100
 29 |             case formatNotFound = -101
 30 |         }
 31 |         
 32 |     }
 33 |     
 34 | }
 35 | 
 36 | open class Cache<T: DataConvertible> where T.Result == T, T : DataRepresentable {
 37 |     
 38 |     let name: String
 39 |     
 40 |     var memoryWarningObserver : NSObjectProtocol!
 41 |     
 42 |     public init(name: String) {
 43 |         self.name = name
 44 |         
 45 |         let notifications = NotificationCenter.default
 46 |         // Using block-based observer to avoid subclassing NSObject
 47 |         memoryWarningObserver = notifications.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification,
 48 |             object: nil,
 49 |             queue: OperationQueue.main,
 50 |             using: { [unowned self] (notification : Notification!) -> Void in
 51 |                 self.onMemoryWarning()
 52 |             }
 53 |         )
 54 |         
 55 |         let originalFormat = Format<T>(name: HanekeGlobals.Cache.OriginalFormatName)
 56 |         self.addFormat(originalFormat)
 57 |     }
 58 |     
 59 |     deinit {
 60 |         let notifications = NotificationCenter.default
 61 |         notifications.removeObserver(memoryWarningObserver as Any, name: UIApplication.didReceiveMemoryWarningNotification, object: nil)
 62 |     }
 63 |     
 64 |     open func set(value: T, key: String, formatName: String = HanekeGlobals.Cache.OriginalFormatName, success succeed: ((T) -> ())? = nil) {
 65 |         if let (format, memoryCache, diskCache) = self.formats[formatName] {
 66 |             self.format(value: value, format: format) { formattedValue in
 67 |                 let wrapper = ObjectWrapper(value: formattedValue)
 68 |                 memoryCache.setObject(wrapper, forKey: key as AnyObject)
 69 |                 // Value data is sent as @autoclosure to be executed in the disk cache queue.
 70 |                 diskCache.setData(self.dataFromValue(formattedValue, format: format), key: key)
 71 |                 succeed?(formattedValue)
 72 |             }
 73 |         } else {
 74 |             assertionFailure("Can't set value before adding format")
 75 |         }
 76 |     }
 77 |     
 78 |     @discardableResult open func fetch(key: String, formatName: String = HanekeGlobals.Cache.OriginalFormatName, failure fail : Fetch<T>.Failer? = nil, success succeed : Fetch<T>.Succeeder? = nil) -> Fetch<T> {
 79 |         let fetch = Cache.buildFetch(failure: fail, success: succeed)
 80 |         if let (format, memoryCache, diskCache) = self.formats[formatName] {
 81 |             if let wrapper = memoryCache.object(forKey: key as AnyObject) as? ObjectWrapper, let result = wrapper.hnk_value as? T {
 82 |                 fetch.succeed(result)
 83 |                 diskCache.updateAccessDate(self.dataFromValue(result, format: format), key: key)
 84 |                 return fetch
 85 |             }
 86 | 
 87 |             self.fetchFromDiskCache(diskCache, key: key, memoryCache: memoryCache, failure: { error in
 88 |                 fetch.fail(error)
 89 |             }) { value in
 90 |                 fetch.succeed(value)
 91 |             }
 92 | 
 93 |         } else {
 94 |             let localizedFormat = NSLocalizedString("Format %@ not found", comment: "Error description")
 95 |             let description = String(format:localizedFormat, formatName)
 96 |             let error = errorWithCode(HanekeGlobals.Cache.ErrorCode.formatNotFound.rawValue, description: description)
 97 |             fetch.fail(error)
 98 |         }
 99 |         return fetch
100 |     }
101 |     
102 |     @discardableResult open func fetch(fetcher : Fetcher<T>, formatName: String = HanekeGlobals.Cache.OriginalFormatName, failure fail : Fetch<T>.Failer? = nil, success succeed : Fetch<T>.Succeeder? = nil) -> Fetch<T> {
103 |         let key = fetcher.key
104 |         let fetch = Cache.buildFetch(failure: fail, success: succeed)
105 |         self.fetch(key: key, formatName: formatName, failure: { error in
106 |             if (error as NSError?)?.code == HanekeGlobals.Cache.ErrorCode.formatNotFound.rawValue {
107 |                 fetch.fail(error)
108 |             }
109 |             
110 |             if let (format, _, _) = self.formats[formatName] {
111 |                 self.fetchAndSet(fetcher, format: format, failure: { error in
112 |                     fetch.fail(error)
113 |                 }) {value in
114 |                     fetch.succeed(value)
115 |                 }
116 |             }
117 |             
118 |             // Unreachable code. Formats can't be removed from Cache.
119 |         }) { value in
120 |             fetch.succeed(value)
121 |         }
122 |         return fetch
123 |     }
124 | 
125 |     open func remove(key: String, formatName: String = HanekeGlobals.Cache.OriginalFormatName) {
126 |         if let (_, memoryCache, diskCache) = self.formats[formatName] {
127 |             memoryCache.removeObject(forKey: key as AnyObject)
128 |             diskCache.removeData(with: key)
129 |         }
130 |     }
131 |     
132 |     open func removeAll(_ completion: (() -> ())? = nil) {
133 |         let group = DispatchGroup()
134 |         for (_, (_, memoryCache, diskCache)) in self.formats {
135 |             memoryCache.removeAllObjects()
136 |             group.enter()
137 |             diskCache.removeAllData {
138 |                 group.leave()
139 |             }
140 |         }
141 |         DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
142 |             let timeout = DispatchTime.now() + Double(Int64(60 * NSEC_PER_SEC)) / Double(NSEC_PER_SEC)
143 |             if group.wait(timeout: timeout) != .success {
144 |                 Log.error(message: "removeAll timed out waiting for disk caches")
145 |             }
146 |             let path = self.cachePath
147 |             do {
148 |                 try FileManager.default.removeItem(atPath: path)
149 |             } catch {
150 |                 Log.error(message: "Failed to remove path \(path)", error: error)
151 |             }
152 |             if let completion = completion {
153 |                 DispatchQueue.main.async {
154 |                     completion()
155 |                 }
156 |             }
157 |         }
158 |     }
159 | 
160 |     // MARK: Size
161 | 
162 |     open var size: UInt64 {
163 |         var size: UInt64 = 0
164 |         for (_, (_, _, diskCache)) in self.formats {
165 |             diskCache.cacheQueue.sync { size += diskCache.size }
166 |         }
167 |         return size
168 |     }
169 | 
170 |     // MARK: Notifications
171 |     
172 |     func onMemoryWarning() {
173 |         for (_, (_, memoryCache, _)) in self.formats {
174 |             memoryCache.removeAllObjects()
175 |         }
176 |     }
177 |     
178 |     // MARK: Formats
179 | 
180 |     public var formats : [String : (Format<T>, NSCache<AnyObject, AnyObject>, DiskCache)] = [:]
181 |     
182 |     open func addFormat(_ format : Format<T>) {
183 |         let name = format.name
184 |         let formatPath = self.formatPath(withFormatName: name)
185 |         let memoryCache = NSCache<AnyObject, AnyObject>()
186 |         let diskCache = DiskCache(path: formatPath, capacity : format.diskCapacity)
187 |         self.formats[name] = (format, memoryCache, diskCache)
188 |     }
189 |     
190 |     // MARK: Internal
191 |     
192 |     lazy var cachePath: String = {
193 |         let basePath = DiskCache.basePath()
194 |         let cachePath = (basePath as NSString).appendingPathComponent(self.name)
195 |         return cachePath
196 |     }()
197 |     
198 |     func formatPath(withFormatName formatName: String) -> String {
199 |         let formatPath = (self.cachePath as NSString).appendingPathComponent(formatName)
200 |         do {
201 |             try FileManager.default.createDirectory(atPath: formatPath, withIntermediateDirectories: true, attributes: nil)
202 |         } catch {
203 |             Log.error(message: "Failed to create directory \(formatPath)", error: error)
204 |         }
205 |         return formatPath
206 |     }
207 |     
208 |     // MARK: Private
209 |     
210 |     func dataFromValue(_ value : T, format : Format<T>) -> Data? {
211 |         if let data = format.convertToData?(value) {
212 |             return data as Data
213 |         }
214 |         return value.asData()
215 |     }
216 |     
217 |     fileprivate func fetchFromDiskCache(_ diskCache : DiskCache, key: String, memoryCache : NSCache<AnyObject, AnyObject>, failure fail : ((Error?) -> ())?, success succeed : @escaping (T) -> ()) {
218 |         diskCache.fetchData(key: key, failure: { error in
219 |             if let block = fail {
220 |                 if (error as NSError?)?.code == NSFileReadNoSuchFileError {
221 |                     let localizedFormat = NSLocalizedString("Object not found for key %@", comment: "Error description")
222 |                     let description = String(format:localizedFormat, key)
223 |                     let error = errorWithCode(HanekeGlobals.Cache.ErrorCode.objectNotFound.rawValue, description: description)
224 |                     block(error)
225 |                 } else {
226 |                     block(error)
227 |                 }
228 |             }
229 |         }) { data in
230 |             DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async(execute: {
231 |                 let value = T.convertFromData(data)
232 |                 if let value = value {
233 |                     let descompressedValue = self.decompressedImageIfNeeded(value)
234 |                     DispatchQueue.main.async(execute: {
235 |                         succeed(descompressedValue)
236 |                         let wrapper = ObjectWrapper(value: descompressedValue)
237 |                         memoryCache.setObject(wrapper, forKey: key as AnyObject)
238 |                     })
239 |                 }
240 |             })
241 |         }
242 |     }
243 |     
244 |     fileprivate func fetchAndSet(_ fetcher : Fetcher<T>, format : Format<T>, failure fail : ((Error?) -> ())?, success succeed : @escaping (T) -> ()) {
245 |         fetcher.fetch(failure: { error in
246 |             let _ = fail?(error)
247 |         }) { value in
248 |             self.set(value: value, key: fetcher.key, formatName: format.name, success: succeed)
249 |         }
250 |     }
251 |     
252 |     fileprivate func format(value : T, format : Format<T>, success succeed : @escaping (T) -> ()) {
253 |         // HACK: Ideally Cache shouldn't treat images differently but I can't think of any other way of doing this that doesn't complicate the API for other types.
254 |         if format.isIdentity && !(value is UIImage) {
255 |             succeed(value)
256 |         } else {
257 |             DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
258 |                 var formatted = format.apply(value)
259 |                 
260 |                 if let formattedImage = formatted as? UIImage {
261 |                     let originalImage = value as? UIImage
262 |                     if formattedImage === originalImage {
263 |                         formatted = self.decompressedImageIfNeeded(formatted)
264 |                     }
265 |                 }
266 |                 
267 |                 DispatchQueue.main.async {
268 |                     succeed(formatted)
269 |                 }
270 |             }
271 |         }
272 |     }
273 |     
274 |     fileprivate func decompressedImageIfNeeded(_ value : T) -> T {
275 |         if let image = value as? UIImage {
276 |             let decompressedImage = image.hnk_decompressedImage() as? T
277 |             return decompressedImage!
278 |         }
279 |         return value
280 |     }
281 |     
282 |     fileprivate class func buildFetch(failure fail : Fetch<T>.Failer? = nil, success succeed : Fetch<T>.Succeeder? = nil) -> Fetch<T> {
283 |         let fetch = Fetch<T>()
284 |         if let succeed = succeed {
285 |             fetch.onSuccess(succeed)
286 |         }
287 |         if let fail = fail {
288 |             fetch.onFailure(fail)
289 |         }
290 |         return fetch
291 |     }
292 |     
293 |     // MARK: Convenience fetch
294 |     // Ideally we would put each of these in the respective fetcher file as a Cache extension. Unfortunately, this fails to link when using the framework in a project as of Xcode 6.1.
295 |     
296 |     open func fetch(key: String, value getValue : @autoclosure @escaping () -> T.Result, formatName: String = HanekeGlobals.Cache.OriginalFormatName, success succeed : Fetch<T>.Succeeder? = nil) -> Fetch<T> {
297 |         let fetcher = SimpleFetcher<T>(key: key, value: getValue())
298 |         return self.fetch(fetcher: fetcher, formatName: formatName, success: succeed)
299 |     }
300 |     
301 |     open func fetch(path: String, formatName: String = HanekeGlobals.Cache.OriginalFormatName,  failure fail : Fetch<T>.Failer? = nil, success succeed : Fetch<T>.Succeeder? = nil) -> Fetch<T> {
302 |         let fetcher = DiskFetcher<T>(path: path)
303 |         return self.fetch(fetcher: fetcher, formatName: formatName, failure: fail, success: succeed)
304 |     }
305 |     
306 |     open func fetch(URL : Foundation.URL, formatName: String = HanekeGlobals.Cache.OriginalFormatName,  failure fail : Fetch<T>.Failer? = nil, success succeed : Fetch<T>.Succeeder? = nil) -> Fetch<T> {
307 |         let fetcher = NetworkFetcher<T>(URL: URL)
308 |         return self.fetch(fetcher: fetcher, formatName: formatName, failure: fail, success: succeed)
309 |     }
310 |     
311 | }
312 | 


--------------------------------------------------------------------------------
/Haneke/CryptoSwiftMD5.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  CryptoSwiftMD5.Swif
  3 | //
  4 | // To date, adding CommonCrypto to a Swift framework is problematic. See:
  5 | // http://stackoverflow.com/questions/25248598/importing-commoncrypto-in-a-swift-framework
  6 | // We're using a subset of CryptoSwift as a (temporary?) alternative.
  7 | // The following is an altered source version that only includes MD5. The original software can be found at:
  8 | // https://github.com/krzyzanowskim/CryptoSwift
  9 | // This is the original copyright notice:
 10 | 
 11 | /*
 12 | Copyright (C) 2014 Marcin Krzyżanowski <marcin.krzyzanowski@gmail.com>
 13 | This software is provided 'as-is', without any express or implied warranty.
 14 | 
 15 | In no event will the authors be held liable for any damages arising from the use of this software.
 16 | 
 17 | Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
 18 | 
 19 | - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
 20 | - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
 21 | - This notice may not be removed or altered from any source or binary distribution.
 22 | */
 23 | 
 24 | import Foundation
 25 | 
 26 | /** array of bytes, little-endian representation */
 27 | func arrayOfBytes<T>(value:T, length:Int? = nil) -> [UInt8] {
 28 |     let totalBytes = length ?? MemoryLayout<T>.size
 29 |     
 30 |     let valuePointer = UnsafeMutablePointer<T>.allocate(capacity: 1)
 31 |     valuePointer.pointee = value
 32 |     
 33 |     let bytesPointer = UnsafeMutablePointer<UInt8>(OpaquePointer(valuePointer))
 34 |     var bytes = Array<UInt8>(repeating: 0, count: totalBytes)
 35 |     for j in 0..<min(MemoryLayout<T>.size,totalBytes) {
 36 |         bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee
 37 |     }
 38 |     
 39 |     valuePointer.deinitialize(count: 1)
 40 |     valuePointer.deallocate()
 41 |     
 42 |     return bytes
 43 | }
 44 | 
 45 | extension Int {
 46 |     /** Array of bytes with optional padding (little-endian) */
 47 |     public func bytes(totalBytes: Int = MemoryLayout<Int>.size) -> [UInt8] {
 48 |         return arrayOfBytes(value: self, length: totalBytes)
 49 |     }
 50 |     
 51 | }
 52 | 
 53 | extension NSMutableData {
 54 |     
 55 |     /** Convenient way to append bytes */
 56 |     internal func appendBytes(arrayOfBytes: [UInt8]) {
 57 |         self.append(arrayOfBytes, length: arrayOfBytes.count)
 58 |     }
 59 |     
 60 | }
 61 | 
 62 | struct BytesSequence: Sequence {
 63 |     let chunkSize: Int
 64 |     let data: [UInt8]
 65 |     
 66 |     func makeIterator() -> AnyIterator<ArraySlice<UInt8>> {
 67 |         var offset:Int = 0
 68 |         return AnyIterator {
 69 |             let end = Swift.min(self.chunkSize, self.data.count - offset)
 70 |             let result = self.data[offset..<offset + end]
 71 |             offset += result.count
 72 |             return !result.isEmpty ? result : nil
 73 |         }
 74 |     }
 75 | }
 76 | 
 77 | class HashBase {
 78 |     
 79 |     static let size:Int = 16 // 128 / 8
 80 |     let message: [UInt8]
 81 |     
 82 |     init (_ message: [UInt8]) {
 83 |         self.message = message
 84 |     }
 85 |     
 86 |     /** Common part for hash calculation. Prepare header data. */
 87 |     func prepare(_ len:Int) -> [UInt8] {
 88 |         var tmpMessage = message
 89 |         
 90 |         // Step 1. Append Padding Bits
 91 |         tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message
 92 |         
 93 |         // append "0" bit until message length in bits ≡ 448 (mod 512)
 94 |         var msgLength = tmpMessage.count
 95 |         var counter = 0
 96 |         
 97 |         while msgLength % len != (len - 8) {
 98 |             counter += 1
 99 |             msgLength += 1
100 |         }
101 |         
102 |         tmpMessage += Array<UInt8>(repeating: 0, count: counter)
103 |         return tmpMessage
104 |     }
105 | }
106 | 
107 | func rotateLeft(v: UInt32, n: UInt32) -> UInt32 {
108 |     return ((v << n) & 0xFFFFFFFF) | (v >> (32 - n))
109 | }
110 | 
111 | func sliceToUInt32Array(_ slice: ArraySlice<UInt8>) -> [UInt32] {
112 |     var result = [UInt32]()
113 |     result.reserveCapacity(16)
114 |     for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout<UInt32>.size) {
115 |         let val1:UInt32 = (UInt32(slice[idx.advanced(by: 3)]) << 24)
116 |         let val2:UInt32 = (UInt32(slice[idx.advanced(by: 2)]) << 16)
117 |         let val3:UInt32 = (UInt32(slice[idx.advanced(by: 1)]) << 8)
118 |         let val4:UInt32 = UInt32(slice[idx])
119 |         let val:UInt32 = val1 | val2 | val3 | val4
120 |         result.append(val)
121 |     }
122 |     return result
123 | }
124 | 
125 | class MD5 : HashBase {
126 |     
127 |     
128 |     /** specifies the per-round shift amounts */
129 |     private let s: [UInt32] = [7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,
130 |                                     5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,
131 |                                     4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,
132 |                                     6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21]
133 |     
134 |     /** binary integer part of the sines of integers (Radians) */
135 |     private let k: [UInt32] = [0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,
136 |                                     0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,
137 |                                     0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,
138 |                                     0x6b901122,0xfd987193,0xa679438e,0x49b40821,
139 |                                     0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa,
140 |                                     0xd62f105d,0x2441453,0xd8a1e681,0xe7d3fbc8,
141 |                                     0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,
142 |                                     0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a,
143 |                                     0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c,
144 |                                     0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70,
145 |                                     0x289b7ec6,0xeaa127fa,0xd4ef3085,0x4881d05,
146 |                                     0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,
147 |                                     0xf4292244,0x432aff97,0xab9423a7,0xfc93a039,
148 |                                     0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1,
149 |                                     0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,
150 |                                     0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391]
151 |     
152 |     private let h: [UInt32] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]
153 |     
154 |     func calculate() -> [UInt8] {
155 |         var tmpMessage = prepare(64)
156 |         tmpMessage.reserveCapacity(tmpMessage.count + 4)
157 |         
158 |         // initialize hh with hash values
159 |         var hh = h
160 |         
161 |         // Step 2. Append Length a 64-bit representation of lengthInBits
162 |         let lengthInBits = (message.count * 8)
163 |         let lengthBytes = lengthInBits.bytes(totalBytes: 64 / 8)
164 |         tmpMessage += lengthBytes.reversed()
165 |         
166 |         // Process the message in successive 512-bit chunks:
167 |         let chunkSizeBytes = 512 / 8 // 64
168 |         for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) {
169 |             // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15
170 |             var M = sliceToUInt32Array(chunk)
171 |             assert(M.count == 16, "Invalid array")
172 |             
173 |             // Initialize hash value for this chunk:
174 |             var A:UInt32 = hh[0]
175 |             var B:UInt32 = hh[1]
176 |             var C:UInt32 = hh[2]
177 |             var D:UInt32 = hh[3]
178 |             
179 |             var dTemp:UInt32 = 0
180 |             
181 |             // Main loop
182 |             for j in 0..<k.count {
183 |                 var g = 0
184 |                 var F:UInt32 = 0
185 |                 
186 |                 switch (j) {
187 |                 case 0...15:
188 |                     F = (B & C) | ((~B) & D)
189 |                     g = j
190 |                     break
191 |                 case 16...31:
192 |                     F = (D & B) | (~D & C)
193 |                     g = (5 * j + 1) % 16
194 |                     break
195 |                 case 32...47:
196 |                     F = B ^ C ^ D
197 |                     g = (3 * j + 5) % 16
198 |                     break
199 |                 case 48...63:
200 |                     F = C ^ (B | (~D))
201 |                     g = (7 * j) % 16
202 |                     break
203 |                 default:
204 |                     break
205 |                 }
206 |                 dTemp = D
207 |                 D = C
208 |                 C = B
209 |                 B = B &+ rotateLeft(v: A &+ F &+ k[j] &+ M[g], n: s[j])
210 |                 A = dTemp
211 |             }
212 |             
213 |             hh[0] = hh[0] &+ A
214 |             hh[1] = hh[1] &+ B
215 |             hh[2] = hh[2] &+ C
216 |             hh[3] = hh[3] &+ D
217 |         }
218 |         
219 |         var result = [UInt8]()
220 |         result.reserveCapacity(hh.count / 4)
221 |         
222 |         hh.forEach {
223 |             let itemLE = $0.littleEndian
224 |             result += [UInt8(itemLE & 0xff), UInt8((itemLE >> 8) & 0xff), UInt8((itemLE >> 16) & 0xff), UInt8((itemLE >> 24) & 0xff)]
225 |         }
226 |         
227 |         return result
228 |     }
229 | }
230 | 


--------------------------------------------------------------------------------
/Haneke/Data.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  Data.swift
  3 | //  Haneke
  4 | //
  5 | //  Created by Hermes Pique on 9/19/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import UIKit
 10 | 
 11 | // See: http://stackoverflow.com/questions/25922152/not-identical-to-self
 12 | public protocol DataConvertible {
 13 |     associatedtype Result
 14 |     
 15 |     static func convertFromData(_ data:Data) -> Result?
 16 | }
 17 | 
 18 | public protocol DataRepresentable {
 19 |     
 20 |     func asData() -> Data!
 21 | }
 22 | 
 23 | private let imageSync = NSLock()
 24 | 
 25 | extension UIImage : DataConvertible, DataRepresentable {
 26 |     
 27 |     public typealias Result = UIImage
 28 | 
 29 |     // HACK: UIImage data initializer is no longer thread safe. See: https://github.com/AFNetworking/AFNetworking/issues/2572#issuecomment-115854482
 30 |     static func safeImageWithData(_ data:Data) -> Result? {
 31 |         imageSync.lock()
 32 |         let image = UIImage(data:data, scale: scale)
 33 |         imageSync.unlock()
 34 |         return image
 35 |     }
 36 |     
 37 |     public class func convertFromData(_ data: Data) -> Result? {
 38 |         let image = UIImage.safeImageWithData(data)
 39 |         return image
 40 |     }
 41 |     
 42 |     public func asData() -> Data! {
 43 |         return self.hnk_data()
 44 |     }
 45 |     
 46 |     fileprivate static let scale = UIScreen.main.scale
 47 |     
 48 | }
 49 | 
 50 | extension String : DataConvertible, DataRepresentable {
 51 |     
 52 |     public typealias Result = String
 53 |     
 54 |     public static func convertFromData(_ data: Data) -> Result? {
 55 |         let string = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
 56 |         return string as Result?
 57 |     }
 58 |     
 59 |     public func asData() -> Data! {
 60 |         return self.data(using: String.Encoding.utf8)
 61 |     }
 62 |     
 63 | }
 64 | 
 65 | extension Data : DataConvertible, DataRepresentable {
 66 |     
 67 |     public typealias Result = Data
 68 |     
 69 |     public static func convertFromData(_ data: Data) -> Result? {
 70 |         return data
 71 |     }
 72 |     
 73 |     public func asData() -> Data! {
 74 |         return self
 75 |     }
 76 |     
 77 | }
 78 | 
 79 | public enum JSON : DataConvertible, DataRepresentable {
 80 |     public typealias Result = JSON
 81 |     
 82 |     case Dictionary([String:AnyObject])
 83 |     case Array([AnyObject])
 84 |     
 85 |     public static func convertFromData(_ data: Data) -> Result? {
 86 |         do {
 87 |             let object : Any = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions())
 88 |             switch (object) {
 89 |             case let dictionary as [String:AnyObject]:
 90 |                 return JSON.Dictionary(dictionary)
 91 |             case let array as [AnyObject]:
 92 |                 return JSON.Array(array)
 93 |             default:
 94 |                 return nil
 95 |             }
 96 |         } catch {
 97 |             Log.error(message: "Invalid JSON data", error: error)
 98 |             return nil
 99 |         }
100 |     }
101 |     
102 |     public func asData() -> Data! {
103 |         switch (self) {
104 |         case .Dictionary(let dictionary):
105 |             return try? JSONSerialization.data(withJSONObject: dictionary, options: JSONSerialization.WritingOptions())
106 |         case .Array(let array):
107 |             return try? JSONSerialization.data(withJSONObject: array, options: JSONSerialization.WritingOptions())
108 |         }
109 |     }
110 |     
111 |     public var array : [AnyObject]! {
112 |         switch (self) {
113 |         case .Dictionary(_):
114 |             return nil
115 |         case .Array(let array):
116 |             return array
117 |         }
118 |     }
119 |     
120 |     public var dictionary : [String:AnyObject]! {
121 |         switch (self) {
122 |         case .Dictionary(let dictionary):
123 |             return dictionary
124 |         case .Array(_):
125 |             return nil
126 |         }
127 |     }
128 |     
129 | }
130 | 


--------------------------------------------------------------------------------
/Haneke/DiskCache.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  DiskCache.swift
  3 | //  Haneke
  4 | //
  5 | //  Created by Hermes Pique on 8/10/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import Foundation
 10 | 
 11 | open class DiskCache {
 12 |     
 13 |     open class func basePath() -> String {
 14 |         let cachesPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]
 15 |         let hanekePathComponent = HanekeGlobals.Domain
 16 |         let basePath = (cachesPath as NSString).appendingPathComponent(hanekePathComponent)
 17 |         // TODO: Do not recaculate basePath value
 18 |         return basePath
 19 |     }
 20 |     
 21 |     public let path: String
 22 | 
 23 |     open var size : UInt64 = 0
 24 | 
 25 |     open var capacity : UInt64 = 0 {
 26 |         didSet {
 27 |             self.cacheQueue.async(execute: {
 28 |                 self.controlCapacity()
 29 |             })
 30 |         }
 31 |     }
 32 | 
 33 |     open lazy var cacheQueue : DispatchQueue = {
 34 |         let queueName = HanekeGlobals.Domain + "." + (self.path as NSString).lastPathComponent
 35 |         let cacheQueue = DispatchQueue(label: queueName, attributes: [])
 36 |         return cacheQueue
 37 |     }()
 38 |     
 39 |     public init(path: String, capacity: UInt64 = UINT64_MAX) {
 40 |         self.path = path
 41 |         self.capacity = capacity
 42 |         self.cacheQueue.async(execute: {
 43 |             self.calculateSize()
 44 |             self.controlCapacity()
 45 |         })
 46 |     }
 47 |     
 48 |     open func setData( _ getData: @autoclosure @escaping () -> Data?, key: String) {
 49 |         cacheQueue.async(execute: {
 50 |             if let data = getData() {
 51 |                 self.setDataSync(data, key: key)
 52 |             } else {
 53 |                 Log.error(message: "Failed to get data for key \(key)")
 54 |             }
 55 |         })
 56 |     }
 57 |     
 58 |     open func fetchData(key: String, failure fail: ((Error?) -> ())? = nil, success succeed: @escaping (Data) -> ()) {
 59 |         cacheQueue.async {
 60 |             let path = self.path(forKey: key)
 61 |             do {
 62 |                 let data = try Data(contentsOf: URL(fileURLWithPath: path), options: Data.ReadingOptions())
 63 |                 DispatchQueue.main.async {
 64 |                     succeed(data)
 65 |                 }
 66 |                 self.updateDiskAccessDate(atPath: path)
 67 |             } catch {
 68 |                 if let block = fail {
 69 |                     DispatchQueue.main.async {
 70 |                         block(error)
 71 |                     }
 72 |                 }
 73 |             }
 74 |         }
 75 |     }
 76 | 
 77 |     open func removeData(with key: String) {
 78 |         cacheQueue.async(execute: {
 79 |             let path = self.path(forKey: key)
 80 |             self.removeFile(atPath: path)
 81 |         })
 82 |     }
 83 |     
 84 |     open func removeAllData(_ completion: (() -> ())? = nil) {
 85 |         let fileManager = FileManager.default
 86 |         let cachePath = self.path
 87 |         cacheQueue.async(execute: {
 88 |             do {
 89 |                 let contents = try fileManager.contentsOfDirectory(atPath: cachePath)
 90 |                 for pathComponent in contents {
 91 |                     let path = (cachePath as NSString).appendingPathComponent(pathComponent)
 92 |                     do {
 93 |                         try fileManager.removeItem(atPath: path)
 94 |                     } catch {
 95 |                         Log.error(message: "Failed to remove path \(path)", error: error)
 96 |                     }
 97 |                 }
 98 |                 self.calculateSize()
 99 |             } catch {
100 |                 Log.error(message: "Failed to list directory", error: error)
101 |             }
102 |             if let completion = completion {
103 |                 DispatchQueue.main.async {
104 |                     completion()
105 |                 }
106 |             }
107 |         })
108 |     }
109 | 
110 |     open func updateAccessDate( _ getData: @autoclosure @escaping () -> Data?, key: String) {
111 |         cacheQueue.async(execute: {
112 |             let path = self.path(forKey: key)
113 |             let fileManager = FileManager.default
114 |             if (!(fileManager.fileExists(atPath: path) && self.updateDiskAccessDate(atPath: path))){
115 |                 if let data = getData() {
116 |                     self.setDataSync(data, key: key)
117 |                 } else {
118 |                     Log.error(message: "Failed to get data for key \(key)")
119 |                 }
120 |             }
121 |         })
122 |     }
123 | 
124 |     open func path(forKey key: String) -> String {
125 |         let escapedFilename = key.escapedFilename()
126 |         let filename = escapedFilename.count < Int(NAME_MAX) ? escapedFilename : key.MD5Filename()
127 |         let keyPath = (self.path as NSString).appendingPathComponent(filename)
128 |         return keyPath
129 |     }
130 |     
131 |     // MARK: Private
132 |     
133 |     fileprivate func calculateSize() {
134 |         let fileManager = FileManager.default
135 |         size = 0
136 |         let cachePath = self.path
137 |         do {
138 |             let contents = try fileManager.contentsOfDirectory(atPath: cachePath)
139 |             for pathComponent in contents {
140 |                 let path = (cachePath as NSString).appendingPathComponent(pathComponent)
141 |                 do {
142 |                     let attributes: [FileAttributeKey: Any] = try fileManager.attributesOfItem(atPath: path)
143 |                     if let fileSize = attributes[FileAttributeKey.size] as? UInt64 {
144 |                         size += fileSize
145 |                     }
146 |                 } catch {
147 |                     Log.error(message: "Failed to list directory", error: error)
148 |                 }
149 |             }
150 |             
151 |         } catch {
152 |             Log.error(message: "Failed to list directory", error: error)
153 |         }
154 |     }
155 |     
156 |     fileprivate func controlCapacity() {
157 |         if self.size <= self.capacity { return }
158 |         
159 |         let fileManager = FileManager.default
160 |         let cachePath = self.path
161 |         fileManager.enumerateContentsOfDirectory(atPath: cachePath, orderedByProperty: URLResourceKey.contentModificationDateKey.rawValue, ascending: true) { (URL : URL, _, stop : inout Bool) -> Void in
162 |             
163 |             self.removeFile(atPath: URL.path)
164 | 
165 |             stop = self.size <= self.capacity
166 |         }
167 |     }
168 |     
169 |     fileprivate func setDataSync(_ data: Data, key: String) {
170 |         let path = self.path(forKey: key)
171 |         let fileManager = FileManager.default
172 |         let previousAttributes : [FileAttributeKey: Any]? = try? fileManager.attributesOfItem(atPath: path)
173 |         
174 |         do {
175 |             try data.write(to: URL(fileURLWithPath: path), options: Data.WritingOptions.atomicWrite)
176 |         } catch {
177 |             Log.error(message: "Failed to write key \(key)", error: error)
178 |         }
179 |         
180 |         if let attributes = previousAttributes {
181 |             if let fileSize = attributes[FileAttributeKey.size] as? UInt64 {
182 |                 substract(size: fileSize)
183 |             }
184 |         }
185 |         self.size += UInt64(data.count)
186 |         self.controlCapacity()
187 |     }
188 |     
189 |     @discardableResult fileprivate func updateDiskAccessDate(atPath path: String) -> Bool {
190 |         let fileManager = FileManager.default
191 |         let now = Date()
192 |         do {
193 |             try fileManager.setAttributes([FileAttributeKey.modificationDate : now], ofItemAtPath: path)
194 |             return true
195 |         } catch {
196 |             Log.error(message: "Failed to update access date", error: error)
197 |             return false
198 |         }
199 |     }
200 |     
201 |     fileprivate func removeFile(atPath path: String) {
202 |         let fileManager = FileManager.default
203 |         do {
204 |             let attributes: [FileAttributeKey: Any] = try fileManager.attributesOfItem(atPath: path)
205 |             do {
206 |                 try fileManager.removeItem(atPath: path)
207 |                 if let fileSize = attributes[FileAttributeKey.size] as? UInt64 {
208 |                     substract(size: fileSize)
209 |                 }
210 |             } catch {
211 |                 Log.error(message: "Failed to remove file", error: error)
212 |             }
213 |         } catch {
214 |             if isNoSuchFileError(error) {
215 |                 Log.debug(message: "File not found", error: error)
216 |             } else {
217 |                 Log.error(message: "Failed to remove file", error: error)
218 |             }
219 |         }
220 |     }
221 | 
222 |     fileprivate func substract(size : UInt64) {
223 |         if (self.size >= size) {
224 |             self.size -= size
225 |         } else {
226 |             Log.error(message: "Disk cache size (\(self.size)) is smaller than size to substract (\(size))")
227 |             self.size = 0
228 |         }
229 |     }
230 | }
231 | 
232 | private func isNoSuchFileError(_ error : Error?) -> Bool {
233 |     if let error = error {
234 |         return NSCocoaErrorDomain == (error as NSError).domain && (error as NSError).code == NSFileReadNoSuchFileError
235 |     }
236 |     return false
237 | }
238 | 


--------------------------------------------------------------------------------
/Haneke/DiskFetcher.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  DiskFetcher.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Joan Romano on 9/16/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import Foundation
10 | 
11 | extension HanekeGlobals {
12 | 
13 |     // It'd be better to define this in the DiskFetcher class but Swift doesn't allow to declare an enum in a generic type
14 |     public struct DiskFetcher {
15 |         
16 |         public enum ErrorCode : Int {
17 |             case invalidData = -500
18 |         }
19 |         
20 |     }
21 |     
22 | }
23 | 
24 | open class DiskFetcher<T : DataConvertible> : Fetcher<T> {
25 |     
26 |     let path: String
27 |     var cancelled = false
28 |     
29 |     public init(path: String) {
30 |         self.path = path
31 |         let key = path
32 |         super.init(key: key)
33 |     }
34 |     
35 |     // MARK: Fetcher
36 |     
37 |     
38 |     open override func fetch(failure fail: @escaping ((Error?) -> ()), success succeed: @escaping (T.Result) -> ()) {
39 |         self.cancelled = false
40 |         DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async(execute: { [weak self] in
41 |             if let strongSelf = self {
42 |                 strongSelf.privateFetch(failure: fail, success: succeed)
43 |             }
44 |         })
45 |     }
46 |     
47 |     open override func cancelFetch() {
48 |         self.cancelled = true
49 |     }
50 |     
51 |     // MARK: Private
52 |     
53 |     fileprivate func privateFetch(failure fail: @escaping ((Error?) -> ()), success succeed: @escaping (T.Result) -> ()) {
54 |         if self.cancelled {
55 |             return
56 |         }
57 |         
58 |         let data : Data
59 |         do {
60 |             data = try Data(contentsOf: URL(fileURLWithPath: self.path), options: Data.ReadingOptions())
61 |         } catch {
62 |             DispatchQueue.main.async {
63 |                 if self.cancelled {
64 |                     return
65 |                 }
66 |                 fail(error)
67 |             }
68 |             return
69 |         }
70 |         
71 |         if self.cancelled {
72 |             return
73 |         }
74 |         
75 |         guard let value : T.Result = T.convertFromData(data) else {
76 |             let localizedFormat = NSLocalizedString("Failed to convert value from data at path %@", comment: "Error description")
77 |             let description = String(format:localizedFormat, self.path)
78 |             let error = errorWithCode(HanekeGlobals.DiskFetcher.ErrorCode.invalidData.rawValue, description: description)
79 |             DispatchQueue.main.async {
80 |                 fail(error)
81 |             }
82 |             return
83 |         }
84 |         
85 |         DispatchQueue.main.async(execute: {
86 |             if self.cancelled {
87 |                 return
88 |             }
89 |             succeed(value)
90 |         })
91 |     }
92 | }
93 | 


--------------------------------------------------------------------------------
/Haneke/Fetch.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  Fetch.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 9/28/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import Foundation
10 | 
11 | enum FetchState<T> {
12 |     case pending
13 |     // Using Wrapper as a workaround for error 'unimplemented IR generation feature non-fixed multi-payload enum layout'
14 |     // See: http://swiftradar.tumblr.com/post/88314603360/swift-fails-to-compile-enum-with-two-data-cases
15 |     // See: http://owensd.io/2014/08/06/fixed-enum-layout.html
16 |     case success(Wrapper<T>)
17 |     case failure(Error?)
18 | }
19 | 
20 | open class Fetch<T> {
21 |     
22 |     public typealias Succeeder = (T) -> ()
23 |     
24 |     public typealias Failer = (Error?) -> ()
25 |     
26 |     fileprivate var onSuccess : Succeeder?
27 |     
28 |     fileprivate var onFailure : Failer?
29 |     
30 |     fileprivate var state : FetchState<T> = FetchState.pending
31 |     
32 |     public init() {}
33 |     
34 |     @discardableResult open func onSuccess(_ onSuccess: @escaping Succeeder) -> Self {
35 |         self.onSuccess = onSuccess
36 |         switch self.state {
37 |         case FetchState.success(let wrapper):
38 |             onSuccess(wrapper.value)
39 |         default:
40 |             break
41 |         }
42 |         return self
43 |     }
44 |     
45 |     @discardableResult open func onFailure(_ onFailure: @escaping Failer) -> Self {
46 |         self.onFailure = onFailure
47 |         switch self.state {
48 |         case FetchState.failure(let error):
49 |             onFailure(error)
50 |         default:
51 |             break
52 |         }
53 |         return self
54 |     }
55 |     
56 |     func succeed(_ value: T) {
57 |         self.state = FetchState.success(Wrapper(value))
58 |         self.onSuccess?(value)
59 |     }
60 |     
61 |     func fail(_ error: Error? = nil) {
62 |         self.state = FetchState.failure(error)
63 |         self.onFailure?(error)
64 |     }
65 |     
66 |     var hasFailed : Bool {
67 |         switch self.state {
68 |         case FetchState.failure(_):
69 |             return true
70 |         default:
71 |             return false
72 |             }
73 |     }
74 |     
75 |     var hasSucceeded : Bool {
76 |         switch self.state {
77 |         case FetchState.success(_):
78 |             return true
79 |         default:
80 |             return false
81 |         }
82 |     }
83 |     
84 | }
85 | 
86 | open class Wrapper<T> {
87 |     public let value: T
88 |     public init(_ value: T) { self.value = value }
89 | }
90 | 


--------------------------------------------------------------------------------
/Haneke/Fetcher.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  Fetcher.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 9/9/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import UIKit
10 | 
11 | // See: http://stackoverflow.com/questions/25915306/generic-closure-in-protocol
12 | open class Fetcher<T : DataConvertible> {
13 | 
14 |     public let key: String
15 |     
16 |     public init(key: String) {
17 |         self.key = key
18 |     }
19 |     
20 |     open func fetch(failure fail: @escaping ((Error?) -> ()), success succeed: @escaping (T.Result) -> ()) {}
21 |     
22 |     open func cancelFetch() {}
23 | }
24 | 
25 | class SimpleFetcher<T : DataConvertible> : Fetcher<T> {
26 |     
27 |     let getValue : () -> T.Result
28 |     
29 |     init(key: String, value getValue : @autoclosure @escaping () -> T.Result) {
30 |         self.getValue = getValue
31 |         super.init(key: key)
32 |     }
33 |     
34 |     override func fetch(failure fail: @escaping ((Error?) -> ()), success succeed: @escaping (T.Result) -> ()) {
35 |         let value = getValue()
36 |         succeed(value)
37 |     }
38 |     
39 |     override func cancelFetch() {}
40 |     
41 | }
42 | 


--------------------------------------------------------------------------------
/Haneke/Format.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  Format.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 8/27/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import UIKit
10 | 
11 | public struct Format<T> {
12 |     
13 |     public let name: String
14 |     
15 |     public let diskCapacity : UInt64
16 |     
17 |     public var transform : ((T) -> (T))?
18 |     
19 |     public var convertToData : ((T) -> Data)?
20 | 
21 |     public init(name: String, diskCapacity : UInt64 = UINT64_MAX, transform: ((T) -> (T))? = nil) {
22 |         self.name = name
23 |         self.diskCapacity = diskCapacity
24 |         self.transform = transform
25 |     }
26 |     
27 |     public func apply(_ value : T) -> T {
28 |         var transformed = value
29 |         if let transform = self.transform {
30 |             transformed = transform(value)
31 |         }
32 |         return transformed
33 |     }
34 |     
35 |     var isIdentity : Bool {
36 |         return self.transform == nil
37 |     }
38 | 
39 | }
40 | 
41 | public struct ImageResizer {
42 |     
43 |     public enum ScaleMode: String {
44 |         case Fill = "fill", AspectFit = "aspectfit", AspectFill = "aspectfill", None = "none"
45 |     }
46 |     
47 |     public typealias T = UIImage
48 |     
49 |     public let allowUpscaling : Bool
50 |     
51 |     public let size : CGSize
52 |     
53 |     public let scaleMode: ScaleMode
54 |     
55 |     public let compressionQuality : Float
56 |     
57 |     public init(size: CGSize = CGSize.zero, scaleMode: ScaleMode = .None, allowUpscaling: Bool = true, compressionQuality: Float = 1.0) {
58 |         self.size = size
59 |         self.scaleMode = scaleMode
60 |         self.allowUpscaling = allowUpscaling
61 |         self.compressionQuality = compressionQuality
62 |     }
63 |     
64 |     public func resizeImage(_ image: UIImage) -> UIImage {
65 |         var resizeToSize: CGSize
66 |         switch self.scaleMode {
67 |         case .Fill:
68 |             resizeToSize = self.size
69 |         case .AspectFit:
70 |             resizeToSize = image.size.hnk_aspectFitSize(self.size)
71 |         case .AspectFill:
72 |             resizeToSize = image.size.hnk_aspectFillSize(self.size)
73 |         case .None:
74 |             return image
75 |         }
76 |         assert(self.size.width > 0 && self.size.height > 0, "Expected non-zero size. Use ScaleMode.None to avoid resizing.")
77 |         
78 |         // If does not allow to scale up the image
79 |         if (!self.allowUpscaling) {
80 |             if (resizeToSize.width > image.size.width || resizeToSize.height > image.size.height) {
81 |                 return image
82 |             }
83 |         }
84 |         
85 |         // Avoid unnecessary computations
86 |         if (resizeToSize.width == image.size.width && resizeToSize.height == image.size.height) {
87 |             return image
88 |         }
89 |         
90 |         let resizedImage = image.hnk_imageByScaling(toSize: resizeToSize)
91 |         return resizedImage
92 |     }
93 | }
94 | 


--------------------------------------------------------------------------------
/Haneke/Haneke.h:
--------------------------------------------------------------------------------
 1 | //
 2 | //  Haneke.h
 3 | //  Haneke
 4 | //
 5 | //  Created by Luis Ascorbe on 23/07/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | #import <UIKit/UIKit.h>
10 | 
11 | //! Project version number for Haneke.
12 | FOUNDATION_EXPORT double HanekeVersionNumber;
13 | 
14 | //! Project version string for Haneke.
15 | FOUNDATION_EXPORT const unsigned char HanekeVersionString[];
16 | 
17 | // In this header, you should import all the public headers of your framework using statements like #import <Haneke/PublicHeader.h>
18 | 
19 | 
20 | 


--------------------------------------------------------------------------------
/Haneke/Haneke.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  Haneke.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 9/9/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import UIKit
10 | 
11 | public struct HanekeGlobals {
12 |     
13 |     public static let Domain = "io.haneke"
14 |     
15 | }
16 | 
17 | public struct Shared {
18 |     
19 |     public static var imageCache : Cache<UIImage> {
20 |         struct Static {
21 |             static let name = "shared-images"
22 |             static let cache = Cache<UIImage>(name: name)
23 |         }
24 |         return Static.cache
25 |     }
26 |     
27 |     public static var dataCache : Cache<Data> {
28 |         struct Static {
29 |             static let name = "shared-data"
30 |             static let cache = Cache<Data>(name: name)
31 |         }
32 |         return Static.cache
33 |     }
34 |     
35 |     public static var stringCache : Cache<String> {
36 |         struct Static {
37 |             static let name = "shared-strings"
38 |             static let cache = Cache<String>(name: name)
39 |         }
40 |         return Static.cache
41 |     }
42 |     
43 |     public static var JSONCache : Cache<JSON> {
44 |         struct Static {
45 |             static let name = "shared-json"
46 |             static let cache = Cache<JSON>(name: name)
47 |         }
48 |         return Static.cache
49 |     }
50 | }
51 | 
52 | func errorWithCode(_ code: Int, description: String) -> Error {
53 |     let userInfo = [NSLocalizedDescriptionKey: description]
54 |     return NSError(domain: HanekeGlobals.Domain, code: code, userInfo: userInfo) as Error
55 | }
56 | 


--------------------------------------------------------------------------------
/Haneke/Info-iOS.plist:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 3 | <plist version="1.0">
 4 | <dict>
 5 | 	<key>CFBundleDevelopmentRegion</key>
 6 | 	<string>en</string>
 7 | 	<key>CFBundleExecutable</key>
 8 | 	<string>${EXECUTABLE_NAME}</string>
 9 | 	<key>CFBundleIdentifier</key>
10 | 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11 | 	<key>CFBundleInfoDictionaryVersion</key>
12 | 	<string>6.0</string>
13 | 	<key>CFBundleName</key>
14 | 	<string>${PRODUCT_NAME}</string>
15 | 	<key>CFBundlePackageType</key>
16 | 	<string>FMWK</string>
17 | 	<key>CFBundleShortVersionString</key>
18 | 	<string>1.1</string>
19 | 	<key>CFBundleSignature</key>
20 | 	<string>????</string>
21 | 	<key>CFBundleVersion</key>
22 | 	<string>${CURRENT_PROJECT_VERSION}</string>
23 | 	<key>NSPrincipalClass</key>
24 | 	<string></string>
25 | </dict>
26 | </plist>
27 | 


--------------------------------------------------------------------------------
/Haneke/Info-tvOS.plist:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 3 | <plist version="1.0">
 4 | <dict>
 5 | 	<key>CFBundleDevelopmentRegion</key>
 6 | 	<string>en</string>
 7 | 	<key>CFBundleExecutable</key>
 8 | 	<string>${EXECUTABLE_NAME}</string>
 9 | 	<key>CFBundleIdentifier</key>
10 | 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11 | 	<key>CFBundleInfoDictionaryVersion</key>
12 | 	<string>6.0</string>
13 | 	<key>CFBundleName</key>
14 | 	<string>${PRODUCT_NAME}</string>
15 | 	<key>CFBundlePackageType</key>
16 | 	<string>FMWK</string>
17 | 	<key>CFBundleShortVersionString</key>
18 | 	<string>1.1</string>
19 | 	<key>CFBundleSignature</key>
20 | 	<string>????</string>
21 | 	<key>CFBundleVersion</key>
22 | 	<string>${CURRENT_PROJECT_VERSION}</string>
23 | 	<key>NSPrincipalClass</key>
24 | 	<string></string>
25 | </dict>
26 | </plist>
27 | 


--------------------------------------------------------------------------------
/Haneke/Log.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  Log.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 11/10/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import Foundation
10 | 
11 | struct Log {
12 |     
13 |     fileprivate static let Tag = "[HANEKE]"
14 |     
15 |     fileprivate enum Level : String {
16 |         case Debug = "[DEBUG]"
17 |         case Error = "[ERROR]"
18 |     }
19 |     
20 |     fileprivate static func log(_ level: Level, _ message: @autoclosure () -> String, _ error: Error? = nil) {
21 |         if let error = error {
22 |             print("\(Tag)\(level.rawValue) \(message()) with error \(error)")
23 |         } else {
24 |             print("\(Tag)\(level.rawValue) \(message())")
25 |         }
26 |     }
27 |     
28 |     static func debug(message: @autoclosure () -> String, error: Error? = nil) {
29 |         #if DEBUG
30 |             log(.Debug, message(), error)
31 |         #endif
32 |     }
33 |     
34 |     static func error(message: @autoclosure () -> String, error: Error? = nil) {
35 |         log(.Error, message(), error)
36 |     }
37 |     
38 | }
39 | 


--------------------------------------------------------------------------------
/Haneke/NSFileManager+Haneke.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  NSFileManager+Haneke.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 8/26/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import Foundation
10 | 
11 | extension FileManager {
12 | 
13 |     func enumerateContentsOfDirectory(atPath path: String, orderedByProperty property: String, ascending: Bool, usingBlock block: (URL, Int, inout Bool) -> Void ) {
14 | 
15 |         let directoryURL = URL(fileURLWithPath: path)
16 |         do {
17 |             let contents = try self.contentsOfDirectory(at: directoryURL, includingPropertiesForKeys: [URLResourceKey(rawValue: property)], options: FileManager.DirectoryEnumerationOptions())
18 |             let sortedContents = contents.sorted(by: {(URL1: URL, URL2: URL) -> Bool in
19 |                 
20 |                 // Maybe there's a better way to do this. See: http://stackoverflow.com/questions/25502914/comparing-anyobject-in-swift
21 |                 
22 |                 var value1 : AnyObject?
23 |                 do {
24 |                     try (URL1 as NSURL).getResourceValue(&value1, forKey: URLResourceKey(rawValue: property))
25 |                 } catch {
26 |                     return true
27 |                 }
28 |                 var value2 : AnyObject?
29 |                 do {
30 |                     try (URL2 as NSURL).getResourceValue(&value2, forKey: URLResourceKey(rawValue: property))
31 |                 } catch {
32 |                     return false
33 |                 }
34 |                 
35 |                 if let string1 = value1 as? String, let string2 = value2 as? String {
36 |                     return ascending ? string1 < string2 : string2 < string1
37 |                 }
38 |                 
39 |                 if let date1 = value1 as? Date, let date2 = value2 as? Date {
40 |                     return ascending ? date1 < date2 : date2 < date1
41 |                 }
42 |                 
43 |                 if let number1 = value1 as? NSNumber, let number2 = value2 as? NSNumber {
44 |                     return ascending ? number1 < number2 : number2 < number1
45 |                 }
46 |                 
47 |                 return false
48 |             })
49 |             
50 |             for (i, v) in sortedContents.enumerated() {
51 |                 var stop : Bool = false
52 |                 block(v, i, &stop)
53 |                 if stop { break }
54 |             }
55 | 
56 |         } catch {
57 |             Log.error(message: "Failed to list directory", error: error)
58 |         }
59 |     }
60 | 
61 | }
62 | 
63 | func < (lhs: NSNumber, rhs: NSNumber) -> Bool {
64 |     return lhs.compare(rhs) == ComparisonResult.orderedAscending
65 | }
66 | 


--------------------------------------------------------------------------------
/Haneke/NSHTTPURLResponse+Haneke.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  NSHTTPURLResponse+Haneke.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 1/2/16.
 6 | //  Copyright © 2016 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import Foundation
10 | 
11 | extension HTTPURLResponse {
12 | 
13 |     func hnk_isValidStatusCode() -> Bool {
14 |         switch self.statusCode {
15 |         case 200...201:
16 |             return true
17 |         default:
18 |             return false
19 |         }
20 |     }
21 | 
22 | }
23 | 


--------------------------------------------------------------------------------
/Haneke/NSURLResponse+Haneke.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  NSHTTPURLResponse+Haneke.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 9/12/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import Foundation
10 | 
11 | extension URLResponse {
12 |     
13 |     func hnk_validateLength(ofData data: Data) -> Bool {
14 |         let expectedContentLength = self.expectedContentLength
15 |         if (expectedContentLength > -1) {
16 |             let dataLength = data.count
17 |             return Int64(dataLength) >= expectedContentLength
18 |         }
19 |         return true
20 |     }
21 |     
22 | }
23 | 


--------------------------------------------------------------------------------
/Haneke/NetworkFetcher.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  NetworkFetcher.swift
  3 | //  Haneke
  4 | //
  5 | //  Created by Hermes Pique on 9/12/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import UIKit
 10 | 
 11 | extension HanekeGlobals {
 12 |     
 13 |     // It'd be better to define this in the NetworkFetcher class but Swift doesn't allow to declare an enum in a generic type
 14 |     public struct NetworkFetcher {
 15 | 
 16 |         public enum ErrorCode : Int {
 17 |             case invalidData = -400
 18 |             case missingData = -401
 19 |             case invalidStatusCode = -402
 20 |         }
 21 |         
 22 |     }
 23 |     
 24 | }
 25 | 
 26 | open class NetworkFetcher<T : DataConvertible> : Fetcher<T> {
 27 |     
 28 |     let URL : Foundation.URL
 29 |     
 30 |     public init(URL : Foundation.URL) {
 31 |         self.URL = URL
 32 | 
 33 |         let key =  URL.absoluteString
 34 |         super.init(key: key)
 35 |     }
 36 |     
 37 |     open var session : URLSession { return URLSession.shared }
 38 |     
 39 |     var task : URLSessionDataTask? = nil
 40 |     
 41 |     var cancelled = false
 42 |     
 43 |     // MARK: Fetcher
 44 |     
 45 |     open override func fetch(failure fail: @escaping ((Error?) -> ()), success succeed: @escaping (T.Result) -> ()) {
 46 |         self.cancelled = false
 47 |         self.task = self.session.dataTask(with: self.URL) {[weak self] (data, response, error) -> Void in
 48 |             if let strongSelf = self {
 49 |                 strongSelf.onReceive(data: data, response: response, error: error, failure: fail, success: succeed)
 50 |             }
 51 |         }
 52 |         self.task?.resume()
 53 |     }
 54 |     
 55 |     open override func cancelFetch() {
 56 |         self.task?.cancel()
 57 |         self.cancelled = true
 58 |     }
 59 |     
 60 |     // MARK: Private
 61 |     
 62 |     fileprivate func onReceive(data: Data!, response: URLResponse!, error: Error!, failure fail: @escaping ((Error?) -> ()), success succeed: @escaping (T.Result) -> ()) {
 63 | 
 64 |         if cancelled { return }
 65 |         
 66 |         let URL = self.URL
 67 |         
 68 |         if let error = error {
 69 |             if ((error as NSError).domain == NSURLErrorDomain && (error as NSError).code == NSURLErrorCancelled) { return }
 70 |             
 71 |             Log.debug(message: "Request \(URL.absoluteString) failed", error: error)
 72 |             DispatchQueue.main.async(execute: { fail(error) })
 73 |             return
 74 |         }
 75 |         
 76 |         if let httpResponse = response as? HTTPURLResponse , !httpResponse.hnk_isValidStatusCode() {
 77 |             let description = HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode)
 78 |             self.failWithCode(.invalidStatusCode, localizedDescription: description, failure: fail)
 79 |             return
 80 |         }
 81 | 
 82 |         if !response.hnk_validateLength(ofData: data) {
 83 |             let localizedFormat = NSLocalizedString("Request expected %ld bytes and received %ld bytes", comment: "Error description")
 84 |             let description = String(format:localizedFormat, response.expectedContentLength, data.count)
 85 |             self.failWithCode(.missingData, localizedDescription: description, failure: fail)
 86 |             return
 87 |         }
 88 |         
 89 |         guard let value = T.convertFromData(data) else {
 90 |             let localizedFormat = NSLocalizedString("Failed to convert value from data at URL %@", comment: "Error description")
 91 |             let description = String(format:localizedFormat, URL.absoluteString)
 92 |             self.failWithCode(.invalidData, localizedDescription: description, failure: fail)
 93 |             return
 94 |         }
 95 | 
 96 |         DispatchQueue.main.async { succeed(value) }
 97 | 
 98 |     }
 99 |     
100 |     fileprivate func failWithCode(_ code: HanekeGlobals.NetworkFetcher.ErrorCode, localizedDescription: String, failure fail: @escaping ((Error?) -> ())) {
101 |         let error = errorWithCode(code.rawValue, description: localizedDescription)
102 |         Log.debug(message: localizedDescription, error: error)
103 |         DispatchQueue.main.async { fail(error) }
104 |     }
105 | }
106 | 


--------------------------------------------------------------------------------
/Haneke/String+Haneke.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  String+Haneke.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 8/30/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import Foundation
10 | 
11 | extension String {
12 |     
13 |     func escapedFilename() -> String {
14 |         return [ "\0":"%00", ":":"%3A", "/":"%2F" ]
15 |             .reduce(self.components(separatedBy: "%").joined(separator: "%25")) {
16 |                 str, m in str.components(separatedBy: m.0).joined(separator: m.1)
17 |         }
18 |     }
19 |     
20 |     func MD5String() -> String {
21 |         guard let data = self.data(using: String.Encoding.utf8) else {
22 |             return self
23 |         }
24 | 
25 |         let MD5Calculator = MD5(Array(data))
26 |         let MD5Data = MD5Calculator.calculate()
27 |         let resultBytes = UnsafeMutablePointer<CUnsignedChar>(mutating: MD5Data)
28 |         let resultEnumerator = UnsafeBufferPointer<CUnsignedChar>(start: resultBytes, count: MD5Data.count)
29 |         let MD5String = NSMutableString()
30 |         for c in resultEnumerator {
31 |             MD5String.appendFormat("%02x", c)
32 |         }
33 |         return MD5String as String
34 |     }
35 |     
36 |     func MD5Filename() -> String {
37 |         let MD5String = self.MD5String()
38 | 
39 |         // NSString.pathExtension alone could return a query string, which can lead to very long filenames.
40 |         let pathExtension = URL(string: self)?.pathExtension ?? (self as NSString).pathExtension
41 | 
42 |         if pathExtension.count > 0 {
43 |             return (MD5String as NSString).appendingPathExtension(pathExtension) ?? MD5String
44 |         } else {
45 |             return MD5String
46 |         }
47 |     }
48 | 
49 | }
50 | 


--------------------------------------------------------------------------------
/Haneke/UIButton+Haneke.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  UIButton+Haneke.swift
  3 | //  Haneke
  4 | //
  5 | //  Created by Joan Romano on 10/1/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import UIKit
 10 | 
 11 | public extension UIButton {
 12 |     
 13 |     var hnk_imageFormat : Format<UIImage> {
 14 |         let bounds = self.bounds
 15 |         assert(bounds.size.width > 0 && bounds.size.height > 0, "[\(Mirror(reflecting: self).description) \(#function)]: UIButton size is zero. Set its frame, call sizeToFit or force layout first. You can also set a custom format with a defined size if you don't want to force layout.")
 16 |             let contentRect = self.contentRect(forBounds: bounds)
 17 |             let imageInsets = self.imageEdgeInsets
 18 |             let scaleMode = self.contentHorizontalAlignment != UIControl.ContentHorizontalAlignment.fill || self.contentVerticalAlignment != UIControl.ContentVerticalAlignment.fill ? ImageResizer.ScaleMode.AspectFit : ImageResizer.ScaleMode.Fill
 19 |             let imageSize = CGSize(width: contentRect.width - imageInsets.left - imageInsets.right, height: contentRect.height - imageInsets.top - imageInsets.bottom)
 20 |             
 21 |             return HanekeGlobals.UIKit.formatWithSize(imageSize, scaleMode: scaleMode, allowUpscaling: scaleMode == ImageResizer.ScaleMode.AspectFit ? false : true)
 22 |     }
 23 |     
 24 |     func hnk_setImageFromURL(_ URL: Foundation.URL, state: UIControl.State = .normal, placeholder: UIImage? = nil, format: Format<UIImage>? = nil, failure fail: ((Error?) -> ())? = nil, success succeed: ((UIImage) -> ())? = nil) {
 25 |         let fetcher = NetworkFetcher<UIImage>(URL: URL)
 26 |         self.hnk_setImageFromFetcher(fetcher, state: state, placeholder: placeholder, format: format, failure: fail, success: succeed)
 27 |     }
 28 |     
 29 |     func hnk_setImage(_ image: UIImage, key: String, state: UIControl.State = .normal, placeholder: UIImage? = nil, format: Format<UIImage>? = nil, success succeed: ((UIImage) -> ())? = nil) {
 30 |         let fetcher = SimpleFetcher<UIImage>(key: key, value: image)
 31 |         self.hnk_setImageFromFetcher(fetcher, state: state, placeholder: placeholder, format: format, success: succeed)
 32 |     }
 33 |     
 34 |     func hnk_setImageFromFile(_ path: String, state: UIControl.State = .normal, placeholder: UIImage? = nil, format: Format<UIImage>? = nil, failure fail: ((Error?) -> ())? = nil, success succeed: ((UIImage) -> ())? = nil) {
 35 |         let fetcher = DiskFetcher<UIImage>(path: path)
 36 |         self.hnk_setImageFromFetcher(fetcher, state: state, placeholder: placeholder, format: format, failure: fail, success: succeed)
 37 |     }
 38 |     
 39 |     func hnk_setImageFromFetcher(_ fetcher: Fetcher<UIImage>, state: UIControl.State = .normal, placeholder: UIImage? = nil, format: Format<UIImage>? = nil, failure fail: ((Error?) -> ())? = nil, success succeed: ((UIImage) -> ())? = nil){
 40 |         self.hnk_cancelSetImage()
 41 |         self.hnk_imageFetcher = fetcher
 42 |         
 43 |         let didSetImage = self.hnk_fetchImageForFetcher(fetcher, state: state, format : format, failure: fail, success: succeed)
 44 |         
 45 |         if didSetImage { return }
 46 |         
 47 |         if let placeholder = placeholder {
 48 |             self.setImage(placeholder, for: state)
 49 |         }
 50 |     }
 51 |     
 52 |     func hnk_cancelSetImage() {
 53 |         if let fetcher = self.hnk_imageFetcher {
 54 |             fetcher.cancelFetch()
 55 |             self.hnk_imageFetcher = nil
 56 |         }
 57 |     }
 58 |     
 59 |     // MARK: Internal Image
 60 |     
 61 |     // See: http://stackoverflow.com/questions/25907421/associating-swift-things-with-nsobject-instances
 62 |     var hnk_imageFetcher : Fetcher<UIImage>! {
 63 |         get {
 64 |             let wrapper = objc_getAssociatedObject(self, &HanekeGlobals.UIKit.SetImageFetcherKey) as? ObjectWrapper
 65 |             let fetcher = wrapper?.hnk_value as? Fetcher<UIImage>
 66 |             return fetcher
 67 |         }
 68 |         set (fetcher) {
 69 |             var wrapper : ObjectWrapper?
 70 |             if let fetcher = fetcher {
 71 |                 wrapper = ObjectWrapper(value: fetcher)
 72 |             }
 73 |             objc_setAssociatedObject(self, &HanekeGlobals.UIKit.SetImageFetcherKey, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
 74 |         }
 75 |     }
 76 |     
 77 |     func hnk_fetchImageForFetcher(_ fetcher : Fetcher<UIImage>, state : UIControl.State = .normal, format : Format<UIImage>? = nil, failure fail : ((Error?) -> ())?, success succeed : ((UIImage) -> ())?) -> Bool {
 78 |         let format = format ?? self.hnk_imageFormat
 79 |         let cache = Shared.imageCache
 80 |         if cache.formats[format.name] == nil {
 81 |             cache.addFormat(format)
 82 |         }
 83 |         var animated = false
 84 |         let fetch = cache.fetch(fetcher: fetcher, formatName: format.name, failure: {[weak self] error in
 85 |             if let strongSelf = self {
 86 |                 if strongSelf.hnk_shouldCancelImageForKey(fetcher.key) { return }
 87 |                 
 88 |                 strongSelf.hnk_imageFetcher = nil
 89 |                 
 90 |                 fail?(error)
 91 |             }
 92 |             }) { [weak self] image in
 93 |                 if let strongSelf = self {
 94 |                     if strongSelf.hnk_shouldCancelImageForKey(fetcher.key) { return }
 95 |                     
 96 |                     strongSelf.hnk_setImage(image, state: state, animated: animated, success: succeed)
 97 |                 }
 98 |         }
 99 |         animated = true
100 |         return fetch.hasSucceeded
101 |     }
102 |     
103 |     
104 |     func hnk_setImage(_ image : UIImage, state : UIControl.State, animated : Bool, success succeed : ((UIImage) -> ())?) {
105 |         self.hnk_imageFetcher = nil
106 |         
107 |         if let succeed = succeed {
108 |             succeed(image)
109 |         } else if animated {
110 |             UIView.transition(with: self, duration: HanekeGlobals.UIKit.SetImageAnimationDuration, options: .transitionCrossDissolve, animations: {
111 |                 self.setImage(image, for: state)
112 |                 }, completion: nil)
113 |         } else {
114 |             self.setImage(image, for: state)
115 |         }
116 |     }
117 |     
118 |     func hnk_shouldCancelImageForKey(_ key:String) -> Bool {
119 |         if self.hnk_imageFetcher?.key == key { return false }
120 |         
121 |         Log.debug(message: "Cancelled set image for \((key as NSString).lastPathComponent)")
122 |         return true
123 |     }
124 |     
125 |     // MARK: Background image
126 |         
127 |     var hnk_backgroundImageFormat : Format<UIImage> {
128 |         let bounds = self.bounds
129 |         assert(bounds.size.width > 0 && bounds.size.height > 0, "[\(Mirror(reflecting: self).description) \(#function)]: UIButton size is zero. Set its frame, call sizeToFit or force layout first. You can also set a custom format with a defined size if you don't want to force layout.")
130 |             let imageSize = self.backgroundRect(forBounds: bounds).size
131 |             
132 |             return HanekeGlobals.UIKit.formatWithSize(imageSize, scaleMode: .Fill)
133 |     }
134 |     
135 |     func hnk_setBackgroundImageFromURL(_ URL : Foundation.URL, state : UIControl.State = .normal, placeholder : UIImage? = nil, format : Format<UIImage>? = nil, failure fail : ((Error?) -> ())? = nil, success succeed : ((UIImage) -> ())? = nil) {
136 |         let fetcher = NetworkFetcher<UIImage>(URL: URL)
137 |         self.hnk_setBackgroundImageFromFetcher(fetcher, state: state, placeholder: placeholder, format: format, failure: fail, success: succeed)
138 |     }
139 |     
140 |     func hnk_setBackgroundImage(_ image : UIImage, key: String, state : UIControl.State = .normal, placeholder : UIImage? = nil, format : Format<UIImage>? = nil, success succeed : ((UIImage) -> ())? = nil) {
141 |         let fetcher = SimpleFetcher<UIImage>(key: key, value: image)
142 |         self.hnk_setBackgroundImageFromFetcher(fetcher, state: state, placeholder: placeholder, format: format, success: succeed)
143 |     }
144 |     
145 |     func hnk_setBackgroundImageFromFile(_ path: String, state : UIControl.State = .normal, placeholder : UIImage? = nil, format : Format<UIImage>? = nil, failure fail : ((Error?) -> ())? = nil, success succeed : ((UIImage) -> ())? = nil) {
146 |         let fetcher = DiskFetcher<UIImage>(path: path)
147 |         self.hnk_setBackgroundImageFromFetcher(fetcher, state: state, placeholder: placeholder, format: format, failure: fail, success: succeed)
148 |     }
149 |     
150 |     func hnk_setBackgroundImageFromFetcher(_ fetcher : Fetcher<UIImage>, state : UIControl.State = .normal, placeholder : UIImage? = nil, format : Format<UIImage>? = nil, failure fail : ((Error?) -> ())? = nil, success succeed : ((UIImage) -> ())? = nil) {
151 |         self.hnk_cancelSetBackgroundImage()
152 |         self.hnk_backgroundImageFetcher = fetcher
153 |         
154 |         let didSetImage = self.hnk_fetchBackgroundImageForFetcher(fetcher, state: state, format : format, failure: fail, success: succeed)
155 |      
156 |         if didSetImage { return }
157 |         
158 |         if let placeholder = placeholder {
159 |             self.setBackgroundImage(placeholder, for: state)
160 |         }
161 |     }
162 |     
163 |     func hnk_cancelSetBackgroundImage() {
164 |         if let fetcher = self.hnk_backgroundImageFetcher {
165 |             fetcher.cancelFetch()
166 |             self.hnk_backgroundImageFetcher = nil
167 |         }
168 |     }
169 |     
170 |     // MARK: Internal Background image
171 |     
172 |     // See: http://stackoverflow.com/questions/25907421/associating-swift-things-with-nsobject-instances
173 |     var hnk_backgroundImageFetcher : Fetcher<UIImage>! {
174 |         get {
175 |             let wrapper = objc_getAssociatedObject(self, &HanekeGlobals.UIKit.SetBackgroundImageFetcherKey) as? ObjectWrapper
176 |             let fetcher = wrapper?.hnk_value as? Fetcher<UIImage>
177 |             return fetcher
178 |         }
179 |         set (fetcher) {
180 |             var wrapper : ObjectWrapper?
181 |             if let fetcher = fetcher {
182 |                 wrapper = ObjectWrapper(value: fetcher)
183 |             }
184 |             objc_setAssociatedObject(self, &HanekeGlobals.UIKit.SetBackgroundImageFetcherKey, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
185 |         }
186 |     }
187 |     
188 |     func hnk_fetchBackgroundImageForFetcher(_ fetcher: Fetcher<UIImage>, state: UIControl.State = .normal, format: Format<UIImage>? = nil, failure fail: ((Error?) -> ())?, success succeed : ((UIImage) -> ())?) -> Bool {
189 |         let format = format ?? self.hnk_backgroundImageFormat
190 |         let cache = Shared.imageCache
191 |         if cache.formats[format.name] == nil {
192 |             cache.addFormat(format)
193 |         }
194 |         var animated = false
195 |         let fetch = cache.fetch(fetcher: fetcher, formatName: format.name, failure: {[weak self] error in
196 |             if let strongSelf = self {
197 |                 if strongSelf.hnk_shouldCancelBackgroundImageForKey(fetcher.key) { return }
198 |                 
199 |                 strongSelf.hnk_backgroundImageFetcher = nil
200 |                 
201 |                 fail?(error)
202 |             }
203 |             }) { [weak self] image in
204 |                 if let strongSelf = self {
205 |                     if strongSelf.hnk_shouldCancelBackgroundImageForKey(fetcher.key) { return }
206 |                     
207 |                     strongSelf.hnk_setBackgroundImage(image, state: state, animated: animated, success: succeed)
208 |                 }
209 |         }
210 |         animated = true
211 |         return fetch.hasSucceeded
212 |     }
213 |     
214 |     func hnk_setBackgroundImage(_ image: UIImage, state: UIControl.State, animated: Bool, success succeed: ((UIImage) -> ())?) {
215 |         self.hnk_backgroundImageFetcher = nil
216 |         
217 |         if let succeed = succeed {
218 |             succeed(image)
219 |         } else if animated {
220 |             UIView.transition(with: self, duration: HanekeGlobals.UIKit.SetImageAnimationDuration, options: .transitionCrossDissolve, animations: {
221 |                 self.setBackgroundImage(image, for: state)
222 |                 }, completion: nil)
223 |         } else {
224 |             self.setBackgroundImage(image, for: state)
225 |         }
226 |     }
227 |     
228 |     func hnk_shouldCancelBackgroundImageForKey(_ key: String) -> Bool {
229 |         if self.hnk_backgroundImageFetcher?.key == key { return false }
230 |         
231 |         Log.debug(message: "Cancelled set background image for \((key as NSString).lastPathComponent)")
232 |         return true
233 |     }
234 | }
235 | 


--------------------------------------------------------------------------------
/Haneke/UIImage+Haneke.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  UIImage+Haneke.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 8/10/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import UIKit
10 | 
11 | extension UIImage {
12 | 
13 |     func hnk_imageByScaling(toSize size: CGSize) -> UIImage {
14 |         UIGraphicsBeginImageContextWithOptions(size, !hnk_hasAlpha(), 0.0)
15 |         draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
16 |         let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
17 |         UIGraphicsEndImageContext()
18 |         return resizedImage!
19 |     }
20 | 
21 |     func hnk_hasAlpha() -> Bool {
22 |         guard let alphaInfo = self.cgImage?.alphaInfo else { return false }
23 |         switch alphaInfo {
24 |         case .first, .last, .premultipliedFirst, .premultipliedLast, .alphaOnly:
25 |             return true
26 |         case .none, .noneSkipFirst, .noneSkipLast:
27 |             return false
28 |         @unknown default:
29 |             fatalError()
30 |         }
31 |     }
32 |     
33 |     func hnk_data(compressionQuality: Float = 1.0) -> Data! {
34 |         let hasAlpha = self.hnk_hasAlpha()
35 |         let data = hasAlpha ? self.pngData() : self.jpegData(compressionQuality: CGFloat(compressionQuality))
36 |         return data
37 |     }
38 |     
39 |     func hnk_decompressedImage() -> UIImage! {
40 |         let originalImageRef = self.cgImage
41 |         let originalBitmapInfo = originalImageRef?.bitmapInfo
42 |         guard let alphaInfo = originalImageRef?.alphaInfo else { return UIImage() }
43 |         
44 |         // See: http://stackoverflow.com/questions/23723564/which-cgimagealphainfo-should-we-use
45 |         var bitmapInfo = originalBitmapInfo
46 |         switch alphaInfo {
47 |         case .none:
48 |             let rawBitmapInfoWithoutAlpha = (bitmapInfo?.rawValue)! & ~CGBitmapInfo.alphaInfoMask.rawValue
49 |             let rawBitmapInfo = rawBitmapInfoWithoutAlpha | CGImageAlphaInfo.noneSkipFirst.rawValue
50 |             bitmapInfo = CGBitmapInfo(rawValue: rawBitmapInfo)
51 |         case .premultipliedFirst, .premultipliedLast, .noneSkipFirst, .noneSkipLast:
52 |             break
53 |         case .alphaOnly, .last, .first: // Unsupported
54 |             return self
55 |         @unknown default:
56 |             fatalError()
57 |         }
58 |         
59 |         let colorSpace = CGColorSpaceCreateDeviceRGB()
60 |         let pixelSize = CGSize(width: self.size.width * self.scale, height: self.size.height * self.scale)
61 |         guard let context = CGContext(data: nil, width: Int(ceil(pixelSize.width)), height: Int(ceil(pixelSize.height)), bitsPerComponent: (originalImageRef?.bitsPerComponent)!, bytesPerRow: 0, space: colorSpace, bitmapInfo: (bitmapInfo?.rawValue)!) else {
62 |             return self
63 |         }
64 | 
65 |         let imageRect = CGRect(x: 0, y: 0, width: pixelSize.width, height: pixelSize.height)
66 |         UIGraphicsPushContext(context)
67 |         
68 |         // Flip coordinate system. See: http://stackoverflow.com/questions/506622/cgcontextdrawimage-draws-image-upside-down-when-passed-uiimage-cgimage
69 |         context.translateBy(x: 0, y: pixelSize.height)
70 |         context.scaleBy(x: 1.0, y: -1.0)
71 |         
72 |         // UIImage and drawInRect takes into account image orientation, unlike CGContextDrawImage.
73 |         self.draw(in: imageRect)
74 |         UIGraphicsPopContext()
75 |         
76 |         guard let decompressedImageRef = context.makeImage() else {
77 |             return self
78 |         }
79 |         
80 |         let scale = UIScreen.main.scale
81 |         let image = UIImage(cgImage: decompressedImageRef, scale:scale, orientation:UIImage.Orientation.up)
82 |         return image
83 |     }
84 | 
85 | }
86 | 


--------------------------------------------------------------------------------
/Haneke/UIImageView+Haneke.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  UIImageView+Haneke.swift
  3 | //  Haneke
  4 | //
  5 | //  Created by Hermes Pique on 9/17/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import UIKit
 10 | 
 11 | public extension UIImageView {
 12 |     
 13 |     var hnk_format : Format<UIImage> {
 14 |         let viewSize = self.bounds.size
 15 |             assert(viewSize.width > 0 && viewSize.height > 0, "[\(Mirror(reflecting: self).description) \(#function)]: UImageView size is zero. Set its frame, call sizeToFit or force layout first.")
 16 |             let scaleMode = self.hnk_scaleMode
 17 |             return HanekeGlobals.UIKit.formatWithSize(viewSize, scaleMode: scaleMode)
 18 |     }
 19 |     
 20 |     func hnk_setImageFromURL(_ URL: Foundation.URL, placeholder : UIImage? = nil, format : Format<UIImage>? = nil, failure fail : ((Error?) -> ())? = nil, success succeed : ((UIImage) -> ())? = nil) {
 21 |         let fetcher = NetworkFetcher<UIImage>(URL: URL)
 22 |         self.hnk_setImage(fromFetcher: fetcher, placeholder: placeholder, format: format, failure: fail, success: succeed)
 23 |     }
 24 |     
 25 |     func hnk_setImage( _ image: @autoclosure @escaping () -> UIImage, key: String, placeholder : UIImage? = nil, format : Format<UIImage>? = nil, success succeed : ((UIImage) -> ())? = nil) {
 26 |         let fetcher = SimpleFetcher<UIImage>(key: key, value: image())
 27 |         self.hnk_setImage(fromFetcher: fetcher, placeholder: placeholder, format: format, success: succeed)
 28 |     }
 29 |     
 30 |     func hnk_setImageFromFile(_ path: String, placeholder : UIImage? = nil, format : Format<UIImage>? = nil, failure fail : ((Error?) -> ())? = nil, success succeed : ((UIImage) -> ())? = nil) {
 31 |         let fetcher = DiskFetcher<UIImage>(path: path)
 32 |         self.hnk_setImage(fromFetcher: fetcher, placeholder: placeholder, format: format, failure: fail, success: succeed)
 33 |     }
 34 |     
 35 |     func hnk_setImage(fromFetcher fetcher : Fetcher<UIImage>,
 36 |         placeholder : UIImage? = nil,
 37 |         format : Format<UIImage>? = nil,
 38 |         failure fail : ((Error?) -> ())? = nil,
 39 |         success succeed : ((UIImage) -> ())? = nil) {
 40 | 
 41 |         self.hnk_cancelSetImage()
 42 |         
 43 |         self.hnk_fetcher = fetcher
 44 |         
 45 |         let didSetImage = self.hnk_fetchImageForFetcher(fetcher, format: format, failure: fail, success: succeed)
 46 |         
 47 |         if didSetImage { return }
 48 |      
 49 |         if let placeholder = placeholder {
 50 |             self.image = placeholder
 51 |         }
 52 |     }
 53 |     
 54 |     func hnk_cancelSetImage() {
 55 |         if let fetcher = self.hnk_fetcher {
 56 |             fetcher.cancelFetch()
 57 |             self.hnk_fetcher = nil
 58 |         }
 59 |     }
 60 |     
 61 |     // MARK: Internal
 62 |     
 63 |     // See: http://stackoverflow.com/questions/25907421/associating-swift-things-with-nsobject-instances
 64 |     var hnk_fetcher : Fetcher<UIImage>! {
 65 |         get {
 66 |             let wrapper = objc_getAssociatedObject(self, &HanekeGlobals.UIKit.SetImageFetcherKey) as? ObjectWrapper
 67 |             let fetcher = wrapper?.hnk_value as? Fetcher<UIImage>
 68 |             return fetcher
 69 |         }
 70 |         set (fetcher) {
 71 |             var wrapper : ObjectWrapper?
 72 |             if let fetcher = fetcher {
 73 |                 wrapper = ObjectWrapper(value: fetcher)
 74 |             }
 75 |             objc_setAssociatedObject(self, &HanekeGlobals.UIKit.SetImageFetcherKey, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
 76 |         }
 77 |     }
 78 |     
 79 |     var hnk_scaleMode : ImageResizer.ScaleMode {
 80 |         switch (self.contentMode) {
 81 |         case .scaleToFill:
 82 |             return .Fill
 83 |         case .scaleAspectFit:
 84 |             return .AspectFit
 85 |         case .scaleAspectFill:
 86 |             return .AspectFill
 87 |         case .redraw, .center, .top, .bottom, .left, .right, .topLeft, .topRight, .bottomLeft, .bottomRight:
 88 |             return .None
 89 |         @unknown default:
 90 |             fatalError()
 91 |         }
 92 |     }
 93 | 
 94 |     func hnk_fetchImageForFetcher(_ fetcher : Fetcher<UIImage>, format : Format<UIImage>? = nil, failure fail : ((Error?) -> ())?, success succeed : ((UIImage) -> ())?) -> Bool {
 95 |         let cache = Shared.imageCache
 96 |         let format = format ?? self.hnk_format
 97 |         if cache.formats[format.name] == nil {
 98 |             cache.addFormat(format)
 99 |         }
100 |         var animated = false
101 |         let fetch = cache.fetch(fetcher: fetcher, formatName: format.name, failure: {[weak self] error in
102 |             if let strongSelf = self {
103 |                 if strongSelf.hnk_shouldCancel(forKey: fetcher.key) { return }
104 |                 
105 |                 strongSelf.hnk_fetcher = nil
106 |                 
107 |                 fail?(error)
108 |             }
109 |         }) { [weak self] image in
110 |             if let strongSelf = self {
111 |                 if strongSelf.hnk_shouldCancel(forKey: fetcher.key) { return }
112 |                 
113 |                 strongSelf.hnk_setImage(image, animated: animated, success: succeed)
114 |             }
115 |         }
116 |         animated = true
117 |         return fetch.hasSucceeded
118 |     }
119 |     
120 |     func hnk_setImage(_ image : UIImage, animated : Bool, success succeed : ((UIImage) -> ())?) {
121 |         self.hnk_fetcher = nil
122 |         
123 |         if let succeed = succeed {
124 |             succeed(image)
125 |         } else if animated {
126 |             UIView.transition(with: self, duration: HanekeGlobals.UIKit.SetImageAnimationDuration, options: .transitionCrossDissolve, animations: {
127 |                 self.image = image
128 |             }, completion: nil)
129 |         } else {
130 |             self.image = image
131 |         }
132 |     }
133 |     
134 |     func hnk_shouldCancel(forKey key:String) -> Bool {
135 |         if self.hnk_fetcher?.key == key { return false }
136 |         
137 |         Log.debug(message: "Cancelled set image for \((key as NSString).lastPathComponent)")
138 |         return true
139 |     }
140 |     
141 | }
142 | 


--------------------------------------------------------------------------------
/Haneke/UIView+Haneke.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  UIView+Haneke.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Joan Romano on 15/10/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import UIKit
10 | 
11 | public extension HanekeGlobals {
12 |     
13 |     struct UIKit {
14 |         
15 |         static func formatWithSize(_ size : CGSize, scaleMode : ImageResizer.ScaleMode, allowUpscaling: Bool = true) -> Format<UIImage> {
16 |             let name = "auto-\(size.width)x\(size.height)-\(scaleMode.rawValue)"
17 |             let cache = Shared.imageCache
18 |             if let (format,_,_) = cache.formats[name] {
19 |                 return format
20 |             }
21 |             
22 |             var format = Format<UIImage>(name: name,
23 |                 diskCapacity: HanekeGlobals.UIKit.DefaultFormat.DiskCapacity) {
24 |                     let resizer = ImageResizer(size:size,
25 |                         scaleMode: scaleMode,
26 |                         allowUpscaling: allowUpscaling,
27 |                         compressionQuality: HanekeGlobals.UIKit.DefaultFormat.CompressionQuality)
28 |                     return resizer.resizeImage($0)
29 |             }
30 |             format.convertToData = {(image : UIImage) -> Data in
31 |                 image.hnk_data(compressionQuality: HanekeGlobals.UIKit.DefaultFormat.CompressionQuality) as Data
32 |             }
33 |             return format
34 |         }
35 |         
36 |         public struct DefaultFormat {
37 |             
38 |             public static let DiskCapacity : UInt64 = 50 * 1024 * 1024
39 |             public static let CompressionQuality : Float = 0.75
40 |             
41 |         }
42 |         
43 |         static var SetImageAnimationDuration = 0.1
44 |         static var SetImageFetcherKey = 0
45 |         static var SetBackgroundImageFetcherKey = 1
46 |     }
47 |     
48 | }
49 | 


--------------------------------------------------------------------------------
/HanekeDemo/AppDelegate.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  AppDelegate.swift
 3 | //  HanekeDemo
 4 | //
 5 | //  Created by Hermes Pique on 9/17/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import UIKit
10 | 
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 | 
14 |     var window: UIWindow?
15 | 
16 |     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
17 |         // Override point for customization after application launch.
18 |         return true
19 |     }
20 | 
21 | }
22 | 
23 | 


--------------------------------------------------------------------------------
/HanekeDemo/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 2 | <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6245" systemVersion="13E28" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
 3 |     <dependencies>
 4 |         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6238"/>
 5 |         <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
 6 |     </dependencies>
 7 |     <objects>
 8 |         <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
 9 |         <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
10 |         <view contentMode="scaleToFill" id="iN0-l3-epB">
11 |             <rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
12 |             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
13 |             <subviews>
14 |                 <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="  Copyright (c) 2014 Haneke. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
15 |                     <rect key="frame" x="20" y="439" width="441" height="21"/>
16 |                     <fontDescription key="fontDescription" type="system" pointSize="17"/>
17 |                     <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
18 |                     <nil key="highlightedColor"/>
19 |                 </label>
20 |                 <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Haneke Demo" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
21 |                     <rect key="frame" x="20" y="140" width="441" height="43"/>
22 |                     <fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
23 |                     <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
24 |                     <nil key="highlightedColor"/>
25 |                 </label>
26 |             </subviews>
27 |             <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
28 |             <constraints>
29 |                 <constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
30 |                 <constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
31 |                 <constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
32 |                 <constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
33 |                 <constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
34 |                 <constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
35 |             </constraints>
36 |             <nil key="simulatedStatusBarMetrics"/>
37 |             <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
38 |             <point key="canvasLocation" x="548" y="455"/>
39 |         </view>
40 |     </objects>
41 | </document>
42 | 


--------------------------------------------------------------------------------
/HanekeDemo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 2 | <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6245" systemVersion="13E28" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
 3 |     <dependencies>
 4 |         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6238"/>
 5 |     </dependencies>
 6 |     <scenes>
 7 |         <!--View Controller-->
 8 |         <scene sceneID="tne-QT-ifu">
 9 |             <objects>
10 |                 <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="HanekeDemo" customModuleProvider="target" sceneMemberID="viewController">
11 |                     <layoutGuides>
12 |                         <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
13 |                         <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
14 |                     </layoutGuides>
15 |                     <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC" customClass="UICollectionView">
16 |                         <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
17 |                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
18 |                         <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
19 |                     </view>
20 |                 </viewController>
21 |                 <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
22 |             </objects>
23 |         </scene>
24 |     </scenes>
25 | </document>
26 | 


--------------------------------------------------------------------------------
/HanekeDemo/CollectionViewCell.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  CollectionViewCell.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 9/17/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import UIKit
10 | import Haneke
11 | 
12 | class CollectionViewCell: UICollectionViewCell {
13 |     
14 |     var imageView : UIImageView!
15 |     
16 |     override init(frame: CGRect) {
17 |         super.init(frame: frame)
18 |         initHelper()
19 |     }
20 | 
21 |     required init?(coder aDecoder: NSCoder) {
22 |         super.init(coder: aDecoder)
23 |         initHelper()
24 |     }
25 |     
26 |     func initHelper() {
27 |         imageView = UIImageView(frame: self.contentView.bounds)
28 |         imageView.clipsToBounds = true
29 |         imageView.contentMode = .scaleAspectFill
30 |         imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
31 |         self.contentView.addSubview(imageView)
32 |     }
33 |     
34 |     override func prepareForReuse() {
35 |         imageView.hnk_cancelSetImage()
36 |         imageView.image = nil
37 |     }
38 |     
39 | }
40 | 


--------------------------------------------------------------------------------
/HanekeDemo/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "images" : [
 3 |     {
 4 |       "idiom" : "iphone",
 5 |       "size" : "20x20",
 6 |       "scale" : "2x"
 7 |     },
 8 |     {
 9 |       "idiom" : "iphone",
10 |       "size" : "20x20",
11 |       "scale" : "3x"
12 |     },
13 |     {
14 |       "idiom" : "iphone",
15 |       "size" : "29x29",
16 |       "scale" : "2x"
17 |     },
18 |     {
19 |       "idiom" : "iphone",
20 |       "size" : "29x29",
21 |       "scale" : "3x"
22 |     },
23 |     {
24 |       "idiom" : "iphone",
25 |       "size" : "40x40",
26 |       "scale" : "2x"
27 |     },
28 |     {
29 |       "idiom" : "iphone",
30 |       "size" : "40x40",
31 |       "scale" : "3x"
32 |     },
33 |     {
34 |       "size" : "60x60",
35 |       "idiom" : "iphone",
36 |       "filename" : "icon-60@2x.png",
37 |       "scale" : "2x"
38 |     },
39 |     {
40 |       "size" : "60x60",
41 |       "idiom" : "iphone",
42 |       "filename" : "icon-60@3x.png",
43 |       "scale" : "3x"
44 |     },
45 |     {
46 |       "idiom" : "ipad",
47 |       "size" : "20x20",
48 |       "scale" : "1x"
49 |     },
50 |     {
51 |       "idiom" : "ipad",
52 |       "size" : "20x20",
53 |       "scale" : "2x"
54 |     },
55 |     {
56 |       "idiom" : "ipad",
57 |       "size" : "29x29",
58 |       "scale" : "1x"
59 |     },
60 |     {
61 |       "idiom" : "ipad",
62 |       "size" : "29x29",
63 |       "scale" : "2x"
64 |     },
65 |     {
66 |       "idiom" : "ipad",
67 |       "size" : "40x40",
68 |       "scale" : "1x"
69 |     },
70 |     {
71 |       "idiom" : "ipad",
72 |       "size" : "40x40",
73 |       "scale" : "2x"
74 |     },
75 |     {
76 |       "size" : "76x76",
77 |       "idiom" : "ipad",
78 |       "filename" : "icon-76.png",
79 |       "scale" : "1x"
80 |     },
81 |     {
82 |       "size" : "76x76",
83 |       "idiom" : "ipad",
84 |       "filename" : "icon-76@2x.png",
85 |       "scale" : "2x"
86 |     },
87 |     {
88 |       "idiom" : "ipad",
89 |       "size" : "83.5x83.5",
90 |       "scale" : "2x"
91 |     }
92 |   ],
93 |   "info" : {
94 |     "version" : 1,
95 |     "author" : "xcode"
96 |   }
97 | }


--------------------------------------------------------------------------------
/HanekeDemo/Images.xcassets/AppIcon.appiconset/icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haneke/HanekeSwift/a2e8e5b9a91eef90138a4f43c9a0044c4e90a6ef/HanekeDemo/Images.xcassets/AppIcon.appiconset/icon-60@2x.png


--------------------------------------------------------------------------------
/HanekeDemo/Images.xcassets/AppIcon.appiconset/icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haneke/HanekeSwift/a2e8e5b9a91eef90138a4f43c9a0044c4e90a6ef/HanekeDemo/Images.xcassets/AppIcon.appiconset/icon-60@3x.png


--------------------------------------------------------------------------------
/HanekeDemo/Images.xcassets/AppIcon.appiconset/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haneke/HanekeSwift/a2e8e5b9a91eef90138a4f43c9a0044c4e90a6ef/HanekeDemo/Images.xcassets/AppIcon.appiconset/icon-76.png


--------------------------------------------------------------------------------
/HanekeDemo/Images.xcassets/AppIcon.appiconset/icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Haneke/HanekeSwift/a2e8e5b9a91eef90138a4f43c9a0044c4e90a6ef/HanekeDemo/Images.xcassets/AppIcon.appiconset/icon-76@2x.png


--------------------------------------------------------------------------------
/HanekeDemo/Info.plist:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 3 | <plist version="1.0">
 4 | <dict>
 5 | 	<key>CFBundleDevelopmentRegion</key>
 6 | 	<string>en</string>
 7 | 	<key>CFBundleExecutable</key>
 8 | 	<string>$(EXECUTABLE_NAME)</string>
 9 | 	<key>CFBundleIdentifier</key>
10 | 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11 | 	<key>CFBundleInfoDictionaryVersion</key>
12 | 	<string>6.0</string>
13 | 	<key>CFBundleName</key>
14 | 	<string>Haneke</string>
15 | 	<key>CFBundlePackageType</key>
16 | 	<string>APPL</string>
17 | 	<key>CFBundleShortVersionString</key>
18 | 	<string>1.1</string>
19 | 	<key>CFBundleSignature</key>
20 | 	<string>????</string>
21 | 	<key>CFBundleVersion</key>
22 | 	<string>1</string>
23 | 	<key>LSRequiresIPhoneOS</key>
24 | 	<true/>
25 | 	<key>NSAppTransportSecurity</key>
26 | 	<dict>
27 | 		<key>NSAllowsArbitraryLoads</key>
28 | 		<true/>
29 | 	</dict>
30 | 	<key>UILaunchStoryboardName</key>
31 | 	<string>LaunchScreen</string>
32 | 	<key>UIMainStoryboardFile</key>
33 | 	<string>Main</string>
34 | 	<key>UIRequiredDeviceCapabilities</key>
35 | 	<array>
36 | 		<string>armv7</string>
37 | 	</array>
38 | 	<key>UISupportedInterfaceOrientations</key>
39 | 	<array>
40 | 		<string>UIInterfaceOrientationPortrait</string>
41 | 		<string>UIInterfaceOrientationLandscapeLeft</string>
42 | 		<string>UIInterfaceOrientationLandscapeRight</string>
43 | 	</array>
44 | 	<key>UISupportedInterfaceOrientations~ipad</key>
45 | 	<array>
46 | 		<string>UIInterfaceOrientationPortrait</string>
47 | 		<string>UIInterfaceOrientationPortraitUpsideDown</string>
48 | 		<string>UIInterfaceOrientationLandscapeLeft</string>
49 | 		<string>UIInterfaceOrientationLandscapeRight</string>
50 | 	</array>
51 | </dict>
52 | </plist>
53 | 


--------------------------------------------------------------------------------
/HanekeDemo/ViewController.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  ViewController.swift
  3 | //  HanekeDemo
  4 | //
  5 | //  Created by Hermes Pique on 9/17/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import UIKit
 10 | 
 11 | let CellReuseIdentifier = "Cell"
 12 | 
 13 | class ViewController: UICollectionViewController {
 14 | 
 15 |     var items : [String] = []
 16 |     
 17 |     override func viewDidLoad() {
 18 |         super.viewDidLoad()
 19 |         self.collectionView!.register(CollectionViewCell.self, forCellWithReuseIdentifier: CellReuseIdentifier)
 20 |         let layout = UICollectionViewFlowLayout()
 21 |         layout.itemSize = CGSize(width: 100, height: 100)
 22 |         self.collectionView!.collectionViewLayout = layout
 23 |         
 24 |         self.initializeItemsWithURLs()
 25 |     }
 26 | 
 27 |     // MARK: UIViewCollectionViewDataSource
 28 |     
 29 |     override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
 30 |         return items.count
 31 |     }
 32 |     
 33 |     override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
 34 |         let CellIdentifier = "Cell"
 35 |         let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellIdentifier, for: indexPath) as! CollectionViewCell
 36 |         let URLString = self.items[(indexPath as NSIndexPath).row]
 37 |         let url = URL(string:URLString)!
 38 |         cell.imageView.hnk_setImageFromURL(url)
 39 |         return cell
 40 |     }
 41 |     
 42 |     // MARK: Helpers
 43 |     
 44 |     func initializeItemsWithURLs() {
 45 |         items = ["http://imgs.xkcd.com/comics/election.png",
 46 |             "http://imgs.xkcd.com/comics/scantron.png",
 47 |             "http://imgs.xkcd.com/comics/secretary_part_5.png",
 48 |             "http://imgs.xkcd.com/comics/secretary_part_4.png",
 49 |             "http://imgs.xkcd.com/comics/secretary_part_3.png",
 50 |             "http://imgs.xkcd.com/comics/secretary_part_2.png",
 51 |             "http://imgs.xkcd.com/comics/secretary_part_1.png",
 52 |             "http://imgs.xkcd.com/comics/actuarial.png",
 53 |             "http://imgs.xkcd.com/comics/scrabble.png",
 54 |             "http://imgs.xkcd.com/comics/twitter.png",
 55 |             "http://imgs.xkcd.com/comics/morning_routine.png",
 56 |             "http://imgs.xkcd.com/comics/going_west.png",
 57 |             "http://imgs.xkcd.com/comics/steal_this_comic.png",
 58 |             "http://imgs.xkcd.com/comics/numerical_sex_positions.png",
 59 |             "http://imgs.xkcd.com/comics/i_am_not_a_ninja.png",
 60 |             "http://imgs.xkcd.com/comics/depth.png",
 61 |             "http://imgs.xkcd.com/comics/flash_games.png",
 62 |             "http://imgs.xkcd.com/comics/fiction_rule_of_thumb.png",
 63 |             "http://imgs.xkcd.com/comics/height.png",
 64 |             "http://imgs.xkcd.com/comics/listen_to_yourself.png",
 65 |             "http://imgs.xkcd.com/comics/spore.png",
 66 |             "http://imgs.xkcd.com/comics/tones.png",
 67 |             "http://imgs.xkcd.com/comics/the_staple_madness.png",
 68 |             "http://imgs.xkcd.com/comics/typewriter.png",
 69 |             "http://imgs.xkcd.com/comics/one-sided.png",
 70 |             "http://imgs.xkcd.com/comics/further_boomerang_difficulties.png",
 71 |             "http://imgs.xkcd.com/comics/turn-on.png",
 72 |             "http://imgs.xkcd.com/comics/still_raw.png",
 73 |             "http://imgs.xkcd.com/comics/house_of_pancakes.png",
 74 |             "http://imgs.xkcd.com/comics/aversion_fads.png",
 75 |             "http://imgs.xkcd.com/comics/the_end_is_not_for_a_while.png",
 76 |             "http://imgs.xkcd.com/comics/improvised.png",
 77 |             "http://imgs.xkcd.com/comics/fetishes.png",
 78 |             "http://imgs.xkcd.com/comics/x_girls_y_cups.png",
 79 |             "http://imgs.xkcd.com/comics/moving.png",
 80 |             "http://imgs.xkcd.com/comics/quantum_teleportation.png",
 81 |             "http://imgs.xkcd.com/comics/rba.png",
 82 |             "http://imgs.xkcd.com/comics/voting_machines.png",
 83 |             "http://imgs.xkcd.com/comics/freemanic_paracusia.png",
 84 |             "http://imgs.xkcd.com/comics/google_maps.png",
 85 |             "http://imgs.xkcd.com/comics/paleontology.png",
 86 |             "http://imgs.xkcd.com/comics/holy_ghost.png",
 87 |             "http://imgs.xkcd.com/comics/regrets.png",
 88 |             "http://imgs.xkcd.com/comics/frustration.png",
 89 |             "http://imgs.xkcd.com/comics/cautionary.png",
 90 |             "http://imgs.xkcd.com/comics/hats.png",
 91 |             "http://imgs.xkcd.com/comics/rewiring.png",
 92 |             "http://imgs.xkcd.com/comics/upcoming_hurricanes.png",
 93 |             "http://imgs.xkcd.com/comics/mission.png",
 94 |             "http://imgs.xkcd.com/comics/impostor.png",
 95 |             "http://imgs.xkcd.com/comics/the_sea.png",
 96 |             "http://imgs.xkcd.com/comics/things_fall_apart.png",
 97 |             "http://imgs.xkcd.com/comics/good_morning.png",
 98 |             "http://imgs.xkcd.com/comics/too_old_for_this_shit.png",
 99 |             "http://imgs.xkcd.com/comics/in_popular_culture.png",
100 |             "http://imgs.xkcd.com/comics/i_am_not_good_with_boomerangs.png",
101 |             "http://imgs.xkcd.com/comics/macgyver_gets_lazy.png",
102 |             "http://imgs.xkcd.com/comics/know_your_vines.png",
103 |             "http://imgs.xkcd.com/comics/xkcd_loves_the_discovery_channel.png",
104 |             "http://imgs.xkcd.com/comics/babies.png",
105 |             "http://imgs.xkcd.com/comics/road_rage.png",
106 |             "http://imgs.xkcd.com/comics/thinking_ahead.png",
107 |             "http://imgs.xkcd.com/comics/internet_argument.png",
108 |             "http://imgs.xkcd.com/comics/suv.png",
109 |             "http://imgs.xkcd.com/comics/how_it_happened.png",
110 |             "http://imgs.xkcd.com/comics/purity.png",
111 |             "http://imgs.xkcd.com/comics/xkcd_goes_to_the_airport.png",
112 |             "http://imgs.xkcd.com/comics/journal_5.png",
113 |             "http://imgs.xkcd.com/comics/journal_4.png",
114 |             "http://imgs.xkcd.com/comics/delivery.png",
115 |             "http://imgs.xkcd.com/comics/every_damn_morning.png",
116 |             "http://imgs.xkcd.com/comics/fantasy.png",
117 |             "http://imgs.xkcd.com/comics/starwatching.png",
118 |             "http://imgs.xkcd.com/comics/bad_timing.png",
119 |             "http://imgs.xkcd.com/comics/geohashing.png",
120 |             "http://imgs.xkcd.com/comics/fortune_cookies.png",
121 |             "http://imgs.xkcd.com/comics/security_holes.png",
122 |             "http://imgs.xkcd.com/comics/finish_line.png",
123 |             "http://imgs.xkcd.com/comics/a_better_idea.png",
124 |             "http://imgs.xkcd.com/comics/making_hash_browns.png",
125 |             "http://imgs.xkcd.com/comics/jealousy.png",
126 |             "http://imgs.xkcd.com/comics/forks_and_spoons.png",
127 |             "http://imgs.xkcd.com/comics/stove_ownership.png",
128 |             "http://imgs.xkcd.com/comics/the_man_who_fell_sideways.png",
129 |             "http://imgs.xkcd.com/comics/zealous_autoconfig.png",
130 |             "http://imgs.xkcd.com/comics/restraining_order.png",
131 |             "http://imgs.xkcd.com/comics/mistranslations.png",
132 |             "http://imgs.xkcd.com/comics/new_pet.png",
133 |             "http://imgs.xkcd.com/comics/startled.png",
134 |             "http://imgs.xkcd.com/comics/techno.png",
135 |             "http://imgs.xkcd.com/comics/math_paper.png",
136 |             "http://imgs.xkcd.com/comics/electric_skateboard_double_comic.png",
137 |             "http://imgs.xkcd.com/comics/overqualified.png",
138 |             "http://imgs.xkcd.com/comics/cheap_gps.png",
139 |             "http://imgs.xkcd.com/comics/venting.png",
140 |             "http://imgs.xkcd.com/comics/journal_3.png",
141 |             "http://imgs.xkcd.com/comics/convincing_pickup_line.png",
142 |             "http://imgs.xkcd.com/comics/1000_miles_north.png",
143 |             "http://imgs.xkcd.com/comics/large_hadron_collider.png",
144 |             "http://imgs.xkcd.com/comics/important_life_lesson.png"]
145 |     }
146 | 
147 | }
148 | 
149 | 


--------------------------------------------------------------------------------
/HanekeSwift.podspec:
--------------------------------------------------------------------------------
 1 | Pod::Spec.new do |s|
 2 |   s.name = 'HanekeSwift'
 3 |   s.module_name = 'Haneke'
 4 |   s.version = '1.2'
 5 |   s.license = 'Apache'
 6 |   s.summary = 'A lightweight generic cache for iOS written in Swift with extra love for images.'
 7 |   s.homepage = 'https://github.com/Haneke/HanekeSwift'
 8 |   s.authors = { 'Hermes Pique' => 'https://twitter.com/hpique' }
 9 |   s.source = { :git => 'https://github.com/Haneke/HanekeSwift.git', :tag => "v#{s.version}" }
10 |   s.swift_version = '5.0'
11 |   s.tvos.deployment_target = '9.1'
12 |   s.ios.deployment_target = '8.0'
13 |   s.source_files = 'Haneke/*.swift'
14 | end
15 | 


--------------------------------------------------------------------------------
/HanekeTests/AsyncFetcher.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  AsyncFetcher.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 1/2/16.
 6 | //  Copyright © 2016 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import Foundation
10 | @testable import Haneke
11 | 
12 | class AsyncFetcher<T : DataConvertible> : Fetcher<T> {
13 | 
14 |     let getValue : () -> T.Result
15 | 
16 |     init(key: String, value getValue : @autoclosure @escaping () -> T.Result) {
17 |         self.getValue = getValue
18 |         super.init(key: key)
19 |     }
20 | 
21 |     override func fetch(failure fail: @escaping ((Error?) -> ()), success succeed: @escaping (T.Result) -> ()) {
22 |         let value = getValue()
23 |         DispatchQueue.global(qos: .default).async {
24 |             DispatchQueue.main.async {
25 |                 succeed(value)
26 |             }
27 |         }
28 |     }
29 | 
30 |     override func cancelFetch() {}
31 | 
32 | }
33 | 


--------------------------------------------------------------------------------
/HanekeTests/CGSize+HanekeTests.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  CGSize+HanekeTests.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Oriol Blanc Gimeno on 9/12/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import UIKit
10 | import XCTest
11 | @testable import Haneke
12 | 
13 | class CGSize_HanekeTests: XCTestCase {
14 |     
15 |     func testAspectFillSize() {
16 |         let image = UIImage.imageWithColor(UIColor.red, CGSize(width: 10, height: 1), false)
17 |         let sut: CGSize = image.size.hnk_aspectFillSize(CGSize(width: 10, height: 10))
18 |         
19 |         XCTAssertTrue(sut.height == 10)
20 |     }
21 |     
22 |     func testAspectFitSize() {
23 |         let image = UIImage.imageWithColor(UIColor.red, CGSize(width: 10, height: 1), false)
24 |         let sut: CGSize = image.size.hnk_aspectFitSize(CGSize(width: 20, height: 20))
25 |         
26 |         XCTAssertTrue(sut.height == 2)
27 |     }
28 | }
29 | 


--------------------------------------------------------------------------------
/HanekeTests/DataTests.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  DataTests.swift
  3 | //  Haneke
  4 | //
  5 | //  Created by Hermes Pique on 9/19/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import UIKit
 10 | import XCTest
 11 | @testable import Haneke
 12 | 
 13 | class ImageDataTests: XCTestCase {
 14 | 
 15 |     func testConvertFromData() {
 16 |         let image = UIImage.imageGradientFromColor()
 17 |         let data = image.hnk_data()
 18 | 
 19 |         let result = UIImage.convertFromData(data!)
 20 | 
 21 |         XCTAssertTrue(image.isEqualPixelByPixel(result!))
 22 |     }
 23 |     
 24 |     func testAsData() {
 25 |         let image = UIImage.imageGradientFromColor()
 26 |         let data = image.hnk_data()
 27 |         
 28 |         let result = image.asData()
 29 |         
 30 |         XCTAssertEqual(result, data)
 31 |     }
 32 |     
 33 | }
 34 | 
 35 | class StringDataTests: XCTestCase {
 36 |     
 37 |     func testConvertFromData() {
 38 |         let string = self.name
 39 |         let data = string.data(using: String.Encoding.utf8)!
 40 |         
 41 |         let result = String.convertFromData(data)
 42 |         
 43 |         XCTAssertEqual(result!, string)
 44 |     }
 45 |     
 46 |     func testAsData() {
 47 |         let string = self.name
 48 |         let data = string.data(using: String.Encoding.utf8)!
 49 |         
 50 |         let result = string.asData()
 51 |         
 52 |         XCTAssertEqual(result, data)
 53 |     }
 54 |     
 55 | }
 56 | 
 57 | class DataDataTests: XCTestCase {
 58 |     
 59 |     func testConvertFromData() {
 60 |         let data = Data.dataWithLength(32)
 61 |         
 62 |         let result = Data.convertFromData(data)
 63 |         
 64 |         XCTAssertEqual(result!, data)
 65 |     }
 66 |     
 67 |     func testAsData() {
 68 |         let data = Data.dataWithLength(32)
 69 |         
 70 |         let result = data.asData()
 71 |         
 72 |         XCTAssertEqual(result, data)
 73 |     }
 74 |     
 75 | }
 76 | 
 77 | class JSONDataTests: XCTestCase {
 78 |     
 79 |     func testConvertFromData_WithArrayData() {
 80 |         let json = [self.name]
 81 |         let data = try! JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions())
 82 |         
 83 |         let result = JSON.convertFromData(data)!
 84 |         
 85 |         switch result {
 86 |         case .Dictionary(_):
 87 |             XCTFail("expected array")
 88 |         case .Array(let object):
 89 |             let resultData = try! JSONSerialization.data(withJSONObject: object, options: JSONSerialization.WritingOptions())
 90 |             XCTAssertEqual(resultData, data)
 91 |         }
 92 |     }
 93 |     
 94 |     func testConvertFromData_WithDictionaryData() {
 95 |         let json = ["test": self.name]
 96 |         let data = try! JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions())
 97 |         
 98 |         let result = JSON.convertFromData(data)!
 99 |         
100 |         switch result {
101 |         case .Dictionary(let object):
102 |             try! JSONSerialization.data(withJSONObject: object, options: JSONSerialization.WritingOptions())
103 |         case .Array(_):
104 |             XCTFail("expected dictionary")
105 |         }
106 |     }
107 | 
108 |     func testConvertFromData_WithInvalidData() {
109 |         let data = Data.dataWithLength(100)
110 | 
111 |         let result = JSON.convertFromData(data)
112 |         
113 |         XCTAssertTrue(result == nil)
114 |     }
115 |     
116 |     func testAsData_Array() {
117 |         let object = [self.name]
118 |         let json = JSON.Array(object as [AnyObject])
119 |         
120 |         let result = json.asData()
121 |         
122 |         let data = try! JSONSerialization.data(withJSONObject: object, options: JSONSerialization.WritingOptions())
123 |         XCTAssertEqual(result, data)
124 |     }
125 |     
126 |     func testAsData_Dictionary() {
127 |         let object = ["test": self.name]
128 |         let json = JSON.Dictionary(object as [String : AnyObject])
129 |         
130 |         let result = json.asData()
131 |         
132 |         let data = try! JSONSerialization.data(withJSONObject: object, options: JSONSerialization.WritingOptions())
133 |         XCTAssertEqual(result, data)
134 |     }
135 |     
136 |     func testAsData_InvalidJSON() {
137 |         // TODO: Swift doesn't support XCAssertThrows yet.
138 |         // See: http://stackoverflow.com/questions/25529625/testing-assertion-in-swift
139 |         
140 |         // let object = ["test": UIImage.imageWithColor(UIColor.redColor())]
141 |         // let json = JSON.Dictionary(object)
142 |         // XCAssertThrows(json.asData())
143 |     }
144 |     
145 |     func testArray_Array() {
146 |         let object = [self.name]
147 |         let json = JSON.Array(object as [AnyObject])
148 |         
149 |         let result = json.array
150 |         
151 |         XCTAssertNotNil(result)
152 |     }
153 |     
154 |     func testArray_Dictionary() {
155 |         let object = ["test": self.name]
156 |         let json = JSON.Dictionary(object as [String : AnyObject])
157 |         
158 |         let result = json.array
159 |         
160 |         XCTAssertNil(result)
161 |     }
162 |     
163 |     func testDictionary_Array() {
164 |         let object = [self.name]
165 |         let json = JSON.Array(object as [AnyObject])
166 |         
167 |         let result = json.dictionary
168 |         
169 |         XCTAssertNil(result)
170 |     }
171 |     
172 |     func testDictionary_Dictionary() {
173 |         let object = ["test": self.name]
174 |         let json = JSON.Dictionary(object as [String : AnyObject])
175 |         
176 |         let result = json.dictionary
177 |         
178 |         XCTAssertNotNil(result)
179 |     }
180 |     
181 | }
182 | 


--------------------------------------------------------------------------------
/HanekeTests/DiskFetcherTests.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  DiskFetcherTests.swift
  3 | //  Haneke
  4 | //
  5 | //  Created by Joan Romano on 21/09/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import UIKit
 10 | import XCTest
 11 | @testable import Haneke
 12 | 
 13 | class DiskFetcherTests: DiskTestCase {
 14 |     
 15 |     var sut : DiskFetcher<UIImage>!
 16 |     var path: String!
 17 | 
 18 |     override func setUp() {
 19 |         super.setUp()
 20 |         path = self.uniquePath()
 21 |         sut = DiskFetcher(path: path)
 22 |     }
 23 |     
 24 |     func testInit() {
 25 |         XCTAssertEqual(sut.path, path)
 26 |     }
 27 |     
 28 |     func testKey() {
 29 |         XCTAssertEqual(sut.key, path)
 30 |     }
 31 |     
 32 |     func testFetchImage_Success() {
 33 |         let image = UIImage.imageWithColor(UIColor.green, CGSize(width: 10, height: 20))
 34 |         let data = image.pngData()!
 35 |         try? data.write(to: URL(fileURLWithPath: sut.path), options: [.atomic])
 36 |         
 37 |         let expectation = self.expectation(description: self.name)
 38 |         
 39 |         sut.fetch(failure: { _ in
 40 |             XCTFail("Expected to succeed")
 41 |             expectation.fulfill()
 42 |         }) {
 43 |             let result = $0 as UIImage
 44 |             XCTAssertTrue(result.isEqualPixelByPixel(image))
 45 |             expectation.fulfill()
 46 |         }
 47 |         
 48 |         self.waitForExpectations(timeout: 1, handler: nil)
 49 |     }
 50 |     
 51 |     func testFetchImage_Failure_NSFileReadNoSuchFileError() {
 52 |         let expectation = self.expectation(description: self.name)
 53 |         
 54 |         sut.fetch(failure: {
 55 |             guard let error = $0 as NSError? else {
 56 |                 XCTFail("expected non-nil error");
 57 |                 expectation.fulfill()
 58 |                 return
 59 |             }
 60 |             XCTAssertEqual(error.code, NSFileReadNoSuchFileError)
 61 |             XCTAssertNotNil(error.localizedDescription)
 62 |             expectation.fulfill()
 63 |         }) { _ in
 64 |             XCTFail("Expected to fail")
 65 |             expectation.fulfill()
 66 |         }
 67 |         
 68 |         self.waitForExpectations(timeout: 1, handler: nil)
 69 |     }
 70 |     
 71 |     func testFetchImage_Failure_HNKDiskEntityInvalidDataError() {
 72 |         let data = Data()
 73 |         try? data.write(to: URL(fileURLWithPath: sut.path), options: [.atomic])
 74 |         
 75 |         let expectation = self.expectation(description: self.name)
 76 |         
 77 |         sut.fetch(failure: {
 78 |             guard let error = $0 as NSError? else {
 79 |                 XCTFail("expected non-nil error");
 80 |                 expectation.fulfill()
 81 |                 return
 82 |             }
 83 |             XCTAssertEqual(error.domain, HanekeGlobals.Domain)
 84 |             XCTAssertEqual(error.code, HanekeGlobals.DiskFetcher.ErrorCode.invalidData.rawValue)
 85 |             XCTAssertNotNil(error.localizedDescription)
 86 |             expectation.fulfill()
 87 |         }) { _ in
 88 |             XCTFail("Expected to fail")
 89 |             expectation.fulfill()
 90 |         }
 91 |         
 92 |         self.waitForExpectations(timeout: 1, handler: nil)
 93 |     }
 94 | 
 95 |     func testCancelFetch() {
 96 |         let image = UIImage.imageWithColor(UIColor.green)
 97 |         let data = image.pngData()!
 98 |         try? data.write(to: URL(fileURLWithPath: directoryPath), options: [.atomic])
 99 |         sut.fetch(failure: { error in
100 |             guard let error = error as NSError? else {
101 |                 XCTFail("expected non-nil error");
102 |                 return
103 |             }
104 |             XCTFail("Unexpected failure with error \(error)")
105 |         }) { _ in
106 |             XCTFail("Unexpected success")
107 |         }
108 |         
109 |         sut.cancelFetch()
110 |         
111 |         self.waitFor(0.1)
112 |     }
113 |     
114 |     func testCancelFetch_NoFetch() {
115 |         sut.cancelFetch()
116 |     }
117 |     
118 |     // MARK: Cache extension
119 |     
120 |     func testCacheFetch_Success() {
121 |         let data = Data.dataWithLength(1)
122 |         let path = self.writeData(data)
123 |         let expectation = self.expectation(description: self.name)
124 |         let cache = Cache<Data>(name: self.name)
125 |         
126 |         _ = cache.fetch(path: path, failure: {_ in
127 |             XCTFail("expected success")
128 |             expectation.fulfill()
129 |         }) {
130 |             XCTAssertEqual($0, data)
131 |             expectation.fulfill()
132 |         }
133 |         
134 |         self.waitForExpectations(timeout: 1, handler: nil)
135 |         
136 |         cache.removeAll()
137 |     }
138 |     
139 |     func testCacheFetch_Failure() {
140 |         let path = (self.directoryPath as NSString).appendingPathComponent(self.name)
141 |         let expectation = self.expectation(description: self.name)
142 |         let cache = Cache<Data>(name: self.name)
143 |         
144 |         _ = cache.fetch(path: path, failure: {_ in
145 |             expectation.fulfill()
146 |         }) { _ in
147 |             XCTFail("expected success")
148 |             expectation.fulfill()
149 |         }
150 |         
151 |         self.waitForExpectations(timeout: 1, handler: nil)
152 |         
153 |         cache.removeAll()
154 |     }
155 |     
156 |     func testCacheFetch_WithFormat() {
157 |         let data = Data.dataWithLength(1)
158 |         let path = self.writeData(data)
159 |         let expectation = self.expectation(description: self.name)
160 |         let cache = Cache<Data>(name: self.name)
161 |         let format = Format<Data>(name: self.name)
162 |         cache.addFormat(format)
163 |         
164 |         _ = cache.fetch(path: path, formatName: format.name, failure: {_ in
165 |             XCTFail("expected success")
166 |             expectation.fulfill()
167 |         }) {
168 |             XCTAssertEqual($0, data)
169 |             expectation.fulfill()
170 |         }
171 |         
172 |         self.waitForExpectations(timeout: 1, handler: nil)
173 |         
174 |         cache.removeAll()
175 |     }
176 | }
177 | 


--------------------------------------------------------------------------------
/HanekeTests/DiskTestCase.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  DiskTestCase.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 8/26/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import XCTest
10 | 
11 | class DiskTestCase : XCTestCase {
12 |  
13 |     lazy var directoryPath: String = {
14 |         let documentsPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]
15 |         let directoryPath = (documentsPath as NSString).appendingPathComponent(self.name)
16 |         return directoryPath
17 |     }()
18 |     
19 |     override func setUp() {
20 |         super.setUp()
21 |         try! FileManager.default.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil)
22 |     }
23 |     
24 |     override func tearDown() {
25 |         try! FileManager.default.removeItem(atPath: directoryPath)
26 |         super.tearDown()
27 |     }
28 |     
29 |     var dataIndex = 0
30 |     
31 |     func writeDataWithLength(_ length : Int) -> String {
32 |         let data = Data.dataWithLength(length)
33 |         return self.writeData(data)
34 |     }
35 |     
36 |     func writeData(_ data : Data) -> String {
37 |         let path = self.uniquePath()
38 |         try? data.write(to: URL(fileURLWithPath: path), options: [.atomic])
39 |         return path
40 |     }
41 |     
42 |     func uniquePath() -> String {
43 |         let path = (self.directoryPath as NSString).appendingPathComponent("\(dataIndex)")
44 |         dataIndex += 1
45 |         return path
46 |     }
47 |     
48 | }
49 | 


--------------------------------------------------------------------------------
/HanekeTests/FetchTests.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  FetchTests.swift
  3 | //  Haneke
  4 | //
  5 | //  Created by Hermes Pique on 9/28/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import Foundation
 10 | import XCTest
 11 | @testable import Haneke
 12 | 
 13 | class FetchTests : XCTestCase {
 14 |     
 15 |     var sut : Fetch<String>!
 16 |     
 17 |     override func setUp() {
 18 |         super.setUp()
 19 |         sut = Fetch<String>()
 20 |     }
 21 | 
 22 |     func testHasSucceded_True() {
 23 |         sut.succeed(self.name)
 24 |         
 25 |         XCTAssertTrue(sut.hasSucceeded)
 26 |     }
 27 |     
 28 |     func testHasSucceded_False() {
 29 |         XCTAssertFalse(sut.hasSucceeded)
 30 |     }
 31 |     
 32 |     func testHasSucceded_AfterFail_False() {
 33 |         sut.fail()
 34 |         
 35 |         XCTAssertFalse(sut.hasSucceeded)
 36 |     }
 37 |     
 38 |     func testHasFailed_True() {
 39 |         sut.fail()
 40 |         
 41 |         XCTAssertTrue(sut.hasFailed)
 42 |     }
 43 |     
 44 |     func testHasFailed_False() {
 45 |         XCTAssertFalse(sut.hasFailed)
 46 |     }
 47 |     
 48 |     func testHasSucceded_AfterSucceed_False() {
 49 |         sut.succeed(self.name)
 50 |         
 51 |         XCTAssertFalse(sut.hasFailed)
 52 |     }
 53 |     
 54 |     func testSucceed() {
 55 |         sut.succeed(self.name)
 56 |     }
 57 | 
 58 |     func testSucceed_AfterOnSuccess() {
 59 |         let value = self.name
 60 |         let expectation = self.expectation(description: value)
 61 |         sut.onSuccess {
 62 |             XCTAssertEqual($0, value)
 63 |             expectation.fulfill()
 64 |         }
 65 |         
 66 |         sut.succeed(value)
 67 |         
 68 |         self.waitForExpectations(timeout: 0, handler: nil)
 69 |     }
 70 |     
 71 |     func testFail() {
 72 |         sut.fail()
 73 |     }
 74 |     
 75 |     func testFail_AfterOnFailure() {
 76 |         let error = NSError(domain: self.name, code: 10, userInfo: nil)
 77 |         let expectation = self.expectation(description: self.name)
 78 |         sut.onFailure {
 79 |             XCTAssertEqual($0!.localizedDescription, error.localizedDescription)
 80 |             expectation.fulfill()
 81 |         }
 82 |         
 83 |         sut.fail(error)
 84 |         
 85 |         self.waitForExpectations(timeout: 0, handler: nil)
 86 |     }
 87 |     
 88 |     func testOnSuccess() {
 89 |         sut.onSuccess { _ in
 90 |             XCTFail("unexpected success")
 91 |         }
 92 |     }
 93 |     
 94 |     func testOnSuccess_AfterSucceed() {
 95 |         let value = self.name
 96 |         sut.succeed(value)
 97 |         let expectation = self.expectation(description: value)
 98 |         
 99 |         sut.onSuccess {
100 |             XCTAssertEqual($0, value)
101 |             expectation.fulfill()
102 |         }
103 |         
104 |         self.waitForExpectations(timeout: 0, handler: nil)
105 |     }
106 |     
107 |     func testOnFailure() {
108 |         sut.onFailure { _ in
109 |             XCTFail("unexpected failure")
110 |         }
111 |     }
112 |     
113 |     func testOnFailure_AfterFail() {
114 |         let error = NSError(domain: self.name, code: 10, userInfo: nil)
115 |         sut.fail(error)
116 |         let expectation = self.expectation(description: self.name)
117 |         
118 |         sut.onFailure {
119 |             XCTAssertEqual($0!.localizedDescription, error.localizedDescription)
120 |             expectation.fulfill()
121 |         }
122 |         
123 |         self.waitForExpectations(timeout: 0, handler: nil)
124 |     }
125 |     
126 | }
127 | 


--------------------------------------------------------------------------------
/HanekeTests/FetcherTests.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  FetcherTests.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 9/10/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import UIKit
10 | import XCTest
11 | @testable import Haneke
12 | 
13 | class FetcherTests: XCTestCase {
14 |     
15 |     func testSimpleFetcherInit() {
16 |         let key = self.name
17 |         let image = UIImage.imageWithColor(UIColor.green)
18 |         
19 |         let fetcher = SimpleFetcher<UIImage>(key: key, value: image)
20 | 
21 |         XCTAssertEqual(fetcher.key, key)
22 |         XCTAssertEqual(fetcher.getValue(), image)
23 |     }
24 |     
25 |     func testSimpleFetcherFetch() {
26 |         let key = self.name
27 |         let image = UIImage.imageWithColor(UIColor.green)
28 |         let fetcher = SimpleFetcher<UIImage>(key: key, value: image)
29 |         let expectation = self.expectation(description: key)
30 |         
31 |         fetcher.fetch(failure: { _ in
32 |             XCTFail("expected success")
33 |         }) {
34 |             XCTAssertEqual($0, image)
35 |             expectation.fulfill()
36 |         }
37 |         
38 |         self.waitForExpectations(timeout: 0, handler: nil)
39 |     }
40 |     
41 |     func testCacheFetch() {
42 |         let data = Data.dataWithLength(1)
43 |         let expectation = self.expectation(description: self.name)
44 |         let cache = Cache<Data>(name: self.name)
45 |         
46 |         _ = cache.fetch(key: self.name, value: data) {
47 |             XCTAssertEqual($0, data)
48 |             expectation.fulfill()
49 |         }
50 |         
51 |         self.waitForExpectations(timeout: 1, handler: nil)
52 |         
53 |         cache.removeAll()
54 |     }
55 |     
56 |     func testCacheFetch_WithFormat() {
57 |         let data = Data.dataWithLength(1)
58 |         let expectation = self.expectation(description: self.name)
59 |         let cache = Cache<Data>(name: self.name)
60 |         let format = Format<Data>(name: self.name)
61 |         cache.addFormat(format)
62 |         
63 |         _ = cache.fetch(key: self.name, value: data, formatName: format.name) {
64 |             XCTAssertEqual($0, data)
65 |             expectation.fulfill()
66 |         }
67 |         
68 |         self.waitForExpectations(timeout: 1, handler: nil)
69 |         
70 |         cache.removeAll()
71 |     }
72 |     
73 | }
74 | 


--------------------------------------------------------------------------------
/HanekeTests/FormatTests.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  FormatTests.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 8/27/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import UIKit
10 | import XCTest
11 | @testable import Haneke
12 | 
13 | class FormatTests: XCTestCase {
14 | 
15 |     func testDefaultInit() {
16 |         let name = self.name
17 |         let sut = Format<UIImage>(name: name)
18 |         
19 |         XCTAssertEqual(sut.name, name)
20 |         XCTAssertEqual(sut.diskCapacity, UINT64_MAX)
21 |         XCTAssertTrue(sut.transform == nil)
22 |     }
23 |     
24 |     func testIsIdentity_WithoutTransform_ExpectTrue() {
25 |         let sut = Format<UIImage>(name: self.name)
26 |         
27 |         XCTAssertTrue(sut.isIdentity)
28 |     }
29 |     
30 |     func testIsIdentity_WithTransform_ExpectFalse() {
31 |         let sut = Format<UIImage>(name: self.name, transform: { return $0 })
32 |         
33 |         XCTAssertFalse(sut.isIdentity)
34 |     }
35 |     
36 |     func testResizeImageScaleNone() {
37 |         
38 |         let originalImage = UIImage.imageWithColor(UIColor.red, CGSize(width: 1, height: 1), false)
39 |         let sut = ImageResizer(size: CGSize(width: 30, height: 5), scaleMode: .None)
40 |         let resizedImage = sut.resizeImage(originalImage)
41 |         
42 |         XCTAssertEqual(originalImage.size.width, resizedImage.size.width)
43 |         XCTAssertEqual(originalImage.size.height, resizedImage.size.height)
44 |         XCTAssertNotEqual(Float(resizedImage.size.width), Float(30))
45 |         XCTAssertNotEqual(Float(resizedImage.size.height), Float(5))
46 |     }
47 |     
48 |     func testResizeImageScaleFill() {
49 |         
50 |         let originalImage = UIImage.imageWithColor(UIColor.red, CGSize(width: 1, height: 1), false)
51 |         let sut = ImageResizer(size: CGSize(width: 30, height: 5), scaleMode : .Fill)
52 |         let resizedImage = sut.resizeImage(originalImage)
53 |         
54 |         XCTAssertNotEqual(originalImage.size.width, resizedImage.size.width)
55 |         XCTAssertNotEqual(originalImage.size.height, resizedImage.size.height)
56 |         XCTAssertEqual(Float(resizedImage.size.width), Float(30))
57 |         XCTAssertEqual(Float(resizedImage.size.height), Float(5))
58 |     }
59 |     
60 |     func testResizeImageScaleAspectFill() {
61 |         
62 |         let originalImage = UIImage.imageWithColor(UIColor.red, CGSize(width: 1, height: 1),  false)
63 |         let sut = ImageResizer(size: CGSize(width: 30, height: 5), scaleMode: .AspectFill)
64 |         let resizedImage = sut.resizeImage(originalImage)
65 |         
66 |         XCTAssertNotEqual(originalImage.size.width, resizedImage.size.width)
67 |         XCTAssertNotEqual(originalImage.size.height, resizedImage.size.height)
68 |         XCTAssertEqual(Float(resizedImage.size.width), Float(30))
69 |         XCTAssertEqual(Float(resizedImage.size.height), Float(30))
70 |     }
71 |     
72 |     func testResizeImageScaleAspectFit() {
73 |         
74 |         let originalImage = UIImage.imageWithColor(UIColor.red, CGSize(width: 1, height: 1), false)
75 |         let sut = ImageResizer(size: CGSize(width: 30, height: 5), scaleMode: .AspectFit)
76 |         let resizedImage = sut.resizeImage(originalImage)
77 |         
78 |         XCTAssertNotEqual(originalImage.size.width, resizedImage.size.width)
79 |         XCTAssertNotEqual(originalImage.size.height, resizedImage.size.height)
80 |         XCTAssertEqual(Float(resizedImage.size.width), Float(5))
81 |         XCTAssertEqual(Float(resizedImage.size.height), Float(5))
82 |     }
83 |     
84 |     func testResizeImageScaleAspectFillWithoutUpscaling() {
85 |         
86 |         let originalImage = UIImage.imageWithColor(UIColor.red, CGSize(width: 1, height: 1), false)
87 |         let sut = ImageResizer(size: CGSize(width: 30, height: 5), scaleMode: .AspectFill, allowUpscaling: false)
88 |         let resizedImage = sut.resizeImage(originalImage)
89 |         
90 |         XCTAssertEqual(originalImage.size.width, resizedImage.size.width)
91 |         XCTAssertEqual(originalImage.size.height, resizedImage.size.height)
92 |         XCTAssertEqual(Float(resizedImage.size.width), Float(1))
93 |         XCTAssertEqual(Float(resizedImage.size.height), Float(1))
94 |     }
95 | }
96 | 
97 | 


--------------------------------------------------------------------------------
/HanekeTests/HanekeTests-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | //  Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 | 
5 | #import <OHHTTPStubs/OHHTTPStubs.h>
6 | 


--------------------------------------------------------------------------------
/HanekeTests/HanekeTests.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  HanekeTests.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 9/9/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import XCTest
10 | @testable import Haneke
11 | 
12 | class HanekeTests: XCTestCase {
13 | 
14 |     func testErrorWithCode() {
15 |         let code = 200
16 |         let description = self.name
17 |         let error = errorWithCode(code, description: description)
18 |         
19 |         XCTAssertEqual(error._domain, HanekeGlobals.Domain)
20 |         XCTAssertEqual(error._code, code)
21 |         XCTAssertEqual(error.localizedDescription, description)
22 |     }
23 |     
24 |     func testSharedImageCache() {
25 |         XCTAssertNoThrow(Shared.imageCache)
26 |     }
27 |     
28 |     func testSharedDataCache() {
29 |         XCTAssertNoThrow(_ = Shared.dataCache)
30 |     }
31 |     
32 |     func testSharedStringCache() {
33 |         XCTAssertNoThrow(_ = Shared.stringCache)
34 |     }
35 |     
36 |     func testSharedJSONCache() {
37 |         XCTAssertNoThrow(_ = Shared.JSONCache)
38 |     }
39 |     
40 | }
41 | 


--------------------------------------------------------------------------------
/HanekeTests/Info.plist:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 3 | <plist version="1.0">
 4 | <dict>
 5 | 	<key>CFBundleDevelopmentRegion</key>
 6 | 	<string>en</string>
 7 | 	<key>CFBundleExecutable</key>
 8 | 	<string>${EXECUTABLE_NAME}</string>
 9 | 	<key>CFBundleIdentifier</key>
10 | 	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11 | 	<key>CFBundleInfoDictionaryVersion</key>
12 | 	<string>6.0</string>
13 | 	<key>CFBundleName</key>
14 | 	<string>${PRODUCT_NAME}</string>
15 | 	<key>CFBundlePackageType</key>
16 | 	<string>BNDL</string>
17 | 	<key>CFBundleShortVersionString</key>
18 | 	<string>1.0</string>
19 | 	<key>CFBundleSignature</key>
20 | 	<string>????</string>
21 | 	<key>CFBundleVersion</key>
22 | 	<string>1</string>
23 | </dict>
24 | </plist>
25 | 


--------------------------------------------------------------------------------
/HanekeTests/NSData+Test.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  NSData.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 8/23/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import Foundation
10 | 
11 | extension Data {
12 |     
13 |     static func dataWithLength(_ length : Int) -> Data {
14 |         let buffer: [UInt8] = [UInt8](repeating: 0, count: length)
15 | //        return Data(bytes: UnsafePointer<UInt8>(&buffer), count: length)
16 |         let pointer = UnsafeRawPointer(buffer)
17 | 
18 |         return NSData(bytes: pointer, length: length) as Data
19 |     }
20 |     
21 | }
22 | 


--------------------------------------------------------------------------------
/HanekeTests/NSFileManager+HanekeTests.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  NSFileManager+HanekeTests.swift
  3 | //  Haneke
  4 | //
  5 | //  Created by Hermes Pique on 8/26/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import XCTest
 10 | @testable import Haneke
 11 | 
 12 | class NSFileManager_HanekeTests: DiskTestCase {
 13 |     
 14 |     func testEnumerateContentsOfDirectoryAtPathEmpty() {
 15 |         let sut = FileManager.default
 16 | 
 17 |         sut.enumerateContentsOfDirectory(atPath: self.directoryPath, orderedByProperty: URLResourceKey.nameKey.rawValue, ascending: true, usingBlock: { (URL : Foundation.URL, index : Int, _) -> Void in
 18 |             XCTFail()
 19 |             })
 20 |     }
 21 |     
 22 |     func testEnumerateContentsOfDirectoryAtPathStop() {
 23 |         let sut = FileManager.default
 24 |         _ = [self.writeDataWithLength(1), self.writeDataWithLength(2)]
 25 |         var count = 0
 26 |         
 27 |         sut.enumerateContentsOfDirectory(atPath: self.directoryPath, orderedByProperty: URLResourceKey.nameKey.rawValue, ascending: true) { (_ : URL, index : Int, stop : inout Bool) -> Void in
 28 |             count += 1
 29 |             stop = true
 30 |         }
 31 |         
 32 |         XCTAssertEqual(count, 1)
 33 |     }
 34 |     
 35 |     func testEnumerateContentsOfDirectoryAtPathNameAscending() {
 36 |         let sut = FileManager.default
 37 |     
 38 |         let paths = [self.writeDataWithLength(1), self.writeDataWithLength(2)].sorted(by: <)
 39 |         var resultPaths : [String] = []
 40 |         var indexes : [Int] = []
 41 |         
 42 |         sut.enumerateContentsOfDirectory(atPath: self.directoryPath, orderedByProperty: URLResourceKey.nameKey.rawValue, ascending: true) { (URL : Foundation.URL, index : Int, _) -> Void in
 43 |             resultPaths.append(URL.path)
 44 |             indexes.append(index)
 45 |         }
 46 |         
 47 |         XCTAssertEqual(resultPaths.count, 2)
 48 |         XCTAssertEqual(resultPaths, paths)
 49 |         XCTAssertEqual(indexes[0], 0)
 50 |         XCTAssertEqual(indexes[1], 1)
 51 |     }
 52 |     
 53 |     func testEnumerateContentsOfDirectoryAtPathNameDescending() {
 54 |         let sut = FileManager.default
 55 |         
 56 |         let paths = [self.writeDataWithLength(1), self.writeDataWithLength(2)].sorted(by: >)
 57 |         var resultPaths : [String] = []
 58 |         var indexes : [Int] = []
 59 |         
 60 |         sut.enumerateContentsOfDirectory(atPath: self.directoryPath, orderedByProperty: URLResourceKey.nameKey.rawValue, ascending: false) { (URL : Foundation.URL, index : Int, _) -> Void in
 61 |             resultPaths.append(URL.path)
 62 |             indexes.append(index)
 63 |         }
 64 |         
 65 |         XCTAssertEqual(resultPaths.count, 2)
 66 |         XCTAssertEqual(resultPaths, paths)
 67 |         XCTAssertEqual(indexes[0], 0)
 68 |         XCTAssertEqual(indexes[1], 1)
 69 |     }
 70 |     
 71 |     func testEnumerateContentsOfDirectoryAtPathFileSizeAscending() {
 72 |         let sut = FileManager.default
 73 |         
 74 |         let paths = [self.writeDataWithLength(1), self.writeDataWithLength(2)]
 75 |         var resultPaths : [String] = []
 76 |         
 77 |         sut.enumerateContentsOfDirectory(atPath: self.directoryPath, orderedByProperty: URLResourceKey.fileSizeKey.rawValue, ascending: true) { (URL : Foundation.URL, index : Int, _) -> Void in
 78 |             resultPaths.append(URL.path)
 79 |         }
 80 |         
 81 |         XCTAssertEqual(resultPaths.count, 2)
 82 |         XCTAssertEqual(resultPaths, paths)
 83 |     }
 84 |     
 85 |     func testEnumerateContentsOfDirectoryAtPathFileSizeDescending() {
 86 |         let sut = FileManager.default
 87 |         
 88 |         let paths : [String] = [self.writeDataWithLength(1), self.writeDataWithLength(2)].reversed()
 89 |         var resultPaths : [String] = []
 90 |         
 91 |         sut.enumerateContentsOfDirectory(atPath: self.directoryPath, orderedByProperty: URLResourceKey.fileSizeKey.rawValue, ascending: false) { (URL : Foundation.URL, index : Int, _) -> Void in
 92 |             resultPaths.append(URL.path)
 93 |         }
 94 |         
 95 |         XCTAssertEqual(resultPaths.count, 2)
 96 |         XCTAssertEqual(resultPaths, paths)
 97 |     }
 98 |     
 99 |     func testEnumerateContentsOfDirectoryAtPathModificationDateAscending() {
100 |         let sut = FileManager.default
101 |         
102 |         let paths = [self.writeDataWithLength(1), self.writeDataWithLength(2)]
103 |         try! sut.setAttributes([FileAttributeKey.modificationDate : Date.distantPast], ofItemAtPath: paths[0])
104 |         
105 |         var resultPaths : [String] = []
106 |         
107 |         sut.enumerateContentsOfDirectory(atPath: self.directoryPath, orderedByProperty: URLResourceKey.contentModificationDateKey.rawValue, ascending: true) { (URL : Foundation.URL, index : Int, _) -> Void in
108 |             resultPaths.append(URL.path)
109 |         }
110 |         
111 |         XCTAssertEqual(resultPaths.count, 2)
112 |         XCTAssertEqual(resultPaths, paths)
113 |     }
114 |     
115 |     func testEnumerateContentsOfDirectoryAtPathModificationDateDescending() {
116 |         let sut = FileManager.default
117 |         
118 |         let paths = [self.writeDataWithLength(1), self.writeDataWithLength(2)]
119 |         try! sut.setAttributes([FileAttributeKey.modificationDate : Date.distantPast], ofItemAtPath: paths[1])
120 |         var resultPaths : [String] = []
121 |         
122 |         sut.enumerateContentsOfDirectory(atPath: self.directoryPath, orderedByProperty: URLResourceKey.contentModificationDateKey.rawValue, ascending: false) { (URL : Foundation.URL, index : Int, _) -> Void in
123 |             resultPaths.append(URL.path)
124 |         }
125 |         
126 |         XCTAssertEqual(resultPaths.count, 2)
127 |         XCTAssertEqual(resultPaths, paths)
128 |     }
129 |     
130 | }
131 | 
132 | 


--------------------------------------------------------------------------------
/HanekeTests/NSHTTPURLResponse+HanekeTests.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  NSHTTPURLResponse+HanekeTests.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 1/2/16.
 6 | //  Copyright © 2016 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import XCTest
10 | @testable import Haneke
11 | 
12 | func responseWithStatusCode(_ statusCode : Int) -> HTTPURLResponse {
13 |     return HTTPURLResponse(url: URL(string: "http://haneke.io")!, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: nil)!
14 | }
15 | 
16 | class NSHTTPURLResponse_HanekeTests: XCTestCase {
17 | 
18 |     func testIsValidStatusCode() {
19 |         XCTAssertTrue(responseWithStatusCode(200).hnk_isValidStatusCode())
20 |         XCTAssertTrue(responseWithStatusCode(201).hnk_isValidStatusCode())
21 |         XCTAssertFalse(responseWithStatusCode(404).hnk_isValidStatusCode())
22 |     }
23 | 
24 | }
25 | 


--------------------------------------------------------------------------------
/HanekeTests/NSURLResponse+HanekeTests.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  NSURLResponse+HanekeTests.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 9/15/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import XCTest
10 | @testable import Haneke
11 | 
12 | class NSURLResponse_HanekeTests: XCTestCase {
13 | 
14 |     let httpURL = URL(string: "http://haneke.io")!
15 |     let fileURL = URL(string: "file:///image.png")!
16 |     
17 |     func testValidateLengthOfData_NSHTTPURLResponse_Unknown() {
18 |         let response = HTTPURLResponse(url: httpURL, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: nil)!
19 |         let data = Data.dataWithLength(132)
20 |         XCTAssertTrue(response.hnk_validateLength(ofData: data))
21 |     }
22 |     
23 |     func testValidateLengthOfData_NSHTTPURLResponse_Expected() {
24 |         let length = 73
25 |         let response = HTTPURLResponse(url: httpURL, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: ["Content-Length": String(length)])!
26 |         let data = Data.dataWithLength(length)
27 |         XCTAssertTrue(response.hnk_validateLength(ofData: data))
28 |     }
29 |     
30 |     func testValidateLengthOfData_NSHTTPURLResponse_LessThanExpected() {
31 |         let length = 73
32 |         let response = HTTPURLResponse(url: httpURL, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: ["Content-Length": String(length)])!
33 |         let data = Data.dataWithLength(length - 10)
34 |         XCTAssertFalse(response.hnk_validateLength(ofData: data))
35 |     }
36 |     
37 |     func testValidateLengthOfData_NSHTTPURLResponse_MoreThanExpected() {
38 |         let length = 73
39 |         let response = HTTPURLResponse(url: httpURL, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: ["Content-Length": String(length)])!
40 |         let data = Data.dataWithLength(length + 10)
41 |         XCTAssertTrue(response.hnk_validateLength(ofData: data))
42 |     }
43 |     
44 |     func testValidateLengthOfData_NSURLResponse_Unknown() {
45 |         let response = URLResponse(url: fileURL, mimeType: "image/png", expectedContentLength: -1, textEncodingName: nil)
46 |         let data = Data.dataWithLength(73)
47 |         XCTAssertTrue(response.hnk_validateLength(ofData: data))
48 |     }
49 |     
50 |     func testValidateLengthOfData_NSURLResponse_Expected() {
51 |         let length = 73
52 |         let response = URLResponse(url: fileURL, mimeType: "image/png", expectedContentLength: length, textEncodingName: nil)
53 |         let data = Data.dataWithLength(length)
54 |         XCTAssertTrue(response.hnk_validateLength(ofData: data))
55 |     }
56 |     
57 |     func testValidateLengthOfData_NSURLResponse_LessThanExpected() {
58 |         let length = 73
59 |         let response = URLResponse(url: fileURL, mimeType: "image/png", expectedContentLength: length, textEncodingName: nil)
60 |         let data = Data.dataWithLength(length - 10)
61 |         XCTAssertFalse(response.hnk_validateLength(ofData: data))
62 |     }
63 |     
64 |     func testValidateLengthOfData_NSURLResponse_MoreThanExpected() {
65 |         let length = 73
66 |         let response = URLResponse(url: fileURL, mimeType: "image/png", expectedContentLength: length, textEncodingName: nil)
67 |         let data = Data.dataWithLength(length + 10)
68 |         XCTAssertTrue(response.hnk_validateLength(ofData: data))
69 |     }
70 | }
71 | 


--------------------------------------------------------------------------------
/HanekeTests/NetworkFetcherTests.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  NetworkFetcherTests.swift
  3 | //  Haneke
  4 | //
  5 | //  Created by Hermes Pique on 9/15/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import UIKit
 10 | import XCTest
 11 | import OHHTTPStubs
 12 | @testable import Haneke
 13 | 
 14 | class NetworkFetcherTests: XCTestCase {
 15 | 
 16 |     let URL = Foundation.URL(string: "http://haneke.io/image.jpg")!
 17 |     var sut : NetworkFetcher<UIImage>!
 18 |     
 19 |     override func setUp() {
 20 |         super.setUp()
 21 |         sut = NetworkFetcher(URL: URL)
 22 |     }
 23 |     
 24 |     override func tearDown() {
 25 |         OHHTTPStubs.removeAllStubs()
 26 |         super.tearDown()
 27 |     }
 28 |     
 29 |     func testInit() {
 30 |         XCTAssertEqual(sut.URL, URL)
 31 |     }
 32 | 
 33 |     func testKey() {
 34 |         XCTAssertEqual(sut.key, URL.absoluteString)
 35 |     }
 36 |     
 37 |     func testFetchImage_Success() {
 38 |         let image = UIImage.imageWithColor(UIColor.green)
 39 |         OHHTTPStubs.stubRequests(passingTest: { _ in
 40 |             return true
 41 |         }, withStubResponse: { _ in
 42 |             let data = image.pngData()
 43 |             return OHHTTPStubsResponse(data: data!, statusCode: 200, headers:nil)
 44 |         })
 45 |         let expectation = self.expectation(description: self.name)
 46 |         
 47 |         sut.fetch(failure: { _ in
 48 |             XCTFail("expected success")
 49 |             expectation.fulfill()
 50 |         }) {
 51 |             let result = $0 as UIImage
 52 |             XCTAssertTrue(result.isEqualPixelByPixel(image))
 53 |             expectation.fulfill()
 54 |         }
 55 |         
 56 |         self.waitForExpectations(timeout: 1, handler: nil)
 57 |     }
 58 |     
 59 |     func testFetchImage_Success_StatusCode200() {
 60 |         self.testFetchImageSuccessWithStatusCode(200)
 61 |     }
 62 | 
 63 |     func testFetchImage_Success_StatusCode201() {
 64 |         self.testFetchImageSuccessWithStatusCode(201)
 65 |     }
 66 | 
 67 |     func testFetchImage_Failure_InvalidStatusCode_401() {
 68 |         self.testFetchImageFailureWithInvalidStatusCode(401)
 69 |     }
 70 |     
 71 |     func testFetchImage_Failure_InvalidStatusCode_402() {
 72 |         self.testFetchImageFailureWithInvalidStatusCode(402)
 73 |     }
 74 |     
 75 |     func testFetchImage_Failure_InvalidStatusCode_403() {
 76 |         self.testFetchImageFailureWithInvalidStatusCode(403)
 77 |     }
 78 |     
 79 |     func testFetchImage_Failure_InvalidStatusCode_404() {
 80 |         self.testFetchImageFailureWithInvalidStatusCode(404)
 81 |     }
 82 | 
 83 |     func testFetchImage_Failure_InvalidData() {
 84 |         OHHTTPStubs.stubRequests(passingTest: { _ in
 85 |             return true
 86 |         }, withStubResponse: { _ in
 87 |             let data = Data()
 88 |             return OHHTTPStubsResponse(data: data, statusCode: 200, headers:nil)
 89 |         })
 90 |         let expectation = self.expectation(description: self.name)
 91 |         
 92 |         sut.fetch(failure: {
 93 |             XCTAssertEqual($0!._domain, HanekeGlobals.Domain)
 94 |             XCTAssertEqual($0!._code, HanekeGlobals.NetworkFetcher.ErrorCode.invalidData.rawValue)
 95 |             XCTAssertNotNil($0!.localizedDescription)
 96 |             expectation.fulfill()
 97 |         }) { _ in
 98 |             XCTFail("expected failure")
 99 |             expectation.fulfill()
100 |         }
101 |         
102 |         self.waitForExpectations(timeout: 100000, handler: nil)
103 |     }
104 |     
105 |     func testFetchImage_Failure_MissingData() {
106 |         OHHTTPStubs.stubRequests(passingTest: { _ in
107 |             return true
108 |         }, withStubResponse: { _ in
109 |             let data = Data.dataWithLength(100)
110 |             return OHHTTPStubsResponse(data: data, statusCode: 200, headers:["Content-Length":String(data.count * 2)])
111 |         })
112 |         let expectation = self.expectation(description: self.name)
113 |         
114 |         sut.fetch(failure: {
115 |             XCTAssertEqual($0!._domain, HanekeGlobals.Domain)
116 |             XCTAssertEqual($0!._code, HanekeGlobals.NetworkFetcher.ErrorCode.missingData.rawValue)
117 |             XCTAssertNotNil($0!.localizedDescription)
118 |             expectation.fulfill()
119 |         }) { _ in
120 |             XCTFail("expected failure")
121 |             expectation.fulfill()
122 |         }
123 |         
124 |         self.waitForExpectations(timeout: 1, handler: nil)
125 |     }
126 |     
127 |     func testCancelFetch() {
128 |         let image = UIImage.imageWithColor(UIColor.green)
129 |         OHHTTPStubs.stubRequests(passingTest: { _ in
130 |             return true
131 |         }, withStubResponse: { _ in
132 |             let data = image.pngData()
133 |             return OHHTTPStubsResponse(data: data!, statusCode: 200, headers:nil)
134 |         })
135 |         sut.fetch(failure: {_ in
136 |             XCTFail("unexpected failure")
137 |         }) { _ in
138 |             XCTFail("unexpected success")
139 |         }
140 |         
141 |         sut.cancelFetch()
142 |         
143 |         self.waitFor(0.1)
144 |     }
145 |     
146 |     func testCancelFetch_NoFetch() {
147 |         sut.cancelFetch()
148 |     }
149 |     
150 |     func testSession() {
151 |         XCTAssertEqual(sut.session, URLSession.shared)
152 |     }
153 |     
154 |     // MARK: Private
155 | 
156 |     fileprivate func testFetchImageSuccessWithStatusCode(_ statusCode : Int32) {
157 |         let image = UIImage.imageWithColor(UIColor.green)
158 |         OHHTTPStubs.stubRequests(passingTest: { _ in
159 |             return true
160 |             }, withStubResponse: { _ in
161 |                 let data = image.pngData()
162 |                 return OHHTTPStubsResponse(data: data!, statusCode: statusCode, headers:nil)
163 |         })
164 |         let expectation = self.expectation(description: self.name)
165 |         sut.cancelFetch()
166 | 
167 |         sut.fetch(failure: { _ in
168 |             XCTFail("expected success")
169 |             expectation.fulfill()
170 |             }) {
171 |                 let result = $0 as UIImage
172 |                 XCTAssertTrue(result.isEqualPixelByPixel(image))
173 |                 expectation.fulfill()
174 |         }
175 | 
176 |         self.waitForExpectations(timeout: 1, handler: nil)
177 |     }
178 | 
179 |     fileprivate func testFetchImageFailureWithInvalidStatusCode(_ statusCode : Int32) {
180 |         OHHTTPStubs.stubRequests(passingTest: { _ in
181 |             return true
182 |         }, withStubResponse: { _ in
183 |             let data = Data.dataWithLength(100)
184 |             return OHHTTPStubsResponse(data: data, statusCode: statusCode, headers:nil)
185 |         })
186 |         let expectation = self.expectation(description: self.name)
187 |         
188 |         sut.fetch(failure: {
189 |             XCTAssertEqual($0!._domain, HanekeGlobals.Domain)
190 |             XCTAssertEqual($0!._code, HanekeGlobals.NetworkFetcher.ErrorCode.invalidStatusCode.rawValue)
191 |             XCTAssertNotNil($0!.localizedDescription)
192 |             expectation.fulfill()
193 |         }) { _ in
194 |             XCTFail("expected failure")
195 |             expectation.fulfill()
196 |         }
197 |         
198 |         self.waitForExpectations(timeout: 1, handler: nil)        
199 |     }
200 |     
201 |     // MARK: Cache extension
202 |     
203 |     func testCacheFetch_Success() {
204 |         let data = Data.dataWithLength(1)
205 |         OHHTTPStubs.stubRequests(passingTest: { _ in
206 |             return true
207 |             }, withStubResponse: { _ in
208 |                 return OHHTTPStubsResponse(data: data, statusCode: 200, headers:nil)
209 |         })
210 |         let expectation = self.expectation(description: self.name)
211 |         let cache = Cache<Data>(name: self.name)
212 | 
213 |         _ = cache.fetch(URL: URL, failure: {_ in
214 |             XCTFail("expected success")
215 |             expectation.fulfill()
216 |         }) {
217 |             XCTAssertEqual($0, data)
218 |             expectation.fulfill()
219 |         }
220 |         
221 |         self.waitForExpectations(timeout: 1, handler: nil)
222 |         
223 |         cache.removeAll()
224 |     }
225 |     
226 |     func testCacheFetch_Failure() {
227 |         let data = Data.dataWithLength(1)
228 |         OHHTTPStubs.stubRequests(passingTest: { _ in
229 |             return true
230 |             }, withStubResponse: { _ in
231 |                 return OHHTTPStubsResponse(data: data, statusCode: 404, headers:nil)
232 |         })
233 |         let expectation = self.expectation(description: self.name)
234 |         let cache = Cache<Data>(name: self.name)
235 |         
236 |         _ = cache.fetch(URL: URL, failure: {_ in
237 |             expectation.fulfill()
238 |         }) { _ in
239 |             XCTFail("expected success")
240 |             expectation.fulfill()
241 |         }
242 |         
243 |         self.waitForExpectations(timeout: 1, handler: nil)
244 |         
245 |         cache.removeAll()
246 |     }
247 |     
248 |     func testCacheFetch_WithFormat() {
249 |         let data = Data.dataWithLength(1)
250 |         OHHTTPStubs.stubRequests(passingTest: { _ in
251 |             return true
252 |             }, withStubResponse: { _ in
253 |                 return OHHTTPStubsResponse(data: data, statusCode: 404, headers:nil)
254 |         })
255 |         let expectation = self.expectation(description: self.name)
256 |         let cache = Cache<Data>(name: self.name)
257 |         let format = Format<Data>(name: self.name)
258 |         cache.addFormat(format)
259 | 
260 |         _ = cache.fetch(URL: URL, formatName: format.name, failure: {_ in
261 |             expectation.fulfill()
262 |         }) { _ in
263 |             XCTFail("expected success")
264 |             expectation.fulfill()
265 |         }
266 |         
267 |         self.waitForExpectations(timeout: 1, handler: nil)
268 |         
269 |         cache.removeAll()
270 |     }
271 |     
272 | }
273 | 


--------------------------------------------------------------------------------
/HanekeTests/String+HanekeTests.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  String+HanekeTests.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 8/30/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import XCTest
10 | @testable import Haneke
11 | 
12 | class String_HanekeTests: XCTestCase {
13 | 
14 |     func testEscapedFilename() {
15 |         XCTAssertEqual("".escapedFilename(), "")
16 |         XCTAssertEqual(":".escapedFilename(), "%3A")
17 |         XCTAssertEqual("/".escapedFilename(), "%2F")
18 |         XCTAssertEqual(" ".escapedFilename(), " ")
19 |         XCTAssertEqual("\\".escapedFilename(), "\\")
20 |         XCTAssertEqual("test".escapedFilename(), "test")
21 |         XCTAssertEqual("http://haneke.io".escapedFilename(), "http%3A%2F%2Fhaneke.io")
22 |         XCTAssertEqual("/path/to/file".escapedFilename(), "%2Fpath%2Fto%2Ffile")
23 |     }
24 |     
25 |     func testMD5String() {
26 |         XCTAssertEqual("".MD5String(), "d41d8cd98f00b204e9800998ecf8427e")
27 |         XCTAssertEqual("Haneke".MD5String(), "aaf750bf2c41f921d0f5c1e9ba36f6f4")
28 |         XCTAssertEqual("http://haneke.io".MD5String(), "e7bbf4e61be4fe99e3dd95f99b666aa0")
29 |         XCTAssertEqual("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam pretium id nibh a pulvinar. Integer id ex in tellus egestas placerat. Praesent ultricies libero ligula, et convallis ligula imperdiet eu. Sed gravida, turpis sed vulputate feugiat, metus nisl scelerisque diam, ac aliquet metus nisi rutrum ipsum. Nulla vulputate pretium dolor, a pellentesque nulla. Nunc pellentesque tortor porttitor, sollicitudin leo in, sollicitudin ligula. Cras malesuada orci at neque interdum elementum. Integer sed sagittis diam. Mauris non elit sed augue consequat feugiat. Nullam volutpat tortor eget tempus pretium. Sed pharetra sem vitae diam hendrerit, sit amet dapibus arcu interdum. Fusce egestas quam libero, ut efficitur turpis placerat eu. Sed velit sapien, aliquam sit amet ultricies a, bibendum ac nibh. Maecenas imperdiet, quam quis tincidunt sollicitudin, nunc tellus ornare ipsum, nec rhoncus nunc nisi a lacus.".MD5String(),
30 |             "36acb564fdf3c31c222c3069ba1d66d1")
31 | 
32 |     }
33 |     
34 |     func testMD5Filename() {
35 |         XCTAssertEqual("".MD5Filename(), "".MD5String())
36 |         XCTAssertEqual("test".MD5Filename(), "test".MD5String())
37 |         XCTAssertEqual("test.png".MD5Filename(), ("test.png".MD5String() as NSString).appendingPathExtension("png"))
38 |     }
39 | 
40 |     func testMD5Filename_QueryString() {
41 |         let sut = "test.png?width=100&height=200"
42 |         XCTAssertEqual(sut.MD5Filename(), (sut.MD5String() as NSString).appendingPathExtension("png"))
43 |     }
44 | 
45 | }
46 | 


--------------------------------------------------------------------------------
/HanekeTests/UIImage+HanekeTests.swift:
--------------------------------------------------------------------------------
  1 | //
  2 | //  UIImage+HanekeTests.swift
  3 | //  Haneke
  4 | //
  5 | //  Created by Hermes Pique on 8/10/14.
  6 | //  Copyright (c) 2014 Haneke. All rights reserved.
  7 | //
  8 | 
  9 | import UIKit
 10 | import XCTest
 11 | import ImageIO
 12 | import MobileCoreServices
 13 | @testable import Haneke
 14 | 
 15 | enum ExifOrientation : UInt32 {
 16 |     case up = 1
 17 |     case down = 3
 18 |     case left = 8
 19 |     case right = 6
 20 |     case upMirrored = 2
 21 |     case downMirrored = 4
 22 |     case leftMirrored = 5
 23 |     case rightMirrored = 7
 24 | }
 25 | 
 26 | class UIImage_HanekeTests: XCTestCase {
 27 | 
 28 |     func testHasAlphaTrue() {
 29 |         let image = UIImage.imageWithColor(UIColor.red, CGSize(width: 1, height: 1), false)
 30 |         XCTAssertTrue(image.hnk_hasAlpha())
 31 |     }
 32 |     
 33 |     func testHasAlphaFalse() {
 34 |         let image = UIImage.imageWithColor(UIColor.red, CGSize(width: 1, height: 1), true)
 35 |         XCTAssertFalse(image.hnk_hasAlpha())
 36 |     }
 37 |     
 38 |     func testDataPNG() {
 39 |         let image = UIImage.imageWithColor(UIColor.red, CGSize(width: 1, height: 1), false)
 40 |         let expectedData = image.pngData()
 41 |         
 42 |         let data = image.hnk_data()
 43 |         
 44 |         XCTAssertEqual(data!, expectedData)
 45 |     }
 46 |     
 47 |     func testDataJPEG() {
 48 |         let image = UIImage.imageWithColor(UIColor.red, CGSize(width: 1, height: 1), true)
 49 |         let expectedData = image.jpegData(compressionQuality: 1)
 50 |         
 51 |         let data = image.hnk_data()
 52 |         
 53 |         XCTAssertEqual(data!, expectedData)
 54 |     }
 55 |     
 56 |     func testDataNil() {
 57 |         let image = UIImage()
 58 | 
 59 |         XCTAssertNil(image.hnk_data())
 60 |     }
 61 |     
 62 |     func testDecompressedImage_UIGraphicsContext_Opaque() {
 63 |         let image = UIImage.imageWithColor(UIColor.red, CGSize(width: 10, height: 10))
 64 | 
 65 |         let decompressedImage = image.hnk_decompressedImage()
 66 |         
 67 |         XCTAssertNotEqual(image, decompressedImage)
 68 |         XCTAssertTrue((decompressedImage?.isEqualPixelByPixel(image))!)
 69 |     }
 70 |     
 71 |     func testDecompressedImage_UIGraphicsContext_NotOpaque() {
 72 |         let image = UIImage.imageWithColor(UIColor.red, CGSize(width: 10, height: 10), false)
 73 |         
 74 |         let decompressedImage = image.hnk_decompressedImage()
 75 |         
 76 |         XCTAssertNotEqual(image, decompressedImage)
 77 |         XCTAssertTrue((decompressedImage?.isEqualPixelByPixel(image))!)
 78 |     }
 79 | 
 80 |     func testDecompressedImage_RGBA() {
 81 |         let color = UIColor(red:255, green:0, blue:0, alpha:0.5)
 82 |         self._testDecompressedImageUsingColor(color, alphaInfo: .premultipliedLast)
 83 |     }
 84 |     
 85 |     func testDecompressedImage_ARGB() {
 86 |         let color = UIColor(red:255, green:0, blue:0, alpha:0.5)
 87 |         self._testDecompressedImageUsingColor(color, alphaInfo: .premultipliedFirst)
 88 |     }
 89 |     
 90 |     func testDecompressedImage_RGBX() {
 91 |         self._testDecompressedImageUsingColor(alphaInfo: .noneSkipLast)
 92 |     }
 93 |     
 94 |     func testDecompressedImage_XRGB() {
 95 |         self._testDecompressedImageUsingColor(alphaInfo: .noneSkipFirst)
 96 |     }
 97 |     
 98 |     func testDecompressedImage_Gray_AlphaNone() {
 99 |         let color = UIColor.gray
100 |         let colorSpaceRef = CGColorSpaceCreateDeviceGray()
101 |         self._testDecompressedImageUsingColor(color, colorSpace: colorSpaceRef, alphaInfo: .none)
102 |     }
103 |     
104 |     func testDecompressedImage_OrientationUp() {
105 |         self._testDecompressedImageWithOrientation(.up)
106 |     }
107 |     
108 |     func testDecompressedImage_OrientationDown() {
109 |         self._testDecompressedImageWithOrientation(.down)
110 |     }
111 |     
112 |     func testDecompressedImage_OrientationLeft() {
113 |         self._testDecompressedImageWithOrientation(.left)
114 |     }
115 |     
116 |     func testDecompressedImage_OrientationRight() {
117 |         self._testDecompressedImageWithOrientation(.right)
118 |     }
119 |     
120 |     func testDecompressedImage_OrientationUpMirrored() {
121 |         self._testDecompressedImageWithOrientation(.upMirrored)
122 |     }
123 |     
124 |     func testDecompressedImage_OrientationDownMirrored() {
125 |         self._testDecompressedImageWithOrientation(.downMirrored)
126 |     }
127 |     
128 |     func testDecompressedImage_OrientationLeftMirrored() {
129 |         self._testDecompressedImageWithOrientation(.leftMirrored)
130 |     }
131 |     
132 |     func testDecompressedImage_OrientationRightMirrored() {
133 |         self._testDecompressedImageWithOrientation(.rightMirrored)
134 |     }
135 |     
136 |     func testDataCompressionQuality() {
137 |         let image = UIImage.imageWithColor(UIColor.red, CGSize(width: 10, height: 10))
138 |         let data = image.hnk_data()
139 |         let notExpectedData = image.hnk_data(compressionQuality: 0.5)
140 |         
141 |         XCTAssertNotEqual(data, notExpectedData)
142 |     }
143 |     
144 |     func testDataCompressionQuality_LessThan0() {
145 |         let image = UIImage.imageWithColor(UIColor.red, CGSize(width: 10, height: 10))
146 |         let data = image.hnk_data(compressionQuality: -1.0)
147 |         let expectedData = image.hnk_data(compressionQuality: 0.0)
148 |         
149 |         XCTAssertEqual(data, expectedData, "The min compression quality is 0.0")
150 |     }
151 |     
152 |     func testDataCompressionQuality_MoreThan1() {
153 |         let image = UIImage.imageWithColor(UIColor.red, CGSize(width: 10, height: 10))
154 |         let data = image.hnk_data(compressionQuality: 10.0)
155 |         let expectedData = image.hnk_data(compressionQuality: 1.0)
156 |         
157 |         XCTAssertEqual(data, expectedData, "The min compression quality is 1.0")
158 |     }
159 |     
160 |     // MARK: Helpers
161 |     
162 |     func _testDecompressedImageUsingColor(_ color : UIColor = UIColor.green, colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB(), alphaInfo :CGImageAlphaInfo, bitsPerComponent : size_t = 8) {
163 |         let size = CGSize(width: 10, height: 20) // Using rectangle to check if image is rotated
164 |         let bitmapInfo = CGBitmapInfo().rawValue | alphaInfo.rawValue
165 |         let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo)
166 |     
167 |         context?.setFillColor(color.cgColor)
168 |         context?.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
169 |         let imageRef = context?.makeImage()!
170 |     
171 |         let image = UIImage(cgImage: imageRef!, scale:UIScreen.main.scale, orientation:.up)
172 |         let decompressedImage = image.hnk_decompressedImage()
173 |     
174 |         XCTAssertNotEqual(image, decompressedImage)
175 |         XCTAssertTrue((decompressedImage?.isEqualPixelByPixel(image))!, self.name)
176 |     }
177 |     
178 |     func _testDecompressedImageWithOrientation(_ orientation : ExifOrientation) {
179 |         // Create a gradient image to truly test orientation
180 |         let gradientImage = UIImage.imageGradientFromColor()
181 |         
182 |         // Use TIFF because PNG doesn't store EXIF orientation
183 |         let exifProperties = NSDictionary(dictionary: [kCGImagePropertyOrientation: Int(orientation.rawValue)])
184 |         let data = NSMutableData()
185 |         let imageDestinationRef = CGImageDestinationCreateWithData(data as CFMutableData, kUTTypeTIFF, 1, nil)!
186 |         CGImageDestinationAddImage(imageDestinationRef, gradientImage.cgImage!, exifProperties as CFDictionary)
187 |         CGImageDestinationFinalize(imageDestinationRef)
188 |         
189 |         let image = UIImage(data:data as Data, scale:UIScreen.main.scale)!
190 |         
191 |         let decompressedImage = image.hnk_decompressedImage()
192 |         
193 |         XCTAssertNotEqual(image, decompressedImage)
194 |         XCTAssertTrue((decompressedImage?.isEqualPixelByPixel(image))!, self.name)
195 |     }
196 |     
197 | }
198 | 


--------------------------------------------------------------------------------
/HanekeTests/UIImage+Test.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  UIImage+Test.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Oriol Blanc Gimeno on 01/08/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import UIKit
10 | 
11 | extension UIImage {
12 |     
13 |     func isEqualPixelByPixel(_ theOtherImage: UIImage) -> Bool {
14 |         let imageData = self.normalizedData()
15 |         let theOtherImageData = theOtherImage.normalizedData()
16 |         return (imageData == theOtherImageData)
17 |     }
18 |     
19 |     func normalizedData() -> Data {
20 |         let pixelSize = CGSize(width : self.size.width * self.scale, height : self.size.height * self.scale)
21 |         NSLog(NSCoder.string(for: pixelSize))
22 |         UIGraphicsBeginImageContext(pixelSize)
23 |         self.draw(in: CGRect(x: 0, y: 0, width: pixelSize.width, height: pixelSize.height))
24 |         let drawnImage = UIGraphicsGetImageFromCurrentImageContext()
25 |         UIGraphicsEndImageContext()
26 |         let provider = drawnImage?.cgImage?.dataProvider
27 |         let data = provider?.data
28 |         return data! as Data
29 |     }
30 |     
31 |     class func imageWithColor(_ color: UIColor, _ size: CGSize = CGSize(width: 1, height: 1), _ opaque: Bool = true) -> UIImage {
32 |         UIGraphicsBeginImageContextWithOptions(size, opaque, 0)
33 |         let context = UIGraphicsGetCurrentContext()
34 |         context?.setFillColor(color.cgColor)
35 |         context?.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
36 |         let image = UIGraphicsGetImageFromCurrentImageContext()
37 |         UIGraphicsEndImageContext()
38 |         return image!
39 |     }
40 |     
41 |     class func imageGradientFromColor(_ fromColor : UIColor = UIColor.red, toColor : UIColor = UIColor.green, size : CGSize = CGSize(width: 10, height: 20)) -> UIImage {
42 |         UIGraphicsBeginImageContextWithOptions(size, false /* opaque */, 0 /* scale */)
43 |         let context = UIGraphicsGetCurrentContext()
44 |         let colorspace = CGColorSpaceCreateDeviceRGB()
45 |         let gradientNumberOfLocations : size_t = 2
46 |         let gradientLocations : [CGFloat] = [ 0.0, 1.0 ]
47 |         var r1 : CGFloat = 0, g1 : CGFloat = 0, b1 : CGFloat = 0, a1 : CGFloat = 0
48 |         fromColor.getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
49 |         var r2 : CGFloat = 0, g2 : CGFloat = 0 , b2 : CGFloat = 0, a2 : CGFloat = 0
50 |         toColor.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)
51 |         let gradientComponents = [r1, g1, b1, a1, r2, g2, b2, a2]
52 |         let gradient = CGGradient (colorSpace: colorspace, colorComponents: gradientComponents, locations: gradientLocations, count: gradientNumberOfLocations)
53 |         context?.drawLinearGradient(gradient!, start: CGPoint(x: 0, y: 0), end: CGPoint(x: 0, y: size.height), options: CGGradientDrawingOptions())
54 |         let image = UIGraphicsGetImageFromCurrentImageContext()
55 |         UIGraphicsEndImageContext()
56 |         return image!
57 |     }
58 |     
59 | }
60 | 
61 | 


--------------------------------------------------------------------------------
/HanekeTests/XCTestCase+Test.swift:
--------------------------------------------------------------------------------
 1 | //
 2 | //  XCTestCase+Test.swift
 3 | //  Haneke
 4 | //
 5 | //  Created by Hermes Pique on 9/15/14.
 6 | //  Copyright (c) 2014 Haneke. All rights reserved.
 7 | //
 8 | 
 9 | import XCTest
10 | 
11 | extension XCTestCase {
12 |     
13 |     func waitFor(_ interval : TimeInterval) {
14 |         let date = Date(timeIntervalSinceNow: interval)
15 |         RunLoop.current.run(mode: RunLoop.Mode.default, before: date)
16 |     }
17 | 
18 |     func wait(_ timeout : TimeInterval, condition: () -> Bool) {
19 |         let timeoutDate = Date(timeIntervalSinceNow: timeout)
20 |         var success = false
21 |         while !success && (NSDate().laterDate(timeoutDate) == timeoutDate) {
22 |             success = condition()
23 |             if !success {
24 |                 RunLoop.current.run(mode: RunLoop.Mode.default, before: timeoutDate)
25 |             }
26 |         }
27 |         if !success {
28 |             XCTFail("Wait timed out.")
29 |         }
30 |     }
31 | 
32 | }
33 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
  1 | Apache License
  2 |                            Version 2.0, January 2004
  3 |                         http://www.apache.org/licenses/
  4 | 
  5 |    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
  6 | 
  7 |    1. Definitions.
  8 | 
  9 |       "License" shall mean the terms and conditions for use, reproduction,
 10 |       and distribution as defined by Sections 1 through 9 of this document.
 11 | 
 12 |       "Licensor" shall mean the copyright owner or entity authorized by
 13 |       the copyright owner that is granting the License.
 14 | 
 15 |       "Legal Entity" shall mean the union of the acting entity and all
 16 |       other entities that control, are controlled by, or are under common
 17 |       control with that entity. For the purposes of this definition,
 18 |       "control" means (i) the power, direct or indirect, to cause the
 19 |       direction or management of such entity, whether by contract or
 20 |       otherwise, or (ii) ownership of fifty percent (50%) or more of the
 21 |       outstanding shares, or (iii) beneficial ownership of such entity.
 22 | 
 23 |       "You" (or "Your") shall mean an individual or Legal Entity
 24 |       exercising permissions granted by this License.
 25 | 
 26 |       "Source" form shall mean the preferred form for making modifications,
 27 |       including but not limited to software source code, documentation
 28 |       source, and configuration files.
 29 | 
 30 |       "Object" form shall mean any form resulting from mechanical
 31 |       transformation or translation of a Source form, including but
 32 |       not limited to compiled object code, generated documentation,
 33 |       and conversions to other media types.
 34 | 
 35 |       "Work" shall mean the work of authorship, whether in Source or
 36 |       Object form, made available under the License, as indicated by a
 37 |       copyright notice that is included in or attached to the work
 38 |       (an example is provided in the Appendix below).
 39 | 
 40 |       "Derivative Works" shall mean any work, whether in Source or Object
 41 |       form, that is based on (or derived from) the Work and for which the
 42 |       editorial revisions, annotations, elaborations, or other modifications
 43 |       represent, as a whole, an original work of authorship. For the purposes
 44 |       of this License, Derivative Works shall not include works that remain
 45 |       separable from, or merely link (or bind by name) to the interfaces of,
 46 |       the Work and Derivative Works thereof.
 47 | 
 48 |       "Contribution" shall mean any work of authorship, including
 49 |       the original version of the Work and any modifications or additions
 50 |       to that Work or Derivative Works thereof, that is intentionally
 51 |       submitted to Licensor for inclusion in the Work by the copyright owner
 52 |       or by an individual or Legal Entity authorized to submit on behalf of
 53 |       the copyright owner. For the purposes of this definition, "submitted"
 54 |       means any form of electronic, verbal, or written communication sent
 55 |       to the Licensor or its representatives, including but not limited to
 56 |       communication on electronic mailing lists, source code control systems,
 57 |       and issue tracking systems that are managed by, or on behalf of, the
 58 |       Licensor for the purpose of discussing and improving the Work, but
 59 |       excluding communication that is conspicuously marked or otherwise
 60 |       designated in writing by the copyright owner as "Not a Contribution."
 61 | 
 62 |       "Contributor" shall mean Licensor and any individual or Legal Entity
 63 |       on behalf of whom a Contribution has been received by Licensor and
 64 |       subsequently incorporated within the Work.
 65 | 
 66 |    2. Grant of Copyright License. Subject to the terms and conditions of
 67 |       this License, each Contributor hereby grants to You a perpetual,
 68 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 69 |       copyright license to reproduce, prepare Derivative Works of,
 70 |       publicly display, publicly perform, sublicense, and distribute the
 71 |       Work and such Derivative Works in Source or Object form.
 72 | 
 73 |    3. Grant of Patent License. Subject to the terms and conditions of
 74 |       this License, each Contributor hereby grants to You a perpetual,
 75 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 76 |       (except as stated in this section) patent license to make, have made,
 77 |       use, offer to sell, sell, import, and otherwise transfer the Work,
 78 |       where such license applies only to those patent claims licensable
 79 |       by such Contributor that are necessarily infringed by their
 80 |       Contribution(s) alone or by combination of their Contribution(s)
 81 |       with the Work to which such Contribution(s) was submitted. If You
 82 |       institute patent litigation against any entity (including a
 83 |       cross-claim or counterclaim in a lawsuit) alleging that the Work
 84 |       or a Contribution incorporated within the Work constitutes direct
 85 |       or contributory patent infringement, then any patent licenses
 86 |       granted to You under this License for that Work shall terminate
 87 |       as of the date such litigation is filed.
 88 | 
 89 |    4. Redistribution. You may reproduce and distribute copies of the
 90 |       Work or Derivative Works thereof in any medium, with or without
 91 |       modifications, and in Source or Object form, provided that You
 92 |       meet the following conditions:
 93 | 
 94 |       (a) You must give any other recipients of the Work or
 95 |           Derivative Works a copy of this License; and
 96 | 
 97 |       (b) You must cause any modified files to carry prominent notices
 98 |           stating that You changed the files; and
 99 | 
100 |       (c) You must retain, in the Source form of any Derivative Works
101 |           that You distribute, all copyright, patent, trademark, and
102 |           attribution notices from the Source form of the Work,
103 |           excluding those notices that do not pertain to any part of
104 |           the Derivative Works; and
105 | 
106 |       (d) If the Work includes a "NOTICE" text file as part of its
107 |           distribution, then any Derivative Works that You distribute must
108 |           include a readable copy of the attribution notices contained
109 |           within such NOTICE file, excluding those notices that do not
110 |           pertain to any part of the Derivative Works, in at least one
111 |           of the following places: within a NOTICE text file distributed
112 |           as part of the Derivative Works; within the Source form or
113 |           documentation, if provided along with the Derivative Works; or,
114 |           within a display generated by the Derivative Works, if and
115 |           wherever such third-party notices normally appear. The contents
116 |           of the NOTICE file are for informational purposes only and
117 |           do not modify the License. You may add Your own attribution
118 |           notices within Derivative Works that You distribute, alongside
119 |           or as an addendum to the NOTICE text from the Work, provided
120 |           that such additional attribution notices cannot be construed
121 |           as modifying the License.
122 | 
123 |       You may add Your own copyright statement to Your modifications and
124 |       may provide additional or different license terms and conditions
125 |       for use, reproduction, or distribution of Your modifications, or
126 |       for any such Derivative Works as a whole, provided Your use,
127 |       reproduction, and distribution of the Work otherwise complies with
128 |       the conditions stated in this License.
129 | 
130 |    5. Submission of Contributions. Unless You explicitly state otherwise,
131 |       any Contribution intentionally submitted for inclusion in the Work
132 |       by You to the Licensor shall be under the terms and conditions of
133 |       this License, without any additional terms or conditions.
134 |       Notwithstanding the above, nothing herein shall supersede or modify
135 |       the terms of any separate license agreement you may have executed
136 |       with Licensor regarding such Contributions.
137 | 
138 |    6. Trademarks. This License does not grant permission to use the trade
139 |       names, trademarks, service marks, or product names of the Licensor,
140 |       except as required for reasonable and customary use in describing the
141 |       origin of the Work and reproducing the content of the NOTICE file.
142 | 
143 |    7. Disclaimer of Warranty. Unless required by applicable law or
144 |       agreed to in writing, Licensor provides the Work (and each
145 |       Contributor provides its Contributions) on an "AS IS" BASIS,
146 |       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 |       implied, including, without limitation, any warranties or conditions
148 |       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 |       PARTICULAR PURPOSE. You are solely responsible for determining the
150 |       appropriateness of using or redistributing the Work and assume any
151 |       risks associated with Your exercise of permissions under this License.
152 | 
153 |    8. Limitation of Liability. In no event and under no legal theory,
154 |       whether in tort (including negligence), contract, or otherwise,
155 |       unless required by applicable law (such as deliberate and grossly
156 |       negligent acts) or agreed to in writing, shall any Contributor be
157 |       liable to You for damages, including any direct, indirect, special,
158 |       incidental, or consequential damages of any character arising as a
159 |       result of this License or out of the use or inability to use the
160 |       Work (including but not limited to damages for loss of goodwill,
161 |       work stoppage, computer failure or malfunction, or any and all
162 |       other commercial damages or losses), even if such Contributor
163 |       has been advised of the possibility of such damages.
164 | 
165 |    9. Accepting Warranty or Additional Liability. While redistributing
166 |       the Work or Derivative Works thereof, You may choose to offer,
167 |       and charge a fee for, acceptance of support, warranty, indemnity,
168 |       or other liability obligations and/or rights consistent with this
169 |       License. However, in accepting such obligations, You may act only
170 |       on Your own behalf and on Your sole responsibility, not on behalf
171 |       of any other Contributor, and only if You agree to indemnify,
172 |       defend, and hold each Contributor harmless for any liability
173 |       incurred by, or claims asserted against, such Contributor by reason
174 |       of your accepting any such warranty or additional liability.
175 | 
176 |    END OF TERMS AND CONDITIONS
177 | 
178 |    APPENDIX: How to apply the Apache License to your work.
179 | 
180 |       To apply the Apache License to your work, attach the following
181 |       boilerplate notice, with the fields enclosed by brackets "{}"
182 |       replaced with your own identifying information. (Don't include
183 |       the brackets!)  The text should be enclosed in the appropriate
184 |       comment syntax for the file format. We also recommend that a
185 |       file or class name and description of purpose be included on the
186 |       same "printed page" as the copyright notice for easier
187 |       identification within third-party archives.
188 | 
189 |    Copyright {yyyy} {name of copyright owner}
190 | 
191 |    Licensed under the Apache License, Version 2.0 (the "License");
192 |    you may not use this file except in compliance with the License.
193 |    You may obtain a copy of the License at
194 | 
195 |        http://www.apache.org/licenses/LICENSE-2.0
196 | 
197 |    Unless required by applicable law or agreed to in writing, software
198 |    distributed under the License is distributed on an "AS IS" BASIS,
199 |    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 |    See the License for the specific language governing permissions and
201 |    limitations under the License.


--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
 1 | // swift-tools-version:5.0
 2 | import PackageDescription
 3 | 
 4 | let package = Package(
 5 |     name: "Haneke",
 6 |     platforms: [.iOS("8.0"), .tvOS("9.1")],
 7 |     products: [.library(name: "Haneke", targets: ["Haneke"])],
 8 |     targets: [.target(name: "Haneke", path: "Haneke")]
 9 | )
10 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | ![Haneke](https://raw.githubusercontent.com/Haneke/HanekeSwift/master/Assets/github-header.png)
  2 | 
  3 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
  4 | [![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager)
  5 | [![Accio supported](https://img.shields.io/badge/Accio-supported-0A7CF5.svg?style=flat)](https://github.com/JamitLabs/Accio)
  6 | [![Platform](https://cocoapod-badges.herokuapp.com/p/HanekeSwift/badge.png)](http://cocoadocs.org/docsets/HanekeSwift)
  7 | [![Build Status](https://travis-ci.org/Haneke/HanekeSwift.svg?branch=master)](https://travis-ci.org/Haneke/HanekeSwift)
  8 | [![Join the chat at https://gitter.im/Haneke/HanekeSwift](https://badges.gitter.im/Haneke/HanekeSwift.svg)](https://gitter.im/Haneke/HanekeSwift?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
  9 | 
 10 | Haneke is a lightweight *generic* cache for iOS and tvOS written in Swift 4. It's designed to be super-simple to use. Here's how you would initalize a JSON cache and fetch objects from a url:
 11 | 
 12 | ```swift
 13 | let cache = Cache<JSON>(name: "github")
 14 | let URL = NSURL(string: "https://api.github.com/users/haneke")!
 15 | 
 16 | cache.fetch(URL: URL).onSuccess { JSON in
 17 |     print(JSON.dictionary?["bio"])
 18 | }
 19 | ```
 20 | 
 21 | Haneke provides a memory and LRU disk cache for `UIImage`, `NSData`, `JSON`, `String` or any other type that can be read or written as data.
 22 | 
 23 | Particularly, Haneke excels at working with images. It includes a zero-config image cache with automatic resizing. Everything is done in background, allowing for fast, responsive scrolling. Asking Haneke to load, resize, cache and display an *appropriately sized image* is as simple as:
 24 | 
 25 | ```swift
 26 | imageView.hnk_setImageFromURL(url)
 27 | ```
 28 | 
 29 | _Really._
 30 | 
 31 | ## Features
 32 | 
 33 | * Generic cache with out-of-the-box support for `UIImage`, `NSData`, `JSON` and `String`
 34 | * First-level memory cache using `NSCache`
 35 | * Second-level LRU disk cache using the file system
 36 | * Asynchronous [fetching](#fetchers) of original values from network or disk
 37 | * All disk access is performed in background
 38 | * Thread-safe
 39 | * Automatic cache eviction on memory warnings or disk capacity reached
 40 | * Comprehensive unit tests
 41 | * Extensible by defining [custom formats](#formats), supporting [additional types](#supporting-additional-types) or implementing [custom fetchers](#custom-fetchers)
 42 | 
 43 | For images:
 44 | 
 45 | * Zero-config `UIImageView` and `UIButton` extensions to use the cache, optimized for `UITableView` and `UICollectionView` cell reuse
 46 | * Background image resizing and decompression
 47 | 
 48 | ## Installation
 49 | 
 50 | Using [CocoaPods](http://cocoapods.org/):
 51 | 
 52 | ```ruby
 53 | use_frameworks!
 54 | pod 'HanekeSwift'
 55 | ```
 56 | 
 57 | Using [Carthage](https://github.com/Carthage/Carthage):
 58 | 
 59 | ```
 60 | github "Haneke/HanekeSwift"
 61 | ```
 62 | 
 63 | Using [SwiftPM](https://github.com/apple/swift-package-manager) or [Accio](https://github.com/JamitLabs/Accio):
 64 | 
 65 | 
 66 | ```swift
 67 | .package(url: "https://github.com/Haneke/HanekeSwift.git", .upToNextMajor(from: "0.11.2")),
 68 | ```
 69 | 
 70 | Then link `Haneke` in your App target like so:
 71 | 
 72 | ```swift
 73 | .target(
 74 |     name: "App",
 75 |     dependencies: [
 76 |         "Haneke",
 77 |     ]
 78 | ),
 79 | ```
 80 | 
 81 | Manually:
 82 | 
 83 | 1. Drag `Haneke.xcodeproj` to your project in the _Project Navigator_.
 84 | 2. Select your project and then your app target. Open the _Build Phases_ panel.
 85 | 3. Expand the _Target Dependencies_ group, and add `Haneke.framework`.
 86 | 4. Click on the `+` button at the top left of the panel and select _New Copy Files Phase_. Set _Destination_ to _Frameworks_, and add `Haneke.framework`.
 87 | 5. `import Haneke` whenever you want to use Haneke.
 88 | 
 89 | ## Requirements
 90 | 
 91 | - iOS 8.0+ or tvOS 9.1+
 92 | - Swift 4
 93 | 
 94 | ## Using the cache
 95 | 
 96 | Haneke provides shared caches for `UIImage`, `NSData`, `JSON` and `String`. You can also create your own caches.
 97 | 
 98 | The cache is a key-value store. For example, here's how you would cache and then fetch some data.
 99 | 
100 | ```Swift
101 | let cache = Shared.dataCache
102 | 
103 | cache.set(value: data, key: "funny-games.mp4")
104 | 
105 | // Eventually...
106 | 
107 | cache.fetch(key: "funny-games.mp4").onSuccess { data in
108 |     // Do something with data
109 | }
110 | ```
111 | 
112 | In most cases the value will not be readily available and will have to be fetched from network or disk. Haneke offers convenience `fetch` functions for these cases. Let's go back to the first example, now using a shared cache:
113 | 
114 | ```Swift
115 | let cache = Shared.JSONCache
116 | let URL = NSURL(string: "https://api.github.com/users/haneke")!
117 | 
118 | cache.fetch(URL: URL).onSuccess { JSON in
119 |    print(JSON.dictionary?["bio"])
120 | }
121 | ```
122 | 
123 | The above call will first attempt to fetch the required JSON from (in order) memory, disk or `NSURLCache`. If not available, Haneke will fetch the JSON from the source, return it and then cache it. In this case, the URL itself is used as the key.
124 | 
125 | Further customization can be achieved by using [formats](#formats), [supporting additional types](#supporting-additional-types) or implementing [custom fetchers](#custom-fetchers).
126 | 
127 | ## Extra ♡ for images
128 | 
129 | Need to cache and display images? Haneke provides convenience methods for `UIImageView` and `UIButton` with optimizations for `UITableView` and `UICollectionView` cell reuse. Images will be resized appropriately and cached in a shared cache.
130 | 
131 | ```swift
132 | // Setting a remote image
133 | imageView.hnk_setImageFromURL(url)
134 | 
135 | // Setting an image manually. Requires you to provide a key.
136 | imageView.hnk_setImage(image, key: key)
137 | ```
138 | 
139 | The above lines take care of:
140 | 
141 | 1. If cached, retrieving an appropriately sized image (based on the `bounds` and `contentMode` of the `UIImageView`) from the memory or disk cache. Disk access is performed in background.
142 | 2. If not cached, loading the original image from web/memory and producing an appropriately sized image, both in background. Remote images will be retrieved from the shared `NSURLCache` if available.
143 | 3. Setting the image and animating the change if appropriate.
144 | 4. Or doing nothing if the `UIImageView` was reused during any of the above steps.
145 | 5. Caching the resulting image.
146 | 6. If needed, evicting the least recently used images in the cache.
147 | 
148 | ## Formats
149 | 
150 | Formats allow to specify the disk cache size and any transformations to the values before being cached. For example, the `UIImageView` extension uses a format that resizes images to fit or fill the image view as needed.
151 | 
152 | You can also use custom formats. Say you want to limit the disk capacity for icons to 10MB and apply rounded corners to the images. This is how it could look like:
153 | 
154 | ```swift
155 | let cache = Shared.imageCache
156 | 
157 | let iconFormat = Format<UIImage>(name: "icons", diskCapacity: 10 * 1024 * 1024) { image in
158 |     return imageByRoundingCornersOfImage(image)
159 | }
160 | cache.addFormat(iconFormat)
161 | 
162 | let URL = NSURL(string: "http://haneke.io/icon.png")!
163 | cache.fetch(URL: URL, formatName: "icons").onSuccess { image in
164 |     // image will be a nice rounded icon
165 | }
166 | ```
167 | 
168 | Because we told the cache to use the `"icons"` format Haneke will execute the format transformation in background and return the resulting value.
169 | 
170 | Formats can also be used from the `UIKit` extensions:
171 | 
172 | ```swift
173 | imageView.hnk_setImageFromURL(url, format: iconFormat)
174 | ```
175 | 
176 | ## Fetchers
177 | 
178 | The `fetch` functions for urls and paths are actually convenience methods. Under the hood Haneke uses fetcher objects. To illustrate, here's another way of fetching from a url by explictly using a network fetcher:
179 | 
180 | ```swift
181 | let URL = NSURL(string: "http://haneke.io/icon.png")!
182 | let fetcher = NetworkFetcher<UIImage>(URL: URL)
183 | cache.fetch(fetcher: fetcher).onSuccess { image in
184 |     // Do something with image
185 | }
186 | ```
187 | 
188 | Fetching an original value from network or disk is an expensive operation. Fetchers act as a proxy for the value, and allow Haneke to perform the fetch operation only if absolutely necessary.
189 | 
190 | In the above example the fetcher will be executed only if there is no value associated with `"http://haneke.io/icon.png"` in the memory or disk cache. If that happens, the fetcher will be responsible from fetching the original value, which will then be cached to avoid further network activity.
191 | 
192 | Haneke provides two specialized fetchers: `NetworkFetcher<T>` and `DiskFetcher<T>`. You can also implement your own fetchers by subclassing `Fetcher<T>`.
193 | 
194 | ### Custom fetchers
195 | 
196 | Through custom fetchers you can fetch original values from other sources than network or disk (e.g., Core Data), or even change how Haneke acceses network or disk (e.g., use [Alamofire](https://github.com/Alamofire/Alamofire) for networking instead of `NSURLSession`). A custom fetcher must subclass `Fetcher<T>` and is responsible for:
197 | 
198 | * Providing the key (e.g., `NSURL.absoluteString` in the case of `NetworkFetcher`) associated with the value to be fetched
199 | * Fetching the value in background and calling the success or failure closure accordingly, both in the main queue
200 | * Cancelling the fetch on demand, if possible
201 | 
202 | Fetchers are generic, and the only restriction on their type is that it must implement `DataConvertible`.
203 | 
204 | ## Supporting additional types
205 | 
206 | Haneke can cache any type that can be read and saved as data. This is indicated to Haneke by implementing the protocols `DataConvertible` and `DataRepresentable`.
207 | 
208 | ```Swift
209 | public protocol DataConvertible {
210 |     typealias Result
211 | 
212 |     class func convertFromData(data:NSData) -> Result?
213 | 
214 | }
215 | 
216 | public protocol DataRepresentable {
217 | 
218 |     func asData() -> NSData!
219 | 
220 | }
221 | ```
222 | 
223 | This is how one could add support for `NSDictionary`:
224 | 
225 | ```Swift
226 | extension NSDictionary : DataConvertible, DataRepresentable {
227 | 
228 |     public typealias Result = NSDictionary
229 | 
230 |     public class func convertFromData(data:NSData) -> Result? {
231 |         return NSKeyedUnarchiver.unarchiveObjectWithData(data) as? NSDictionary
232 |     }
233 | 
234 |     public func asData() -> NSData! {
235 |         return NSKeyedArchiver.archivedDataWithRootObject(self)
236 |     }
237 | 
238 | }
239 | ```
240 | 
241 | Then creating a `NSDictionary` cache would be as simple as:
242 | 
243 | ```swift
244 | let cache = Cache<NSDictionary>(name: "dictionaries")
245 | ```
246 | 
247 | ## Roadmap
248 | 
249 | Haneke Swift is in initial development and its public API should not be considered stable.
250 | 
251 | ## License
252 | 
253 |  Copyright 2014 Hermes Pique ([@hpique](https://twitter.com/hpique))    
254 | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2014 Joan Romano ([@joanromano](https://twitter.com/joanromano))   
255 | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2014 Luis Ascorbe ([@lascorbe](https://twitter.com/Lascorbe))   
256 | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2014 Oriol Blanc ([@oriolblanc](https://twitter.com/oriolblanc))   
257 | 
258 |  Licensed under the Apache License, Version 2.0 (the "License");
259 |  you may not use this file except in compliance with the License.
260 |  You may obtain a copy of the License at
261 | 
262 |  http://www.apache.org/licenses/LICENSE-2.0
263 | 
264 |  Unless required by applicable law or agreed to in writing, software
265 |  distributed under the License is distributed on an "AS IS" BASIS,
266 |  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
267 |  See the License for the specific language governing permissions and
268 |  limitations under the License.
269 | 


--------------------------------------------------------------------------------