├── .gitignore ├── .travis.yml ├── FlatCache.podspec ├── FlatCache.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── FlatCache.xcscheme │ └── FlatCacheTests.xcscheme ├── FlatCache ├── FlatCache.h ├── FlatCache.swift └── Info.plist ├── FlatCacheTests ├── FlatCacheTests.swift └── Info.plist ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## OSX 6 | .DS_Store 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData/ 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata/ 22 | 23 | ## Other 24 | *.moved-aside 25 | *.xccheckout 26 | *.xcscmblueprint 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | # Package.pins 43 | .build/ 44 | 45 | # CocoaPods 46 | # 47 | # We recommend against adding the Pods directory to your .gitignore. However 48 | # you should judge for yourself, the pros and cons are mentioned at: 49 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 50 | # 51 | # Pods/ 52 | 53 | # Carthage 54 | # 55 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 56 | # Carthage/Checkouts 57 | 58 | Carthage/Build 59 | 60 | # fastlane 61 | # 62 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 63 | # screenshots whenever they are needed. 64 | # For more information about the recommended setup visit: 65 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 66 | 67 | fastlane/report.xml 68 | fastlane/Preview.html 69 | fastlane/screenshots 70 | fastlane/test_output 71 | node_modules 72 | 73 | # Jekyll 74 | _site/ 75 | .sass-cache/ 76 | .jekyll-metadata 77 | 78 | # secrets 79 | Resources/*.xcconfig 80 | *Secrets.swift* 81 | 82 | 83 | # FBSnapshotTestCase Failure Diffs 84 | FailureDiffs/ 85 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode9.3 3 | script: 4 | - xcodebuild clean test -project FlatCache.xcodeproj -scheme FlatCache -destination "platform=iOS Simulator,name=iPhone X,OS=11.3" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO -quiet 5 | -------------------------------------------------------------------------------- /FlatCache.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'FlatCache' 3 | spec.version = '0.1.0' 4 | spec.license = { :type => 'MIT' } 5 | spec.homepage = 'https://github.com/GitHawkApp/FlatCache' 6 | spec.authors = { 'Ryan Nystrom' => 'rnystrom@whoisryannystrom.com' } 7 | spec.summary = 'In memory flat cache.' 8 | spec.source = { :git => 'https://github.com/GitHawkApp/FlatCache.git', :tag => spec.version.to_s } 9 | spec.source_files = 'FlatCache/*.swift' 10 | spec.platform = :ios, '9.0' 11 | spec.swift_version = '4.2' 12 | end 13 | -------------------------------------------------------------------------------- /FlatCache.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 29EEB2131F9BCE7400AB237B /* FlatCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29EEB2091F9BCE7400AB237B /* FlatCache.framework */; }; 11 | 29EEB2181F9BCE7400AB237B /* FlatCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EEB2171F9BCE7400AB237B /* FlatCacheTests.swift */; }; 12 | 29EEB21A1F9BCE7400AB237B /* FlatCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 29EEB20C1F9BCE7400AB237B /* FlatCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 29EEB2241F9BCED500AB237B /* FlatCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EEB2231F9BCED500AB237B /* FlatCache.swift */; }; 14 | 29EEB2251F9BCED700AB237B /* FlatCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EEB2231F9BCED500AB237B /* FlatCache.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | 29EEB2141F9BCE7400AB237B /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = 29EEB2001F9BCE7400AB237B /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = 29EEB2081F9BCE7400AB237B; 23 | remoteInfo = FlatCache; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 29EEB2091F9BCE7400AB237B /* FlatCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FlatCache.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 29EEB20C1F9BCE7400AB237B /* FlatCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FlatCache.h; sourceTree = ""; }; 30 | 29EEB20D1F9BCE7400AB237B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 29EEB2121F9BCE7400AB237B /* FlatCacheTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlatCacheTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 29EEB2171F9BCE7400AB237B /* FlatCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlatCacheTests.swift; sourceTree = ""; }; 33 | 29EEB2191F9BCE7400AB237B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | 29EEB2231F9BCED500AB237B /* FlatCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlatCache.swift; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 29EEB2051F9BCE7400AB237B /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | 29EEB20F1F9BCE7400AB237B /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | 29EEB2131F9BCE7400AB237B /* FlatCache.framework in Frameworks */, 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | 29EEB1FF1F9BCE7400AB237B = { 57 | isa = PBXGroup; 58 | children = ( 59 | 29EEB20B1F9BCE7400AB237B /* FlatCache */, 60 | 29EEB2161F9BCE7400AB237B /* FlatCacheTests */, 61 | 29EEB20A1F9BCE7400AB237B /* Products */, 62 | ); 63 | sourceTree = ""; 64 | }; 65 | 29EEB20A1F9BCE7400AB237B /* Products */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 29EEB2091F9BCE7400AB237B /* FlatCache.framework */, 69 | 29EEB2121F9BCE7400AB237B /* FlatCacheTests.xctest */, 70 | ); 71 | name = Products; 72 | sourceTree = ""; 73 | }; 74 | 29EEB20B1F9BCE7400AB237B /* FlatCache */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 29EEB2231F9BCED500AB237B /* FlatCache.swift */, 78 | 29EEB20C1F9BCE7400AB237B /* FlatCache.h */, 79 | 29EEB20D1F9BCE7400AB237B /* Info.plist */, 80 | ); 81 | path = FlatCache; 82 | sourceTree = ""; 83 | }; 84 | 29EEB2161F9BCE7400AB237B /* FlatCacheTests */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 29EEB2171F9BCE7400AB237B /* FlatCacheTests.swift */, 88 | 29EEB2191F9BCE7400AB237B /* Info.plist */, 89 | ); 90 | path = FlatCacheTests; 91 | sourceTree = ""; 92 | }; 93 | /* End PBXGroup section */ 94 | 95 | /* Begin PBXHeadersBuildPhase section */ 96 | 29EEB2061F9BCE7400AB237B /* Headers */ = { 97 | isa = PBXHeadersBuildPhase; 98 | buildActionMask = 2147483647; 99 | files = ( 100 | 29EEB21A1F9BCE7400AB237B /* FlatCache.h in Headers */, 101 | ); 102 | runOnlyForDeploymentPostprocessing = 0; 103 | }; 104 | /* End PBXHeadersBuildPhase section */ 105 | 106 | /* Begin PBXNativeTarget section */ 107 | 29EEB2081F9BCE7400AB237B /* FlatCache */ = { 108 | isa = PBXNativeTarget; 109 | buildConfigurationList = 29EEB21D1F9BCE7400AB237B /* Build configuration list for PBXNativeTarget "FlatCache" */; 110 | buildPhases = ( 111 | 29EEB2041F9BCE7400AB237B /* Sources */, 112 | 29EEB2051F9BCE7400AB237B /* Frameworks */, 113 | 29EEB2061F9BCE7400AB237B /* Headers */, 114 | 29EEB2071F9BCE7400AB237B /* Resources */, 115 | ); 116 | buildRules = ( 117 | ); 118 | dependencies = ( 119 | ); 120 | name = FlatCache; 121 | productName = FlatCache; 122 | productReference = 29EEB2091F9BCE7400AB237B /* FlatCache.framework */; 123 | productType = "com.apple.product-type.framework"; 124 | }; 125 | 29EEB2111F9BCE7400AB237B /* FlatCacheTests */ = { 126 | isa = PBXNativeTarget; 127 | buildConfigurationList = 29EEB2201F9BCE7400AB237B /* Build configuration list for PBXNativeTarget "FlatCacheTests" */; 128 | buildPhases = ( 129 | 29EEB20E1F9BCE7400AB237B /* Sources */, 130 | 29EEB20F1F9BCE7400AB237B /* Frameworks */, 131 | 29EEB2101F9BCE7400AB237B /* Resources */, 132 | ); 133 | buildRules = ( 134 | ); 135 | dependencies = ( 136 | 29EEB2151F9BCE7400AB237B /* PBXTargetDependency */, 137 | ); 138 | name = FlatCacheTests; 139 | productName = FlatCacheTests; 140 | productReference = 29EEB2121F9BCE7400AB237B /* FlatCacheTests.xctest */; 141 | productType = "com.apple.product-type.bundle.unit-test"; 142 | }; 143 | /* End PBXNativeTarget section */ 144 | 145 | /* Begin PBXProject section */ 146 | 29EEB2001F9BCE7400AB237B /* Project object */ = { 147 | isa = PBXProject; 148 | attributes = { 149 | LastSwiftUpdateCheck = 0900; 150 | LastUpgradeCheck = 1000; 151 | ORGANIZATIONNAME = "Ryan Nystrom"; 152 | TargetAttributes = { 153 | 29EEB2081F9BCE7400AB237B = { 154 | CreatedOnToolsVersion = 9.0; 155 | LastSwiftMigration = 1000; 156 | ProvisioningStyle = Automatic; 157 | }; 158 | 29EEB2111F9BCE7400AB237B = { 159 | CreatedOnToolsVersion = 9.0; 160 | LastSwiftMigration = 1000; 161 | ProvisioningStyle = Automatic; 162 | }; 163 | }; 164 | }; 165 | buildConfigurationList = 29EEB2031F9BCE7400AB237B /* Build configuration list for PBXProject "FlatCache" */; 166 | compatibilityVersion = "Xcode 8.0"; 167 | developmentRegion = en; 168 | hasScannedForEncodings = 0; 169 | knownRegions = ( 170 | en, 171 | ); 172 | mainGroup = 29EEB1FF1F9BCE7400AB237B; 173 | productRefGroup = 29EEB20A1F9BCE7400AB237B /* Products */; 174 | projectDirPath = ""; 175 | projectRoot = ""; 176 | targets = ( 177 | 29EEB2081F9BCE7400AB237B /* FlatCache */, 178 | 29EEB2111F9BCE7400AB237B /* FlatCacheTests */, 179 | ); 180 | }; 181 | /* End PBXProject section */ 182 | 183 | /* Begin PBXResourcesBuildPhase section */ 184 | 29EEB2071F9BCE7400AB237B /* Resources */ = { 185 | isa = PBXResourcesBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | 29EEB2101F9BCE7400AB237B /* Resources */ = { 192 | isa = PBXResourcesBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXResourcesBuildPhase section */ 199 | 200 | /* Begin PBXSourcesBuildPhase section */ 201 | 29EEB2041F9BCE7400AB237B /* Sources */ = { 202 | isa = PBXSourcesBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | 29EEB2241F9BCED500AB237B /* FlatCache.swift in Sources */, 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | }; 209 | 29EEB20E1F9BCE7400AB237B /* Sources */ = { 210 | isa = PBXSourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | 29EEB2181F9BCE7400AB237B /* FlatCacheTests.swift in Sources */, 214 | 29EEB2251F9BCED700AB237B /* FlatCache.swift in Sources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXSourcesBuildPhase section */ 219 | 220 | /* Begin PBXTargetDependency section */ 221 | 29EEB2151F9BCE7400AB237B /* PBXTargetDependency */ = { 222 | isa = PBXTargetDependency; 223 | target = 29EEB2081F9BCE7400AB237B /* FlatCache */; 224 | targetProxy = 29EEB2141F9BCE7400AB237B /* PBXContainerItemProxy */; 225 | }; 226 | /* End PBXTargetDependency section */ 227 | 228 | /* Begin XCBuildConfiguration section */ 229 | 29EEB21B1F9BCE7400AB237B /* Debug */ = { 230 | isa = XCBuildConfiguration; 231 | buildSettings = { 232 | ALWAYS_SEARCH_USER_PATHS = NO; 233 | CLANG_ANALYZER_NONNULL = YES; 234 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 235 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 236 | CLANG_CXX_LIBRARY = "libc++"; 237 | CLANG_ENABLE_MODULES = YES; 238 | CLANG_ENABLE_OBJC_ARC = YES; 239 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 240 | CLANG_WARN_BOOL_CONVERSION = YES; 241 | CLANG_WARN_COMMA = YES; 242 | CLANG_WARN_CONSTANT_CONVERSION = YES; 243 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 244 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 245 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 246 | CLANG_WARN_EMPTY_BODY = YES; 247 | CLANG_WARN_ENUM_CONVERSION = YES; 248 | CLANG_WARN_INFINITE_RECURSION = YES; 249 | CLANG_WARN_INT_CONVERSION = YES; 250 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 251 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 252 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 253 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 254 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 255 | CLANG_WARN_STRICT_PROTOTYPES = YES; 256 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 257 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 258 | CLANG_WARN_UNREACHABLE_CODE = YES; 259 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 260 | CODE_SIGN_IDENTITY = "iPhone Developer"; 261 | COPY_PHASE_STRIP = NO; 262 | CURRENT_PROJECT_VERSION = 1; 263 | DEBUG_INFORMATION_FORMAT = dwarf; 264 | ENABLE_STRICT_OBJC_MSGSEND = YES; 265 | ENABLE_TESTABILITY = YES; 266 | GCC_C_LANGUAGE_STANDARD = gnu11; 267 | GCC_DYNAMIC_NO_PIC = NO; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_OPTIMIZATION_LEVEL = 0; 270 | GCC_PREPROCESSOR_DEFINITIONS = ( 271 | "DEBUG=1", 272 | "$(inherited)", 273 | ); 274 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 275 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 276 | GCC_WARN_UNDECLARED_SELECTOR = YES; 277 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 278 | GCC_WARN_UNUSED_FUNCTION = YES; 279 | GCC_WARN_UNUSED_VARIABLE = YES; 280 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 281 | MTL_ENABLE_DEBUG_INFO = YES; 282 | ONLY_ACTIVE_ARCH = YES; 283 | SDKROOT = iphoneos; 284 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 285 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 286 | VERSIONING_SYSTEM = "apple-generic"; 287 | VERSION_INFO_PREFIX = ""; 288 | }; 289 | name = Debug; 290 | }; 291 | 29EEB21C1F9BCE7400AB237B /* Release */ = { 292 | isa = XCBuildConfiguration; 293 | buildSettings = { 294 | ALWAYS_SEARCH_USER_PATHS = NO; 295 | CLANG_ANALYZER_NONNULL = YES; 296 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 297 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 298 | CLANG_CXX_LIBRARY = "libc++"; 299 | CLANG_ENABLE_MODULES = YES; 300 | CLANG_ENABLE_OBJC_ARC = YES; 301 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 302 | CLANG_WARN_BOOL_CONVERSION = YES; 303 | CLANG_WARN_COMMA = YES; 304 | CLANG_WARN_CONSTANT_CONVERSION = YES; 305 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 306 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 307 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 308 | CLANG_WARN_EMPTY_BODY = YES; 309 | CLANG_WARN_ENUM_CONVERSION = YES; 310 | CLANG_WARN_INFINITE_RECURSION = YES; 311 | CLANG_WARN_INT_CONVERSION = YES; 312 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 313 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 314 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 315 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 316 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 317 | CLANG_WARN_STRICT_PROTOTYPES = YES; 318 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 319 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 320 | CLANG_WARN_UNREACHABLE_CODE = YES; 321 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 322 | CODE_SIGN_IDENTITY = "iPhone Developer"; 323 | COPY_PHASE_STRIP = NO; 324 | CURRENT_PROJECT_VERSION = 1; 325 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 326 | ENABLE_NS_ASSERTIONS = NO; 327 | ENABLE_STRICT_OBJC_MSGSEND = YES; 328 | GCC_C_LANGUAGE_STANDARD = gnu11; 329 | GCC_NO_COMMON_BLOCKS = YES; 330 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 331 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 332 | GCC_WARN_UNDECLARED_SELECTOR = YES; 333 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 334 | GCC_WARN_UNUSED_FUNCTION = YES; 335 | GCC_WARN_UNUSED_VARIABLE = YES; 336 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 337 | MTL_ENABLE_DEBUG_INFO = NO; 338 | SDKROOT = iphoneos; 339 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 340 | VALIDATE_PRODUCT = YES; 341 | VERSIONING_SYSTEM = "apple-generic"; 342 | VERSION_INFO_PREFIX = ""; 343 | }; 344 | name = Release; 345 | }; 346 | 29EEB21E1F9BCE7400AB237B /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | CLANG_ENABLE_MODULES = YES; 350 | CODE_SIGN_IDENTITY = ""; 351 | CODE_SIGN_STYLE = Automatic; 352 | DEFINES_MODULE = YES; 353 | DEVELOPMENT_TEAM = 523C4DWBTH; 354 | DYLIB_COMPATIBILITY_VERSION = 1; 355 | DYLIB_CURRENT_VERSION = 1; 356 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 357 | INFOPLIST_FILE = FlatCache/Info.plist; 358 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 359 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 360 | PRODUCT_BUNDLE_IDENTIFIER = com.whoisryannystrom.FlatCache; 361 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 362 | SKIP_INSTALL = YES; 363 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 364 | SWIFT_VERSION = 4.2; 365 | TARGETED_DEVICE_FAMILY = "1,2"; 366 | }; 367 | name = Debug; 368 | }; 369 | 29EEB21F1F9BCE7400AB237B /* Release */ = { 370 | isa = XCBuildConfiguration; 371 | buildSettings = { 372 | CLANG_ENABLE_MODULES = YES; 373 | CODE_SIGN_IDENTITY = ""; 374 | CODE_SIGN_STYLE = Automatic; 375 | DEFINES_MODULE = YES; 376 | DEVELOPMENT_TEAM = 523C4DWBTH; 377 | DYLIB_COMPATIBILITY_VERSION = 1; 378 | DYLIB_CURRENT_VERSION = 1; 379 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 380 | INFOPLIST_FILE = FlatCache/Info.plist; 381 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 382 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 383 | PRODUCT_BUNDLE_IDENTIFIER = com.whoisryannystrom.FlatCache; 384 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 385 | SKIP_INSTALL = YES; 386 | SWIFT_VERSION = 4.2; 387 | TARGETED_DEVICE_FAMILY = "1,2"; 388 | }; 389 | name = Release; 390 | }; 391 | 29EEB2211F9BCE7400AB237B /* Debug */ = { 392 | isa = XCBuildConfiguration; 393 | buildSettings = { 394 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 395 | CODE_SIGN_STYLE = Automatic; 396 | DEVELOPMENT_TEAM = 523C4DWBTH; 397 | INFOPLIST_FILE = FlatCacheTests/Info.plist; 398 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 399 | PRODUCT_BUNDLE_IDENTIFIER = com.whoisryannystrom.FlatCacheTests; 400 | PRODUCT_NAME = "$(TARGET_NAME)"; 401 | SWIFT_VERSION = 4.2; 402 | TARGETED_DEVICE_FAMILY = "1,2"; 403 | }; 404 | name = Debug; 405 | }; 406 | 29EEB2221F9BCE7400AB237B /* Release */ = { 407 | isa = XCBuildConfiguration; 408 | buildSettings = { 409 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 410 | CODE_SIGN_STYLE = Automatic; 411 | DEVELOPMENT_TEAM = 523C4DWBTH; 412 | INFOPLIST_FILE = FlatCacheTests/Info.plist; 413 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 414 | PRODUCT_BUNDLE_IDENTIFIER = com.whoisryannystrom.FlatCacheTests; 415 | PRODUCT_NAME = "$(TARGET_NAME)"; 416 | SWIFT_VERSION = 4.2; 417 | TARGETED_DEVICE_FAMILY = "1,2"; 418 | }; 419 | name = Release; 420 | }; 421 | /* End XCBuildConfiguration section */ 422 | 423 | /* Begin XCConfigurationList section */ 424 | 29EEB2031F9BCE7400AB237B /* Build configuration list for PBXProject "FlatCache" */ = { 425 | isa = XCConfigurationList; 426 | buildConfigurations = ( 427 | 29EEB21B1F9BCE7400AB237B /* Debug */, 428 | 29EEB21C1F9BCE7400AB237B /* Release */, 429 | ); 430 | defaultConfigurationIsVisible = 0; 431 | defaultConfigurationName = Release; 432 | }; 433 | 29EEB21D1F9BCE7400AB237B /* Build configuration list for PBXNativeTarget "FlatCache" */ = { 434 | isa = XCConfigurationList; 435 | buildConfigurations = ( 436 | 29EEB21E1F9BCE7400AB237B /* Debug */, 437 | 29EEB21F1F9BCE7400AB237B /* Release */, 438 | ); 439 | defaultConfigurationIsVisible = 0; 440 | defaultConfigurationName = Release; 441 | }; 442 | 29EEB2201F9BCE7400AB237B /* Build configuration list for PBXNativeTarget "FlatCacheTests" */ = { 443 | isa = XCConfigurationList; 444 | buildConfigurations = ( 445 | 29EEB2211F9BCE7400AB237B /* Debug */, 446 | 29EEB2221F9BCE7400AB237B /* Release */, 447 | ); 448 | defaultConfigurationIsVisible = 0; 449 | defaultConfigurationName = Release; 450 | }; 451 | /* End XCConfigurationList section */ 452 | }; 453 | rootObject = 29EEB2001F9BCE7400AB237B /* Project object */; 454 | } 455 | -------------------------------------------------------------------------------- /FlatCache.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FlatCache.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FlatCache.xcodeproj/xcshareddata/xcschemes/FlatCache.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 | -------------------------------------------------------------------------------- /FlatCache.xcodeproj/xcshareddata/xcschemes/FlatCacheTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /FlatCache/FlatCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // FlatCache.h 3 | // FlatCache 4 | // 5 | // Created by Ryan Nystrom on 10/21/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for FlatCache. 12 | FOUNDATION_EXPORT double FlatCacheVersionNumber; 13 | 14 | //! Project version string for FlatCache. 15 | FOUNDATION_EXPORT const unsigned char FlatCacheVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /FlatCache/FlatCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlatCacheKey.swift 3 | // Freetime 4 | // 5 | // Created by Ryan Nystrom on 10/20/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct FlatCacheKey: Equatable, Hashable { 12 | let typeName: String 13 | let id: String 14 | } 15 | 16 | public protocol Identifiable { 17 | var id: String { get } 18 | } 19 | 20 | public protocol Cachable: Identifiable { } 21 | 22 | private extension Cachable { 23 | static var typeName: String { 24 | return String(describing: self) 25 | } 26 | 27 | var flatCacheKey: FlatCacheKey { 28 | return FlatCacheKey(typeName: Self.typeName, id: id) 29 | } 30 | } 31 | 32 | public protocol FlatCacheListener: AnyObject { 33 | func flatCacheDidUpdate(cache: FlatCache, update: FlatCache.Update) 34 | } 35 | 36 | public final class FlatCache { 37 | 38 | public enum Update { 39 | case item(Any) 40 | case list([Any]) 41 | case clear 42 | } 43 | 44 | private var storage: [FlatCacheKey: Any] = [:] 45 | private let queue = DispatchQueue( 46 | label: "com.freetime.FlatCache.queue", 47 | qos: .userInitiated, 48 | attributes: .concurrent 49 | ) 50 | 51 | private var listeners: [FlatCacheKey: NSHashTable] = [:] 52 | 53 | public init() { } 54 | 55 | public func add(listener: FlatCacheListener, value: T) { 56 | assert(Thread.isMainThread) 57 | 58 | let key = value.flatCacheKey 59 | let table: NSHashTable 60 | if let existing = listeners[key] { 61 | table = existing 62 | } else { 63 | table = NSHashTable.weakObjects() 64 | } 65 | table.add(listener) 66 | listeners[key] = table 67 | } 68 | 69 | public func set(value: T) { 70 | assert(Thread.isMainThread) 71 | 72 | let key = value.flatCacheKey 73 | storage[key] = value 74 | 75 | enumerateListeners(key: key) { listener in 76 | listener.flatCacheDidUpdate(cache: self, update: .item(value)) 77 | } 78 | } 79 | 80 | private func enumerateListeners(key: FlatCacheKey, block: (FlatCacheListener) -> ()) { 81 | assert(Thread.isMainThread) 82 | 83 | if let table = listeners[key] { 84 | for object in table.objectEnumerator() { 85 | if let listener = object as? FlatCacheListener { 86 | block(listener) 87 | } 88 | } 89 | } 90 | } 91 | 92 | public func set(values: [T]) { 93 | assert(Thread.isMainThread) 94 | 95 | var listenerHashToValuesMap = [Int: [T]]() 96 | var listenerHashToListenerMap = [Int: FlatCacheListener]() 97 | 98 | for value in values { 99 | let key = value.flatCacheKey 100 | storage[key] = value 101 | 102 | enumerateListeners(key: key, block: { listener in 103 | let hash = ObjectIdentifier(listener).hashValue 104 | if var arr = listenerHashToValuesMap[hash] { 105 | arr.append(value) 106 | listenerHashToValuesMap[hash] = arr 107 | } else { 108 | listenerHashToValuesMap[hash] = [value] 109 | } 110 | listenerHashToListenerMap[hash] = listener 111 | }) 112 | } 113 | 114 | for (hash, arr) in listenerHashToValuesMap { 115 | guard let listener = listenerHashToListenerMap[hash] else { continue } 116 | if arr.count == 1, let first = arr.first { 117 | listener.flatCacheDidUpdate(cache: self, update: .item(first)) 118 | } else { 119 | listener.flatCacheDidUpdate(cache: self, update: .list(arr)) 120 | } 121 | } 122 | } 123 | 124 | public func get(id: String) -> T? { 125 | assert(Thread.isMainThread) 126 | 127 | let key = FlatCacheKey(typeName: T.typeName, id: id) 128 | return storage[key] as? T 129 | } 130 | 131 | public func clear() { 132 | assert(Thread.isMainThread) 133 | 134 | storage = [:] 135 | 136 | for key in listeners.keys { 137 | enumerateListeners(key: key) { listener in 138 | listener.flatCacheDidUpdate(cache: self, update: .clear) 139 | } 140 | } 141 | } 142 | 143 | } 144 | 145 | -------------------------------------------------------------------------------- /FlatCache/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /FlatCacheTests/FlatCacheTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlatCacheTests.swift 3 | // FreetimeTests 4 | // 5 | // Created by Ryan Nystrom on 10/21/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | struct CacheModel: Cachable { 12 | let id: String 13 | let value: String 14 | } 15 | 16 | class CacheModelListener: FlatCacheListener { 17 | var receivedItemQueue = [CacheModel]() 18 | var receivedListQueue = [[CacheModel]]() 19 | var hasCleared = false 20 | 21 | func flatCacheDidUpdate(cache: FlatCache, update: FlatCache.Update) { 22 | switch update { 23 | case .item(let item): receivedItemQueue.append(item as! CacheModel) 24 | case .list(let list): receivedListQueue.append(list as! [CacheModel]) 25 | case .clear: hasCleared = true 26 | } 27 | } 28 | } 29 | 30 | struct OtherCacheModel: Cachable { 31 | let id: String 32 | } 33 | 34 | class FlatCacheTests: XCTestCase { 35 | 36 | func test_whenSettingSingleModel_thatResultExistsForType() { 37 | let cache = FlatCache() 38 | cache.set(value: CacheModel(id: "1", value: "")) 39 | XCTAssertNotNil(cache.get(id: "1") as CacheModel?) 40 | } 41 | 42 | func test_whenSettingSingleModel_withUupdatedModel_thatResultMostRecent() { 43 | let cache = FlatCache() 44 | cache.set(value: CacheModel(id: "1", value: "foo")) 45 | cache.set(value: CacheModel(id: "1", value: "bar")) 46 | XCTAssertEqual((cache.get(id: "1") as CacheModel?)?.value, "bar") 47 | } 48 | 49 | func test_whenSettingSingleModel_thatNoResultExsistForUnsetId() { 50 | let cache = FlatCache() 51 | cache.set(value: CacheModel(id: "1", value: "")) 52 | XCTAssertNil(cache.get(id: "2") as CacheModel?) 53 | } 54 | 55 | func test_whenSettingSingleModel_thatNoResultExistsForOtherType() { 56 | let cache = FlatCache() 57 | cache.set(value: CacheModel(id: "1", value: "")) 58 | XCTAssertNil(cache.get(id: "1") as OtherCacheModel?) 59 | } 60 | 61 | func test_whenSettingManyModels_thatResultsExistForType() { 62 | let cache = FlatCache() 63 | cache.set(values: [ 64 | CacheModel(id: "1", value: ""), 65 | CacheModel(id: "2", value: ""), 66 | CacheModel(id: "3", value: ""), 67 | ]) 68 | XCTAssertNotNil(cache.get(id: "1") as CacheModel?) 69 | XCTAssertNotNil(cache.get(id: "2") as CacheModel?) 70 | XCTAssertNotNil(cache.get(id: "3") as CacheModel?) 71 | } 72 | 73 | func test_whenSettingSingleModel_withListeners_whenMultipleUpdates_thatCorrectListenerReceivesUpdate() { 74 | let cache = FlatCache() 75 | let l1 = CacheModelListener() 76 | let l2 = CacheModelListener() 77 | let m1 = CacheModel(id: "1", value: "") 78 | let m2 = CacheModel(id: "2", value: "") 79 | cache.add(listener: l1, value: m1) 80 | cache.add(listener: l2, value: m2) 81 | cache.set(value: m1) 82 | cache.set(value: CacheModel(id: "1", value: "foo")) 83 | XCTAssertEqual(l1.receivedItemQueue.count, 2) 84 | XCTAssertEqual(l1.receivedListQueue.count, 0) 85 | XCTAssertEqual(l1.receivedItemQueue.last?.id, "1") 86 | XCTAssertEqual(l1.receivedItemQueue.last?.value, "foo") 87 | XCTAssertEqual(l2.receivedItemQueue.count, 0) 88 | XCTAssertEqual(l2.receivedListQueue.count, 0) 89 | } 90 | 91 | func test_whenSettingMultipleModels_withListenerOnAll_whenMultipleUpdates_thatListenerReceivesUpdate() { 92 | let cache = FlatCache() 93 | let l1 = CacheModelListener() 94 | let m1 = CacheModel(id: "1", value: "foo") 95 | let m2 = CacheModel(id: "2", value: "bar") 96 | cache.add(listener: l1, value: m1) 97 | cache.add(listener: l1, value: m2) 98 | cache.set(values: [m1, m2]) 99 | XCTAssertEqual(l1.receivedItemQueue.count, 0) 100 | XCTAssertEqual(l1.receivedListQueue.count, 1) 101 | XCTAssertEqual(l1.receivedListQueue.last?.count, 2) 102 | XCTAssertEqual(l1.receivedListQueue.last?.first?.value, "foo") 103 | XCTAssertEqual(l1.receivedListQueue.last?.last?.value, "bar") 104 | } 105 | 106 | func test_whenSettingTwoModels_withListenerForEach_thatListenersReceiveItemUpdates() { 107 | let cache = FlatCache() 108 | let l1 = CacheModelListener() 109 | let l2 = CacheModelListener() 110 | let m1 = CacheModel(id: "1", value: "foo") 111 | let m2 = CacheModel(id: "2", value: "bar") 112 | cache.add(listener: l1, value: m1) 113 | cache.add(listener: l2, value: m2) 114 | cache.set(values: [m1, m2]) 115 | XCTAssertEqual(l1.receivedItemQueue.count, 1) 116 | XCTAssertEqual(l1.receivedListQueue.count, 0) 117 | XCTAssertEqual(l1.receivedItemQueue.last?.value, "foo") 118 | XCTAssertEqual(l2.receivedItemQueue.count, 1) 119 | XCTAssertEqual(l2.receivedListQueue.count, 0) 120 | XCTAssertEqual(l2.receivedItemQueue.last?.value, "bar") 121 | } 122 | 123 | func test_whenClearingCache() { 124 | let cache = FlatCache() 125 | cache.set(value: CacheModel(id: "1", value: "")) 126 | cache.clear() 127 | XCTAssertNil(cache.get(id: "1") as CacheModel?) 128 | } 129 | 130 | func test_whenClearing_withListenerForEach_thatListenersReceiveClearUpdates() { 131 | let cache = FlatCache() 132 | let l1 = CacheModelListener() 133 | let l2 = CacheModelListener() 134 | let m1 = CacheModel(id: "1", value: "foo") 135 | let m2 = CacheModel(id: "2", value: "bar") 136 | cache.add(listener: l1, value: m1) 137 | cache.add(listener: l2, value: m2) 138 | cache.set(values: [m1, m2]) 139 | 140 | XCTAssertFalse(l1.hasCleared) 141 | XCTAssertFalse(l2.hasCleared) 142 | 143 | cache.clear() 144 | 145 | XCTAssertTrue(l1.hasCleared) 146 | XCTAssertTrue(l2.hasCleared) 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /FlatCacheTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2017 Ryan Nystrom http://whoisryannystrom.com/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlatCache 2 | 3 | Store any object in a flat cache by `String` id and notify listeners when changes occur. 4 | 5 | This is an implementation of [Soroush Khanlou](https://twitter.com/khanlou)'s [The Flat Cache](http://khanlou.com/2017/10/the-flat-cache/) blog post used in [GitHawk](http://githawk.com). 6 | 7 | ## Installation 8 | 9 | Just add `FlatCache` to your Podfile and pod install. Done! 10 | 11 | ``` 12 | pod 'FlatCache' 13 | ``` 14 | 15 | ## Usage 16 | 17 | Add the `Cachable` protocol to models that you want to store in the cache. Conform to the protocol by returning a `String` identifier used for lookup. 18 | 19 | ```swift 20 | struct User { 21 | let name: String 22 | } 23 | 24 | extension User: Cachable { 25 | var id: String { 26 | return name 27 | } 28 | } 29 | ``` 30 | 31 | > Don't worry about `id` collisions between objects. The cache "namespaces" different models by type. 32 | 33 | Now just create a `FlatCache` object and start reading & writing with it. 34 | 35 | ```swift 36 | let cache = FlatCache() 37 | let user = User(name: "ryan") 38 | cache.set(value: user) 39 | if let cached = cache.get(id: user.id) as User? { 40 | print(cached.name) // "ryan" 41 | } 42 | ``` 43 | 44 | > `FlatCache` uses the type information with the `get(id:)` function to lookup the appropriate object. You must type the result somehow. 45 | 46 | ### Listeners 47 | 48 | One of the strengths of `FlatCache` is adding "listeners" to the cache to be notified when an object is changed. This powerful tool lets multiple systems respond to object changes. 49 | 50 | ```swift 51 | let cache = FlatCache() 52 | let user = User(name: "ryan") 53 | let listener = MyUserListener() 54 | cache.add(listener: listener, value: user) 55 | ``` 56 | 57 | Now whenever `user` is set again in the cache, `MyUserListener` will be notified with the following function: 58 | 59 | ```swift 60 | 61 | func flatCacheDidUpdate(cache: FlatCache, update: FlatCache.Update) { 62 | switch update { 63 | case .item(let item): 64 | // just a single object updated 65 | case .list: 66 | // a list of subscribed objects updated 67 | } 68 | } 69 | ``` 70 | 71 | > `FlatCache` coalesces so only a single event is delivered when something changes, no matter if an update has just a single object or hundreds. 72 | 73 | ## Acknowledgements 74 | 75 | - Code used and inspired from [Soroush Khanlou](https://twitter.com/khanlou)'s [The Flat Cache](http://khanlou.com/2017/10/the-flat-cache/) 76 | - Created with ❤️ by [Ryan Nystrom](https://twitter.com/_ryannystrom) 77 | --------------------------------------------------------------------------------