├── .gitattributes ├── .gitconfig ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CacheKit.podspec ├── CacheKit.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── CacheKit.xcscheme ├── CacheKit.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── CacheKit.xcscmblueprint ├── CacheKit ├── Classes │ ├── .gitkeep │ ├── CKCache.h │ ├── CKCache.m │ ├── CKCacheContent.h │ ├── CKCacheContent.m │ ├── CKFileCache.h │ ├── CKFileCache.m │ ├── CKMemoryCache.h │ ├── CKMemoryCache.m │ ├── CKNullCache.h │ ├── CKNullCache.m │ ├── CKSQLiteCache.h │ ├── CKSQLiteCache.m │ └── CacheKit.h ├── FastImages │ ├── CKCache+CKFastImages.h │ ├── CKCache+CKFastImages.m │ ├── CKFastImage.h │ └── CKFastImage.m ├── Info.plist └── ModuleMap ├── CacheKitTests ├── CKFileCacheTests.m ├── CKMemoryCacheTests.m ├── CKNullCacheTests.m ├── CKSQLiteCacheTests.m └── Info.plist ├── Cartfile ├── Cartfile.resolved ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.m diff=objc 2 | *.strings diff=localizablestrings 3 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [diff "localizablestrings"] 2 | textconv = "iconv -f utf-16 -t utf-8" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage/ 26 | *.framework.zip 27 | # xcode noise 28 | build/* 29 | *.mode1 30 | *.mode1v3 31 | *.mode2v3 32 | *.perspective 33 | *.perspectivev3 34 | *.pbxuser 35 | xcuserdata 36 | InfoPlist.h 37 | 38 | # old skool 39 | .svn 40 | 41 | # osx noise 42 | .DS_Store 43 | *~.nib 44 | *.swp 45 | profile 46 | 47 | # other 48 | Support 49 | Research 50 | Artwork 51 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "fmdb"] 2 | path = fmdb 3 | url = https://davbeck@github.com/ccgus/fmdb.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode7.2 3 | xcode_workspace: CacheKit.xcworkspace 4 | xcode_scheme: CacheKit 5 | xcode_sdk: iphonesimulator9.2 6 | env: 7 | global: 8 | - FRAMEWORK_NAME=CacheKit 9 | before_install: 10 | - brew update 11 | - brew outdated carthage || brew upgrade carthage 12 | before_deploy: 13 | - carthage build --no-skip-current 14 | - carthage archive $FRAMEWORK_NAME 15 | deploy: 16 | provider: releases 17 | api_key: 18 | secure: FmSjotx7xtSYibODqnJH1ztGuucXWrhPOG4Yi6DCl4DdFD14pqNdPKbEpTUPsMyNmtiNZhrw5Ey8QCbhnL986eQhltsEGlGcY1V3lRdRd7BONuVEZ0kx/Fd5Br3p37Xz1FUAJnzM3M0Q14KPpUbYPRqyVhxPgfosl6SSkmmDpqQ= 19 | file: "$FRAMEWORK_NAME.framework.zip" 20 | skip_cleanup: true 21 | on: 22 | repo: davbeck/CacheKit 23 | tags: true 24 | -------------------------------------------------------------------------------- /CacheKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "CacheKit" 3 | spec.version = "0.6.4" 4 | spec.summary = "Easily cache objects in memory, to files, a database or not at all." 5 | spec.homepage = "https://github.com/davbeck/CacheKit" 6 | spec.license = 'MIT' 7 | spec.author = { "David Beck" => "code@thinkultimate.com" } 8 | spec.source = { :git => "https://github.com/davbeck/CacheKit.git", :tag => spec.version.to_s } 9 | spec.social_media_url = 'https://twitter.com/davbeck' 10 | 11 | spec.requires_arc = true 12 | 13 | spec.ios.deployment_target = '6.0' 14 | spec.osx.deployment_target = '10.8' 15 | 16 | spec.dependency 'FMDB', '~> 2.4' 17 | 18 | spec.subspec "Core" do |core_spec| 19 | core_spec.source_files = "CacheKit/Classes/**/*" 20 | end 21 | 22 | spec.subspec "FastImages" do |fast_images_spec| 23 | fast_images_spec.dependency 'CacheKit/Core' 24 | 25 | fast_images_spec.ios.source_files = 'CacheKit/FastImages/**/*' 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /CacheKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1158916A1C50115A004BC21F /* CacheKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1158915F1C50115A004BC21F /* CacheKit.framework */; }; 11 | 1158917A1C50126D004BC21F /* FMDB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 115891791C50126D004BC21F /* FMDB.framework */; }; 12 | 115891B21C5012C1004BC21F /* CacheKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 115891A41C5012C1004BC21F /* CacheKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 115891B31C5012C1004BC21F /* CKCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 115891A51C5012C1004BC21F /* CKCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 14 | 115891B41C5012C1004BC21F /* CKCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 115891A61C5012C1004BC21F /* CKCache.m */; }; 15 | 115891B51C5012C1004BC21F /* CKCacheContent.h in Headers */ = {isa = PBXBuildFile; fileRef = 115891A71C5012C1004BC21F /* CKCacheContent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16 | 115891B61C5012C1004BC21F /* CKCacheContent.m in Sources */ = {isa = PBXBuildFile; fileRef = 115891A81C5012C1004BC21F /* CKCacheContent.m */; }; 17 | 115891B71C5012C1004BC21F /* CKFileCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 115891A91C5012C1004BC21F /* CKFileCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18 | 115891B81C5012C1004BC21F /* CKFileCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 115891AA1C5012C1004BC21F /* CKFileCache.m */; }; 19 | 115891B91C5012C1004BC21F /* CKMemoryCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 115891AB1C5012C1004BC21F /* CKMemoryCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20 | 115891BA1C5012C1004BC21F /* CKMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 115891AC1C5012C1004BC21F /* CKMemoryCache.m */; }; 21 | 115891BB1C5012C1004BC21F /* CKNullCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 115891AD1C5012C1004BC21F /* CKNullCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 22 | 115891BC1C5012C1004BC21F /* CKNullCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 115891AE1C5012C1004BC21F /* CKNullCache.m */; }; 23 | 115891BD1C5012C1004BC21F /* CKSQLiteCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 115891AF1C5012C1004BC21F /* CKSQLiteCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 24 | 115891BE1C5012C1004BC21F /* CKSQLiteCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 115891B01C5012C1004BC21F /* CKSQLiteCache.m */; }; 25 | 115891C41C5012D3004BC21F /* CKCache+CKFastImages.h in Headers */ = {isa = PBXBuildFile; fileRef = 115891C01C5012D3004BC21F /* CKCache+CKFastImages.h */; settings = {ATTRIBUTES = (Public, ); }; }; 26 | 115891C51C5012D3004BC21F /* CKCache+CKFastImages.m in Sources */ = {isa = PBXBuildFile; fileRef = 115891C11C5012D3004BC21F /* CKCache+CKFastImages.m */; }; 27 | 115891C61C5012D3004BC21F /* CKFastImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 115891C21C5012D3004BC21F /* CKFastImage.h */; settings = {ATTRIBUTES = (Public, ); }; }; 28 | 115891C71C5012D3004BC21F /* CKFastImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 115891C31C5012D3004BC21F /* CKFastImage.m */; }; 29 | 115891CC1C501333004BC21F /* CKFileCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 115891C81C501333004BC21F /* CKFileCacheTests.m */; }; 30 | 115891CD1C501333004BC21F /* CKMemoryCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 115891C91C501333004BC21F /* CKMemoryCacheTests.m */; }; 31 | 115891CE1C501333004BC21F /* CKNullCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 115891CA1C501333004BC21F /* CKNullCacheTests.m */; }; 32 | 115891CF1C501333004BC21F /* CKSQLiteCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 115891CB1C501333004BC21F /* CKSQLiteCacheTests.m */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXContainerItemProxy section */ 36 | 1158916B1C50115A004BC21F /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = 115891561C50115A004BC21F /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = 1158915E1C50115A004BC21F; 41 | remoteInfo = CacheKit; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | 1113BDAF1C501B4E007E9FD6 /* ModuleMap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ModuleMap; sourceTree = ""; }; 47 | 1158915F1C50115A004BC21F /* CacheKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CacheKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 115891641C50115A004BC21F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | 115891691C50115A004BC21F /* CacheKit-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CacheKit-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 115891701C50115A004BC21F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | 115891791C50126D004BC21F /* FMDB.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FMDB.framework; path = "fmdb/build/Debug-iphoneos/FMDB.framework"; sourceTree = ""; }; 52 | 115891A41C5012C1004BC21F /* CacheKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CacheKit.h; sourceTree = ""; }; 53 | 115891A51C5012C1004BC21F /* CKCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKCache.h; sourceTree = ""; }; 54 | 115891A61C5012C1004BC21F /* CKCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKCache.m; sourceTree = ""; }; 55 | 115891A71C5012C1004BC21F /* CKCacheContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKCacheContent.h; sourceTree = ""; }; 56 | 115891A81C5012C1004BC21F /* CKCacheContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKCacheContent.m; sourceTree = ""; }; 57 | 115891A91C5012C1004BC21F /* CKFileCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKFileCache.h; sourceTree = ""; }; 58 | 115891AA1C5012C1004BC21F /* CKFileCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKFileCache.m; sourceTree = ""; }; 59 | 115891AB1C5012C1004BC21F /* CKMemoryCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKMemoryCache.h; sourceTree = ""; }; 60 | 115891AC1C5012C1004BC21F /* CKMemoryCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKMemoryCache.m; sourceTree = ""; }; 61 | 115891AD1C5012C1004BC21F /* CKNullCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKNullCache.h; sourceTree = ""; }; 62 | 115891AE1C5012C1004BC21F /* CKNullCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKNullCache.m; sourceTree = ""; }; 63 | 115891AF1C5012C1004BC21F /* CKSQLiteCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKSQLiteCache.h; sourceTree = ""; }; 64 | 115891B01C5012C1004BC21F /* CKSQLiteCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKSQLiteCache.m; sourceTree = ""; }; 65 | 115891C01C5012D3004BC21F /* CKCache+CKFastImages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CKCache+CKFastImages.h"; sourceTree = ""; }; 66 | 115891C11C5012D3004BC21F /* CKCache+CKFastImages.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CKCache+CKFastImages.m"; sourceTree = ""; }; 67 | 115891C21C5012D3004BC21F /* CKFastImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKFastImage.h; sourceTree = ""; }; 68 | 115891C31C5012D3004BC21F /* CKFastImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKFastImage.m; sourceTree = ""; }; 69 | 115891C81C501333004BC21F /* CKFileCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKFileCacheTests.m; sourceTree = ""; }; 70 | 115891C91C501333004BC21F /* CKMemoryCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKMemoryCacheTests.m; sourceTree = ""; }; 71 | 115891CA1C501333004BC21F /* CKNullCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKNullCacheTests.m; sourceTree = ""; }; 72 | 115891CB1C501333004BC21F /* CKSQLiteCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKSQLiteCacheTests.m; sourceTree = ""; usesTabs = 1; }; 73 | 1158925B1C501706004BC21F /* FMDB.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FMDB.framework; path = fmdb/build/Debug/FMDB.framework; sourceTree = ""; }; 74 | /* End PBXFileReference section */ 75 | 76 | /* Begin PBXFrameworksBuildPhase section */ 77 | 1158915B1C50115A004BC21F /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | 1158917A1C50126D004BC21F /* FMDB.framework in Frameworks */, 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | 115891661C50115A004BC21F /* Frameworks */ = { 86 | isa = PBXFrameworksBuildPhase; 87 | buildActionMask = 2147483647; 88 | files = ( 89 | 1158916A1C50115A004BC21F /* CacheKit.framework in Frameworks */, 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | /* End PBXFrameworksBuildPhase section */ 94 | 95 | /* Begin PBXGroup section */ 96 | 115891551C50115A004BC21F = { 97 | isa = PBXGroup; 98 | children = ( 99 | 1158917B1C501276004BC21F /* Frameworks */, 100 | 115891611C50115A004BC21F /* CacheKit */, 101 | 1158916D1C50115A004BC21F /* CacheKitTests */, 102 | 115891601C50115A004BC21F /* Products */, 103 | ); 104 | sourceTree = ""; 105 | }; 106 | 115891601C50115A004BC21F /* Products */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 1158915F1C50115A004BC21F /* CacheKit.framework */, 110 | 115891691C50115A004BC21F /* CacheKit-iOSTests.xctest */, 111 | ); 112 | name = Products; 113 | sourceTree = ""; 114 | }; 115 | 115891611C50115A004BC21F /* CacheKit */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 115891A21C5012C1004BC21F /* Classes */, 119 | 115891BF1C5012D3004BC21F /* FastImages */, 120 | 115891641C50115A004BC21F /* Info.plist */, 121 | 1113BDAF1C501B4E007E9FD6 /* ModuleMap */, 122 | ); 123 | path = CacheKit; 124 | sourceTree = ""; 125 | }; 126 | 1158916D1C50115A004BC21F /* CacheKitTests */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 115891C81C501333004BC21F /* CKFileCacheTests.m */, 130 | 115891C91C501333004BC21F /* CKMemoryCacheTests.m */, 131 | 115891CA1C501333004BC21F /* CKNullCacheTests.m */, 132 | 115891CB1C501333004BC21F /* CKSQLiteCacheTests.m */, 133 | 115891701C50115A004BC21F /* Info.plist */, 134 | ); 135 | path = CacheKitTests; 136 | sourceTree = ""; 137 | }; 138 | 1158917B1C501276004BC21F /* Frameworks */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 1158925B1C501706004BC21F /* FMDB.framework */, 142 | 115891791C50126D004BC21F /* FMDB.framework */, 143 | ); 144 | name = Frameworks; 145 | sourceTree = ""; 146 | }; 147 | 115891A21C5012C1004BC21F /* Classes */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 115891A41C5012C1004BC21F /* CacheKit.h */, 151 | 115891A51C5012C1004BC21F /* CKCache.h */, 152 | 115891A61C5012C1004BC21F /* CKCache.m */, 153 | 115891A71C5012C1004BC21F /* CKCacheContent.h */, 154 | 115891A81C5012C1004BC21F /* CKCacheContent.m */, 155 | 115891A91C5012C1004BC21F /* CKFileCache.h */, 156 | 115891AA1C5012C1004BC21F /* CKFileCache.m */, 157 | 115891AB1C5012C1004BC21F /* CKMemoryCache.h */, 158 | 115891AC1C5012C1004BC21F /* CKMemoryCache.m */, 159 | 115891AD1C5012C1004BC21F /* CKNullCache.h */, 160 | 115891AE1C5012C1004BC21F /* CKNullCache.m */, 161 | 115891AF1C5012C1004BC21F /* CKSQLiteCache.h */, 162 | 115891B01C5012C1004BC21F /* CKSQLiteCache.m */, 163 | ); 164 | path = Classes; 165 | sourceTree = ""; 166 | }; 167 | 115891BF1C5012D3004BC21F /* FastImages */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 115891C01C5012D3004BC21F /* CKCache+CKFastImages.h */, 171 | 115891C11C5012D3004BC21F /* CKCache+CKFastImages.m */, 172 | 115891C21C5012D3004BC21F /* CKFastImage.h */, 173 | 115891C31C5012D3004BC21F /* CKFastImage.m */, 174 | ); 175 | path = FastImages; 176 | sourceTree = ""; 177 | }; 178 | /* End PBXGroup section */ 179 | 180 | /* Begin PBXHeadersBuildPhase section */ 181 | 1158915C1C50115A004BC21F /* Headers */ = { 182 | isa = PBXHeadersBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | 115891BD1C5012C1004BC21F /* CKSQLiteCache.h in Headers */, 186 | 115891B21C5012C1004BC21F /* CacheKit.h in Headers */, 187 | 115891B71C5012C1004BC21F /* CKFileCache.h in Headers */, 188 | 115891C41C5012D3004BC21F /* CKCache+CKFastImages.h in Headers */, 189 | 115891BB1C5012C1004BC21F /* CKNullCache.h in Headers */, 190 | 115891B91C5012C1004BC21F /* CKMemoryCache.h in Headers */, 191 | 115891B51C5012C1004BC21F /* CKCacheContent.h in Headers */, 192 | 115891C61C5012D3004BC21F /* CKFastImage.h in Headers */, 193 | 115891B31C5012C1004BC21F /* CKCache.h in Headers */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXHeadersBuildPhase section */ 198 | 199 | /* Begin PBXNativeTarget section */ 200 | 1158915E1C50115A004BC21F /* CacheKit-iOS */ = { 201 | isa = PBXNativeTarget; 202 | buildConfigurationList = 115891731C50115A004BC21F /* Build configuration list for PBXNativeTarget "CacheKit-iOS" */; 203 | buildPhases = ( 204 | 1158915A1C50115A004BC21F /* Sources */, 205 | 1158915B1C50115A004BC21F /* Frameworks */, 206 | 1158915C1C50115A004BC21F /* Headers */, 207 | 1158915D1C50115A004BC21F /* Resources */, 208 | ); 209 | buildRules = ( 210 | ); 211 | dependencies = ( 212 | ); 213 | name = "CacheKit-iOS"; 214 | productName = CacheKit; 215 | productReference = 1158915F1C50115A004BC21F /* CacheKit.framework */; 216 | productType = "com.apple.product-type.framework"; 217 | }; 218 | 115891681C50115A004BC21F /* CacheKit-iOSTests */ = { 219 | isa = PBXNativeTarget; 220 | buildConfigurationList = 115891761C50115A004BC21F /* Build configuration list for PBXNativeTarget "CacheKit-iOSTests" */; 221 | buildPhases = ( 222 | 115891651C50115A004BC21F /* Sources */, 223 | 115891661C50115A004BC21F /* Frameworks */, 224 | 115891671C50115A004BC21F /* Resources */, 225 | ); 226 | buildRules = ( 227 | ); 228 | dependencies = ( 229 | 1158916C1C50115A004BC21F /* PBXTargetDependency */, 230 | ); 231 | name = "CacheKit-iOSTests"; 232 | productName = CacheKitTests; 233 | productReference = 115891691C50115A004BC21F /* CacheKit-iOSTests.xctest */; 234 | productType = "com.apple.product-type.bundle.unit-test"; 235 | }; 236 | /* End PBXNativeTarget section */ 237 | 238 | /* Begin PBXProject section */ 239 | 115891561C50115A004BC21F /* Project object */ = { 240 | isa = PBXProject; 241 | attributes = { 242 | LastUpgradeCheck = 0720; 243 | ORGANIZATIONNAME = "Think Ultimate"; 244 | TargetAttributes = { 245 | 1158915E1C50115A004BC21F = { 246 | CreatedOnToolsVersion = 7.2; 247 | }; 248 | 115891681C50115A004BC21F = { 249 | CreatedOnToolsVersion = 7.2; 250 | }; 251 | }; 252 | }; 253 | buildConfigurationList = 115891591C50115A004BC21F /* Build configuration list for PBXProject "CacheKit" */; 254 | compatibilityVersion = "Xcode 3.2"; 255 | developmentRegion = English; 256 | hasScannedForEncodings = 0; 257 | knownRegions = ( 258 | en, 259 | ); 260 | mainGroup = 115891551C50115A004BC21F; 261 | productRefGroup = 115891601C50115A004BC21F /* Products */; 262 | projectDirPath = ""; 263 | projectRoot = ""; 264 | targets = ( 265 | 1158915E1C50115A004BC21F /* CacheKit-iOS */, 266 | 115891681C50115A004BC21F /* CacheKit-iOSTests */, 267 | ); 268 | }; 269 | /* End PBXProject section */ 270 | 271 | /* Begin PBXResourcesBuildPhase section */ 272 | 1158915D1C50115A004BC21F /* Resources */ = { 273 | isa = PBXResourcesBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | 115891671C50115A004BC21F /* Resources */ = { 280 | isa = PBXResourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | ); 284 | runOnlyForDeploymentPostprocessing = 0; 285 | }; 286 | /* End PBXResourcesBuildPhase section */ 287 | 288 | /* Begin PBXSourcesBuildPhase section */ 289 | 1158915A1C50115A004BC21F /* Sources */ = { 290 | isa = PBXSourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 115891BE1C5012C1004BC21F /* CKSQLiteCache.m in Sources */, 294 | 115891B81C5012C1004BC21F /* CKFileCache.m in Sources */, 295 | 115891BC1C5012C1004BC21F /* CKNullCache.m in Sources */, 296 | 115891BA1C5012C1004BC21F /* CKMemoryCache.m in Sources */, 297 | 115891B61C5012C1004BC21F /* CKCacheContent.m in Sources */, 298 | 115891C51C5012D3004BC21F /* CKCache+CKFastImages.m in Sources */, 299 | 115891B41C5012C1004BC21F /* CKCache.m in Sources */, 300 | 115891C71C5012D3004BC21F /* CKFastImage.m in Sources */, 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | 115891651C50115A004BC21F /* Sources */ = { 305 | isa = PBXSourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | 115891CC1C501333004BC21F /* CKFileCacheTests.m in Sources */, 309 | 115891CE1C501333004BC21F /* CKNullCacheTests.m in Sources */, 310 | 115891CF1C501333004BC21F /* CKSQLiteCacheTests.m in Sources */, 311 | 115891CD1C501333004BC21F /* CKMemoryCacheTests.m in Sources */, 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | /* End PBXSourcesBuildPhase section */ 316 | 317 | /* Begin PBXTargetDependency section */ 318 | 1158916C1C50115A004BC21F /* PBXTargetDependency */ = { 319 | isa = PBXTargetDependency; 320 | target = 1158915E1C50115A004BC21F /* CacheKit-iOS */; 321 | targetProxy = 1158916B1C50115A004BC21F /* PBXContainerItemProxy */; 322 | }; 323 | /* End PBXTargetDependency section */ 324 | 325 | /* Begin XCBuildConfiguration section */ 326 | 115891711C50115A004BC21F /* Debug */ = { 327 | isa = XCBuildConfiguration; 328 | buildSettings = { 329 | ALWAYS_SEARCH_USER_PATHS = NO; 330 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 331 | CLANG_CXX_LIBRARY = "libc++"; 332 | CLANG_ENABLE_MODULES = YES; 333 | CLANG_ENABLE_OBJC_ARC = YES; 334 | CLANG_WARN_BOOL_CONVERSION = YES; 335 | CLANG_WARN_CONSTANT_CONVERSION = YES; 336 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 337 | CLANG_WARN_EMPTY_BODY = YES; 338 | CLANG_WARN_ENUM_CONVERSION = YES; 339 | CLANG_WARN_INT_CONVERSION = YES; 340 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 341 | CLANG_WARN_UNREACHABLE_CODE = YES; 342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 343 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 344 | COPY_PHASE_STRIP = NO; 345 | CURRENT_PROJECT_VERSION = 1; 346 | DEBUG_INFORMATION_FORMAT = dwarf; 347 | ENABLE_STRICT_OBJC_MSGSEND = YES; 348 | ENABLE_TESTABILITY = YES; 349 | GCC_C_LANGUAGE_STANDARD = gnu99; 350 | GCC_DYNAMIC_NO_PIC = NO; 351 | GCC_NO_COMMON_BLOCKS = YES; 352 | GCC_OPTIMIZATION_LEVEL = 0; 353 | GCC_PREPROCESSOR_DEFINITIONS = ( 354 | "DEBUG=1", 355 | "$(inherited)", 356 | ); 357 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 358 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 359 | GCC_WARN_UNDECLARED_SELECTOR = YES; 360 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 361 | GCC_WARN_UNUSED_FUNCTION = YES; 362 | GCC_WARN_UNUSED_VARIABLE = YES; 363 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 364 | MTL_ENABLE_DEBUG_INFO = YES; 365 | ONLY_ACTIVE_ARCH = YES; 366 | SDKROOT = iphoneos; 367 | TARGETED_DEVICE_FAMILY = "1,2"; 368 | VERSIONING_SYSTEM = "apple-generic"; 369 | VERSION_INFO_PREFIX = ""; 370 | }; 371 | name = Debug; 372 | }; 373 | 115891721C50115A004BC21F /* Release */ = { 374 | isa = XCBuildConfiguration; 375 | buildSettings = { 376 | ALWAYS_SEARCH_USER_PATHS = NO; 377 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 378 | CLANG_CXX_LIBRARY = "libc++"; 379 | CLANG_ENABLE_MODULES = YES; 380 | CLANG_ENABLE_OBJC_ARC = YES; 381 | CLANG_WARN_BOOL_CONVERSION = YES; 382 | CLANG_WARN_CONSTANT_CONVERSION = YES; 383 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 384 | CLANG_WARN_EMPTY_BODY = YES; 385 | CLANG_WARN_ENUM_CONVERSION = YES; 386 | CLANG_WARN_INT_CONVERSION = YES; 387 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 388 | CLANG_WARN_UNREACHABLE_CODE = YES; 389 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 390 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 391 | COPY_PHASE_STRIP = NO; 392 | CURRENT_PROJECT_VERSION = 1; 393 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 394 | ENABLE_NS_ASSERTIONS = NO; 395 | ENABLE_STRICT_OBJC_MSGSEND = YES; 396 | GCC_C_LANGUAGE_STANDARD = gnu99; 397 | GCC_NO_COMMON_BLOCKS = YES; 398 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 399 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 400 | GCC_WARN_UNDECLARED_SELECTOR = YES; 401 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 402 | GCC_WARN_UNUSED_FUNCTION = YES; 403 | GCC_WARN_UNUSED_VARIABLE = YES; 404 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 405 | MTL_ENABLE_DEBUG_INFO = NO; 406 | SDKROOT = iphoneos; 407 | TARGETED_DEVICE_FAMILY = "1,2"; 408 | VALIDATE_PRODUCT = YES; 409 | VERSIONING_SYSTEM = "apple-generic"; 410 | VERSION_INFO_PREFIX = ""; 411 | }; 412 | name = Release; 413 | }; 414 | 115891741C50115A004BC21F /* Debug */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | DEFINES_MODULE = YES; 418 | DYLIB_COMPATIBILITY_VERSION = 1; 419 | DYLIB_CURRENT_VERSION = 1; 420 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 421 | INFOPLIST_FILE = CacheKit/Info.plist; 422 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 423 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 424 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 425 | PRODUCT_BUNDLE_IDENTIFIER = "com.ThinkUltimate.CacheKit-iOS"; 426 | PRODUCT_NAME = CacheKit; 427 | SKIP_INSTALL = YES; 428 | }; 429 | name = Debug; 430 | }; 431 | 115891751C50115A004BC21F /* Release */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | DEFINES_MODULE = YES; 435 | DYLIB_COMPATIBILITY_VERSION = 1; 436 | DYLIB_CURRENT_VERSION = 1; 437 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 438 | INFOPLIST_FILE = CacheKit/Info.plist; 439 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 440 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 441 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 442 | PRODUCT_BUNDLE_IDENTIFIER = "com.ThinkUltimate.CacheKit-iOS"; 443 | PRODUCT_NAME = CacheKit; 444 | SKIP_INSTALL = YES; 445 | }; 446 | name = Release; 447 | }; 448 | 115891771C50115A004BC21F /* Debug */ = { 449 | isa = XCBuildConfiguration; 450 | buildSettings = { 451 | INFOPLIST_FILE = CacheKitTests/Info.plist; 452 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 453 | PRODUCT_BUNDLE_IDENTIFIER = com.ThinkUltimate.CacheKitTests; 454 | PRODUCT_NAME = "$(TARGET_NAME)"; 455 | }; 456 | name = Debug; 457 | }; 458 | 115891781C50115A004BC21F /* Release */ = { 459 | isa = XCBuildConfiguration; 460 | buildSettings = { 461 | INFOPLIST_FILE = CacheKitTests/Info.plist; 462 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 463 | PRODUCT_BUNDLE_IDENTIFIER = com.ThinkUltimate.CacheKitTests; 464 | PRODUCT_NAME = "$(TARGET_NAME)"; 465 | }; 466 | name = Release; 467 | }; 468 | /* End XCBuildConfiguration section */ 469 | 470 | /* Begin XCConfigurationList section */ 471 | 115891591C50115A004BC21F /* Build configuration list for PBXProject "CacheKit" */ = { 472 | isa = XCConfigurationList; 473 | buildConfigurations = ( 474 | 115891711C50115A004BC21F /* Debug */, 475 | 115891721C50115A004BC21F /* Release */, 476 | ); 477 | defaultConfigurationIsVisible = 0; 478 | defaultConfigurationName = Release; 479 | }; 480 | 115891731C50115A004BC21F /* Build configuration list for PBXNativeTarget "CacheKit-iOS" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | 115891741C50115A004BC21F /* Debug */, 484 | 115891751C50115A004BC21F /* Release */, 485 | ); 486 | defaultConfigurationIsVisible = 0; 487 | defaultConfigurationName = Release; 488 | }; 489 | 115891761C50115A004BC21F /* Build configuration list for PBXNativeTarget "CacheKit-iOSTests" */ = { 490 | isa = XCConfigurationList; 491 | buildConfigurations = ( 492 | 115891771C50115A004BC21F /* Debug */, 493 | 115891781C50115A004BC21F /* Release */, 494 | ); 495 | defaultConfigurationIsVisible = 0; 496 | defaultConfigurationName = Release; 497 | }; 498 | /* End XCConfigurationList section */ 499 | }; 500 | rootObject = 115891561C50115A004BC21F /* Project object */; 501 | } 502 | -------------------------------------------------------------------------------- /CacheKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CacheKit.xcodeproj/xcshareddata/xcschemes/CacheKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /CacheKit.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CacheKit.xcworkspace/xcshareddata/CacheKit.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "0B331F0B667B0FAC098090313C5011A52E6D35EC", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "04101981B66E318109934C9516F88D824BB96459" : 0, 8 | "0B331F0B667B0FAC098090313C5011A52E6D35EC" : 0 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "EB79B9F8-E0F7-493D-B36E-B4865BB14B1E", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "04101981B66E318109934C9516F88D824BB96459" : "CacheKit\/fmdb\/", 13 | "0B331F0B667B0FAC098090313C5011A52E6D35EC" : "CacheKit\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "CacheKit", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CacheKit.xcworkspace", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/ccgus\/fmdb.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "04101981B66E318109934C9516F88D824BB96459" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:davbeck\/CacheKit.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "0B331F0B667B0FAC098090313C5011A52E6D35EC" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /CacheKit/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davbeck/CacheKit/9e3ad972d172dae921d5b450ce18b879de716d34/CacheKit/Classes/.gitkeep -------------------------------------------------------------------------------- /CacheKit/Classes/CKCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKCache.h 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | 12 | typedef _Nonnull id(^CKCacheContentBlock)(); 13 | 14 | 15 | /** The CacheKit base class. 16 | 17 | This class serves as the abstract base class for other caches. You cannot create a CKCache 18 | directly. Instead, you should create an instance of one of it's concrete subclasses. 19 | 20 | Subclasses: 21 | 22 | - `CKMemoryCache`: An in memory only cache that does not persist when the cache is destroyed. 23 | - `CKFileCache`: A cache that stores it's objects as files on disk. 24 | - `CKSQLiteCache`: A cache that stores it's objects in an SQLite database. 25 | - `CKNullCache`: A cache that never stores it's objects. Use this for testing. 26 | 27 | `CKSQLiteCache` is much faster than `CKFileCache` in almost every situation. However, as the size 28 | of the objects and even the number of objects grow, `CKFileCache` can become faster. Both use 29 | `NSCoding` to convert their objects to raw data. 30 | 31 | ## Keys 32 | 33 | Keys are always `NSString`s. If you are using a shared or global cache, you need to make sure that 34 | the keys you use are unique. The easiest way to do this is to prefix them with your class name. 35 | 36 | ## Objects 37 | 38 | Objects are copied when added to a cache. Currently, for persistent caches, objects must conform 39 | to NSCoding to be written to disk. In the future those caches should include a transformer property 40 | to allow for different methods of storage. 41 | */ 42 | 43 | @interface CKCache<__covariant ObjectType:id> : NSObject 44 | 45 | /** Init cache with a given name. 46 | 47 | @param name The name for the new cache. If a cache is persistent, passing the same name 2 caches 48 | will cause them to share data, however there may be issues with concurrency. You should use the 49 | same name for the cache each time the app is launched. 50 | @return A new cache with the given name. 51 | */ 52 | - (nonnull instancetype)initWithName:(nonnull NSString *)name NS_DESIGNATED_INITIALIZER; 53 | 54 | /** The name for the cache. 55 | 56 | If a cache is persistent, passing the same name 2 caches 57 | will cause them to share data, however there may be issues with concurrency. You should use the 58 | same name for the cache each time the app is launched. 59 | 60 | The name of the cache should not change. 61 | */ 62 | @property (readonly, copy, nonnull) NSString *name; 63 | 64 | 65 | /** Whether an object exists for the given key. 66 | 67 | If the object has expired, this method returns `NO`. 68 | 69 | @param key The key to look up the object with. 70 | @return `YES` if any object exists with that key, `NO` otherwise. 71 | */ 72 | - (BOOL)objectExistsForKey:(nonnull NSString *)key; 73 | 74 | /** Get the object for the given key. 75 | 76 | If the object has expired, this method returns `nil`. 77 | 78 | @param key The key to look up the object with. 79 | @return The object for the given key, or nil. 80 | */ 81 | - (nullable ObjectType)objectForKey:(nonnull NSString *)key; 82 | 83 | /** Get the object for the given key. 84 | 85 | If the object has expired, or does not exist in the cache, the content block will be called and 86 | it's results added to the cache and returned. 87 | 88 | @param key The key to look up the object with. 89 | @param content The block that provides an object when the cache misses. Can be nil. 90 | @return The object for the given key, or nil. 91 | */ 92 | - (nullable ObjectType)objectForKey:(nonnull NSString *)key withContent:(nullable CKCacheContentBlock)content; 93 | 94 | /** Get the object for the given key. 95 | 96 | If the object has expired, or does not exist in the cache, the content block will be called and 97 | it's results added to the cache and returned. In that case, `expiresIn` will be used for the 98 | returned value when it is stored in the cache. 99 | 100 | @param key The key to look up the object with. 101 | @param expiresIn The amount of seconds before the content object will expire. If this number is 102 | <= 0 the object will typically be stored but ignored on subsequent requests. 103 | @param content The block that provides an object when the cache misses. Can be nil. 104 | @return The object for the given key, or nil. 105 | */ 106 | - (nullable ObjectType)objectForKey:(nonnull NSString *)key expiresIn:(NSTimeInterval)expiresIn withContent:(nullable CKCacheContentBlock)content; 107 | 108 | /** Get the object for the given key. 109 | 110 | If the object has expired, or does not exist in the cache, the content block will be called and 111 | it's results added to the cache and returned. In that case expires will be used for the 112 | returned value when it is stored in the cache. 113 | 114 | @param key The key to look up the object with. 115 | @param expires When the content object should expire. 116 | @param content The block that provides an object when the cache misses. Can be nil. 117 | @return The object for the given key, or nil. 118 | */ 119 | - (nullable ObjectType)objectForKey:(nonnull NSString *)key expires:(nullable NSDate *)expires withContent:(nullable CKCacheContentBlock)content; 120 | 121 | /** Get the object for the given key if it is cached in memory. 122 | 123 | Most caches have an in memory cache to suplement their on disk cache. This method will return 124 | the object for the given key only if it can be retrieved quickly from that in memory cache. 125 | You can call this and if it returns nil, load the object from disk in a background queue. 126 | 127 | Subclasses should return nil if they do not have an in memory cache (the default behavior). 128 | 129 | @param key The key to look up the object with. 130 | @return The object for the given key, or nil. 131 | */ 132 | - (nullable ObjectType)objectInMemoryForKey:(nonnull NSString *)key; 133 | 134 | 135 | /** Add or replace the object for the given key 136 | 137 | If the object already exists in the cache, it is replaced. 138 | 139 | @param obj The object to store in the cache. 140 | @param key The key to store the object as. 141 | */ 142 | - (void)setObject:(nonnull ObjectType)obj forKey:(nonnull NSString *)key; 143 | 144 | /** Add or replace the object for the given key 145 | 146 | If the object already exists in the cache, it is replaced. The object will be returned for the 147 | given key for expiresIn seconds. 148 | 149 | @param obj The object to store in the cache. 150 | @param key The key to store the object as. 151 | @param expiresIn The amount of seconds before the content object will expire. If this number is 152 | <= 0 the object will typically be stored but ignored on subsequent requests. 153 | */ 154 | - (void)setObject:(nonnull ObjectType)obj forKey:(nonnull NSString *)key expiresIn:(NSTimeInterval)expiresIn; 155 | 156 | /** Add or replace the object for the given key 157 | 158 | If the object already exists in the cache, it is replaced. The object will be returned for the 159 | given key until after expires. 160 | 161 | @param obj The object to store in the cache. 162 | @param key The key to store the object as. 163 | @param expires When the content object should expire. 164 | */ 165 | - (void)setObject:(nonnull ObjectType)obj forKey:(nonnull NSString *)key expires:(nullable NSDate *)expires; 166 | 167 | 168 | /** Remove the object stored in key. 169 | 170 | If an object is stored in the cache for the given key, it will be removed. 171 | 172 | @param key The key to store the object as. 173 | */ 174 | - (void)removeObjectForKey:(nonnull NSString *)key; 175 | 176 | /** Remove all objects in the cache. 177 | 178 | Empties any and all objects in the cache. For persistent caches, this is the only way to clear the 179 | cache permanently. 180 | */ 181 | - (void)removeAllObjects; 182 | 183 | /** Removes any objects in the cache that have expired 184 | 185 | This does not garuntee that expired objects will be removed. Some caches, like `CKMemoryCache` 186 | are not able to enumerate all of their cached objects. 187 | 188 | Subclasses should override this if they can remove only expired objects. 189 | */ 190 | - (void)removeExpiredObjects; 191 | 192 | /** Checks the current filesize of the cache and removes enough objects to reduce the size below the maxFilesize. 193 | 194 | Subclasses can call this at their discression. Subclasses decide how they will impliment this as well. By default, if the currentFilesize is larger than the maxFilesize, all objects are removed. 195 | */ 196 | - (void)trimFilesize; 197 | 198 | /** Maximum filesize to use for cached content 199 | 200 | When a max file size is set, if the current file size is larger, all the objects in the cache will be removed. This is not checked until the next time the max is set, which is usually on application start. 201 | */ 202 | @property (nonatomic) NSUInteger maxFilesize; 203 | 204 | /** The current size on disk of the cache 205 | 206 | 207 | */ 208 | @property (nonatomic, readonly) NSUInteger currentFilesize; 209 | 210 | @end 211 | -------------------------------------------------------------------------------- /CacheKit/Classes/CKCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKCache.m 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | // 8 | 9 | #import "CKCache.h" 10 | 11 | @implementation CKCache 12 | 13 | - (instancetype)initWithName:(NSString *)name 14 | { 15 | NSAssert([self class] != [CKCache class], @"You cannot init this class directly. Instead, use a subclass e.g. CKMemoryCache"); 16 | 17 | self = [super init]; 18 | if (self != nil) { 19 | _name = [name copy]; 20 | } 21 | 22 | return self; 23 | } 24 | 25 | - (instancetype)init 26 | { 27 | return [self initWithName:NSStringFromClass(self.class)]; 28 | } 29 | 30 | 31 | - (BOOL)objectExistsForKey:(NSString *)key 32 | { 33 | return [self objectForKey:key] != nil; 34 | } 35 | 36 | - (id)objectForKey:(NSString *)key 37 | { 38 | return [self objectForKey:key expires:nil withContent:nil]; 39 | } 40 | 41 | - (nullable id)objectForKey:(nonnull NSString *)key withContent:(nullable CKCacheContentBlock)content 42 | { 43 | return [self objectForKey:key expires:nil withContent:content]; 44 | } 45 | 46 | - (nullable id)objectForKey:(nonnull NSString *)key expiresIn:(NSTimeInterval)expiresIn withContent:(nullable CKCacheContentBlock)content 47 | { 48 | return [self objectForKey:key expires:[NSDate dateWithTimeIntervalSinceNow:expiresIn] withContent:content]; 49 | } 50 | 51 | - (nullable id)objectForKey:(nonnull NSString *)key expires:(nullable NSDate *)expires withContent:(nullable CKCacheContentBlock)content 52 | { 53 | @throw [NSException exceptionWithName:NSInternalInconsistencyException 54 | reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] 55 | userInfo:nil]; 56 | } 57 | 58 | - (nullable id)objectInMemoryForKey:(nonnull NSString *)key 59 | { 60 | return nil; 61 | } 62 | 63 | 64 | - (void)setObject:(nonnull id)obj forKey:(nonnull NSString *)key 65 | { 66 | [self setObject:obj forKey:key expires:nil]; 67 | } 68 | 69 | - (void)setObject:(nonnull id)obj forKey:(nonnull NSString *)key expiresIn:(NSTimeInterval)expiresIn 70 | { 71 | [self setObject:obj forKey:key expires:[NSDate dateWithTimeIntervalSinceNow:expiresIn]]; 72 | } 73 | 74 | - (void)setObject:(nonnull id)obj forKey:(nonnull NSString *)key expires:(NSDate *)expires 75 | { 76 | @throw [NSException exceptionWithName:NSInternalInconsistencyException 77 | reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] 78 | userInfo:nil]; 79 | } 80 | 81 | 82 | - (void)removeObjectForKey:(NSString *)key 83 | { 84 | @throw [NSException exceptionWithName:NSInternalInconsistencyException 85 | reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] 86 | userInfo:nil]; 87 | } 88 | 89 | - (void)removeAllObjects 90 | { 91 | @throw [NSException exceptionWithName:NSInternalInconsistencyException 92 | reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] 93 | userInfo:nil]; 94 | } 95 | 96 | - (void)removeExpiredObjects 97 | { 98 | } 99 | 100 | - (void)trimFilesize { 101 | if (_maxFilesize > 0 && self.currentFilesize > _maxFilesize) { 102 | [self removeAllObjects]; 103 | } 104 | } 105 | 106 | - (void)setMaxFilesize:(NSUInteger)maxFilesize { 107 | _maxFilesize = maxFilesize; 108 | 109 | [self trimFilesize]; 110 | } 111 | 112 | - (NSUInteger)currentFilesize { 113 | return 0; 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /CacheKit/Classes/CKCacheContent.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKCacheContent.h 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface CKCacheContent<__covariant ObjectType:id> : NSObject 12 | 13 | + (nonnull instancetype)cacheContentWithObject:(nonnull ObjectType)object expires:(nullable NSDate *)expires; 14 | 15 | @property (nonatomic, readonly, strong, nonnull) ObjectType object; 16 | @property (nonatomic, readonly, copy, nullable) NSDate *expires; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /CacheKit/Classes/CKCacheContent.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKCacheContent.m 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | // 8 | 9 | #import "CKCacheContent.h" 10 | 11 | 12 | @interface CKCacheContent () 13 | 14 | @property (nonatomic, readwrite, strong) id object; 15 | @property (nonatomic, readwrite, copy) NSDate *expires; 16 | 17 | @end 18 | 19 | @implementation CKCacheContent 20 | 21 | + (instancetype)cacheContentWithObject:(id)object expires:(NSDate *)expires 22 | { 23 | CKCacheContent *content = [[self alloc] init]; 24 | content.object = object; 25 | content.expires = expires; 26 | 27 | return content; 28 | } 29 | 30 | - (instancetype)initWithCoder:(NSCoder *)decoder 31 | { 32 | self = [super init]; 33 | if (self) { 34 | self.object = [decoder decodeObjectForKey:NSStringFromSelector(@selector(object))]; 35 | self.expires = [decoder decodeObjectForKey:NSStringFromSelector(@selector(expires))]; 36 | } 37 | 38 | return self; 39 | } 40 | 41 | - (void)encodeWithCoder:(NSCoder *)encoder 42 | { 43 | [encoder encodeObject:self.object forKey:NSStringFromSelector(@selector(object))]; 44 | [encoder encodeObject:self.expires forKey:NSStringFromSelector(@selector(expires))]; 45 | } 46 | 47 | - (id)copyWithZone:(NSZone *)zone 48 | { 49 | CKCacheContent *cacheContent = [[[self class] alloc] init]; 50 | cacheContent.object = self.object; 51 | cacheContent.expires = self.expires; 52 | 53 | return cacheContent; 54 | } 55 | 56 | - (BOOL)isEqual:(CKCacheContent *)object 57 | { 58 | if (self == object) { 59 | return YES; 60 | } 61 | 62 | if (![object isKindOfClass:[CKCacheContent class]]) { 63 | return NO; 64 | } 65 | 66 | return [self.object isEqual:object.object] && [self.expires isEqualToDate:object.expires]; 67 | } 68 | 69 | - (NSUInteger)hash 70 | { 71 | return [self.object hash] ^ self.expires.hash; 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /CacheKit/Classes/CKFileCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKFileCache.h 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | // 8 | 9 | #import "CKCache.h" 10 | 11 | 12 | /** CKCache that stores it's objects on the file system 13 | 14 | Objects are persisted to the cache directory using this cache. In addition, there is an in 15 | memory NSCache for quick access. 16 | 17 | All file system access is performed on a serial queue. Writes are done asynchronously and 18 | reads are performed synchronously. This means that a large object write will return 19 | immediately, but if you try to read that object before it has been written to disk, you will 20 | have to wait until it has finished. 21 | 22 | Notice: Objects must conform to the `NSCoding` protocol. 23 | */ 24 | @interface CKFileCache<__covariant ObjectType:id> : CKCache 25 | 26 | /** A shared file cache. 27 | 28 | You can use this cache for general content you want stored on disk. Make sure your keys are 29 | unique across your app by prefixing them with class names or other unique data. 30 | 31 | @return A singleton instance of a file cache. 32 | */ 33 | + (nonnull instancetype)sharedCache; 34 | 35 | /** Clear the internal in memory cache 36 | 37 | This is primarily for testing purposes. 38 | */ 39 | - (void)clearInternalCache; 40 | 41 | /** Wait for file operations to complete 42 | 43 | This method waits until all the file operations have been completed before returning. Use this 44 | to ensure that all files are written to disk before continuing. 45 | 46 | Note that any file operations added after this is called may not be completed when this method 47 | returns. 48 | */ 49 | - (void)waitUntilFilesAreWritten; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /CacheKit/Classes/CKFileCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKFileCache.m 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | // 8 | 9 | #import "CKFileCache.h" 10 | 11 | #import "CKCacheContent.h" 12 | 13 | 14 | @interface CKFileCache () 15 | { 16 | // we still use an internal NSCache to cache what we get from the file system 17 | // the file system is the truth though 18 | NSCache *_internalCache; 19 | NSURL *_directory; 20 | dispatch_queue_t _queue; 21 | } 22 | 23 | @end 24 | 25 | @implementation CKFileCache 26 | 27 | + (instancetype)sharedCache 28 | { 29 | static id sharedInstance; 30 | static dispatch_once_t done; 31 | dispatch_once(&done, ^{ 32 | sharedInstance = [[self alloc] initWithName:@"SharedCache"]; 33 | }); 34 | 35 | return sharedInstance; 36 | } 37 | 38 | - (instancetype)initWithName:(NSString *)name 39 | { 40 | NSAssert(name.length > 0, @"You must provide a name for %@. Use +sharedCache instead.", NSStringFromClass([self class])); 41 | 42 | self = [super initWithName:name]; 43 | if (self) { 44 | NSString *cacheDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 45 | cacheDirectory = [cacheDirectory stringByAppendingPathComponent:[name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 46 | _directory = [NSURL fileURLWithPath:cacheDirectory]; 47 | 48 | NSLog(@"Creating CKFileCache in directory: %@", _directory.path); 49 | 50 | NSError *error = nil; 51 | [[NSFileManager defaultManager] createDirectoryAtURL:_directory withIntermediateDirectories:YES attributes:nil error:&error]; 52 | if (error != nil) { 53 | NSLog(@"Error creating cache directory (%@): %@", _directory, error); 54 | return nil; 55 | } 56 | 57 | _internalCache = [NSCache new]; 58 | _internalCache.name = name; 59 | 60 | _queue = dispatch_queue_create(name.UTF8String, DISPATCH_QUEUE_SERIAL); 61 | } 62 | 63 | return self; 64 | } 65 | 66 | - (instancetype)init 67 | { 68 | @throw [NSException exceptionWithName:NSInternalInconsistencyException 69 | reason:[NSString stringWithFormat:@"You must provide a name for %@. Use +sharedCache instead.", NSStringFromClass([self class])] 70 | userInfo:nil]; 71 | } 72 | 73 | - (NSURL *)_URLForKey:(NSString *)key 74 | { 75 | return [_directory URLByAppendingPathComponent:key isDirectory:NO]; 76 | } 77 | 78 | 79 | - (BOOL)objectExistsForKey:(NSString *)key 80 | { 81 | CKCacheContent *cacheContent = [_internalCache objectForKey:key]; 82 | if (cacheContent != nil) { 83 | return cacheContent.expires.timeIntervalSinceNow >= 0; 84 | } 85 | 86 | return [self objectForKey:key] != nil; 87 | } 88 | 89 | - (id)objectForKey:(NSString *)key expires:(NSDate *)expires withContent:(CKCacheContentBlock)content 90 | { 91 | __block CKCacheContent *cacheContent = [_internalCache objectForKey:key]; 92 | 93 | if (cacheContent == nil) { 94 | dispatch_sync(_queue, ^{ 95 | cacheContent = [NSKeyedUnarchiver unarchiveObjectWithFile:[self _URLForKey:key].path]; 96 | }); 97 | } 98 | 99 | if (cacheContent.expires != nil && cacheContent.expires.timeIntervalSinceNow < 0.0) { 100 | [self removeObjectForKey:key]; 101 | cacheContent = nil; 102 | } 103 | 104 | if (cacheContent == nil && content != nil) { 105 | id object = content(); 106 | if (object != nil) { 107 | cacheContent = [CKCacheContent cacheContentWithObject:object expires:expires]; 108 | [_internalCache setObject:cacheContent forKey:key]; 109 | 110 | dispatch_async(_queue, ^{ 111 | [NSKeyedArchiver archiveRootObject:cacheContent toFile:[self _URLForKey:key].path]; 112 | }); 113 | } 114 | } 115 | 116 | return cacheContent.object; 117 | } 118 | 119 | - (id)objectInMemoryForKey:(NSString *)key 120 | { 121 | CKCacheContent *cacheContent = [_internalCache objectForKey:key]; 122 | 123 | if (cacheContent.expires.timeIntervalSinceNow < 0.0) { 124 | [_internalCache removeObjectForKey:key]; 125 | cacheContent = nil; 126 | } 127 | 128 | return cacheContent.object; 129 | } 130 | 131 | 132 | - (void)setObject:(id)object forKey:(NSString *)key expires:(NSDate *)expires 133 | { 134 | CKCacheContent *cacheContent = [CKCacheContent cacheContentWithObject:object expires:expires]; 135 | [_internalCache setObject:cacheContent forKey:key]; 136 | 137 | dispatch_async(_queue, ^{ 138 | [NSKeyedArchiver archiveRootObject:cacheContent toFile:[self _URLForKey:key].path]; 139 | }); 140 | } 141 | 142 | 143 | - (void)removeObjectForKey:(NSString *)key 144 | { 145 | [_internalCache removeObjectForKey:key]; 146 | dispatch_async(_queue, ^{ 147 | [[NSFileManager defaultManager] removeItemAtURL:[self _URLForKey:key] error:NULL]; 148 | }); 149 | } 150 | 151 | - (void)removeAllObjects 152 | { 153 | [_internalCache removeAllObjects]; 154 | dispatch_async(_queue, ^{ 155 | [[NSFileManager defaultManager] removeItemAtURL:_directory error:NULL]; 156 | [[NSFileManager defaultManager] createDirectoryAtURL:_directory withIntermediateDirectories:YES attributes:nil error:NULL]; 157 | }); 158 | } 159 | 160 | - (void)clearInternalCache 161 | { 162 | [_internalCache removeAllObjects]; 163 | } 164 | 165 | - (void)waitUntilFilesAreWritten 166 | { 167 | dispatch_sync(_queue, ^{}); 168 | } 169 | 170 | - (NSUInteger)currentFilesize { 171 | NSUInteger filesize = 0; 172 | 173 | NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:_directory.path]; 174 | NSString *file = nil; 175 | while ((file = [enumerator nextObject])) { 176 | NSString *path = [_directory.path stringByAppendingPathComponent:file]; 177 | NSError *error = nil; 178 | NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; 179 | if (attributes == nil) { 180 | NSLog(@"Error reading file attributes: %@", error); 181 | } 182 | 183 | filesize += attributes.fileSize; 184 | } 185 | 186 | return filesize; 187 | } 188 | 189 | @end 190 | -------------------------------------------------------------------------------- /CacheKit/Classes/CKMemoryCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKMemoryCache.h 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | // 8 | 9 | #import "CKCache.h" 10 | 11 | 12 | /** An in memory only cache. 13 | 14 | Instances of this class only store their objects in memory, and never persist them. If you create 15 | another cache with the same name, it will not have the same objects. 16 | 17 | Internally, memory caches use `NSCache` to store their objects. Because of this, objects will be 18 | released during memory warning. 19 | */ 20 | @interface CKMemoryCache<__covariant ObjectType:id> : CKCache 21 | 22 | /** A shared memory cache. 23 | 24 | You can use this cache for general content you want stored in memory. Make sure your keys are 25 | unique across your app by prefixing them with class names or other unique data. 26 | 27 | @return A singleton instance of a memory cache. 28 | */ 29 | + (nonnull instancetype)sharedCache; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /CacheKit/Classes/CKMemoryCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKMemoryCache.m 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | // 8 | 9 | #import "CKMemoryCache.h" 10 | 11 | #import "CKCacheContent.h" 12 | 13 | 14 | @interface CKMemoryCache () 15 | { 16 | NSCache *_internalCache; 17 | } 18 | 19 | @end 20 | 21 | @implementation CKMemoryCache 22 | 23 | + (instancetype)sharedCache 24 | { 25 | static id sharedInstance; 26 | static dispatch_once_t done; 27 | dispatch_once(&done, ^{ 28 | sharedInstance = [[self alloc] initWithName:@"SharedCache"]; 29 | }); 30 | 31 | return sharedInstance; 32 | } 33 | 34 | - (instancetype)initWithName:(NSString *)name 35 | { 36 | self = [super initWithName:name]; 37 | if (self) { 38 | _internalCache = [NSCache new]; 39 | _internalCache.name = name; 40 | } 41 | 42 | return self; 43 | } 44 | 45 | 46 | - (BOOL)objectExistsForKey:(NSString *)key 47 | { 48 | CKCacheContent *cacheContent = [_internalCache objectForKey:key]; 49 | 50 | return cacheContent != nil && cacheContent.expires.timeIntervalSinceNow >= 0; 51 | } 52 | 53 | - (id)objectForKey:(NSString *)key expires:(NSDate *)expires withContent:(CKCacheContentBlock)content 54 | { 55 | id object = [self objectInMemoryForKey:key]; 56 | 57 | if (object == nil && content != nil) { 58 | object = content(); 59 | if (object != nil) { 60 | id cacheContent = [CKCacheContent cacheContentWithObject:object expires:expires]; 61 | [_internalCache setObject:cacheContent forKey:key]; 62 | } 63 | } 64 | 65 | return object; 66 | } 67 | 68 | - (id)objectInMemoryForKey:(NSString *)key 69 | { 70 | CKCacheContent *cacheContent = [_internalCache objectForKey:key]; 71 | 72 | if (cacheContent.expires.timeIntervalSinceNow < 0.0) { 73 | [_internalCache removeObjectForKey:key]; 74 | cacheContent = nil; 75 | } 76 | 77 | return cacheContent.object; 78 | } 79 | 80 | 81 | - (void)setObject:(id)object forKey:(NSString *)key expires:(NSDate *)expires 82 | { 83 | CKCacheContent *cacheContent = [CKCacheContent cacheContentWithObject:object expires:expires]; 84 | [_internalCache setObject:cacheContent forKey:key]; 85 | } 86 | 87 | 88 | - (void)removeObjectForKey:(NSString *)key 89 | { 90 | [_internalCache removeObjectForKey:key]; 91 | } 92 | 93 | - (void)removeAllObjects 94 | { 95 | [_internalCache removeAllObjects]; 96 | } 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /CacheKit/Classes/CKNullCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKNullCache.h 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | // 8 | 9 | #import "CKCache.h" 10 | 11 | /** A cache that doesn't store anything anywhere 12 | 13 | Use this for testing. It will always return nil unless given a content block, in which case it 14 | will return the result of that block. 15 | 16 | This is useful for testing your app without caching, but using the same interface as your 17 | production cache. 18 | */ 19 | @interface CKNullCache<__covariant ObjectType:id> : CKCache 20 | 21 | /** A shared null cache. 22 | 23 | This is the safest shared cache because it doesn't store anything anyway. There is no reason 24 | to create multiple null caches. 25 | 26 | @return A shared useless cache. 27 | */ 28 | + (nonnull instancetype)sharedCache; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /CacheKit/Classes/CKNullCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKNullCache.m 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | // 8 | 9 | #import "CKNullCache.h" 10 | 11 | @implementation CKNullCache 12 | 13 | + (instancetype)sharedCache 14 | { 15 | static id sharedInstance; 16 | static dispatch_once_t done; 17 | dispatch_once(&done, ^{ 18 | sharedInstance = [[self alloc] initWithName:@"SharedCache"]; 19 | }); 20 | 21 | return sharedInstance; 22 | } 23 | 24 | - (instancetype)initWithName:(NSString *)name 25 | { 26 | self = [super initWithName:name]; 27 | if (self) { 28 | } 29 | 30 | return self; 31 | } 32 | 33 | 34 | - (BOOL)objectExistsForKey:(NSString *)key 35 | { 36 | return NO; 37 | } 38 | 39 | - (id)objectForKey:(NSString *)key expires:(NSDate *)expires withContent:(CKCacheContentBlock)content 40 | { 41 | if (content != nil) { 42 | return content(); 43 | } 44 | 45 | return nil; 46 | } 47 | 48 | - (id)objectInMemoryForKey:(NSString *)key 49 | { 50 | return [self objectForKey:key expires:nil withContent:nil]; 51 | } 52 | 53 | 54 | - (void)setObject:(id)object forKey:(NSString *)key expires:(NSDate *)expires 55 | { 56 | } 57 | 58 | 59 | - (void)removeObjectForKey:(NSString *)key 60 | { 61 | } 62 | 63 | - (void)removeAllObjects 64 | { 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /CacheKit/Classes/CKSQLiteCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKSQLiteCache.h 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | // 8 | 9 | #import "CKCache.h" 10 | 11 | 12 | /** CKCache that stores it's objects in an SQLite database. 13 | 14 | Objects are persisted to the cache database using this cache. In addition, there is an in 15 | memory NSCache for quick access. 16 | 17 | All database access is performed on a serial queue and is thread safe. 18 | 19 | Notice: Objects must conform to the `NSCoding` protocol. Internally, objects are encoded 20 | using `NSCoding`. Properties are not stored as columns. 21 | */ 22 | @interface CKSQLiteCache<__covariant ObjectType:id> : CKCache 23 | 24 | /** A shared database cache. 25 | 26 | You can use this cache for general content you want stored in a database. Make sure your keys are 27 | unique across your app by prefixing them with class names or other unique data. 28 | 29 | @return A singleton instance of a database cache. 30 | */ 31 | + (nonnull instancetype)sharedCache; 32 | 33 | /** Clear the internal in memory cache 34 | 35 | This is primarily for testing purposes. 36 | */ 37 | - (void)clearInternalCache; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /CacheKit/Classes/CKSQLiteCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKSQLiteCache.m 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | // 8 | 9 | #import "CKSQLiteCache.h" 10 | 11 | #import 12 | 13 | #import "CKCacheContent.h" 14 | 15 | 16 | @interface CKSQLiteCache () 17 | { 18 | // we still use an internal NSCache to cache what we get from the file system 19 | // the file system is the truth though 20 | NSCache *_internalCache; 21 | FMDatabaseQueue *_queue; 22 | } 23 | 24 | @end 25 | 26 | @implementation CKSQLiteCache 27 | 28 | + (instancetype)sharedCache 29 | { 30 | static id sharedInstance; 31 | static dispatch_once_t done; 32 | dispatch_once(&done, ^{ 33 | sharedInstance = [[self alloc] initWithName:@"SharedCache"]; 34 | }); 35 | 36 | return sharedInstance; 37 | } 38 | 39 | - (void)dealloc 40 | { 41 | [_queue close]; 42 | } 43 | 44 | - (instancetype)initWithName:(NSString *)name 45 | { 46 | NSAssert(name.length > 0, @"You must provide a name for %@. Use +sharedCache instead.", NSStringFromClass([self class])); 47 | 48 | self = [super initWithName:name]; 49 | if (self) { 50 | NSString *cacheDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 51 | 52 | NSError *error = nil; 53 | [[NSFileManager defaultManager] createDirectoryAtPath:cacheDirectory withIntermediateDirectories:YES attributes:nil error:&error]; 54 | if (error != nil) { 55 | NSLog(@"Error creating cache directory (%@): %@", cacheDirectory, error); 56 | return nil; 57 | } 58 | 59 | cacheDirectory = [cacheDirectory stringByAppendingPathComponent:[name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 60 | cacheDirectory = [cacheDirectory stringByAppendingPathExtension:@"sqlite"]; 61 | NSLog(@"Creating CKSQLiteCache at: %@", cacheDirectory); 62 | 63 | _queue = [FMDatabaseQueue databaseQueueWithPath:cacheDirectory]; 64 | [_queue inDatabase:^(FMDatabase *db) { 65 | [db executeUpdate:@"CREATE TABLE IF NOT EXISTS objects (key TEXT PRIMARY KEY, object BLOB, expires INTEGER);"]; 66 | 67 | if (![db columnExists:@"createdAt" inTableWithName:@"objects"]) { 68 | [db executeUpdate:@"ALTER TABLE objects ADD COLUMN createdAt INTEGER"]; 69 | } 70 | }]; 71 | 72 | _internalCache = [NSCache new]; 73 | _internalCache.name = name; 74 | } 75 | 76 | return self; 77 | } 78 | 79 | - (instancetype)init 80 | { 81 | @throw [NSException exceptionWithName:NSInternalInconsistencyException 82 | reason:[NSString stringWithFormat:@"You must provide a name for %@. Use +sharedCache instead.", NSStringFromClass([self class])] 83 | userInfo:nil]; 84 | } 85 | 86 | 87 | - (BOOL)objectExistsForKey:(NSString *)key 88 | { 89 | CKCacheContent *cacheContent = [_internalCache objectForKey:key]; 90 | if (cacheContent != nil) { 91 | return cacheContent.expires.timeIntervalSinceNow >= 0; 92 | } 93 | 94 | return [self objectForKey:key] != nil; 95 | } 96 | 97 | - (id)objectForKey:(NSString *)key expires:(NSDate *)expires withContent:(CKCacheContentBlock)content 98 | { 99 | __block CKCacheContent *cacheContent = [_internalCache objectForKey:key]; 100 | 101 | if (cacheContent == nil) { 102 | [_queue inDatabase:^(FMDatabase *db) { 103 | //expires == null? 104 | FMResultSet *s = [db executeQuery:@"SELECT object, expires FROM objects WHERE key = ? AND (expires IS NULL OR expires > ?);", key, @([NSDate new].timeIntervalSince1970)]; 105 | if ([s next]) { 106 | id object = [NSKeyedUnarchiver unarchiveObjectWithData:[s dataForColumn:@"object"]]; 107 | NSDate *expires = nil; 108 | if (![s columnIsNull:@"expires"]) { 109 | expires = [NSDate dateWithTimeIntervalSince1970:[s doubleForColumn:@"expires"]]; 110 | } 111 | cacheContent = [CKCacheContent cacheContentWithObject:object expires:expires]; 112 | } 113 | 114 | [s close]; 115 | }]; 116 | 117 | if (cacheContent != nil) { 118 | [_internalCache setObject:cacheContent forKey:key]; 119 | } 120 | } 121 | 122 | if (cacheContent.expires != nil && cacheContent.expires.timeIntervalSinceNow < 0.0) { 123 | [self removeObjectForKey:key]; 124 | cacheContent = nil; 125 | } 126 | 127 | if (cacheContent == nil && content != nil) { 128 | id object = content(); 129 | if (object != nil) { 130 | cacheContent = [CKCacheContent cacheContentWithObject:object expires:expires]; 131 | [_internalCache setObject:cacheContent forKey:key]; 132 | 133 | NSData *objectData = [NSKeyedArchiver archivedDataWithRootObject:object]; 134 | [_queue inDatabase:^(FMDatabase *db) { 135 | [db executeUpdate:@"INSERT OR REPLACE INTO objects (key, object, expires, createdAt) VALUES (?, ?, ?, ?)", key, objectData, expires, @([NSDate new].timeIntervalSince1970)]; 136 | }]; 137 | 138 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 139 | [self trimFilesize]; 140 | }); 141 | } 142 | } 143 | 144 | return cacheContent.object; 145 | } 146 | 147 | - (id)objectInMemoryForKey:(NSString *)key 148 | { 149 | CKCacheContent *cacheContent = [_internalCache objectForKey:key]; 150 | 151 | if (cacheContent.expires.timeIntervalSinceNow < 0.0) { 152 | [_internalCache removeObjectForKey:key]; 153 | cacheContent = nil; 154 | } 155 | 156 | return cacheContent.object; 157 | } 158 | 159 | 160 | - (void)setObject:(id)object forKey:(NSString *)key expires:(NSDate *)expires 161 | { 162 | CKCacheContent *cacheContent = [CKCacheContent cacheContentWithObject:object expires:expires]; 163 | [_internalCache setObject:cacheContent forKey:key]; 164 | 165 | NSData *objectData = [NSKeyedArchiver archivedDataWithRootObject:object]; 166 | [_queue inDatabase:^(FMDatabase *db) { 167 | [db executeUpdate:@"INSERT OR REPLACE INTO objects (key, object, expires, createdAt) VALUES (?, ?, ?, ?)", key, objectData, expires, @([NSDate new].timeIntervalSince1970)]; 168 | }]; 169 | 170 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 171 | [self trimFilesize]; 172 | }); 173 | } 174 | 175 | 176 | - (void)removeObjectForKey:(NSString *)key 177 | { 178 | [_internalCache removeObjectForKey:key]; 179 | [_queue inDatabase:^(FMDatabase *db) { 180 | [db executeUpdate:@"DELETE FROM objects WHERE key = ?", key]; 181 | }]; 182 | } 183 | 184 | - (void)removeAllObjects 185 | { 186 | [_internalCache removeAllObjects]; 187 | [_queue inDatabase:^(FMDatabase *db) { 188 | [db executeUpdate:@"DELETE FROM objects"]; 189 | 190 | // without this, the db file will not actually get any smaller 191 | [db executeUpdate:@"VACUUM"]; 192 | }]; 193 | } 194 | 195 | - (void)removeExpiredObjects 196 | { 197 | [_queue inDatabase:^(FMDatabase *db) { 198 | [db executeUpdate:@"DELETE FROM objects WHERE expires IS NOT NULL AND expires < ?", @([[NSDate date] timeIntervalSince1970])]; 199 | 200 | // without this, the db file will not actually get any smaller 201 | [db executeUpdate:@"VACUUM"]; 202 | }]; 203 | } 204 | 205 | - (void)clearInternalCache 206 | { 207 | [_internalCache removeAllObjects]; 208 | } 209 | 210 | - (NSUInteger)currentFilesize { 211 | NSError *error = nil; 212 | NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:_queue.path error:&error]; 213 | if (attributes == nil) { 214 | NSLog(@"Error reading file attributes: %@", error); 215 | } 216 | 217 | return (NSUInteger)attributes.fileSize; 218 | } 219 | 220 | - (void)trimFilesize { 221 | NSUInteger currentFileSize = self.currentFilesize; 222 | if (self.maxFilesize > 0 && currentFileSize > self.maxFilesize) { 223 | NSLog(@"%@ currentFilesize (%lu) is greater than maxFilesize (%lu). Trimming cache.", self, (unsigned long)currentFileSize, (unsigned long)self.maxFilesize); 224 | 225 | [_queue inDatabase:^(FMDatabase *db) { 226 | [db executeUpdate:@"DELETE FROM objects WHERE expires IS NOT NULL AND expires < ?", @([[NSDate date] timeIntervalSince1970])]; 227 | 228 | [db executeUpdate:@"DELETE FROM objects WHERE key IN (SELECT key FROM objects ORDER BY createdAt ASC LIMIT (SELECT COUNT(*) FROM objects) / 2)"]; 229 | 230 | // without this, the db file will not actually get any smaller 231 | [db executeUpdate:@"VACUUM"]; 232 | }]; 233 | } 234 | } 235 | 236 | @end 237 | -------------------------------------------------------------------------------- /CacheKit/Classes/CacheKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // CacheKit.h 3 | // Pods 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for CacheKit. 11 | FOUNDATION_EXPORT double CacheKitVersionNumber; 12 | 13 | //! Project version string for CacheKit. 14 | FOUNDATION_EXPORT const unsigned char CacheKitVersionString[]; 15 | 16 | 17 | #import "CKCache.h" 18 | #import "CKCacheContent.h" 19 | #import "CKMemoryCache.h" 20 | #import "CKFileCache.h" 21 | #import "CKSQLiteCache.h" 22 | #import "CKNullCache.h" 23 | 24 | #if TARGET_OS_IOS || TARGET_OS_TV 25 | 26 | #import "CKCache+CKFastImages.h" 27 | #import "CKFastImage.h" 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /CacheKit/FastImages/CKCache+CKFastImages.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKCache+CKFastImages.h 3 | // FastImageCacheDemo 4 | // 5 | // Created by David Beck on 7/24/15. 6 | // Copyright (c) 2015 Path. All rights reserved. 7 | // 8 | 9 | #import "CKCache.h" 10 | 11 | #if TARGET_OS_IOS || TARGET_OS_TV 12 | 13 | #import 14 | 15 | 16 | @interface CKCache (CKFastImages) 17 | 18 | /** Add an image to the cache using `CKFastImage`. 19 | 20 | When you use this method, it encodes the image much faster than using `UIImage`s implimentation of NSCoding. For more info, see `CKFastImage`. 21 | 22 | @param image The image to save to the cache. 23 | @param key The key to store the object as. 24 | @param expires When the content object should expire. 25 | @return The inflated image, which is similar to the original but matches what you would get from `imageForKey:`. 26 | */ 27 | - (UIImage *)setImage:(UIImage *)image forKey:(NSString *)key expires:(NSDate *)expires; 28 | 29 | /** Add an image to the cache using `CKFastImage`. 30 | 31 | When you use this method, it encodes the image much faster than using `UIImage`s implimentation of NSCoding. For more info, see `CKFastImage`. 32 | 33 | The scale of the main screen is used for the generated image. You can get more fine grained control of how the image is encoded using `CKFastImage` directly and using `setObject:forKey:`. You can still use `imageForKey:` to decode the image. 34 | 35 | @param size The size of the image to draw into. 36 | @param drawing The block to execute to draw the image. 37 | @param key The key to store the object as. 38 | @param expires When the content object should expire. 39 | @return The drawn image, ready to be used for drawing. 40 | */ 41 | - (UIImage *)setImageWithSize:(CGSize)size drawing:(void(^)(CGContextRef context))drawing forKey:(NSString *)key expires:(NSDate *)expires; 42 | 43 | /** Decode an image that was encoded with `CKFastImage`. 44 | 45 | The image that you get back from `CKFastImage` (and by extention, this method) is already inflated and ready for drawing without delay. 46 | 47 | @param key The key to look up the object with. 48 | @return The image for the given key, or nil if it doesn't exist in the cache. 49 | */ 50 | - (UIImage *)imageForKey:(NSString *)key; 51 | 52 | @end 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /CacheKit/FastImages/CKCache+CKFastImages.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKCache+CKFastImages.m 3 | // FastImageCacheDemo 4 | // 5 | // Created by David Beck on 7/24/15. 6 | // Copyright (c) 2015 Path. All rights reserved. 7 | // 8 | 9 | #import "CKCache+CKFastImages.h" 10 | 11 | #import "CKFastImage.h" 12 | 13 | 14 | #if TARGET_OS_IOS || TARGET_OS_TV 15 | 16 | 17 | inline size_t FICByteAlign(size_t width, size_t alignment) { 18 | return ((width + (alignment - 1)) / alignment) * alignment; 19 | } 20 | 21 | inline size_t FICByteAlignForCoreAnimation(size_t bytesPerRow) { 22 | return FICByteAlign(bytesPerRow, 64); 23 | } 24 | 25 | 26 | @implementation CKCache (CKFastImages) 27 | 28 | - (UIImage *)setImage:(UIImage *)image forKey:(NSString *)key expires:(NSDate *)expires { 29 | CKFastImage *imageInfo = [[CKFastImage alloc] initWithImage:image]; 30 | 31 | [self setObject:imageInfo forKey:key expires:expires]; 32 | 33 | return imageInfo.image; 34 | } 35 | 36 | - (UIImage *)setImageWithSize:(CGSize)size drawing:(void(^)(CGContextRef context))drawing forKey:(NSString *)key expires:(NSDate *)expires { 37 | CKFastImage *imageInfo = [[CKFastImage alloc] initWithSize:size scale:[UIScreen mainScreen].scale style:CKFastImageStyle32BitBGRA drawing:drawing]; 38 | 39 | [self setObject:imageInfo forKey:key expires:expires]; 40 | 41 | return imageInfo.image; 42 | } 43 | 44 | - (UIImage *)imageForKey:(NSString *)key { 45 | CKFastImage *imageInfo = [self objectForKey:key]; 46 | 47 | if ([imageInfo isKindOfClass:[UIImage class]]) { 48 | return (UIImage *)imageInfo; 49 | } else if ([imageInfo isKindOfClass:[CKFastImage class]]) { 50 | return imageInfo.image; 51 | } 52 | 53 | return nil; 54 | } 55 | 56 | @end 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /CacheKit/FastImages/CKFastImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKFastImage.h 3 | // FastImageCacheDemo 4 | // 5 | // Created by David Beck on 7/24/15. 6 | // Copyright (c) 2015 Path. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | #if TARGET_OS_IOS || TARGET_OS_TV 13 | 14 | #import 15 | 16 | 17 | typedef NS_ENUM(uint8_t, CKFastImageStyle) { 18 | CKFastImageStyle32BitBGRA = 0, 19 | CKFastImageStyle32BitBGR, 20 | CKFastImageStyle16BitBGR, 21 | CKFastImageStyle8BitGrayscale, 22 | }; 23 | 24 | 25 | /** A wrapper for encoding and decoding images very fast 26 | 27 | You can use this class to encode `UIImage`s with the exact memory layout that it uses at runtime. Normally, when you save an image, either as a JPG, PNG or using NSCoding, it compresses the data into a format that is smaller on disk. When you create an image from that data, it doesn't inflate the data to it's natural layout until it is used. This causes issues where an image is created in a background thread, but the work of inflating it still happens on the main thread when the image is drawn. For caching, it wastes time decoding an image every time, and often the filesize improvements are negligable for small thumbnails. 28 | 29 | By drawing an image at the exact thumbnail size you display on screen and caching it, you can significantly speed up your app's scrolling and loading experience. 30 | */ 31 | 32 | @interface CKFastImage : NSObject 33 | 34 | /** Create a `CKFastImage` from an existing image. 35 | 36 | This method calls `initWithSize:scale:style:style:drawing:` and draws the image into the new context. For this reason, the `image` property will be a different object than the one passed into this method. Only the size and scale will be preserved. Any animations, resize settings or other properties on the image will be lost. The image that is generated will be inflated, and ready to draw, so you may see a performance improvement by using the generated image instead of the original passed to this method. 37 | 38 | If you have any adjustments to make to the image such as rounded corners of drop shadows, you should consider calling `initWithSize:scale:style:style:drawing:` so that those operations can be cached. 39 | 40 | @param image The image to encode. 41 | @return A new `CKFastImage` instance. 42 | */ 43 | - (instancetype)initWithImage:(UIImage *)image; 44 | 45 | 46 | /** Create a `CKFastImage` from drawing code. 47 | 48 | You can get the generated image from this drawing using the `image` property. When an image is created with this method, the image property is set directly from the drawing and does not need to be decoded again. 49 | 50 | @param size The size of the image to create. This will be the size, in points of the context passed to `drawing`. 51 | @param scale The scale of the image to create. For instance, you could pass `-[UIScreen scale]` to match the devices scale. The context will be scaled using this value so that the drawing block will draw at this level of detail. 52 | @param style The color style of the image to draw. Use something besides `CKFastImageStyle32BitBGRA` to create smaller files if you don't need the highest color profile or are using images without alpha. 53 | @param drawing The drawing block that provides the content of the image. This is helpful to apply any kind of adjustments to the image like rounded corners or drop shadows, or to provide a completely custom drawing. 54 | @return A new `CKFastImage` instance. 55 | */ 56 | - (instancetype)initWithSize:(CGSize)size scale:(CGFloat)scale style:(CKFastImageStyle)style drawing:(void(^)(CGContextRef context))drawing; 57 | 58 | /** Create a `CKFastImage` directly from bytes. 59 | 60 | Use this if you want to serialize an image in some way other than NSCoding. 61 | 62 | @warning `CKFastImage` does not copy the bytes you pass into this initializere. The bytes you pass in here will be freed when the object is deallocated. If you have a reference to a buffer that you do not control, make sure to memcpy them to a new buffer and pass that to this method. 63 | 64 | @param bytes The buffer containing image data. 65 | @return A new `CKFastImage` instance. 66 | */ 67 | - (instancetype)initWithBytesNoCopy:(void *)data length:(NSUInteger)length size:(CGSize)size scale:(CGFloat)scale style:(CKFastImageStyle)style __attribute((deprecated(("Use initWithData:size:scale:style: instead.")))); 68 | 69 | /** Create a `CKFastImage` directly from data. 70 | 71 | Use this if you want to serialize an image in some way other than NSCoding. 72 | 73 | @param bytes The buffer containing image data. 74 | @return A new `CKFastImage` instance. 75 | */ 76 | - (instancetype)initWithData:(NSData *)data size:(CGSize)size scale:(CGFloat)scale style:(CKFastImageStyle)style NS_DESIGNATED_INITIALIZER; 77 | 78 | 79 | /** The bytes representing the image. 80 | 81 | You can use this to save the image in some form besides NSCoding. 82 | */ 83 | @property (nonatomic, readonly) NSData *data; 84 | 85 | 86 | /** The image backed by `bytes`. 87 | 88 | If the `CKFastImage` was created from bytes directly, or decoded with NSCoding, this will be created the first time it is called. 89 | */ 90 | @property (nonatomic, readonly) UIImage *image; 91 | 92 | 93 | /** The scale of the image. 94 | 95 | For instance, 2.0 for retina screens. 96 | */ 97 | @property (nonatomic, readonly) CGFloat scale; 98 | 99 | /** The size, in points of the image. 100 | */ 101 | @property (nonatomic, readonly) CGSize size; 102 | 103 | /** The color style of the image. 104 | 105 | If you have images that don't use the full color profile, for instance images without alpha, grayscale images, or really small images that don't use the full range of colors, you can use a different color style than `CKFastImageStyle32BitBGRA` to save space on disk as well as speed up drawing. 106 | */ 107 | @property (nonatomic, readonly) CKFastImageStyle style; 108 | 109 | @end 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /CacheKit/FastImages/CKFastImage.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKFastImage.m 3 | // FastImageCacheDemo 4 | // 5 | // Created by David Beck on 7/24/15. 6 | // Copyright (c) 2015 Path. All rights reserved. 7 | // 8 | 9 | #import "CKFastImage.h" 10 | 11 | 12 | #if TARGET_OS_IOS || TARGET_OS_TV 13 | 14 | @implementation CKFastImage 15 | 16 | @synthesize image = _image; 17 | 18 | - (CGSize)pixelSize { 19 | return CGSizeMake(_size.width * _scale, _size.height * _scale); 20 | } 21 | 22 | - (CGBitmapInfo)bitmapInfo { 23 | CGBitmapInfo info; 24 | switch (_style) { 25 | case CKFastImageStyle32BitBGRA: 26 | info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; 27 | break; 28 | case CKFastImageStyle32BitBGR: 29 | info = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; 30 | break; 31 | case CKFastImageStyle16BitBGR: 32 | info = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder16Host; 33 | break; 34 | case CKFastImageStyle8BitGrayscale: 35 | info = (CGBitmapInfo)kCGImageAlphaNone; 36 | break; 37 | } 38 | return info; 39 | } 40 | 41 | - (NSInteger)bytesPerPixel { 42 | NSInteger bytesPerPixel; 43 | switch (_style) { 44 | case CKFastImageStyle32BitBGRA: 45 | case CKFastImageStyle32BitBGR: 46 | bytesPerPixel = 4; 47 | break; 48 | case CKFastImageStyle16BitBGR: 49 | bytesPerPixel = 2; 50 | break; 51 | case CKFastImageStyle8BitGrayscale: 52 | bytesPerPixel = 1; 53 | break; 54 | } 55 | return bytesPerPixel; 56 | } 57 | 58 | - (NSInteger)bitsPerComponent { 59 | NSInteger bitsPerComponent; 60 | switch (_style) { 61 | case CKFastImageStyle32BitBGRA: 62 | case CKFastImageStyle32BitBGR: 63 | case CKFastImageStyle8BitGrayscale: 64 | bitsPerComponent = 8; 65 | break; 66 | case CKFastImageStyle16BitBGR: 67 | bitsPerComponent = 5; 68 | break; 69 | } 70 | return bitsPerComponent; 71 | } 72 | 73 | - (BOOL)isGrayscale { 74 | BOOL isGrayscale; 75 | switch (_style) { 76 | case CKFastImageStyle32BitBGRA: 77 | case CKFastImageStyle32BitBGR: 78 | case CKFastImageStyle16BitBGR: 79 | isGrayscale = NO; 80 | break; 81 | case CKFastImageStyle8BitGrayscale: 82 | isGrayscale = YES; 83 | break; 84 | } 85 | return isGrayscale; 86 | } 87 | 88 | - (NSInteger)imageRowLength { 89 | NSInteger alignment = 64; // magic number the cpu/gpu likes 90 | NSInteger bytesPerRow = [self pixelSize].width * [self bytesPerPixel]; 91 | return ((bytesPerRow + (alignment - 1)) / alignment) * alignment; 92 | } 93 | 94 | - (UIImage *)image { 95 | if (_image == nil) { 96 | _image = [self _decodeImage]; 97 | } 98 | 99 | return _image; 100 | } 101 | 102 | 103 | #pragma mark - Initialization 104 | 105 | - (instancetype)init 106 | { 107 | return [self initWithData:nil size:CGSizeZero scale:1.0 style:CKFastImageStyle32BitBGRA]; 108 | } 109 | 110 | - (instancetype)initWithImage:(UIImage *)image 111 | { 112 | BOOL hasAlpha = CGImageGetAlphaInfo(image.CGImage) != kCGImageAlphaNone; 113 | CKFastImageStyle style = hasAlpha ? CKFastImageStyle32BitBGRA : CKFastImageStyle32BitBGR; 114 | 115 | return [self initWithSize:image.size scale:image.scale style:style drawing:^(CGContextRef context) { 116 | [image drawAtPoint:CGPointZero]; 117 | }]; 118 | } 119 | 120 | - (instancetype)initWithSize:(CGSize)size scale:(CGFloat)scale style:(CKFastImageStyle)style drawing:(void(^)(CGContextRef context))drawing { 121 | _size = size; 122 | _scale = scale; 123 | _style = style; 124 | 125 | 126 | CGBitmapInfo bitmapInfo = [self bitmapInfo]; 127 | CGColorSpaceRef colorSpace = [self isGrayscale] ? CGColorSpaceCreateDeviceGray() : CGColorSpaceCreateDeviceRGB(); 128 | NSInteger bitsPerComponent = [self bitsPerComponent]; 129 | NSInteger imageRowLength = [self imageRowLength]; 130 | 131 | 132 | NSUInteger length = imageRowLength * [self pixelSize].height; 133 | void *bytes = malloc(length); 134 | 135 | 136 | CGContextRef context = CGBitmapContextCreate(bytes, [self pixelSize].width, [self pixelSize].height, bitsPerComponent, imageRowLength, colorSpace, bitmapInfo); 137 | 138 | CGContextTranslateCTM(context, 0, [self pixelSize].height); 139 | CGContextScaleCTM(context, _scale, -_scale); 140 | 141 | if (_style == CKFastImageStyle32BitBGRA) { 142 | CGContextClearRect(context, CGRectMake(0.0, 0.0, size.width, size.height)); 143 | } 144 | 145 | // Call drawing block to allow client to draw into the context 146 | UIGraphicsPushContext(context); 147 | drawing(context); 148 | UIGraphicsPopContext(); 149 | 150 | CGImageRef imageRef = CGBitmapContextCreateImage(context); 151 | CGContextRelease(context); 152 | CGColorSpaceRelease(colorSpace); 153 | 154 | 155 | NSData *data = [NSData dataWithBytesNoCopy:bytes length:length]; 156 | self = [self initWithData:data size:size scale:scale style:style]; 157 | if (self != nil) { 158 | _image = [UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp]; 159 | } 160 | 161 | CGImageRelease(imageRef); 162 | 163 | return self; 164 | } 165 | 166 | - (instancetype)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)length size:(CGSize)size scale:(CGFloat)scale style:(CKFastImageStyle)style 167 | { 168 | NSData *data = [NSData dataWithBytesNoCopy:bytes length:length]; 169 | 170 | return [self initWithData:data size:size scale:scale style:style]; 171 | } 172 | 173 | - (instancetype)initWithData:(NSData *)data size:(CGSize)size scale:(CGFloat)scale style:(CKFastImageStyle)style { 174 | self = [super init]; 175 | if (self != nil) { 176 | _data = [data copy]; 177 | _size = size; 178 | _scale = scale; 179 | _style = style; 180 | } 181 | 182 | return self; 183 | } 184 | 185 | 186 | #pragma mark - NSCoding 187 | 188 | - (instancetype)initWithCoder:(NSCoder *)coder 189 | { 190 | self = [self init]; 191 | if (self) { 192 | _data = [coder decodeObjectForKey:@"data"]; 193 | if (_data == nil) { // backwards compatability 194 | NSUInteger length = 0; 195 | const void *bytes = [coder decodeBytesForKey:@"bytes" returnedLength:&length]; 196 | 197 | _data = [NSData dataWithBytes:bytes length:length]; 198 | } 199 | 200 | _scale = [coder decodeDoubleForKey:@"scale"]; 201 | _size = CGSizeMake([coder decodeDoubleForKey:@"size.x"], [coder decodeDoubleForKey:@"size.y"]); 202 | _style = [coder decodeInt32ForKey:@"style"]; 203 | } 204 | 205 | return self; 206 | } 207 | 208 | - (void)encodeWithCoder:(NSCoder *)coder { 209 | [coder encodeObject:_data forKey:@"data"]; 210 | 211 | [coder encodeDouble:_scale forKey:@"scale"]; 212 | [coder encodeDouble:_size.width forKey:@"size.x"]; 213 | [coder encodeDouble:_size.height forKey:@"size.y"]; 214 | [coder encodeInt32:_style forKey:@"style"]; 215 | } 216 | 217 | - (UIImage *)_decodeImage { 218 | if (_data.length == 0) { 219 | return nil; 220 | } 221 | 222 | UIImage *image = nil; 223 | 224 | 225 | CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((CFDataRef)_data); 226 | 227 | CGBitmapInfo bitmapInfo = [self bitmapInfo]; 228 | CGColorSpaceRef colorSpace = [self isGrayscale] ? CGColorSpaceCreateDeviceGray() : CGColorSpaceCreateDeviceRGB(); 229 | NSInteger bitsPerComponent = [self bitsPerComponent]; 230 | NSInteger imageRowLength = [self imageRowLength]; 231 | NSInteger bytesPerPixel = [self bytesPerPixel]; 232 | NSInteger bitsPerPixel = bytesPerPixel * 8; 233 | 234 | CGImageRef imageRef = CGImageCreate([self pixelSize].width, [self pixelSize].height, bitsPerComponent, bitsPerPixel, imageRowLength, colorSpace, bitmapInfo, dataProvider, NULL, false, (CGColorRenderingIntent)0); 235 | CGDataProviderRelease(dataProvider); 236 | CGColorSpaceRelease(colorSpace); 237 | 238 | if (imageRef != NULL) { 239 | image = [[UIImage alloc] initWithCGImage:imageRef scale:_scale orientation:UIImageOrientationUp]; 240 | CGImageRelease(imageRef); 241 | } else { 242 | NSLog(@"-[CKFastImage decodeImage] failed to decode image."); 243 | } 244 | 245 | return image; 246 | } 247 | 248 | @end 249 | 250 | #endif 251 | -------------------------------------------------------------------------------- /CacheKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CacheKit/ModuleMap: -------------------------------------------------------------------------------- 1 | framework module CacheKit { 2 | umbrella header "CacheKit/CacheKit.h" 3 | 4 | export * 5 | module * { export * } 6 | } -------------------------------------------------------------------------------- /CacheKitTests/CKFileCacheTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKFileCacheTests.m 3 | // CacheKit 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // Copyright (c) 2014 David Beck. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import 13 | 14 | 15 | @interface CKFileCacheTests : XCTestCase 16 | { 17 | CKFileCache *_cache; 18 | } 19 | 20 | @end 21 | 22 | @implementation CKFileCacheTests 23 | 24 | - (void)setUp 25 | { 26 | [super setUp]; 27 | 28 | _cache = [[CKFileCache alloc] initWithName:@"Tests"]; 29 | } 30 | 31 | - (void)tearDown 32 | { 33 | [_cache removeAllObjects]; 34 | [_cache waitUntilFilesAreWritten]; 35 | 36 | [super tearDown]; 37 | } 38 | 39 | - (void)testReadWrite 40 | { 41 | [_cache setObject:@1 forKey:@"A"]; 42 | XCTAssertTrue([_cache objectExistsForKey:@"A"], @"objectExistsForKey for key just added"); 43 | XCTAssertEqualObjects([_cache objectForKey:@"A"], @1, @"objectForKey for key just added"); 44 | 45 | [_cache removeObjectForKey:@"A"]; 46 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"removeObjectForKey did not remove object"); 47 | } 48 | 49 | - (void)testRemoveAll 50 | { 51 | [_cache setObject:@1 forKey:@"A"]; 52 | [_cache setObject:@2 forKey:@"B"]; 53 | [_cache setObject:@3 forKey:@"C"]; 54 | 55 | [_cache removeAllObjects]; 56 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"removeAllObjects did not remove object"); 57 | XCTAssertFalse([_cache objectExistsForKey:@"B"], @"removeAllObjects did not remove object"); 58 | XCTAssertFalse([_cache objectExistsForKey:@"C"], @"removeAllObjects did not remove object"); 59 | } 60 | 61 | - (void)testContentBlock 62 | { 63 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"objectExistsForKey at beginning of test."); 64 | 65 | id object = [_cache objectForKey:@"A" withContent:^{ 66 | return @1; 67 | }]; 68 | XCTAssertEqualObjects(object, @1, @"objectForKey:withContent: did not return correct object."); 69 | 70 | XCTAssertEqualObjects([_cache objectForKey:@"A"], @1, @"objectForKey for key just added"); 71 | } 72 | 73 | - (void)testExpiration 74 | { 75 | [_cache setObject:@1 forKey:@"A" expires:[NSDate dateWithTimeIntervalSinceNow:-1]]; 76 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"Expired object exists."); 77 | XCTAssertEqualObjects([_cache objectForKey:@"A"], nil, @"Expired object returned."); 78 | 79 | id object = [_cache objectForKey:@"A" withContent:^{ 80 | return @2; 81 | }]; 82 | XCTAssertEqualObjects(object, @2, @"objectForKey:withContent: did not return correct object."); 83 | 84 | XCTAssertEqualObjects([_cache objectForKey:@"A"], @2, @"objectForKey for key just added"); 85 | } 86 | 87 | - (void)testPersistence 88 | { 89 | NSString *name = _cache.name; 90 | 91 | [_cache setObject:@1 forKey:@"A"]; 92 | [_cache setObject:@2 forKey:@"B"]; 93 | [_cache setObject:@3 forKey:@"C"]; 94 | [_cache waitUntilFilesAreWritten]; 95 | 96 | _cache = [[CKFileCache alloc] initWithName:name]; 97 | 98 | XCTAssertEqualObjects([_cache objectForKey:@"B"], @2, @"Cache not persisted."); 99 | } 100 | 101 | - (void)testCacheHitPerformance 102 | { 103 | [_cache setObject:@1 forKey:@"A"]; 104 | [_cache setObject:@2 forKey:@"B"]; 105 | [_cache setObject:@3 forKey:@"C"]; 106 | for (NSUInteger i = 0; i < 1000; i++) { 107 | [_cache setObject:@(i) forKey:[NSString stringWithFormat:@"%lu", (unsigned long)i]]; 108 | } 109 | 110 | [self measureBlock:^{ 111 | [_cache clearInternalCache]; 112 | XCTAssertEqualObjects([_cache objectForKey:@"B" withContent:^{ 113 | return @5; 114 | }], @2, @"Inccorrect cache hit."); 115 | }]; 116 | } 117 | 118 | - (void)testCacheLargeHitPerformance 119 | { 120 | NSUInteger size = 1024 * 1024; 121 | NSMutableData* data = [NSMutableData dataWithCapacity:size]; 122 | for(NSUInteger i = 0; i < size/sizeof(u_int32_t); i++) { 123 | u_int32_t randomBits = arc4random(); 124 | [data appendBytes:(void*)&randomBits length:sizeof(u_int32_t)]; 125 | } 126 | NSData *aData = [data copy]; 127 | 128 | [_cache setObject:aData forKey:@"A"]; 129 | [_cache setObject:@2 forKey:@"B"]; 130 | [_cache setObject:@3 forKey:@"C"]; 131 | for (NSUInteger i = 0; i < 100; i++) { 132 | [data appendBytes:(void*)&i length:sizeof(i)]; 133 | [_cache setObject:data forKey:[NSString stringWithFormat:@"%lu", (unsigned long)i]]; 134 | } 135 | 136 | [self measureBlock:^{ 137 | [_cache clearInternalCache]; 138 | XCTAssertEqualObjects([_cache objectForKey:@"A"], aData, @"Inccorrect cache hit."); 139 | }]; 140 | } 141 | 142 | @end 143 | -------------------------------------------------------------------------------- /CacheKitTests/CKMemoryCacheTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKMemoryCacheTests.m 3 | // CacheKit 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // Copyright (c) 2014 David Beck. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import 13 | 14 | 15 | @interface CKMemoryCacheTests : XCTestCase 16 | { 17 | CKMemoryCache *_cache; 18 | } 19 | 20 | @end 21 | 22 | @implementation CKMemoryCacheTests 23 | 24 | - (void)setUp 25 | { 26 | [super setUp]; 27 | 28 | _cache = [[CKMemoryCache alloc] initWithName:@"Tests"]; 29 | } 30 | 31 | - (void)tearDown 32 | { 33 | // Put teardown code here. This method is called after the invocation of each test method in the class. 34 | [super tearDown]; 35 | } 36 | 37 | - (void)testReadWrite 38 | { 39 | [_cache setObject:@1 forKey:@"A"]; 40 | XCTAssertTrue([_cache objectExistsForKey:@"A"], @"objectExistsForKey for key just added"); 41 | XCTAssertEqualObjects([_cache objectForKey:@"A"], @1, @"objectForKey for key just added"); 42 | 43 | [_cache removeObjectForKey:@"A"]; 44 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"removeObjectForKey did not remove object"); 45 | } 46 | 47 | - (void)testRemoveAll 48 | { 49 | [_cache setObject:@1 forKey:@"A"]; 50 | [_cache setObject:@2 forKey:@"B"]; 51 | [_cache setObject:@3 forKey:@"C"]; 52 | 53 | [_cache removeAllObjects]; 54 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"removeAllObjects did not remove object"); 55 | XCTAssertFalse([_cache objectExistsForKey:@"B"], @"removeAllObjects did not remove object"); 56 | XCTAssertFalse([_cache objectExistsForKey:@"C"], @"removeAllObjects did not remove object"); 57 | } 58 | 59 | - (void)testContentBlock 60 | { 61 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"removeObjectForKey did not remove object"); 62 | 63 | id object = [_cache objectForKey:@"A" withContent:^{ 64 | return @1; 65 | }]; 66 | XCTAssertEqualObjects(object, @1, @"objectForKey:withContent: did not return correct object."); 67 | 68 | XCTAssertEqualObjects([_cache objectForKey:@"A"], @1, @"objectForKey for key just added"); 69 | } 70 | 71 | - (void)testExpiration 72 | { 73 | [_cache setObject:@1 forKey:@"A" expires:[NSDate dateWithTimeIntervalSinceNow:-1]]; 74 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"Expired object exists."); 75 | XCTAssertEqualObjects([_cache objectForKey:@"A"], nil, @"Expired object returned."); 76 | 77 | id object = [_cache objectForKey:@"A" withContent:^{ 78 | return @2; 79 | }]; 80 | XCTAssertEqualObjects(object, @2, @"objectForKey:withContent: did not return correct object."); 81 | 82 | XCTAssertEqualObjects([_cache objectForKey:@"A"], @2, @"objectForKey for key just added"); 83 | } 84 | 85 | - (void)testCacheHitPerformance 86 | { 87 | [_cache setObject:@1 forKey:@"A"]; 88 | [_cache setObject:@2 forKey:@"B"]; 89 | [_cache setObject:@3 forKey:@"C"]; 90 | for (NSUInteger i = 0; i < 1000; i++) { 91 | [_cache setObject:@(i) forKey:[NSString stringWithFormat:@"%lu", (unsigned long)i]]; 92 | } 93 | 94 | [self measureBlock:^{ 95 | XCTAssertEqualObjects([_cache objectForKey:@"B" withContent:^{ 96 | return @5; 97 | }], @2, @"Inccorrect cache hit."); 98 | }]; 99 | } 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /CacheKitTests/CKNullCacheTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKNullCacheTests.m 3 | // CacheKit 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // Copyright (c) 2014 David Beck. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import 13 | 14 | 15 | @interface CKNullCacheTests : XCTestCase 16 | { 17 | CKNullCache *_cache; 18 | } 19 | 20 | @end 21 | 22 | @implementation CKNullCacheTests 23 | 24 | - (void)setUp 25 | { 26 | [super setUp]; 27 | 28 | _cache = [[CKNullCache alloc] initWithName:@"Tests"]; 29 | } 30 | 31 | - (void)tearDown 32 | { 33 | [_cache removeAllObjects]; 34 | 35 | [super tearDown]; 36 | } 37 | 38 | - (void)testReadWrite 39 | { 40 | [_cache setObject:@1 forKey:@"A"]; 41 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"objectExistsForKey should always be NO"); 42 | XCTAssertEqualObjects([_cache objectForKey:@"A"], nil, @"objectForKey should always be nil"); 43 | 44 | [_cache removeObjectForKey:@"A"]; 45 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"removeObjectForKey did not remove object"); 46 | } 47 | 48 | - (void)testContentBlock 49 | { 50 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"Cache should be empty at the begining of the test."); 51 | 52 | id object = [_cache objectForKey:@"A" withContent:^{ 53 | return @1; 54 | }]; 55 | XCTAssertEqualObjects(object, @1, @"objectForKey:withContent: did not return correct object."); 56 | 57 | XCTAssertEqualObjects([_cache objectForKey:@"A"], nil, @"objectForKey should always be nil"); 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /CacheKitTests/CKSQLiteCacheTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKSQLiteCacheTests.m 3 | // CacheKit 4 | // 5 | // Created by David Beck on 10/13/14. 6 | // Copyright (c) 2014 David Beck. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import 13 | 14 | 15 | @interface CKSQLiteCacheTests : XCTestCase 16 | { 17 | CKSQLiteCache *_cache; 18 | } 19 | 20 | @end 21 | 22 | @implementation CKSQLiteCacheTests 23 | 24 | - (void)setUp 25 | { 26 | [super setUp]; 27 | 28 | _cache = [[CKSQLiteCache alloc] initWithName:@"Tests"]; 29 | } 30 | 31 | - (void)tearDown 32 | { 33 | [_cache removeAllObjects]; 34 | 35 | [super tearDown]; 36 | } 37 | 38 | - (NSData *)_randomDataWithSize:(NSInteger)size { 39 | NSMutableData* data = [NSMutableData dataWithCapacity:size]; 40 | for(NSUInteger i = 0; i < size/sizeof(u_int32_t); i++) { 41 | u_int32_t randomBits = arc4random(); 42 | [data appendBytes:(void*)&randomBits length:sizeof(u_int32_t)]; 43 | } 44 | return [data copy]; 45 | } 46 | 47 | - (void)testReadWrite 48 | { 49 | [_cache setObject:@1 forKey:@"A"]; 50 | XCTAssertTrue([_cache objectExistsForKey:@"A"], @"objectExistsForKey for key just added"); 51 | XCTAssertEqualObjects([_cache objectForKey:@"A"], @1, @"objectForKey for key just added"); 52 | 53 | [_cache removeObjectForKey:@"A"]; 54 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"removeObjectForKey did not remove object"); 55 | } 56 | 57 | - (void)testRemoveAll 58 | { 59 | [_cache setObject:@1 forKey:@"A"]; 60 | [_cache setObject:@2 forKey:@"B"]; 61 | [_cache setObject:@3 forKey:@"C"]; 62 | 63 | [_cache removeAllObjects]; 64 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"removeAllObjects did not remove object"); 65 | XCTAssertFalse([_cache objectExistsForKey:@"B"], @"removeAllObjects did not remove object"); 66 | XCTAssertFalse([_cache objectExistsForKey:@"C"], @"removeAllObjects did not remove object"); 67 | } 68 | 69 | - (void)testContentBlock 70 | { 71 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"objectExistsForKey at beginning of test."); 72 | 73 | NSNumber *object = [_cache objectForKey:@"A" withContent:^{ 74 | return @1; 75 | }]; 76 | XCTAssertEqualObjects(object, @1, @"objectForKey:withContent: did not return correct object."); 77 | 78 | XCTAssertEqualObjects([_cache objectForKey:@"A"], @1, @"objectForKey for key just added"); 79 | } 80 | 81 | - (void)testExpiration 82 | { 83 | [_cache setObject:@1 forKey:@"A" expires:[NSDate dateWithTimeIntervalSinceNow:-1]]; 84 | XCTAssertFalse([_cache objectExistsForKey:@"A"], @"Expired object exists."); 85 | XCTAssertEqualObjects([_cache objectForKey:@"A"], nil, @"Expired object returned."); 86 | 87 | NSNumber *object = [_cache objectForKey:@"A" withContent:^{ 88 | return @2; 89 | }]; 90 | XCTAssertEqualObjects(object, @2, @"objectForKey:withContent: did not return correct object."); 91 | 92 | XCTAssertEqualObjects([_cache objectForKey:@"A"], @2, @"objectForKey for key just added"); 93 | } 94 | 95 | - (void)testPersistence 96 | { 97 | NSString *name = _cache.name; 98 | 99 | [_cache setObject:@1 forKey:@"A"]; 100 | [_cache setObject:@2 forKey:@"B"]; 101 | [_cache setObject:@3 forKey:@"C"]; 102 | 103 | _cache = [[CKSQLiteCache alloc] initWithName:name]; 104 | 105 | XCTAssertEqualObjects([_cache objectForKey:@"B"], @2, @"Cache not persisted."); 106 | } 107 | 108 | - (void)testRemovingExpiredObjects 109 | { 110 | [_cache setObject:@1 forKey:@"A" expiresIn:100]; 111 | [_cache setObject:@2 forKey:@"B"]; 112 | [_cache setObject:@3 forKey:@"C" expiresIn:-100]; 113 | 114 | [_cache removeExpiredObjects]; 115 | 116 | XCTAssertEqualObjects([_cache objectForKey:@"A"], @1, @"Object with future expiration removed."); 117 | XCTAssertEqualObjects([_cache objectForKey:@"B"], @2, @"Object without expiration removed."); 118 | XCTAssertFalse([_cache objectExistsForKey:@"C"], @"Expired object exists."); 119 | XCTAssertEqualObjects([_cache objectForKey:@"C"], nil, @"Object with past expiration not removed."); 120 | } 121 | 122 | - (void)testCacheHitPerformance 123 | { 124 | [_cache setObject:@1 forKey:@"A"]; 125 | [_cache setObject:@2 forKey:@"B"]; 126 | [_cache setObject:@3 forKey:@"C"]; 127 | for (NSUInteger i = 0; i < 1000; i++) { 128 | [_cache setObject:@(i) forKey:[NSString stringWithFormat:@"%lu", (unsigned long)i]]; 129 | } 130 | 131 | [self measureBlock:^{ 132 | [_cache clearInternalCache]; 133 | XCTAssertEqualObjects([_cache objectForKey:@"B" withContent:^{ 134 | return @5; 135 | }], @2, @"Inccorrect cache hit."); 136 | }]; 137 | } 138 | 139 | - (void)testCacheLargeHitPerformance 140 | { 141 | NSData *data = [self _randomDataWithSize:1024 * 1024]; 142 | 143 | [_cache setObject:data forKey:@"A"]; 144 | [_cache setObject:@2 forKey:@"B"]; 145 | [_cache setObject:@3 forKey:@"C"]; 146 | for (NSInteger i = 0; i < 100; i++) { 147 | [_cache setObject:[self _randomDataWithSize:1024 * 1024] forKey:[NSNumber numberWithInteger:i].stringValue]; 148 | } 149 | 150 | [self measureBlock:^{ 151 | [_cache clearInternalCache]; 152 | XCTAssertEqualObjects([_cache objectForKey:@"A"], data, @"Inccorrect cache hit."); 153 | }]; 154 | } 155 | 156 | - (void)testMaxFilesize 157 | { 158 | _cache.maxFilesize = 100 * 1024; 159 | 160 | NSData *data = [self _randomDataWithSize:1024]; 161 | for (NSInteger i = 0; i < 200; i++) { 162 | [_cache setObject:data forKey:[NSNumber numberWithInteger:i].stringValue]; 163 | } 164 | [_cache trimFilesize]; 165 | 166 | XCTAssertLessThan(_cache.currentFilesize, _cache.maxFilesize); 167 | } 168 | 169 | @end 170 | -------------------------------------------------------------------------------- /CacheKitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "ccgus/fmdb" ~> 2.6 2 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "ccgus/fmdb" "2.6.2" 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 David Beck 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CacheKit 2 | 3 | [![CI Status](http://img.shields.io/travis/davbeck/CacheKit.svg?style=flat)](https://travis-ci.org/davbeck/CacheKit) 4 | [![Version](https://img.shields.io/cocoapods/v/CacheKit.svg?style=flat)](http://cocoapods.org/pods/CacheKit) 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | [![License](https://img.shields.io/cocoapods/l/CacheKit.svg?style=flat)](http://cocoapods.org/pods/CacheKit) 7 | [![Platform](https://img.shields.io/cocoapods/p/CacheKit.svg?style=flat)](http://cocoapods.org/pods/CacheKit) 8 | 9 | For a more Swift focused caching solution, check out [PersistentCacheKit](https://github.com/davbeck/PersistentCacheKit), which is the spiritual successor to this project. 10 | 11 | ## Usage 12 | 13 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 14 | 15 | ## Requirements 16 | 17 | ## Installation 18 | 19 | ### CocoaPods 20 | 21 | CacheKit is available through [CocoaPods](http://cocoapods.org). To install 22 | it, simply add the following line to your Podfile: 23 | 24 | ```ruby 25 | pod "CacheKit" 26 | ``` 27 | 28 | ### Carthage 29 | 30 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. 31 | 32 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 33 | 34 | ```bash 35 | $ brew update 36 | $ brew install carthage 37 | ``` 38 | 39 | To integrate CacheKit into your Xcode project using Carthage, specify it in your `Cartfile`: 40 | 41 | ```ogdl 42 | github "davbeck/CacheKit" ~> 0.6 43 | ``` 44 | 45 | Run `carthage` to build the framework and drag the built `CacheKit.framework` and `FMDB.framework` into your Xcode project. 46 | 47 | ## Author 48 | 49 | David Beck, code@thinkultimate.com 50 | 51 | ## License 52 | 53 | CacheKit is available under the MIT license. See the LICENSE file for more info. 54 | --------------------------------------------------------------------------------