├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── -en-bug-report-.md │ ├── -en-feature-request-.md │ └── -en-q-a-.md ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── .travis.yml ├── BuildScripts └── lint.swift ├── ClaretCache.podspec ├── ClaretCacheDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ ├── ClaretCacheDemo.xcscheme │ └── ClaretCacheDemoTests.xcscheme ├── ClaretCacheDemo.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ └── Package.resolved ├── ClaretCacheDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── ClaretCacheDemoTests ├── ClaretCacheDemoTests.swift └── Info.plist ├── Design └── banner.png ├── LICENSE ├── Package.swift ├── Podfile ├── Podfile.lock ├── Pods ├── Manifest.lock ├── Pods.xcodeproj │ └── project.pbxproj ├── SwiftLint │ ├── LICENSE │ └── swiftlint └── Target Support Files │ ├── Pods-ClaretCacheDemo │ ├── Pods-ClaretCacheDemo-Info.plist │ ├── Pods-ClaretCacheDemo-acknowledgements.markdown │ ├── Pods-ClaretCacheDemo-acknowledgements.plist │ ├── Pods-ClaretCacheDemo-dummy.m │ ├── Pods-ClaretCacheDemo-umbrella.h │ ├── Pods-ClaretCacheDemo.debug.xcconfig │ ├── Pods-ClaretCacheDemo.modulemap │ └── Pods-ClaretCacheDemo.release.xcconfig │ └── SwiftLint │ └── SwiftLint.xcconfig ├── README.md ├── Sources └── ClaretCache │ ├── ClaretCache.swift │ ├── KVStorage.swift │ └── MemoryCache.swift ├── Swift_Style_Guide.md ├── Tests ├── ClaretCacheTests │ ├── ClaretCacheTests.swift │ └── XCTestManifests.swift └── LinuxMain.swift └── configs └── .swiftlint.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Swift gitattributes 2 | *.pbxproj merge=union 3 | *.swift text diff=swift 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-en-bug-report-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "[EN]Bug report:" 3 | about: "[CN]bug反馈:" 4 | title: "[CN]bug反馈." 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ![](http://ww2.sinaimg.cn/large/006tNc79gy1g59kizl2apj31l80e5wgi.jpg) 11 | 12 | ---------- 13 | 14 | 15 |

16 | 17 | 18 | 19 | 20 |

21 | 22 |

23 | 24 |

25 | 26 |

27 | 28 | 29 | 30 |

31 | 32 | ---------- 33 | 34 | 35 | ## Base Info for this issue 36 | 37 | 38 | 39 | 40 | 41 | 1. Version:Latest Version as [here](https://github.com/iteatimeteam/ClaretCache/blob/master/ClaretCache.podspec) 42 | 2. main language of App :Objective-C/Swift 43 | 4. iOS System Version:iOS12 44 | 5. Prototype(是否是真机):YES 45 | 6. Issue Type:Crash、Bug、Enhancement(希望能支持一个新需求)、Q-A 46 | 47 | ## 1. How to reproduce the problem. 48 | 49 | 50 | ## 2. Please help me in this way. 51 | 52 | 53 | ## 3. Here is a Demo and screenshots. 54 | 55 | 56 | 57 | ## 4. Here is my Debug log 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | ---------- 66 | 67 |

68 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-en-feature-request-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "[EN]Feature request:" 3 | about: "[CN]希望添加新功能:" 4 | title: "[CN]希望添加新功能:" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | -------------------------------------------- 11 | ![](http://ww2.sinaimg.cn/large/006tNc79gy1g59kizl2apj31l80e5wgi.jpg) 12 | 13 | ---------- 14 | 15 | 16 |

17 | 18 | 19 | 20 | 21 |

22 | 23 |

24 | 25 |

26 | 27 |

28 | 29 | 30 | 31 |

32 | 33 | ---------- 34 | ---------- 35 | 36 | 37 | 38 | **Is your feature request related to a problem? Please describe.** 39 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 40 | 41 | **Describe the solution you'd like** 42 | A clear and concise description of what you want to happen. 43 | 44 | **Describe alternatives you've considered** 45 | A clear and concise description of any alternative solutions or features you've considered. 46 | 47 | **Additional context** 48 | Add any other context or screenshots about the feature request here. 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-en-q-a-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "[EN]Q-A:" 3 | about: "[CN]Q-A问题询问,使用方法或者无法确认是否为bug,可以选择该模版。" 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | -------------------------------------------- 11 | ![](http://ww2.sinaimg.cn/large/006tNc79gy1g59kizl2apj31l80e5wgi.jpg) 12 | 13 | ---------- 14 | 15 | 16 |

17 | 18 | 19 | 20 | 21 |

22 | 23 |

24 | 25 |

26 | 27 |

28 | 29 | 30 | 31 |

32 | 33 | ---------- 34 | 35 | ## My issue: 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ![](https://github.com/iteatimeteam/ClaretCache/blob/master/Design/banner.png) 5 | 6 | 9 | 10 | ---------- 11 | 12 | 13 |

14 | 15 | 16 | 17 | 18 |

19 | 20 |

21 | 22 |

23 | 24 |

25 | 26 | 27 | 28 |

29 | 30 | ---------- 31 | 32 | 33 | ## Base Info for this issue 34 | 35 | 36 | 37 | 38 | 39 | 1. Version:Latest Version as [here](https://github.com/iteatimeteam/ClaretCache/blob/master/ClaretCache.podspec) 40 | 2. main language of App :Objective-C/Swift 41 | 4. iOS System Version:iOS12 42 | 5. Prototype(是否是真机):YES 43 | 6. Issue Type:Crash、Bug、Enhancement(希望能支持一个新需求)、Q-A 44 | 45 | ## 1. How to reproduce the problem. 46 | 47 | 48 | ## 2. Please help me in this way. 49 | 50 | 51 | ## 3. Here is a Demo and screenshots. 52 | 53 | 54 | 55 | ## 4. Here is my Debug log 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ---------- 64 | 65 |

66 | 67 | 68 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ![](http://ww2.sinaimg.cn/large/006tNc79gy1g59kizl2apj31l80e5wgi.jpg) 2 | 3 | 4 | ## CheckList 5 | 6 | 7 | Thanks for considering to this repository. Before you submit your issue, please confirm these boxes are checked. 8 | 9 | - [ ] I have read [《[EN]Style guide for Swift repo [CN]Swift项目代码规范、规约选型》](https://github.com/iteatimeteam/ClaretCache/issues/3). 10 | - [ ] 我遵守一下 Merge 规则:邀请群组中任意一人进行review,即可合并。让 reviewer 在 PR 所在页面写下review意见表示通过,即可合并。最简单写一句 LGTM 也可以。不按照本规则执行,自己 PR 自己 Merge 并引入问题,会被收回 Merge 权限。 11 | 12 | 13 | ## My issue: 14 | ------- 15 | 16 | 17 | 18 | ## What I have done: 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Created by https://www.gitignore.io/api/swift 4 | # Edit at https://www.gitignore.io/?templates=swift 5 | 6 | ### Swift ### 7 | # Xcode 8 | # 9 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 10 | 11 | ## Build generated 12 | build/ 13 | DerivedData/ 14 | 15 | ## Various settings 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | xcuserdata/ 25 | 26 | ## Other 27 | *.moved-aside 28 | *.xccheckout 29 | *.xcscmblueprint 30 | 31 | ## Obj-C/Swift specific 32 | *.hmap 33 | *.ipa 34 | *.dSYM.zip 35 | *.dSYM 36 | 37 | ## Playgrounds 38 | timeline.xctimeline 39 | playground.xcworkspace 40 | 41 | # Swift Package Manager 42 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 43 | # Packages/ 44 | # Package.pins 45 | # Package.resolved 46 | .build/ 47 | 48 | # CocoaPods 49 | # We recommend against adding the Pods directory to your .gitignore. However 50 | # you should judge for yourself, the pros and cons are mentioned at: 51 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 52 | # Pods/ 53 | # Add this line if you want to avoid checking in source code from the Xcode workspace 54 | # *.xcworkspace 55 | 56 | # Carthage 57 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 58 | # Carthage/Checkouts 59 | 60 | Carthage/Build 61 | 62 | # Accio dependency management 63 | Dependencies/ 64 | .accio/ 65 | 66 | # fastlane 67 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 68 | # screenshots whenever they are needed. 69 | # For more information about the recommended setup visit: 70 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 71 | 72 | fastlane/report.xml 73 | fastlane/Preview.html 74 | fastlane/screenshots/**/*.png 75 | fastlane/test_output 76 | 77 | # Code Injection 78 | # After new code Injection tools there's a generated folder /iOSInjectionProject 79 | # https://github.com/johnno1962/injectionforxcode 80 | 81 | iOSInjectionProject/ 82 | 83 | # End of https://www.gitignore.io/api/swift -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode11 2 | language: swift 3 | 4 | env: 5 | - PROJECT_NAME=ClaretCacheDemo 6 | 7 | jobs: 8 | include: 9 | # - stage: Lint 10 | install: 11 | - wget --output-document /tmp/SwiftLint.pkg https://github.com/realm/SwiftLint/releases/download/0.33.1/SwiftLint.pkg && sudo installer -pkg /tmp/SwiftLint.pkg -target / 12 | script: 13 | - swiftlint --config configs/.swiftlint.yml 14 | - swift test 15 | - swift build 16 | - xcodebuild -workspace ${PROJECT_NAME}.xcworkspace -scheme ${PROJECT_NAME} -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone XR,OS=12.1' build 17 | # stages: 18 | # - Lint 19 | # - Test 20 | -------------------------------------------------------------------------------- /BuildScripts/lint.swift: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xcrun --sdk macosx swift 2 | 3 | import Foundation 4 | 5 | func run(targetPath: String) { 6 | let relativePath = CommandLine.arguments[0] as NSString 7 | print(relativePath) 8 | let configFilePath = relativePath.deletingLastPathComponent + "/../configs/.swiftlint.yml" 9 | let configFileUrl = URL(fileURLWithPath: configFilePath) 10 | let configPath = configFileUrl.path 11 | let launchPath = relativePath.deletingLastPathComponent.components(separatedBy: "/..").first! + "/../Pods/SwiftLint/swiftlint" 12 | 13 | let process = Process() 14 | process.launchPath = launchPath 15 | process.arguments = ["lint", "--config", configPath, "--path", targetPath] 16 | process.launch() 17 | process.waitUntilExit() 18 | 19 | let status = process.terminationStatus 20 | exit(status) 21 | } 22 | 23 | run(targetPath: CommandLine.arguments[1]) 24 | -------------------------------------------------------------------------------- /ClaretCache.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'ClaretCache' 3 | s.summary = 'High performance cache framework for iOS.' 4 | s.version = '0.0.1' 5 | s.license = { :type => 'MIT', :file => 'LICENSE' } 6 | s.authors = { 'iTeaTime(技术清谈)' => 'luohanchenyilong@163.com' } 7 | s.social_media_url = 'https://github.com/iteatimeteam/ClaretCache' 8 | s.homepage = 'https://github.com/iteatimeteam/ClaretCache' 9 | s.source = { :git => 'https://github.com/iteatimeteam/ClaretCache.git', :tag => s.version.to_s } 10 | 11 | s.platform = :ios, '10.0' 12 | s.ios.deployment_target = '10.0' 13 | 14 | s.swift_version = '5.0' 15 | 16 | s.source_files = 'Sources/ClaretCache/*.swift' 17 | 18 | s.libraries = 'sqlite3' 19 | s.frameworks = 'UIKit', 'CoreFoundation', 'QuartzCore' 20 | 21 | end 22 | -------------------------------------------------------------------------------- /ClaretCacheDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 01C75DEC22E0723E00C7D03F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C75DEB22E0723E00C7D03F /* AppDelegate.swift */; }; 11 | 01C75DEE22E0723E00C7D03F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C75DED22E0723E00C7D03F /* ViewController.swift */; }; 12 | 01C75DF122E0723E00C7D03F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01C75DEF22E0723E00C7D03F /* Main.storyboard */; }; 13 | 01C75DF322E0724000C7D03F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01C75DF222E0724000C7D03F /* Assets.xcassets */; }; 14 | 01C75DF622E0724000C7D03F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01C75DF422E0724000C7D03F /* LaunchScreen.storyboard */; }; 15 | 01FA686D22E719DB008E24FC /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FA686B22E719DB008E24FC /* MemoryCache.swift */; }; 16 | 01FA686E22E719DB008E24FC /* ClaretCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FA686C22E719DB008E24FC /* ClaretCache.swift */; }; 17 | 8E077CCCBC628B6EF406E97A /* Pods_ClaretCacheDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1BF5D3C4BBDD1FACD276DAA1 /* Pods_ClaretCacheDemo.framework */; }; 18 | C30EB06522ED8E8A0064ED79 /* KVStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30EB06422ED8E8A0064ED79 /* KVStorage.swift */; }; 19 | C3BC07DC22EF369600345659 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = C3BC07DB22EF369600345659 /* libsqlite3.tbd */; }; 20 | F8C53E7722EB9EBC00B53664 /* ClaretCacheDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C53E7622EB9EBC00B53664 /* ClaretCacheDemoTests.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | F8C53E7922EB9EBC00B53664 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 01C75DE022E0723E00C7D03F /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 01C75DE722E0723E00C7D03F; 29 | remoteInfo = ClaretCacheDemo; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 01C75DE822E0723E00C7D03F /* ClaretCacheDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ClaretCacheDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 01C75DEB22E0723E00C7D03F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 36 | 01C75DED22E0723E00C7D03F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 37 | 01C75DF022E0723E00C7D03F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 38 | 01C75DF222E0724000C7D03F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 39 | 01C75DF522E0724000C7D03F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 40 | 01C75DF722E0724000C7D03F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | 01FA686B22E719DB008E24FC /* MemoryCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryCache.swift; sourceTree = ""; }; 42 | 01FA686C22E719DB008E24FC /* ClaretCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaretCache.swift; sourceTree = ""; }; 43 | 1BF5D3C4BBDD1FACD276DAA1 /* Pods_ClaretCacheDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ClaretCacheDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 71DE9F8F64DFC38FD8ABCF96 /* Pods-ClaretCacheDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClaretCacheDemo.release.xcconfig"; path = "Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.release.xcconfig"; sourceTree = ""; }; 45 | 96892E93F01C8D0A4FEC2EC1 /* Pods-ClaretCacheDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClaretCacheDemo.debug.xcconfig"; path = "Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.debug.xcconfig"; sourceTree = ""; }; 46 | C30EB06422ED8E8A0064ED79 /* KVStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KVStorage.swift; sourceTree = ""; }; 47 | C3BC07DB22EF369600345659 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; 48 | F8C53E7422EB9EBC00B53664 /* ClaretCacheDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ClaretCacheDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | F8C53E7622EB9EBC00B53664 /* ClaretCacheDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaretCacheDemoTests.swift; sourceTree = ""; }; 50 | F8C53E7822EB9EBC00B53664 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | 01C75DE522E0723E00C7D03F /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | C3BC07DC22EF369600345659 /* libsqlite3.tbd in Frameworks */, 59 | 8E077CCCBC628B6EF406E97A /* Pods_ClaretCacheDemo.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | F8C53E7122EB9EBC00B53664 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | 01C75DDF22E0723E00C7D03F = { 74 | isa = PBXGroup; 75 | children = ( 76 | 01C75DEA22E0723E00C7D03F /* ClaretCacheDemo */, 77 | F8C53E7522EB9EBC00B53664 /* ClaretCacheDemoTests */, 78 | 01C75DE922E0723E00C7D03F /* Products */, 79 | 79D8C1A4BEAF0AE66614A083 /* Pods */, 80 | 53C8A8FFD2D1D8A4F837C496 /* Frameworks */, 81 | ); 82 | sourceTree = ""; 83 | }; 84 | 01C75DE922E0723E00C7D03F /* Products */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 01C75DE822E0723E00C7D03F /* ClaretCacheDemo.app */, 88 | F8C53E7422EB9EBC00B53664 /* ClaretCacheDemoTests.xctest */, 89 | ); 90 | name = Products; 91 | sourceTree = ""; 92 | }; 93 | 01C75DEA22E0723E00C7D03F /* ClaretCacheDemo */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 01C75DEB22E0723E00C7D03F /* AppDelegate.swift */, 97 | 01C75DED22E0723E00C7D03F /* ViewController.swift */, 98 | 01C75DEF22E0723E00C7D03F /* Main.storyboard */, 99 | 01FA686922E719DB008E24FC /* Sources */, 100 | 01C75DF222E0724000C7D03F /* Assets.xcassets */, 101 | 01C75DF422E0724000C7D03F /* LaunchScreen.storyboard */, 102 | 01C75DF722E0724000C7D03F /* Info.plist */, 103 | ); 104 | path = ClaretCacheDemo; 105 | sourceTree = ""; 106 | }; 107 | 01FA686922E719DB008E24FC /* Sources */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 01FA686A22E719DB008E24FC /* ClaretCache */, 111 | ); 112 | path = Sources; 113 | sourceTree = SOURCE_ROOT; 114 | }; 115 | 01FA686A22E719DB008E24FC /* ClaretCache */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 01FA686B22E719DB008E24FC /* MemoryCache.swift */, 119 | 01FA686C22E719DB008E24FC /* ClaretCache.swift */, 120 | C30EB06422ED8E8A0064ED79 /* KVStorage.swift */, 121 | ); 122 | path = ClaretCache; 123 | sourceTree = ""; 124 | }; 125 | 53C8A8FFD2D1D8A4F837C496 /* Frameworks */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | C3BC07DB22EF369600345659 /* libsqlite3.tbd */, 129 | 1BF5D3C4BBDD1FACD276DAA1 /* Pods_ClaretCacheDemo.framework */, 130 | ); 131 | name = Frameworks; 132 | sourceTree = ""; 133 | }; 134 | 79D8C1A4BEAF0AE66614A083 /* Pods */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 96892E93F01C8D0A4FEC2EC1 /* Pods-ClaretCacheDemo.debug.xcconfig */, 138 | 71DE9F8F64DFC38FD8ABCF96 /* Pods-ClaretCacheDemo.release.xcconfig */, 139 | ); 140 | path = Pods; 141 | sourceTree = ""; 142 | }; 143 | F8C53E7522EB9EBC00B53664 /* ClaretCacheDemoTests */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | F8C53E7622EB9EBC00B53664 /* ClaretCacheDemoTests.swift */, 147 | F8C53E7822EB9EBC00B53664 /* Info.plist */, 148 | ); 149 | path = ClaretCacheDemoTests; 150 | sourceTree = ""; 151 | }; 152 | /* End PBXGroup section */ 153 | 154 | /* Begin PBXNativeTarget section */ 155 | 01C75DE722E0723E00C7D03F /* ClaretCacheDemo */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = 01C75DFA22E0724000C7D03F /* Build configuration list for PBXNativeTarget "ClaretCacheDemo" */; 158 | buildPhases = ( 159 | F3E5A49D06B895518A09FF37 /* [CP] Check Pods Manifest.lock */, 160 | 01C75DE422E0723E00C7D03F /* Sources */, 161 | 01C75DE522E0723E00C7D03F /* Frameworks */, 162 | 01C75DE622E0723E00C7D03F /* Resources */, 163 | 01FA686F22E719F3008E24FC /* Lint Script */, 164 | ); 165 | buildRules = ( 166 | ); 167 | dependencies = ( 168 | ); 169 | name = ClaretCacheDemo; 170 | packageProductDependencies = ( 171 | ); 172 | productName = ClaretCacheDemo; 173 | productReference = 01C75DE822E0723E00C7D03F /* ClaretCacheDemo.app */; 174 | productType = "com.apple.product-type.application"; 175 | }; 176 | F8C53E7322EB9EBC00B53664 /* ClaretCacheDemoTests */ = { 177 | isa = PBXNativeTarget; 178 | buildConfigurationList = F8C53E7D22EB9EBC00B53664 /* Build configuration list for PBXNativeTarget "ClaretCacheDemoTests" */; 179 | buildPhases = ( 180 | F8C53E7022EB9EBC00B53664 /* Sources */, 181 | F8C53E7122EB9EBC00B53664 /* Frameworks */, 182 | F8C53E7222EB9EBC00B53664 /* Resources */, 183 | F8C53E7E22EB9ECC00B53664 /* Lint Script */, 184 | ); 185 | buildRules = ( 186 | ); 187 | dependencies = ( 188 | F8C53E7A22EB9EBC00B53664 /* PBXTargetDependency */, 189 | ); 190 | name = ClaretCacheDemoTests; 191 | productName = ClaretCacheDemoTests; 192 | productReference = F8C53E7422EB9EBC00B53664 /* ClaretCacheDemoTests.xctest */; 193 | productType = "com.apple.product-type.bundle.unit-test"; 194 | }; 195 | /* End PBXNativeTarget section */ 196 | 197 | /* Begin PBXProject section */ 198 | 01C75DE022E0723E00C7D03F /* Project object */ = { 199 | isa = PBXProject; 200 | attributes = { 201 | LastSwiftUpdateCheck = 1100; 202 | LastUpgradeCheck = 1020; 203 | ORGANIZATIONNAME = com.ClaretCache; 204 | TargetAttributes = { 205 | 01C75DE722E0723E00C7D03F = { 206 | CreatedOnToolsVersion = 10.2.1; 207 | }; 208 | F8C53E7322EB9EBC00B53664 = { 209 | CreatedOnToolsVersion = 11.0; 210 | TestTargetID = 01C75DE722E0723E00C7D03F; 211 | }; 212 | }; 213 | }; 214 | buildConfigurationList = 01C75DE322E0723E00C7D03F /* Build configuration list for PBXProject "ClaretCacheDemo" */; 215 | compatibilityVersion = "Xcode 9.3"; 216 | developmentRegion = en; 217 | hasScannedForEncodings = 0; 218 | knownRegions = ( 219 | en, 220 | Base, 221 | ); 222 | mainGroup = 01C75DDF22E0723E00C7D03F; 223 | packageReferences = ( 224 | ); 225 | productRefGroup = 01C75DE922E0723E00C7D03F /* Products */; 226 | projectDirPath = ""; 227 | projectRoot = ""; 228 | targets = ( 229 | 01C75DE722E0723E00C7D03F /* ClaretCacheDemo */, 230 | F8C53E7322EB9EBC00B53664 /* ClaretCacheDemoTests */, 231 | ); 232 | }; 233 | /* End PBXProject section */ 234 | 235 | /* Begin PBXResourcesBuildPhase section */ 236 | 01C75DE622E0723E00C7D03F /* Resources */ = { 237 | isa = PBXResourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | 01C75DF622E0724000C7D03F /* LaunchScreen.storyboard in Resources */, 241 | 01C75DF322E0724000C7D03F /* Assets.xcassets in Resources */, 242 | 01C75DF122E0723E00C7D03F /* Main.storyboard in Resources */, 243 | ); 244 | runOnlyForDeploymentPostprocessing = 0; 245 | }; 246 | F8C53E7222EB9EBC00B53664 /* Resources */ = { 247 | isa = PBXResourcesBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | /* End PBXResourcesBuildPhase section */ 254 | 255 | /* Begin PBXShellScriptBuildPhase section */ 256 | 01FA686F22E719F3008E24FC /* Lint Script */ = { 257 | isa = PBXShellScriptBuildPhase; 258 | buildActionMask = 12; 259 | files = ( 260 | ); 261 | inputFileListPaths = ( 262 | ); 263 | inputPaths = ( 264 | ); 265 | name = "Lint Script"; 266 | outputFileListPaths = ( 267 | ); 268 | outputPaths = ( 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | shellPath = /bin/sh; 272 | shellScript = "\"${SRCROOT}/BuildScripts/lint.swift\" \"${SRCROOT}\"\n"; 273 | }; 274 | F3E5A49D06B895518A09FF37 /* [CP] Check Pods Manifest.lock */ = { 275 | isa = PBXShellScriptBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | ); 279 | inputFileListPaths = ( 280 | ); 281 | inputPaths = ( 282 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 283 | "${PODS_ROOT}/Manifest.lock", 284 | ); 285 | name = "[CP] Check Pods Manifest.lock"; 286 | outputFileListPaths = ( 287 | ); 288 | outputPaths = ( 289 | "$(DERIVED_FILE_DIR)/Pods-ClaretCacheDemo-checkManifestLockResult.txt", 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | shellPath = /bin/sh; 293 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 294 | showEnvVarsInLog = 0; 295 | }; 296 | F8C53E7E22EB9ECC00B53664 /* Lint Script */ = { 297 | isa = PBXShellScriptBuildPhase; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | ); 301 | inputFileListPaths = ( 302 | ); 303 | inputPaths = ( 304 | ); 305 | name = "Lint Script"; 306 | outputFileListPaths = ( 307 | ); 308 | outputPaths = ( 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | shellPath = /bin/sh; 312 | shellScript = "\"${SRCROOT}/BuildScripts/lint.swift\" \"${SRCROOT}\"\n"; 313 | }; 314 | /* End PBXShellScriptBuildPhase section */ 315 | 316 | /* Begin PBXSourcesBuildPhase section */ 317 | 01C75DE422E0723E00C7D03F /* Sources */ = { 318 | isa = PBXSourcesBuildPhase; 319 | buildActionMask = 2147483647; 320 | files = ( 321 | 01FA686D22E719DB008E24FC /* MemoryCache.swift in Sources */, 322 | 01C75DEE22E0723E00C7D03F /* ViewController.swift in Sources */, 323 | C30EB06522ED8E8A0064ED79 /* KVStorage.swift in Sources */, 324 | 01C75DEC22E0723E00C7D03F /* AppDelegate.swift in Sources */, 325 | 01FA686E22E719DB008E24FC /* ClaretCache.swift in Sources */, 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | }; 329 | F8C53E7022EB9EBC00B53664 /* Sources */ = { 330 | isa = PBXSourcesBuildPhase; 331 | buildActionMask = 2147483647; 332 | files = ( 333 | F8C53E7722EB9EBC00B53664 /* ClaretCacheDemoTests.swift in Sources */, 334 | ); 335 | runOnlyForDeploymentPostprocessing = 0; 336 | }; 337 | /* End PBXSourcesBuildPhase section */ 338 | 339 | /* Begin PBXTargetDependency section */ 340 | F8C53E7A22EB9EBC00B53664 /* PBXTargetDependency */ = { 341 | isa = PBXTargetDependency; 342 | target = 01C75DE722E0723E00C7D03F /* ClaretCacheDemo */; 343 | targetProxy = F8C53E7922EB9EBC00B53664 /* PBXContainerItemProxy */; 344 | }; 345 | /* End PBXTargetDependency section */ 346 | 347 | /* Begin PBXVariantGroup section */ 348 | 01C75DEF22E0723E00C7D03F /* Main.storyboard */ = { 349 | isa = PBXVariantGroup; 350 | children = ( 351 | 01C75DF022E0723E00C7D03F /* Base */, 352 | ); 353 | name = Main.storyboard; 354 | sourceTree = ""; 355 | }; 356 | 01C75DF422E0724000C7D03F /* LaunchScreen.storyboard */ = { 357 | isa = PBXVariantGroup; 358 | children = ( 359 | 01C75DF522E0724000C7D03F /* Base */, 360 | ); 361 | name = LaunchScreen.storyboard; 362 | sourceTree = ""; 363 | }; 364 | /* End PBXVariantGroup section */ 365 | 366 | /* Begin XCBuildConfiguration section */ 367 | 01C75DF822E0724000C7D03F /* Debug */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | ALWAYS_SEARCH_USER_PATHS = NO; 371 | CLANG_ANALYZER_NONNULL = YES; 372 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 373 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 374 | CLANG_CXX_LIBRARY = "libc++"; 375 | CLANG_ENABLE_MODULES = YES; 376 | CLANG_ENABLE_OBJC_ARC = YES; 377 | CLANG_ENABLE_OBJC_WEAK = YES; 378 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 379 | CLANG_WARN_BOOL_CONVERSION = YES; 380 | CLANG_WARN_COMMA = YES; 381 | CLANG_WARN_CONSTANT_CONVERSION = YES; 382 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 383 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 384 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 385 | CLANG_WARN_EMPTY_BODY = YES; 386 | CLANG_WARN_ENUM_CONVERSION = YES; 387 | CLANG_WARN_INFINITE_RECURSION = YES; 388 | CLANG_WARN_INT_CONVERSION = YES; 389 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 390 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 391 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 392 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 393 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 394 | CLANG_WARN_STRICT_PROTOTYPES = YES; 395 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 396 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 397 | CLANG_WARN_UNREACHABLE_CODE = YES; 398 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 399 | CODE_SIGN_IDENTITY = "iPhone Developer"; 400 | COPY_PHASE_STRIP = NO; 401 | DEBUG_INFORMATION_FORMAT = dwarf; 402 | ENABLE_STRICT_OBJC_MSGSEND = YES; 403 | ENABLE_TESTABILITY = YES; 404 | GCC_C_LANGUAGE_STANDARD = gnu11; 405 | GCC_DYNAMIC_NO_PIC = NO; 406 | GCC_NO_COMMON_BLOCKS = YES; 407 | GCC_OPTIMIZATION_LEVEL = 0; 408 | GCC_PREPROCESSOR_DEFINITIONS = ( 409 | "DEBUG=1", 410 | "$(inherited)", 411 | ); 412 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 413 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 414 | GCC_WARN_UNDECLARED_SELECTOR = YES; 415 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 416 | GCC_WARN_UNUSED_FUNCTION = YES; 417 | GCC_WARN_UNUSED_VARIABLE = YES; 418 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 419 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 420 | MTL_FAST_MATH = YES; 421 | ONLY_ACTIVE_ARCH = YES; 422 | SDKROOT = iphoneos; 423 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 424 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 425 | }; 426 | name = Debug; 427 | }; 428 | 01C75DF922E0724000C7D03F /* Release */ = { 429 | isa = XCBuildConfiguration; 430 | buildSettings = { 431 | ALWAYS_SEARCH_USER_PATHS = NO; 432 | CLANG_ANALYZER_NONNULL = YES; 433 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 434 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 435 | CLANG_CXX_LIBRARY = "libc++"; 436 | CLANG_ENABLE_MODULES = YES; 437 | CLANG_ENABLE_OBJC_ARC = YES; 438 | CLANG_ENABLE_OBJC_WEAK = YES; 439 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 440 | CLANG_WARN_BOOL_CONVERSION = YES; 441 | CLANG_WARN_COMMA = YES; 442 | CLANG_WARN_CONSTANT_CONVERSION = YES; 443 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 444 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 445 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 446 | CLANG_WARN_EMPTY_BODY = YES; 447 | CLANG_WARN_ENUM_CONVERSION = YES; 448 | CLANG_WARN_INFINITE_RECURSION = YES; 449 | CLANG_WARN_INT_CONVERSION = YES; 450 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 451 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 452 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 453 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 454 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 455 | CLANG_WARN_STRICT_PROTOTYPES = YES; 456 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 457 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 458 | CLANG_WARN_UNREACHABLE_CODE = YES; 459 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 460 | CODE_SIGN_IDENTITY = "iPhone Developer"; 461 | COPY_PHASE_STRIP = NO; 462 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 463 | ENABLE_NS_ASSERTIONS = NO; 464 | ENABLE_STRICT_OBJC_MSGSEND = YES; 465 | GCC_C_LANGUAGE_STANDARD = gnu11; 466 | GCC_NO_COMMON_BLOCKS = YES; 467 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 468 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 469 | GCC_WARN_UNDECLARED_SELECTOR = YES; 470 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 471 | GCC_WARN_UNUSED_FUNCTION = YES; 472 | GCC_WARN_UNUSED_VARIABLE = YES; 473 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 474 | MTL_ENABLE_DEBUG_INFO = NO; 475 | MTL_FAST_MATH = YES; 476 | SDKROOT = iphoneos; 477 | SWIFT_COMPILATION_MODE = wholemodule; 478 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 479 | VALIDATE_PRODUCT = YES; 480 | }; 481 | name = Release; 482 | }; 483 | 01C75DFB22E0724000C7D03F /* Debug */ = { 484 | isa = XCBuildConfiguration; 485 | baseConfigurationReference = 96892E93F01C8D0A4FEC2EC1 /* Pods-ClaretCacheDemo.debug.xcconfig */; 486 | buildSettings = { 487 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 488 | CODE_SIGN_STYLE = Automatic; 489 | DEVELOPMENT_TEAM = 3289Y6BF27; 490 | INFOPLIST_FILE = ClaretCacheDemo/Info.plist; 491 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 492 | LD_RUNPATH_SEARCH_PATHS = ( 493 | "$(inherited)", 494 | "@executable_path/Frameworks", 495 | ); 496 | PRODUCT_BUNDLE_IDENTIFIER = "com.ClaretCache.-222"; 497 | PRODUCT_NAME = "$(TARGET_NAME)"; 498 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG ClaretCacheLOG"; 499 | SWIFT_VERSION = 5.0; 500 | TARGETED_DEVICE_FAMILY = "1,2"; 501 | }; 502 | name = Debug; 503 | }; 504 | 01C75DFC22E0724000C7D03F /* Release */ = { 505 | isa = XCBuildConfiguration; 506 | baseConfigurationReference = 71DE9F8F64DFC38FD8ABCF96 /* Pods-ClaretCacheDemo.release.xcconfig */; 507 | buildSettings = { 508 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 509 | CODE_SIGN_STYLE = Automatic; 510 | DEVELOPMENT_TEAM = 3289Y6BF27; 511 | INFOPLIST_FILE = ClaretCacheDemo/Info.plist; 512 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 513 | LD_RUNPATH_SEARCH_PATHS = ( 514 | "$(inherited)", 515 | "@executable_path/Frameworks", 516 | ); 517 | PRODUCT_BUNDLE_IDENTIFIER = "com.ClaretCache.-222"; 518 | PRODUCT_NAME = "$(TARGET_NAME)"; 519 | SWIFT_VERSION = 5.0; 520 | TARGETED_DEVICE_FAMILY = "1,2"; 521 | }; 522 | name = Release; 523 | }; 524 | F8C53E7B22EB9EBC00B53664 /* Debug */ = { 525 | isa = XCBuildConfiguration; 526 | buildSettings = { 527 | BUNDLE_LOADER = "$(TEST_HOST)"; 528 | CODE_SIGN_STYLE = Automatic; 529 | INFOPLIST_FILE = ClaretCacheDemoTests/Info.plist; 530 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 531 | LD_RUNPATH_SEARCH_PATHS = ( 532 | "$(inherited)", 533 | "@executable_path/Frameworks", 534 | "@loader_path/Frameworks", 535 | ); 536 | PRODUCT_BUNDLE_IDENTIFIER = com.ClaretCache.ClaretCacheDemoTests; 537 | PRODUCT_NAME = "$(TARGET_NAME)"; 538 | SWIFT_VERSION = 5.0; 539 | TARGETED_DEVICE_FAMILY = "1,2"; 540 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ClaretCacheDemo.app/ClaretCacheDemo"; 541 | }; 542 | name = Debug; 543 | }; 544 | F8C53E7C22EB9EBC00B53664 /* Release */ = { 545 | isa = XCBuildConfiguration; 546 | buildSettings = { 547 | BUNDLE_LOADER = "$(TEST_HOST)"; 548 | CODE_SIGN_STYLE = Automatic; 549 | INFOPLIST_FILE = ClaretCacheDemoTests/Info.plist; 550 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 551 | LD_RUNPATH_SEARCH_PATHS = ( 552 | "$(inherited)", 553 | "@executable_path/Frameworks", 554 | "@loader_path/Frameworks", 555 | ); 556 | PRODUCT_BUNDLE_IDENTIFIER = com.ClaretCache.ClaretCacheDemoTests; 557 | PRODUCT_NAME = "$(TARGET_NAME)"; 558 | SWIFT_VERSION = 5.0; 559 | TARGETED_DEVICE_FAMILY = "1,2"; 560 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ClaretCacheDemo.app/ClaretCacheDemo"; 561 | }; 562 | name = Release; 563 | }; 564 | /* End XCBuildConfiguration section */ 565 | 566 | /* Begin XCConfigurationList section */ 567 | 01C75DE322E0723E00C7D03F /* Build configuration list for PBXProject "ClaretCacheDemo" */ = { 568 | isa = XCConfigurationList; 569 | buildConfigurations = ( 570 | 01C75DF822E0724000C7D03F /* Debug */, 571 | 01C75DF922E0724000C7D03F /* Release */, 572 | ); 573 | defaultConfigurationIsVisible = 0; 574 | defaultConfigurationName = Release; 575 | }; 576 | 01C75DFA22E0724000C7D03F /* Build configuration list for PBXNativeTarget "ClaretCacheDemo" */ = { 577 | isa = XCConfigurationList; 578 | buildConfigurations = ( 579 | 01C75DFB22E0724000C7D03F /* Debug */, 580 | 01C75DFC22E0724000C7D03F /* Release */, 581 | ); 582 | defaultConfigurationIsVisible = 0; 583 | defaultConfigurationName = Release; 584 | }; 585 | F8C53E7D22EB9EBC00B53664 /* Build configuration list for PBXNativeTarget "ClaretCacheDemoTests" */ = { 586 | isa = XCConfigurationList; 587 | buildConfigurations = ( 588 | F8C53E7B22EB9EBC00B53664 /* Debug */, 589 | F8C53E7C22EB9EBC00B53664 /* Release */, 590 | ); 591 | defaultConfigurationIsVisible = 0; 592 | defaultConfigurationName = Release; 593 | }; 594 | /* End XCConfigurationList section */ 595 | }; 596 | rootObject = 01C75DE022E0723E00C7D03F /* Project object */; 597 | } 598 | -------------------------------------------------------------------------------- /ClaretCacheDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ClaretCacheDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ClaretCacheDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "ClaretCache", 6 | "repositoryURL": "https://github.com/iteatimeteam/ClaretCache.git", 7 | "state": { 8 | "branch": "master", 9 | "revision": "f4afb1f10d1d35076eef323354cd4c8678bb7317", 10 | "version": null 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /ClaretCacheDemo.xcodeproj/xcshareddata/xcschemes/ClaretCacheDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /ClaretCacheDemo.xcodeproj/xcshareddata/xcschemes/ClaretCacheDemoTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 16 | 17 | 19 | 25 | 26 | 27 | 28 | 29 | 39 | 41 | 47 | 48 | 49 | 50 | 56 | 57 | 63 | 64 | 65 | 66 | 68 | 69 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /ClaretCacheDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ClaretCacheDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ClaretCacheDemo.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "ClaretCache", 6 | "repositoryURL": "https://github.com/iteatimeteam/ClaretCache.git", 7 | "state": { 8 | "branch": "master", 9 | "revision": "f4afb1f10d1d35076eef323354cd4c8678bb7317", 10 | "version": null 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /ClaretCacheDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ClaretCacheDemo 4 | // 5 | // Created by BirdMichael on 2019/7/18. 6 | // Copyright © 2019 com.ClaretCache. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /ClaretCacheDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /ClaretCacheDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ClaretCacheDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ClaretCacheDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ClaretCacheDemo/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ClaretCacheDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ClaretCacheDemo 4 | // 5 | // Created by BirdMichael on 2019/7/18. 6 | // Copyright © 2019 com.ClaretCache. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /ClaretCacheDemoTests/ClaretCacheDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClaretCacheDemoTests.swift 3 | // ClaretCacheDemoTests 4 | // 5 | // Created by DearLan on 27/07/19. 6 | // Copyright © 2019 com.ClaretCache. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ClaretCacheDemo 11 | 12 | class ClaretCacheDemoTests: XCTestCase { 13 | 14 | let cache: MemoryCache = MemoryCache() 15 | 16 | override func setUp() { 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | // self.cache = MemoryCache() 19 | } 20 | 21 | override func tearDown() { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testInsert() { 26 | // This is an example of a functional test case. 27 | // Use XCTAssert and related functions to verify your tests produce the correct results. 28 | self.cache.countLimit = 800 29 | 30 | for idx in 1...1000 { 31 | self.cache[idx] = idx 32 | } 33 | 34 | assert(self.cache.totalCount == 800, "淘汰策略出错") 35 | } 36 | 37 | func testRead() { 38 | self.cache.removeAll() 39 | 40 | self.cache.countLimit = 800 41 | 42 | for idx in 1...1000 { 43 | self.cache[idx] = idx 44 | } 45 | // print("key: 1, value: \(self.cache[1] ?? -1)") 46 | // print("key: 888, value: \(self.cache[888] ?? -1)") 47 | // print("key: 777, value: \(self.cache[777] ?? -1)") 48 | // print("key: 999, value: \(self.cache[999] ?? -1)") 49 | assert(self.cache[1] == nil, "淘汰策略出错") 50 | assert((self.cache[888] ?? -1) == 888, "读取key为888,失败") 51 | assert((self.cache[777] ?? -1) == 777, "读取key为777,失败") 52 | assert((self.cache[999] ?? -1) == 999, "读取key为999,失败") 53 | } 54 | 55 | // func testPerformanceExample() { 56 | // // This is an example of a performance test case. 57 | // measure(metrics: [XCTCPUMetric()]) { 58 | // // Put the code whose CPU performance you want to measure here. 59 | // for idx in 1...100_000 { 60 | // self.cache[idx] = idx 61 | // } 62 | // } 63 | // } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /ClaretCacheDemoTests/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Design/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iteatimeteam/ClaretCache/e573fd4c1655907c0260c545de8c3f9cc670a882/Design/banner.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 iTeaTime(技术清谈) 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ClaretCache", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "ClaretCache", 12 | targets: ["ClaretCache"]) 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "ClaretCache", 23 | dependencies: []), 24 | .testTarget( 25 | name: "ClaretCacheTests", 26 | dependencies: ["ClaretCache"]) 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | use_frameworks! 3 | 4 | def swiftlint_pods 5 | pod 'SwiftLint' 6 | end 7 | 8 | target 'ClaretCacheDemo' do 9 | swiftlint_pods 10 | end 11 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - SwiftLint (0.34.0) 3 | 4 | DEPENDENCIES: 5 | - SwiftLint 6 | 7 | SPEC REPOS: 8 | https://github.com/cocoapods/specs.git: 9 | - SwiftLint 10 | 11 | SPEC CHECKSUMS: 12 | SwiftLint: 79d48a17c6565dc286c37efb8322c7b450f95c67 13 | 14 | PODFILE CHECKSUM: 350c6113d834d0113b948683438820eaac86c165 15 | 16 | COCOAPODS: 1.7.5 17 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - SwiftLint (0.34.0) 3 | 4 | DEPENDENCIES: 5 | - SwiftLint 6 | 7 | SPEC REPOS: 8 | https://github.com/cocoapods/specs.git: 9 | - SwiftLint 10 | 11 | SPEC CHECKSUMS: 12 | SwiftLint: 79d48a17c6565dc286c37efb8322c7b450f95c67 13 | 14 | PODFILE CHECKSUM: 350c6113d834d0113b948683438820eaac86c165 15 | 16 | COCOAPODS: 1.7.5 17 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXAggregateTarget section */ 10 | 52B60EC2A583F24ACBB69C113F5488B9 /* SwiftLint */ = { 11 | isa = PBXAggregateTarget; 12 | buildConfigurationList = AE7B4FB01588B9E6DF09CB79FC7CE7BD /* Build configuration list for PBXAggregateTarget "SwiftLint" */; 13 | buildPhases = ( 14 | ); 15 | dependencies = ( 16 | ); 17 | name = SwiftLint; 18 | }; 19 | /* End PBXAggregateTarget section */ 20 | 21 | /* Begin PBXBuildFile section */ 22 | 1545FA0F372F7F536A95C412391296FA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3212113385A8FBBDB272BD23C409FF61 /* Foundation.framework */; }; 23 | D08F2B30E6E3EEDAEA20B9D6C7E19FEA /* Pods-ClaretCacheDemo-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7925E73964F85077B86B1627D7143318 /* Pods-ClaretCacheDemo-dummy.m */; }; 24 | FB45D196FE39F2F4092F5B5AC9AE608E /* Pods-ClaretCacheDemo-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 51693845BD2FA4CD6E10BE86CE1A412F /* Pods-ClaretCacheDemo-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | B22D5EB1EC5051B2E7711A49C1048584 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = 52B60EC2A583F24ACBB69C113F5488B9; 33 | remoteInfo = SwiftLint; 34 | }; 35 | /* End PBXContainerItemProxy section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 0C8A551FDDC9A8FE3B053CCE857B24EA /* Pods-ClaretCacheDemo.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-ClaretCacheDemo.modulemap"; sourceTree = ""; }; 39 | 3212113385A8FBBDB272BD23C409FF61 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 40 | 51693845BD2FA4CD6E10BE86CE1A412F /* Pods-ClaretCacheDemo-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-ClaretCacheDemo-umbrella.h"; sourceTree = ""; }; 41 | 6DD1E28C6C36FFC83305E6C849CED21A /* Pods-ClaretCacheDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ClaretCacheDemo.release.xcconfig"; sourceTree = ""; }; 42 | 709AB956124B9D6A556A971AC9DF2A9F /* Pods-ClaretCacheDemo-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-ClaretCacheDemo-acknowledgements.markdown"; sourceTree = ""; }; 43 | 7665F48DF774A41833998E41BA1D3C54 /* Pods_ClaretCacheDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_ClaretCacheDemo.framework; path = "Pods-ClaretCacheDemo.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 7925E73964F85077B86B1627D7143318 /* Pods-ClaretCacheDemo-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-ClaretCacheDemo-dummy.m"; sourceTree = ""; }; 45 | 89F85ACF3D78B39506B75103B4694B36 /* SwiftLint.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftLint.xcconfig; sourceTree = ""; }; 46 | 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 47 | AAEA8C8384B11749144512E4E7A315C7 /* Pods-ClaretCacheDemo-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ClaretCacheDemo-Info.plist"; sourceTree = ""; }; 48 | B0F004D9A78B6D873D312EB2D9B5002C /* Pods-ClaretCacheDemo-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ClaretCacheDemo-acknowledgements.plist"; sourceTree = ""; }; 49 | E4E9FE683D5E75026B361E1A2E7ED6D9 /* Pods-ClaretCacheDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ClaretCacheDemo.debug.xcconfig"; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 7893321BD4F33A28DEB9CB51B6A4DF9F /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 1545FA0F372F7F536A95C412391296FA /* Foundation.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 127668E3C5A20E1E2535F705BDFC5935 /* Targets Support Files */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 52B682B36B5025B805B1CA0BBB032C15 /* Pods-ClaretCacheDemo */, 68 | ); 69 | name = "Targets Support Files"; 70 | sourceTree = ""; 71 | }; 72 | 52B682B36B5025B805B1CA0BBB032C15 /* Pods-ClaretCacheDemo */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 0C8A551FDDC9A8FE3B053CCE857B24EA /* Pods-ClaretCacheDemo.modulemap */, 76 | 709AB956124B9D6A556A971AC9DF2A9F /* Pods-ClaretCacheDemo-acknowledgements.markdown */, 77 | B0F004D9A78B6D873D312EB2D9B5002C /* Pods-ClaretCacheDemo-acknowledgements.plist */, 78 | 7925E73964F85077B86B1627D7143318 /* Pods-ClaretCacheDemo-dummy.m */, 79 | AAEA8C8384B11749144512E4E7A315C7 /* Pods-ClaretCacheDemo-Info.plist */, 80 | 51693845BD2FA4CD6E10BE86CE1A412F /* Pods-ClaretCacheDemo-umbrella.h */, 81 | E4E9FE683D5E75026B361E1A2E7ED6D9 /* Pods-ClaretCacheDemo.debug.xcconfig */, 82 | 6DD1E28C6C36FFC83305E6C849CED21A /* Pods-ClaretCacheDemo.release.xcconfig */, 83 | ); 84 | name = "Pods-ClaretCacheDemo"; 85 | path = "Target Support Files/Pods-ClaretCacheDemo"; 86 | sourceTree = ""; 87 | }; 88 | 965877409E01FB3D85D85E90E6B30185 /* Pods */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 9AED61B9A1CFA211C061344F741DBA07 /* SwiftLint */, 92 | ); 93 | name = Pods; 94 | sourceTree = ""; 95 | }; 96 | 9AED61B9A1CFA211C061344F741DBA07 /* SwiftLint */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | EEA599FE5C15138FF0981CCFB01E55AA /* Support Files */, 100 | ); 101 | name = SwiftLint; 102 | path = SwiftLint; 103 | sourceTree = ""; 104 | }; 105 | C0834CEBB1379A84116EF29F93051C60 /* iOS */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 3212113385A8FBBDB272BD23C409FF61 /* Foundation.framework */, 109 | ); 110 | name = iOS; 111 | sourceTree = ""; 112 | }; 113 | CF1408CF629C7361332E53B88F7BD30C = { 114 | isa = PBXGroup; 115 | children = ( 116 | 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, 117 | D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */, 118 | 965877409E01FB3D85D85E90E6B30185 /* Pods */, 119 | DDF9FCB28C5C05272C13C9F6D61BE6B5 /* Products */, 120 | 127668E3C5A20E1E2535F705BDFC5935 /* Targets Support Files */, 121 | ); 122 | sourceTree = ""; 123 | }; 124 | D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | C0834CEBB1379A84116EF29F93051C60 /* iOS */, 128 | ); 129 | name = Frameworks; 130 | sourceTree = ""; 131 | }; 132 | DDF9FCB28C5C05272C13C9F6D61BE6B5 /* Products */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 7665F48DF774A41833998E41BA1D3C54 /* Pods_ClaretCacheDemo.framework */, 136 | ); 137 | name = Products; 138 | sourceTree = ""; 139 | }; 140 | EEA599FE5C15138FF0981CCFB01E55AA /* Support Files */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 89F85ACF3D78B39506B75103B4694B36 /* SwiftLint.xcconfig */, 144 | ); 145 | name = "Support Files"; 146 | path = "../Target Support Files/SwiftLint"; 147 | sourceTree = ""; 148 | }; 149 | /* End PBXGroup section */ 150 | 151 | /* Begin PBXHeadersBuildPhase section */ 152 | A77453827E66D7565EB820308273BFED /* Headers */ = { 153 | isa = PBXHeadersBuildPhase; 154 | buildActionMask = 2147483647; 155 | files = ( 156 | FB45D196FE39F2F4092F5B5AC9AE608E /* Pods-ClaretCacheDemo-umbrella.h in Headers */, 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | /* End PBXHeadersBuildPhase section */ 161 | 162 | /* Begin PBXNativeTarget section */ 163 | 1697114A1DE7BB63D9CAD67693D44DBF /* Pods-ClaretCacheDemo */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = B490CF3B9554414A29BDFD3239957AF0 /* Build configuration list for PBXNativeTarget "Pods-ClaretCacheDemo" */; 166 | buildPhases = ( 167 | A77453827E66D7565EB820308273BFED /* Headers */, 168 | 6679A687BBDFA6C201E69746FFE5AA2E /* Sources */, 169 | 7893321BD4F33A28DEB9CB51B6A4DF9F /* Frameworks */, 170 | B0DD032C0D4533BC51C9D454BFBD7384 /* Resources */, 171 | ); 172 | buildRules = ( 173 | ); 174 | dependencies = ( 175 | 1D2338711D541CFBD66E54FA4F34A5DB /* PBXTargetDependency */, 176 | ); 177 | name = "Pods-ClaretCacheDemo"; 178 | productName = "Pods-ClaretCacheDemo"; 179 | productReference = 7665F48DF774A41833998E41BA1D3C54 /* Pods_ClaretCacheDemo.framework */; 180 | productType = "com.apple.product-type.framework"; 181 | }; 182 | /* End PBXNativeTarget section */ 183 | 184 | /* Begin PBXProject section */ 185 | BFDFE7DC352907FC980B868725387E98 /* Project object */ = { 186 | isa = PBXProject; 187 | attributes = { 188 | LastSwiftUpdateCheck = 1100; 189 | LastUpgradeCheck = 1100; 190 | }; 191 | buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; 192 | compatibilityVersion = "Xcode 9.3"; 193 | developmentRegion = en; 194 | hasScannedForEncodings = 0; 195 | knownRegions = ( 196 | en, 197 | ); 198 | mainGroup = CF1408CF629C7361332E53B88F7BD30C; 199 | productRefGroup = DDF9FCB28C5C05272C13C9F6D61BE6B5 /* Products */; 200 | projectDirPath = ""; 201 | projectRoot = ""; 202 | targets = ( 203 | 1697114A1DE7BB63D9CAD67693D44DBF /* Pods-ClaretCacheDemo */, 204 | 52B60EC2A583F24ACBB69C113F5488B9 /* SwiftLint */, 205 | ); 206 | }; 207 | /* End PBXProject section */ 208 | 209 | /* Begin PBXResourcesBuildPhase section */ 210 | B0DD032C0D4533BC51C9D454BFBD7384 /* Resources */ = { 211 | isa = PBXResourcesBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | /* End PBXResourcesBuildPhase section */ 218 | 219 | /* Begin PBXSourcesBuildPhase section */ 220 | 6679A687BBDFA6C201E69746FFE5AA2E /* Sources */ = { 221 | isa = PBXSourcesBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | D08F2B30E6E3EEDAEA20B9D6C7E19FEA /* Pods-ClaretCacheDemo-dummy.m in Sources */, 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | /* End PBXSourcesBuildPhase section */ 229 | 230 | /* Begin PBXTargetDependency section */ 231 | 1D2338711D541CFBD66E54FA4F34A5DB /* PBXTargetDependency */ = { 232 | isa = PBXTargetDependency; 233 | name = SwiftLint; 234 | target = 52B60EC2A583F24ACBB69C113F5488B9 /* SwiftLint */; 235 | targetProxy = B22D5EB1EC5051B2E7711A49C1048584 /* PBXContainerItemProxy */; 236 | }; 237 | /* End PBXTargetDependency section */ 238 | 239 | /* Begin XCBuildConfiguration section */ 240 | 84D7C4574E8F0F3095623F0E06F5B402 /* Release */ = { 241 | isa = XCBuildConfiguration; 242 | baseConfigurationReference = 89F85ACF3D78B39506B75103B4694B36 /* SwiftLint.xcconfig */; 243 | buildSettings = { 244 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 245 | CODE_SIGN_IDENTITY = "iPhone Developer"; 246 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 247 | LD_RUNPATH_SEARCH_PATHS = ( 248 | "$(inherited)", 249 | "@executable_path/Frameworks", 250 | ); 251 | SDKROOT = iphoneos; 252 | TARGETED_DEVICE_FAMILY = "1,2"; 253 | VALIDATE_PRODUCT = YES; 254 | }; 255 | name = Release; 256 | }; 257 | 8F17DC3A99F99FBAD606CE6963886315 /* Release */ = { 258 | isa = XCBuildConfiguration; 259 | buildSettings = { 260 | ALWAYS_SEARCH_USER_PATHS = NO; 261 | CLANG_ANALYZER_NONNULL = YES; 262 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 263 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 264 | CLANG_CXX_LIBRARY = "libc++"; 265 | CLANG_ENABLE_MODULES = YES; 266 | CLANG_ENABLE_OBJC_ARC = YES; 267 | CLANG_ENABLE_OBJC_WEAK = YES; 268 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 269 | CLANG_WARN_BOOL_CONVERSION = YES; 270 | CLANG_WARN_COMMA = YES; 271 | CLANG_WARN_CONSTANT_CONVERSION = YES; 272 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 273 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 274 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 275 | CLANG_WARN_EMPTY_BODY = YES; 276 | CLANG_WARN_ENUM_CONVERSION = YES; 277 | CLANG_WARN_INFINITE_RECURSION = YES; 278 | CLANG_WARN_INT_CONVERSION = YES; 279 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 280 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 281 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 282 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 283 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 284 | CLANG_WARN_STRICT_PROTOTYPES = YES; 285 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 286 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 287 | CLANG_WARN_UNREACHABLE_CODE = YES; 288 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 289 | COPY_PHASE_STRIP = NO; 290 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 291 | ENABLE_NS_ASSERTIONS = NO; 292 | ENABLE_STRICT_OBJC_MSGSEND = YES; 293 | GCC_C_LANGUAGE_STANDARD = gnu11; 294 | GCC_NO_COMMON_BLOCKS = YES; 295 | GCC_PREPROCESSOR_DEFINITIONS = ( 296 | "POD_CONFIGURATION_RELEASE=1", 297 | "$(inherited)", 298 | ); 299 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 300 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 301 | GCC_WARN_UNDECLARED_SELECTOR = YES; 302 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 303 | GCC_WARN_UNUSED_FUNCTION = YES; 304 | GCC_WARN_UNUSED_VARIABLE = YES; 305 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 306 | MTL_ENABLE_DEBUG_INFO = NO; 307 | MTL_FAST_MATH = YES; 308 | PRODUCT_NAME = "$(TARGET_NAME)"; 309 | STRIP_INSTALLED_PRODUCT = NO; 310 | SWIFT_COMPILATION_MODE = wholemodule; 311 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 312 | SWIFT_VERSION = 5.0; 313 | SYMROOT = "${SRCROOT}/../build"; 314 | }; 315 | name = Release; 316 | }; 317 | 916E0404255105F480DC4950B7625F7A /* Debug */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ALWAYS_SEARCH_USER_PATHS = NO; 321 | CLANG_ANALYZER_NONNULL = YES; 322 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 324 | CLANG_CXX_LIBRARY = "libc++"; 325 | CLANG_ENABLE_MODULES = YES; 326 | CLANG_ENABLE_OBJC_ARC = YES; 327 | CLANG_ENABLE_OBJC_WEAK = YES; 328 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 329 | CLANG_WARN_BOOL_CONVERSION = YES; 330 | CLANG_WARN_COMMA = YES; 331 | CLANG_WARN_CONSTANT_CONVERSION = YES; 332 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 333 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 334 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INFINITE_RECURSION = YES; 338 | CLANG_WARN_INT_CONVERSION = YES; 339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 344 | CLANG_WARN_STRICT_PROTOTYPES = YES; 345 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 346 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 347 | CLANG_WARN_UNREACHABLE_CODE = YES; 348 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = dwarf; 351 | ENABLE_STRICT_OBJC_MSGSEND = YES; 352 | ENABLE_TESTABILITY = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu11; 354 | GCC_DYNAMIC_NO_PIC = NO; 355 | GCC_NO_COMMON_BLOCKS = YES; 356 | GCC_OPTIMIZATION_LEVEL = 0; 357 | GCC_PREPROCESSOR_DEFINITIONS = ( 358 | "POD_CONFIGURATION_DEBUG=1", 359 | "DEBUG=1", 360 | "$(inherited)", 361 | ); 362 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 363 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 364 | GCC_WARN_UNDECLARED_SELECTOR = YES; 365 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 366 | GCC_WARN_UNUSED_FUNCTION = YES; 367 | GCC_WARN_UNUSED_VARIABLE = YES; 368 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 369 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 370 | MTL_FAST_MATH = YES; 371 | ONLY_ACTIVE_ARCH = YES; 372 | PRODUCT_NAME = "$(TARGET_NAME)"; 373 | STRIP_INSTALLED_PRODUCT = NO; 374 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 375 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 376 | SWIFT_VERSION = 5.0; 377 | SYMROOT = "${SRCROOT}/../build"; 378 | }; 379 | name = Debug; 380 | }; 381 | C1EF790ADE2ABA9BA5FB669FFE7702D1 /* Debug */ = { 382 | isa = XCBuildConfiguration; 383 | baseConfigurationReference = E4E9FE683D5E75026B361E1A2E7ED6D9 /* Pods-ClaretCacheDemo.debug.xcconfig */; 384 | buildSettings = { 385 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; 386 | CODE_SIGN_IDENTITY = ""; 387 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; 388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 389 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; 390 | CURRENT_PROJECT_VERSION = 1; 391 | DEFINES_MODULE = YES; 392 | DYLIB_COMPATIBILITY_VERSION = 1; 393 | DYLIB_CURRENT_VERSION = 1; 394 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 395 | INFOPLIST_FILE = "Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo-Info.plist"; 396 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 397 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 398 | LD_RUNPATH_SEARCH_PATHS = ( 399 | "$(inherited)", 400 | "@executable_path/Frameworks", 401 | "@loader_path/Frameworks", 402 | ); 403 | MACH_O_TYPE = staticlib; 404 | MODULEMAP_FILE = "Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.modulemap"; 405 | OTHER_LDFLAGS = ""; 406 | OTHER_LIBTOOLFLAGS = ""; 407 | PODS_ROOT = "$(SRCROOT)"; 408 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; 409 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 410 | SDKROOT = iphoneos; 411 | SKIP_INSTALL = YES; 412 | TARGETED_DEVICE_FAMILY = "1,2"; 413 | VERSIONING_SYSTEM = "apple-generic"; 414 | VERSION_INFO_PREFIX = ""; 415 | }; 416 | name = Debug; 417 | }; 418 | C9BE49D9EA22DAC17AD4F5188F4CD81F /* Release */ = { 419 | isa = XCBuildConfiguration; 420 | baseConfigurationReference = 6DD1E28C6C36FFC83305E6C849CED21A /* Pods-ClaretCacheDemo.release.xcconfig */; 421 | buildSettings = { 422 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; 423 | CODE_SIGN_IDENTITY = ""; 424 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; 425 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 426 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; 427 | CURRENT_PROJECT_VERSION = 1; 428 | DEFINES_MODULE = YES; 429 | DYLIB_COMPATIBILITY_VERSION = 1; 430 | DYLIB_CURRENT_VERSION = 1; 431 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 432 | INFOPLIST_FILE = "Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo-Info.plist"; 433 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 434 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 435 | LD_RUNPATH_SEARCH_PATHS = ( 436 | "$(inherited)", 437 | "@executable_path/Frameworks", 438 | "@loader_path/Frameworks", 439 | ); 440 | MACH_O_TYPE = staticlib; 441 | MODULEMAP_FILE = "Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.modulemap"; 442 | OTHER_LDFLAGS = ""; 443 | OTHER_LIBTOOLFLAGS = ""; 444 | PODS_ROOT = "$(SRCROOT)"; 445 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; 446 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 447 | SDKROOT = iphoneos; 448 | SKIP_INSTALL = YES; 449 | TARGETED_DEVICE_FAMILY = "1,2"; 450 | VALIDATE_PRODUCT = YES; 451 | VERSIONING_SYSTEM = "apple-generic"; 452 | VERSION_INFO_PREFIX = ""; 453 | }; 454 | name = Release; 455 | }; 456 | DEED47E09AF743F48544C1C4FEADEF47 /* Debug */ = { 457 | isa = XCBuildConfiguration; 458 | baseConfigurationReference = 89F85ACF3D78B39506B75103B4694B36 /* SwiftLint.xcconfig */; 459 | buildSettings = { 460 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 461 | CODE_SIGN_IDENTITY = "iPhone Developer"; 462 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 463 | LD_RUNPATH_SEARCH_PATHS = ( 464 | "$(inherited)", 465 | "@executable_path/Frameworks", 466 | ); 467 | SDKROOT = iphoneos; 468 | TARGETED_DEVICE_FAMILY = "1,2"; 469 | }; 470 | name = Debug; 471 | }; 472 | /* End XCBuildConfiguration section */ 473 | 474 | /* Begin XCConfigurationList section */ 475 | 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { 476 | isa = XCConfigurationList; 477 | buildConfigurations = ( 478 | 916E0404255105F480DC4950B7625F7A /* Debug */, 479 | 8F17DC3A99F99FBAD606CE6963886315 /* Release */, 480 | ); 481 | defaultConfigurationIsVisible = 0; 482 | defaultConfigurationName = Release; 483 | }; 484 | AE7B4FB01588B9E6DF09CB79FC7CE7BD /* Build configuration list for PBXAggregateTarget "SwiftLint" */ = { 485 | isa = XCConfigurationList; 486 | buildConfigurations = ( 487 | DEED47E09AF743F48544C1C4FEADEF47 /* Debug */, 488 | 84D7C4574E8F0F3095623F0E06F5B402 /* Release */, 489 | ); 490 | defaultConfigurationIsVisible = 0; 491 | defaultConfigurationName = Release; 492 | }; 493 | B490CF3B9554414A29BDFD3239957AF0 /* Build configuration list for PBXNativeTarget "Pods-ClaretCacheDemo" */ = { 494 | isa = XCConfigurationList; 495 | buildConfigurations = ( 496 | C1EF790ADE2ABA9BA5FB669FFE7702D1 /* Debug */, 497 | C9BE49D9EA22DAC17AD4F5188F4CD81F /* Release */, 498 | ); 499 | defaultConfigurationIsVisible = 0; 500 | defaultConfigurationName = Release; 501 | }; 502 | /* End XCConfigurationList section */ 503 | }; 504 | rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; 505 | } 506 | -------------------------------------------------------------------------------- /Pods/SwiftLint/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Realm Inc. 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pods/SwiftLint/swiftlint: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iteatimeteam/ClaretCache/e573fd4c1655907c0260c545de8c3f9cc670a882/Pods/SwiftLint/swiftlint -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo-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.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## SwiftLint 5 | 6 | The MIT License (MIT) 7 | 8 | Copyright (c) 2015 Realm Inc. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | Generated by CocoaPods - https://cocoapods.org 29 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | The MIT License (MIT) 18 | 19 | Copyright (c) 2015 Realm Inc. 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | License 40 | MIT 41 | Title 42 | SwiftLint 43 | Type 44 | PSGroupSpecifier 45 | 46 | 47 | FooterText 48 | Generated by CocoaPods - https://cocoapods.org 49 | Title 50 | 51 | Type 52 | PSGroupSpecifier 53 | 54 | 55 | StringsTable 56 | Acknowledgements 57 | Title 58 | Acknowledgements 59 | 60 | 61 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_ClaretCacheDemo : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_ClaretCacheDemo 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_ClaretCacheDemoVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_ClaretCacheDemoVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.debug.xcconfig: -------------------------------------------------------------------------------- 1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 2 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 3 | PODS_BUILD_DIR = ${BUILD_DIR} 4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 5 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 6 | PODS_ROOT = ${SRCROOT}/Pods 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_ClaretCacheDemo { 2 | umbrella header "Pods-ClaretCacheDemo-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-ClaretCacheDemo/Pods-ClaretCacheDemo.release.xcconfig: -------------------------------------------------------------------------------- 1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 2 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 3 | PODS_BUILD_DIR = ${BUILD_DIR} 4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 5 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 6 | PODS_ROOT = ${SRCROOT}/Pods 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SwiftLint/SwiftLint.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | PODS_BUILD_DIR = ${BUILD_DIR} 4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 5 | PODS_ROOT = ${SRCROOT} 6 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftLint 7 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 8 | SKIP_INSTALL = YES 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ClaretCache | YYKit-Cache模块 Swift 版本 2 | 3 | 【声明:未发布,勿使用,预计2020年10月底发布】 4 | 5 | 6 | -------------------------------------------- 7 | 8 | ![](https://github.com/iteatimeteam/ClaretCache/blob/master/Design/banner.png) 9 | 10 | 13 | 14 | -------------------------------------------- 15 |

16 | 17 | iTeaTime(技术清谈)团队出品。 18 | 19 | 项目简介 20 | -------------------------------------------- 21 | 22 | 本项目意在将 YYKit-Cache 模块迁移至 Swift 版本,与 YYKit 作者沟通得知暂无 Swift 版本计划,这就是本项目的来源。 23 | 24 | 本库在版本发布后会陆续解决原仓库已存 issue, YYKit 原作者暂时未参与本项目开发,故本库与 YYKit-Cache 模块为两个独立项目,本仓库新引入的问题请在本仓库提交issue,以免给 YYKit 作者增加额外的维护成本。 25 | 26 | 27 | 进度 28 | -------------------------------------------- 29 | 30 | ### 项目进度以及任务认领: 31 | 32 | 人员 | 地址 33 | :-------------:|:-------------: 34 | iTeaTime(技术清谈)团队内成员 | [**GitHub team Discussions**](https://github.com/orgs/iteatimeteam/teams/iteatime) 35 | 团队外成员 | [GitHub Projects 详情]( https://github.com/iteatimeteam/ClaretCache/projects). 36 | 37 | 38 | 39 | deadline 40 | -------------------------------------------- 41 | 42 | 如果 2020年12月1日还未看到发布alpha版本,那么本项目可能就是黄了,那就只能说声抱歉了,请在项目发布后再 star。 43 | 44 | 45 | -------------------------------------------------------------------------------- /Sources/ClaretCache/ClaretCache.swift: -------------------------------------------------------------------------------- 1 | struct ClaretCache { 2 | var text = "Hello, World!" 3 | } 4 | -------------------------------------------------------------------------------- /Sources/ClaretCache/KVStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KVStorage.swift 3 | // ClaretCacheDemo 4 | // 5 | // Created by HZheng on 2019/7/28. 6 | // Copyright © 2019 com.ClaretCache. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SQLite3 11 | 12 | #if canImport(UIKit) 13 | import UIKit.UIApplication 14 | #endif 15 | 16 | #if canImport(QuartzCore) 17 | import QuartzCore.CABase 18 | #endif 19 | 20 | var isAppExtension: Bool = { 21 | return Bundle.main.bundleURL.pathExtension == "appex" 22 | }() 23 | 24 | public enum KVStorageType { 25 | case file 26 | case sqlite 27 | case mixed 28 | } 29 | 30 | /** 31 | KVStorageItem is used by `KVStorage` to store key-value pair and meta data. 32 | Typically, you should not use this class directly. 33 | */ 34 | public class KVStorageItem { 35 | var key: String? ///< key 36 | var value: Data? ///< value 37 | var fileName: String? ///< fileName (nil if inline) 38 | var size: Int = 0 ///< value's size in bytes 39 | var modTime: Int = 0 ///< modification unix timestamp 40 | var accessTime: Int = 0 ///< last access unix timestamp 41 | var extendedData: Data? ///< extended data (nil if no extended data) 42 | } 43 | 44 | /* 45 | File: 46 | /path/ 47 | /manifest.sqlite 48 | /manifest.sqlite-shm 49 | /manifest.sqlite-wal 50 | /data/ 51 | /e10adc3949ba59abbe56e057f20f883e 52 | /e10adc3949ba59abbe56e057f20f883e 53 | /trash/ 54 | /unused_file_or_folder 55 | 56 | SQL: 57 | create table if not exists manifest ( 58 | key text, 59 | filename text, 60 | size integer, 61 | inline_data blob, 62 | modification_time integer, 63 | last_access_time integer, 64 | extended_data blob, 65 | primary key(key) 66 | ); 67 | create index if not exists last_access_time_idx on manifest(last_access_time); 68 | */ 69 | 70 | public class KVStorage { 71 | fileprivate let kMaxErrorRetryCount = 8 72 | fileprivate let kMinRetryTimeInterval = 2.0 73 | fileprivate let kPathLengthMax = PATH_MAX - 64 74 | fileprivate let kDBFileName = "manifest.sqlite" 75 | fileprivate let kDBShmFileName = "manifest.sqlite-shm" 76 | fileprivate let kDBWalFileName = "manifest.sqlite-wal" 77 | fileprivate let kDataDirectoryName = "data" 78 | fileprivate let kTrashDirectoryName = "trash" 79 | 80 | fileprivate var trashQueue: DispatchQueue 81 | fileprivate var path: URL 82 | fileprivate var dbPath: URL 83 | fileprivate var dataPath: URL 84 | fileprivate var trashPath: URL 85 | fileprivate var database: OpaquePointer? 86 | fileprivate var dbStmtCache: [String: Any]? 87 | fileprivate var dbLastOpenErrorTime: TimeInterval = 0 88 | fileprivate var dbOpenErrorCount: UInt = 0 89 | fileprivate(set) var type: KVStorageType 90 | fileprivate var errorLogsEnabled: Bool = true 91 | 92 | fileprivate let fileManger = FileManager.default 93 | 94 | init?(path: URL, type: KVStorageType) { 95 | guard !path.absoluteString.isEmpty, path.absoluteString.count <= kPathLengthMax else { 96 | print("KVStorage init error: invalid path: [\(path)].") 97 | return nil 98 | } 99 | 100 | self.path = path 101 | self.type = type 102 | trashQueue = OS_dispatch_queue_serial(label: "com.iteatime.cache.disk.trash") 103 | dataPath = path.appendingPathComponent(kDataDirectoryName) 104 | trashPath = path.appendingPathComponent(kTrashDirectoryName) 105 | dbPath = path.appendingPathComponent(kDBFileName) 106 | do { 107 | try fileManger.createDirectory(at: path, withIntermediateDirectories: true, attributes: nil) 108 | try fileManger.createDirectory(at: dataPath, withIntermediateDirectories: true, attributes: nil) 109 | try fileManger.createDirectory(at: trashPath, withIntermediateDirectories: true, attributes: nil) 110 | } catch { 111 | return nil 112 | } 113 | 114 | if !dbOpen() || !dbInitialize() { 115 | // db file may broken... 116 | dbClose() 117 | reset() // rebuild 118 | if !dbOpen() || !dbInitialize() { 119 | dbClose() 120 | log("KVStorage init error: fail to open sqlite db.") 121 | return nil 122 | } 123 | } 124 | fileEmptyTrashInBackground() 125 | } 126 | 127 | #if canImport(UIKit) 128 | func sharedExtensionApplication() -> UIApplication? { 129 | return isAppExtension ? nil : UIApplication.shared 130 | } 131 | #endif 132 | 133 | deinit { 134 | #if canImport(UIKit) 135 | let taskID = sharedExtensionApplication()?.beginBackgroundTask(expirationHandler: nil) 136 | #endif 137 | dbClose() 138 | #if canImport(UIKit) 139 | if let task = taskID { 140 | sharedExtensionApplication()?.endBackgroundTask(task) 141 | } 142 | #endif 143 | } 144 | 145 | // MARK: private 146 | fileprivate func reset() { 147 | do { 148 | try fileManger.removeItem(at: path.appendingPathComponent(kDBFileName)) 149 | try fileManger.removeItem(at: path.appendingPathComponent(kDBShmFileName)) 150 | try fileManger.removeItem(at: path.appendingPathComponent(kDBWalFileName)) 151 | fileMoveAllToTrash() 152 | fileEmptyTrashInBackground() 153 | } catch { 154 | log("reset error: \(error)") 155 | } 156 | } 157 | 158 | fileprivate final func currentTime() -> TimeInterval { 159 | #if canImport(QuartzCore) 160 | return CACurrentMediaTime() 161 | #else 162 | return Date().timeIntervalSince1970 163 | #endif 164 | } 165 | 166 | fileprivate func log(_ items: Any..., separator: String = " ", terminator: String = "\n") { 167 | if errorLogsEnabled { 168 | print(items, separator, terminator) 169 | } 170 | } 171 | 172 | // MARK: File 173 | 174 | fileprivate func fileWrite(fileName: String, data: Data) -> Bool { 175 | do { 176 | try data.write(to: dataPath.appendingPathComponent(fileName)) 177 | } catch { 178 | log("\(#function) line:(\(#line) file write error. fileName: (\(fileName)") 179 | return false 180 | } 181 | return true 182 | } 183 | 184 | fileprivate func fileRead(fileName: String) -> Data? { 185 | do { 186 | return try Data(contentsOf: dataPath.appendingPathComponent(fileName)) 187 | } catch { 188 | log("\(#function) line:(\(#line) file read error. fileName: (\(fileName)") 189 | return nil 190 | } 191 | } 192 | 193 | @discardableResult 194 | fileprivate func fileDelete(fileName: String) -> Bool { 195 | do { 196 | try fileManger.removeItem(at: dataPath.appendingPathComponent(fileName)) 197 | } catch { 198 | log("\(#function) line:(\(#line) file delete error. fileName: (\(fileName)") 199 | return false 200 | } 201 | return true 202 | } 203 | 204 | @discardableResult 205 | fileprivate func fileMoveAllToTrash() -> Bool { 206 | let uuid = UUID().uuidString 207 | let tmpPath = trashPath.appendingPathComponent(uuid) 208 | do { 209 | try fileManger.moveItem(at: dataPath, to: tmpPath) 210 | try fileManger.createDirectory(at: dataPath, withIntermediateDirectories: true, attributes: nil) 211 | } catch { 212 | log("\(#function) line:(\(#line) file move all to trash error.") 213 | return false 214 | } 215 | return true 216 | } 217 | 218 | // empty the trash if failed at last time 219 | fileprivate func fileEmptyTrashInBackground() { 220 | let trashPath = self.trashPath 221 | DispatchQueue.global().async { 222 | do { 223 | let directoryContents = try self.fileManger.contentsOfDirectory(atPath: trashPath.absoluteString) 224 | for path in directoryContents { 225 | let fullPath = trashPath.appendingPathComponent(path) 226 | try self.fileManger.removeItem(at: fullPath) 227 | } 228 | } catch { 229 | self.log("remove trash error: \(error)") 230 | } 231 | } 232 | } 233 | 234 | // MARK: DataBase 235 | 236 | fileprivate func dbOpen() -> Bool { 237 | guard database == nil else { return true } 238 | let result = sqlite3_open(dbPath.absoluteString, &database) 239 | guard result == SQLITE_OK else { 240 | database = nil 241 | dbStmtCache = nil 242 | dbLastOpenErrorTime = currentTime() 243 | dbOpenErrorCount+=1 244 | log("\(#function) line:\(#line) sqlite open failed (\(result)).") 245 | return false 246 | } 247 | dbStmtCache = Dictionary() 248 | dbLastOpenErrorTime = 0 249 | dbOpenErrorCount = 0 250 | return true 251 | } 252 | 253 | @discardableResult 254 | fileprivate func dbClose() -> Bool { 255 | guard let database = database else { return true } 256 | var retry = false 257 | var stmtFinalized = false 258 | dbStmtCache = nil 259 | repeat { 260 | retry = false 261 | let result = sqlite3_close(database) 262 | if result == SQLITE_BUSY || result == SQLITE_LOCKED { 263 | if !stmtFinalized { 264 | stmtFinalized = true 265 | var stmt = sqlite3_next_stmt(database, nil) 266 | while stmt != nil { 267 | sqlite3_finalize(stmt) 268 | stmt = sqlite3_next_stmt(database, nil) 269 | retry = true 270 | } 271 | } 272 | } else if result != SQLITE_OK { 273 | log("\(#function) line:\(#line) sqlite close failed (\(result).") 274 | } 275 | } while(retry) 276 | self.database = nil 277 | return true 278 | } 279 | 280 | fileprivate func dbCheck() -> Bool { 281 | guard database == nil else { return true } 282 | if dbOpenErrorCount < kMaxErrorRetryCount && 283 | currentTime() - dbLastOpenErrorTime > kMinRetryTimeInterval { 284 | return dbOpen() && dbInitialize() 285 | } else { 286 | return false 287 | } 288 | } 289 | 290 | fileprivate func dbInitialize() -> Bool { 291 | let sql = "pragma journal_mode = wal; pragma synchronous = normal;" 292 | + " create table if not exists manifest (key text, filename text, size integer, inline_data blob, modification_time integer, last_access_time integer, extended_data blob, primary key(key));" 293 | + " create index if not exists last_access_time_idx on manifest(last_access_time);" 294 | return dbExecute(sql) 295 | } 296 | 297 | fileprivate func dbCheckpoint() { 298 | guard dbCheck() else { return } 299 | // Cause a checkpoint to occur, merge `sqlite-wal` file to `sqlite` file. 300 | sqlite3_wal_checkpoint(database, nil) 301 | } 302 | 303 | fileprivate func dbExecute(_ sql: String) -> Bool { 304 | guard !sql.isEmpty, dbCheck() else { return false } 305 | return sqlite3_exec(database, sql, nil, nil, nil) == SQLITE_OK 306 | } 307 | 308 | fileprivate func dbPrepareStmt(_ sql: String) -> OpaquePointer? { 309 | guard dbCheck(), !sql.isEmpty, dbStmtCache != nil else { return nil } 310 | var stmt = dbStmtCache?[sql] as? OpaquePointer 311 | if stmt == nil { 312 | let result = sqlite3_prepare_v2(database, sql, -1, &stmt, nil) 313 | guard result == SQLITE_OK else { 314 | log("\(#function) line:\(#line) sqlite stmt prepare error (\(result)): \(errorMessage)") 315 | return nil 316 | } 317 | dbStmtCache?[sql] = stmt 318 | } else { 319 | sqlite3_reset(stmt) 320 | } 321 | return stmt 322 | } 323 | 324 | fileprivate var errorMessage: String { 325 | if let errorPointer = sqlite3_errmsg(database) { 326 | let errorMessage = String(cString: errorPointer) 327 | return errorMessage 328 | } else { 329 | return "No error message provided from sqlite." 330 | } 331 | } 332 | 333 | fileprivate func dbJoinedKeys(_ keys: [Any]) -> String { 334 | var string = "" 335 | let max = keys.count 336 | for index in 0 ..< max { 337 | string.append("?") 338 | if index + 1 != max { 339 | string.append(",") 340 | } 341 | } 342 | return string 343 | } 344 | 345 | fileprivate func dbBindJoinedKeys(keys: [String], stmt: OpaquePointer, fromIndex index: Int) { 346 | let max = keys.count 347 | for index in 0 ..< max { 348 | let key = keys[index] as NSString 349 | sqlite3_bind_text(stmt, Int32(index + index), key.utf8String, -1, nil) 350 | } 351 | } 352 | 353 | fileprivate func dbSave(key: String, value: Data, fileName: String?, extendedData: Data?) -> Bool { 354 | let sql = "insert or replace into manifest (key, filename, size, inline_data, modification_time, last_access_time, extended_data) values (?1, ?2, ?3, ?4, ?5, ?6, ?7);" 355 | guard let stmt = dbPrepareStmt(sql) else { return false } 356 | let timestamp = Int32(time(nil)) 357 | sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) 358 | if let file = fileName { 359 | sqlite3_bind_text(stmt, 2, (file as NSString).utf8String, -1, nil) 360 | } else { 361 | sqlite3_bind_text(stmt, 2, nil, -1, nil) 362 | } 363 | sqlite3_bind_int(stmt, 3, Int32(value.count)) 364 | if fileName?.isEmpty ?? false { 365 | sqlite3_bind_blob(stmt, 4, (value as NSData).bytes, Int32(value.count), nil) 366 | } else { 367 | sqlite3_bind_blob(stmt, 4, nil, 0, nil) 368 | } 369 | sqlite3_bind_int(stmt, 5, timestamp) 370 | sqlite3_bind_int(stmt, 6, timestamp) 371 | if let exData = extendedData { 372 | sqlite3_bind_blob(stmt, 7, (exData as NSData).bytes, Int32(exData.count), nil) 373 | } else { 374 | sqlite3_bind_blob(stmt, 7, nil, 0, nil) 375 | } 376 | 377 | let result = sqlite3_step(stmt) 378 | if result != SQLITE_DONE { 379 | log("\(#function) line:(\(#line) sqlite insert error (\(result): (\(errorMessage))") 380 | return false 381 | } 382 | return true 383 | } 384 | 385 | @discardableResult 386 | fileprivate func dbUpdateAccessTime(_ key: String) -> Bool { 387 | let sql = "update manifest set last_access_time = ?1 where key = ?2;" 388 | guard let stmt = dbPrepareStmt(sql) else { return false } 389 | sqlite3_bind_int(stmt, 1, Int32(time(nil))) 390 | sqlite3_bind_text(stmt, 2, (key as NSString).utf8String, -1, nil) 391 | let result = sqlite3_step(stmt) 392 | if (result != SQLITE_DONE) { 393 | log("\(#function) line:(\(#line) sqlite update error (\(result): (\(errorMessage))") 394 | return false 395 | } 396 | return true 397 | } 398 | 399 | @discardableResult 400 | fileprivate func dbUpdateAccessTimes(_ keys: [String]) -> Bool { 401 | guard dbCheck() else { return false } 402 | let sql = "update manifest set last_access_time = \(Int32(time(nil))) where key in (\(dbJoinedKeys(keys)));" 403 | var stmtPointer: OpaquePointer? 404 | var result = sqlite3_prepare_v2(database, sql, -1, &stmtPointer, nil) 405 | guard result == SQLITE_OK, let stmt = stmtPointer else { 406 | log("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") 407 | return false 408 | } 409 | dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) 410 | result = sqlite3_step(stmt) 411 | sqlite3_finalize(stmt) 412 | if (result != SQLITE_DONE) { 413 | log("\(#function) line:(\(#line) sqlite update error (\(result): (\(errorMessage))") 414 | return false 415 | } 416 | return true 417 | } 418 | 419 | @discardableResult 420 | fileprivate func dbDeleteItem(_ key: String) -> Bool { 421 | let sql = "delete from manifest where key = ?1;" 422 | guard let stmt = dbPrepareStmt(sql) else { return false } 423 | sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) 424 | let result = sqlite3_step(stmt) 425 | if (result != SQLITE_DONE) { 426 | log("\(#function) line:(\(#line) sqlite delete error (\(result): (\(errorMessage))") 427 | return false 428 | } 429 | return true 430 | } 431 | 432 | fileprivate func dbDeleteItems(_ keys: [String]) -> Bool { 433 | guard dbCheck() else { return false } 434 | let sql = "delete from manifest where key in (\(dbJoinedKeys(keys));" 435 | var stmtPointer: OpaquePointer? 436 | var result = sqlite3_prepare_v2(database, sql, -1, &stmtPointer, nil) 437 | guard result == SQLITE_OK, let stmt = stmtPointer else { 438 | log("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") 439 | return false 440 | } 441 | dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) 442 | result = sqlite3_step(stmt) 443 | sqlite3_finalize(stmt) 444 | if (result == SQLITE_ERROR) { 445 | log("\(#function) line:(\(#line) sqlite delete error (\(result): (\(errorMessage))") 446 | return false 447 | } 448 | return true 449 | } 450 | 451 | fileprivate func dbDeleteItem(sql: String, param: Int32) -> Bool { 452 | guard let stmt = dbPrepareStmt(sql) else { return false } 453 | sqlite3_bind_int(stmt, 1, param) 454 | let result = sqlite3_step(stmt) 455 | if (result != SQLITE_DONE) { 456 | log("\(#function) line:(\(#line) sqlite delete error (\(result): (\(errorMessage))") 457 | return false 458 | } 459 | return true 460 | } 461 | 462 | fileprivate func dbDeleteItemsWithSizeLargerThan(_ size: Int) -> Bool { 463 | return dbDeleteItem(sql: "delete from manifest where size > ?1;", param: Int32(size)) 464 | } 465 | 466 | fileprivate func dbDeleteItemsWithTimeEarlierThan(_ time: Int) -> Bool { 467 | return dbDeleteItem(sql: "delete from manifest where last_access_time < ?1;", param: Int32(time)) 468 | } 469 | 470 | fileprivate func dbGetItemFromStmt(stmt: OpaquePointer, excludeInlineData: Bool) -> KVStorageItem { 471 | let item = KVStorageItem() 472 | var index: Int32 = 0 473 | item.key = String(cString: UnsafePointer(sqlite3_column_text(stmt, index))) 474 | index += 1 475 | item.fileName = String(cString: UnsafePointer(sqlite3_column_text(stmt, index))) 476 | index += 1 477 | item.size = Int(sqlite3_column_int(stmt, index)) 478 | index += 1 479 | let inlineData: UnsafeRawPointer? = excludeInlineData ? nil : sqlite3_column_blob(stmt, index) 480 | let inlineDataLength = excludeInlineData ? 0 : sqlite3_column_bytes(stmt, index) 481 | index += 1 482 | if inlineDataLength > 0 && (inlineData != nil) { 483 | item.value = NSData(bytes: inlineData, length: Int(inlineDataLength)) as Data 484 | } 485 | item.modTime = Int(sqlite3_column_int(stmt, index)) 486 | index += 1 487 | item.accessTime = Int(sqlite3_column_int(stmt, index)) 488 | index += 1 489 | let extendedData: UnsafeRawPointer? = sqlite3_column_blob(stmt, index) 490 | let extendedDataLength = sqlite3_column_bytes(stmt, index) 491 | if extendedDataLength > 0 && (extendedData != nil) { 492 | item.extendedData = NSData(bytes: extendedData, length: Int(extendedDataLength)) as Data 493 | } 494 | return item 495 | } 496 | 497 | fileprivate func dbGetItem(key: String, excludeInlineData: Bool) -> KVStorageItem? { 498 | let sql = excludeInlineData ? "select key, filename, size, modification_time, last_access_time, extended_data from manifest where key = ?1;" 499 | : "select key, filename, size, inline_data, modification_time, last_access_time, extended_data from manifest where key = ?1;" 500 | guard let stmt = dbPrepareStmt(sql) else { return nil } 501 | sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) 502 | var item: KVStorageItem? 503 | let result = sqlite3_step(stmt) 504 | if (result == SQLITE_ROW) { 505 | item = dbGetItemFromStmt(stmt: stmt, excludeInlineData: excludeInlineData) 506 | } else { 507 | if (result != SQLITE_DONE) { 508 | log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") 509 | } 510 | } 511 | return item 512 | } 513 | 514 | fileprivate func dbGetItems(keys: [String], excludeInlineData: Bool) -> [KVStorageItem]? { 515 | guard dbCheck() else { return nil } 516 | let sql: String 517 | if (excludeInlineData) { 518 | sql = "select key, filename, size, modification_time, last_access_time, extended_data from manifest where key in (\(dbJoinedKeys(keys)));" 519 | } else { 520 | sql = "select key, filename, size, inline_data, modification_time, last_access_time, extended_data from manifest where key in (\(dbJoinedKeys(keys))" 521 | } 522 | var stmtPointer: OpaquePointer? 523 | var result = sqlite3_prepare_v2(database, sql, -1, &stmtPointer, nil) 524 | guard result == SQLITE_OK, let stmt = stmtPointer else { 525 | log("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") 526 | return nil 527 | } 528 | dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) 529 | var items: [KVStorageItem]? = [KVStorageItem]() 530 | repeat { 531 | result = sqlite3_step(stmt) 532 | if (result == SQLITE_ROW) { 533 | items?.append(dbGetItemFromStmt(stmt: stmt, excludeInlineData: excludeInlineData)) 534 | } else if (result == SQLITE_DONE) { 535 | break 536 | } else { 537 | log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") 538 | items = nil 539 | break 540 | } 541 | } while(true) 542 | sqlite3_finalize(stmt) 543 | return items 544 | } 545 | 546 | fileprivate func dbGetValue(key: String) -> Data? { 547 | let sql = "select inline_data from manifest where key = ?1;" 548 | guard let stmt = dbPrepareStmt(sql) else { return nil } 549 | sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) 550 | let result = sqlite3_step(stmt) 551 | if (result == SQLITE_ROW) { 552 | let inlineData: UnsafeRawPointer? = sqlite3_column_blob(stmt, 0) 553 | let inlineDataLength = sqlite3_column_bytes(stmt, 0) 554 | guard inlineDataLength > 0 && (inlineData != nil) else { 555 | return nil 556 | } 557 | return NSData(bytes: inlineData, length: Int(inlineDataLength)) as Data 558 | } else { 559 | if (result != SQLITE_DONE) { 560 | log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") 561 | } 562 | return nil 563 | } 564 | } 565 | 566 | fileprivate func dbGetFilename(key: String) -> String? { 567 | let sql = "select filename from manifest where key = ?1;" 568 | guard let stmt = dbPrepareStmt(sql) else { return nil } 569 | sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) 570 | let result = sqlite3_step(stmt) 571 | if (result == SQLITE_ROW) { 572 | return String(cString: UnsafePointer(sqlite3_column_text(stmt, 0))) 573 | } else { 574 | if (result != SQLITE_DONE) { 575 | log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") 576 | } 577 | return nil 578 | } 579 | } 580 | 581 | fileprivate func dbGetFileNames(keys: [String]) -> [String]? { 582 | guard dbCheck() else { return nil } 583 | let sql = "select filename from manifest where key in (\(dbJoinedKeys(keys)));" 584 | var stmtPointer: OpaquePointer? 585 | var result = sqlite3_prepare_v2(database, sql, -1, &stmtPointer, nil) 586 | guard result == SQLITE_OK, let stmt = stmtPointer else { 587 | log("\(#function) line:(\(#line) sqlite stmt prepare error (\(result): (\(errorMessage))") 588 | return nil 589 | } 590 | dbBindJoinedKeys(keys: keys, stmt: stmt, fromIndex: 1) 591 | var fileNames: [String]? = [String]() 592 | repeat { 593 | result = sqlite3_step(stmt) 594 | if (result == SQLITE_ROW) { 595 | fileNames?.append(String(cString: UnsafePointer(sqlite3_column_text(stmt, 0)))) 596 | } else if (result == SQLITE_DONE) { 597 | break 598 | } else { 599 | log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") 600 | fileNames = nil 601 | break 602 | } 603 | } while(true) 604 | sqlite3_finalize(stmt) 605 | return fileNames 606 | } 607 | 608 | fileprivate func dbGetFilenames(sql: String, param: Int32) -> [String]? { 609 | guard let stmt = dbPrepareStmt(sql) else { return nil } 610 | sqlite3_bind_int(stmt, 1, Int32(param)) 611 | var fileNames: [String]? = [String]() 612 | repeat { 613 | let result = sqlite3_step(stmt) 614 | if (result == SQLITE_ROW) { 615 | fileNames?.append(String(cString: UnsafePointer(sqlite3_column_text(stmt, 0)))) 616 | } else if (result == SQLITE_DONE) { 617 | break 618 | } else { 619 | log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") 620 | fileNames = nil 621 | break 622 | } 623 | } while(true) 624 | sqlite3_finalize(stmt) 625 | return fileNames 626 | } 627 | 628 | fileprivate func dbGetFilenamesWithSizeLargerThan(_ size: Int) -> [String]? { 629 | let sql = "select filename from manifest where size > ?1 and filename is not null;" 630 | return dbGetFilenames(sql: sql, param: Int32(size)) 631 | } 632 | 633 | fileprivate func dbGetFilenamesWithTimeEarlierThan(_ time: Int) -> [String]? { 634 | let sql = "select filename from manifest where last_access_time < ?1 and filename is not null;" 635 | return dbGetFilenames(sql: sql, param: Int32(time)) 636 | } 637 | 638 | fileprivate func dbGetItemSizeInfoOrderByTimeAscWithLimit(count: Int) -> [KVStorageItem]? { 639 | let sql = "select key, filename, size from manifest order by last_access_time asc limit ?1;" 640 | guard let stmt = dbPrepareStmt(sql) else { return nil } 641 | var items: [KVStorageItem]? = [KVStorageItem]() 642 | repeat { 643 | let result = sqlite3_step(stmt) 644 | if (result == SQLITE_ROW) { 645 | let item = KVStorageItem() 646 | item.key = String(cString: UnsafePointer(sqlite3_column_text(stmt, 0))) 647 | item.fileName = String(cString: UnsafePointer(sqlite3_column_text(stmt, 1))) 648 | item.size = Int(sqlite3_column_int(stmt, 2)) 649 | items?.append(item) 650 | } else if (result == SQLITE_DONE) { 651 | break 652 | } else { 653 | log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") 654 | items = nil 655 | break 656 | } 657 | } while(true) 658 | sqlite3_finalize(stmt) 659 | return items 660 | } 661 | 662 | fileprivate func dbGetItemCount(key: String) -> Int { 663 | let sql = "select count(key) from manifest where key = ?1;" 664 | guard let stmt = dbPrepareStmt(sql) else { return -1 } 665 | sqlite3_bind_text(stmt, 1, (key as NSString).utf8String, -1, nil) 666 | let result = sqlite3_step(stmt) 667 | if result != SQLITE_ROW { 668 | log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") 669 | return -1 670 | } 671 | return Int(sqlite3_column_int(stmt, 0)) 672 | } 673 | 674 | fileprivate func dbGetInt(_ sql: String) -> Int { 675 | guard let stmt = dbPrepareStmt(sql) else { return -1 } 676 | let result = sqlite3_step(stmt) 677 | if result != SQLITE_ROW { 678 | log("\(#function) line:(\(#line) sqlite query error (\(result): (\(errorMessage))") 679 | return -1 680 | } 681 | return Int(sqlite3_column_int(stmt, 0)) 682 | } 683 | 684 | fileprivate func dbGetTotalItemSize() -> Int { 685 | return dbGetInt("select sum(size) from manifest;") 686 | } 687 | 688 | fileprivate func dbGetTotalItemCount() -> Int { 689 | return dbGetInt("select count(*) from manifest;") 690 | } 691 | 692 | // MARK: Public 693 | public func saveItem(key: String, value: Data, fileName: String?, extendedData: Data?) -> Bool { 694 | guard !key.isEmpty, !value.isEmpty else { return false } 695 | if type == .file && fileName?.isEmpty ?? true { return false } 696 | if let file = fileName, !file.isEmpty { 697 | if !fileWrite(fileName: file, data: value) { 698 | return false 699 | } 700 | if !dbSave(key: key, value: value, fileName: file, extendedData: extendedData) { 701 | fileDelete(fileName: file) 702 | return false 703 | } 704 | return true 705 | } else { 706 | if type != .sqlite { 707 | if let file = dbGetFilename(key: key) { 708 | fileDelete(fileName: file) 709 | } 710 | } 711 | return dbSave(key: key, value: value, fileName: nil, extendedData: extendedData) 712 | } 713 | } 714 | 715 | public func removeItem(key: String) -> Bool { 716 | guard !key.isEmpty else { return false } 717 | switch type { 718 | case .sqlite: 719 | return dbDeleteItem(key) 720 | case .file, .mixed: 721 | if let fileName = dbGetFilename(key: key) { 722 | fileDelete(fileName: fileName) 723 | } 724 | return dbDeleteItem(key) 725 | } 726 | } 727 | 728 | public func removeItems(keys: [String]) -> Bool { 729 | guard !keys.isEmpty else { return false } 730 | switch type { 731 | case .sqlite: 732 | return dbDeleteItems(keys) 733 | case .file, .mixed: 734 | if let fileNames = dbGetFileNames(keys: keys), !fileNames.isEmpty { 735 | for file in fileNames { 736 | fileDelete(fileName: file) 737 | } 738 | } 739 | return dbDeleteItems(keys) 740 | } 741 | } 742 | 743 | public func removeAllItems() -> Bool { 744 | guard dbClose() else { return false } 745 | reset() 746 | guard dbOpen() else { return false } 747 | guard dbInitialize() else { return false } 748 | return true 749 | } 750 | 751 | public func removeItemsLargerThanSize(_ size: Int) -> Bool { 752 | guard size != Int.max else { return true } 753 | guard size > 0 else { return removeAllItems() } 754 | switch type { 755 | case .sqlite: 756 | if dbDeleteItemsWithSizeLargerThan(size) { 757 | dbCheckpoint() 758 | return true 759 | } 760 | case .file, .mixed: 761 | if let fileNames = dbGetFilenamesWithSizeLargerThan(size) { 762 | for file in fileNames { 763 | fileDelete(fileName: file) 764 | } 765 | } 766 | if dbDeleteItemsWithSizeLargerThan(size) { 767 | dbCheckpoint() 768 | return true 769 | } 770 | } 771 | return false 772 | } 773 | 774 | public func removeItemsEarlierThanTime(_ time: Int) -> Bool { 775 | guard time > 0 else { return true } 776 | guard time != Int.max else { return removeAllItems() } 777 | switch type { 778 | case .sqlite: 779 | if dbDeleteItemsWithTimeEarlierThan(time) { 780 | dbCheckpoint() 781 | return true 782 | } 783 | case .file, .mixed: 784 | if let fileNames = dbGetFilenamesWithTimeEarlierThan(time) { 785 | for file in fileNames { 786 | fileDelete(fileName: file) 787 | } 788 | } 789 | if dbDeleteItemsWithTimeEarlierThan(time) { 790 | dbCheckpoint() 791 | return true 792 | } 793 | } 794 | return false 795 | } 796 | 797 | public func removeItemsToFitSize(_ maxSize: Int) -> Bool { 798 | guard maxSize != Int.max else { return true } 799 | guard maxSize > 0 else { return removeAllItems() } 800 | var total = dbGetTotalItemSize() 801 | guard total >= 0 else { return false } 802 | guard total > maxSize else { return true } 803 | var suc = false 804 | dbGetItemSizeInfoOrderByTimeAscWithLimit(count: 16)?.forEach({ (item) in 805 | if let fileName = item.fileName { 806 | fileDelete(fileName: fileName) 807 | } 808 | if let key = item.key { 809 | suc = dbDeleteItem(key) 810 | } else { 811 | suc = true 812 | } 813 | total -= item.size 814 | if total <= maxSize || !suc { 815 | return 816 | } 817 | }) 818 | if suc { 819 | dbCheckpoint() 820 | } 821 | return suc 822 | } 823 | 824 | public func removeItemsToFitCount(_ maxCount: Int) -> Bool { 825 | guard maxCount != Int.max else { return true } 826 | guard maxCount > 0 else { return removeAllItems() } 827 | var total = dbGetTotalItemCount() 828 | guard total >= 0 else { return false } 829 | guard total > maxCount else { return true } 830 | var suc = false 831 | dbGetItemSizeInfoOrderByTimeAscWithLimit(count: 16)?.forEach({ (item) in 832 | if let fileName = item.fileName { 833 | fileDelete(fileName: fileName) 834 | } 835 | if let key = item.key { 836 | suc = dbDeleteItem(key) 837 | } else { 838 | suc = true 839 | } 840 | total -= 1 841 | if total <= maxCount || !suc { 842 | return 843 | } 844 | }) 845 | if suc { 846 | dbCheckpoint() 847 | } 848 | return suc 849 | } 850 | 851 | public func removeAllItemsWithProgressBlock(progress: ((_ removedCount: Int, _ totalCount: Int) -> Void)?, 852 | end: ((_ error: Bool) -> Void)?) { 853 | let total = dbGetTotalItemCount() 854 | if total <= 0 { 855 | end?(total < 0) 856 | } else { 857 | var left = total 858 | var suc = false 859 | dbGetItemSizeInfoOrderByTimeAscWithLimit(count: 32)?.forEach({ (item) in 860 | if let fileName = item.fileName { 861 | fileDelete(fileName: fileName) 862 | } 863 | if let key = item.key { 864 | suc = dbDeleteItem(key) 865 | } else { 866 | suc = true 867 | } 868 | left -= 1 869 | if left <= 0 || !suc { 870 | return 871 | } 872 | progress?(total - left, total) 873 | }) 874 | if suc { 875 | dbCheckpoint() 876 | } 877 | end?(!suc) 878 | } 879 | } 880 | 881 | public func getItemForKey(_ key: String) -> KVStorageItem? { 882 | guard !key.isEmpty else { return nil } 883 | guard let item = dbGetItem(key: key, excludeInlineData: false) else { return nil } 884 | dbUpdateAccessTime(key) 885 | if let fileName = item.fileName { 886 | if let value = fileRead(fileName: fileName) { 887 | item.value = value 888 | } else { 889 | dbDeleteItem(key) 890 | return nil 891 | } 892 | } 893 | return item 894 | } 895 | 896 | public func getItemInfoForKey(_ key: String) -> KVStorageItem? { 897 | guard !key.isEmpty else { return nil } 898 | return dbGetItem(key: key, excludeInlineData: true) 899 | } 900 | 901 | public func getItemValueForKey(_ key: String) -> Data? { 902 | guard !key.isEmpty else { return nil } 903 | var value: Data? 904 | switch type { 905 | case .file: 906 | if let fileName = dbGetFilename(key: key) { 907 | value = fileRead(fileName: fileName) 908 | if value == nil { 909 | dbDeleteItem(key) 910 | } 911 | } 912 | case .sqlite: 913 | value = dbGetValue(key: key) 914 | case .mixed: 915 | if let fileName = dbGetFilename(key: key) { 916 | value = fileRead(fileName: fileName) 917 | if value == nil { 918 | dbDeleteItem(key) 919 | } 920 | } else { 921 | value = dbGetValue(key: key) 922 | } 923 | } 924 | if value != nil { 925 | dbUpdateAccessTime(key) 926 | } 927 | return value 928 | } 929 | 930 | public func getItemForKeys(_ keys: [String]) -> [KVStorageItem]? { 931 | guard !keys.isEmpty else { return nil } 932 | if var items = dbGetItems(keys: keys, excludeInlineData: false), !items.isEmpty { 933 | if type == .sqlite { 934 | var index = 0 935 | var max = items.count 936 | repeat { 937 | let item = items[index] 938 | if let fileName = item.fileName { 939 | if let value = fileRead(fileName: fileName) { 940 | item.value = value 941 | } else { 942 | if let key = item.key { 943 | dbDeleteItem(key) 944 | } 945 | items.remove(at: index) 946 | index -= 1 947 | max -= 1 948 | } 949 | } 950 | index += 1 951 | } while(index < max) 952 | } 953 | return items.isEmpty ? nil : items 954 | } else { 955 | return nil 956 | } 957 | } 958 | 959 | public func getItemInfoForKeys(_ keys: [String]) -> [KVStorageItem]? { 960 | guard !keys.isEmpty else { return nil } 961 | return dbGetItems(keys: keys, excludeInlineData: true) 962 | } 963 | 964 | public func getItemValueForKeys(_ keys: [String]) -> [String: Any]? { 965 | guard let items = getItemForKeys(keys) else { return nil } 966 | var keyAndValue = [String: Any]() 967 | for item in items { 968 | if let key = item.key, let value = item.value { 969 | keyAndValue[key] = value 970 | } 971 | } 972 | return keyAndValue.isEmpty ? nil : keyAndValue 973 | } 974 | 975 | public func itemExistsForKey(_ key: String) -> Bool { 976 | guard !key.isEmpty else { return false } 977 | return dbGetItemCount(key: key) > 0 978 | } 979 | 980 | public func getItemsCount() -> Int { 981 | return dbGetTotalItemCount() 982 | } 983 | 984 | public func getItemsSize() -> Int { 985 | return dbGetTotalItemSize() 986 | } 987 | } 988 | -------------------------------------------------------------------------------- /Sources/ClaretCache/MemoryCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MemoryCache.swift 3 | // ClaretCache 4 | // 5 | // Created by BirdMichael on 2019/7/23. 6 | // Copyright © 2019 com.ClaretCache. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if canImport(UIKit) 12 | import UIKit.UIApplication 13 | #endif 14 | 15 | #if canImport(QuartzCore) 16 | import QuartzCore.CABase 17 | #endif 18 | 19 | public final class MemoryCache where Key: Hashable, Value: Equatable { 20 | 21 | /// The name of the cache. **Default** is **"com.iteatimeteam.ClaretCache.memory.default"** 22 | let name: String 23 | 24 | /// The maximum number of objects the cache should hold. 25 | /// 26 | /// The default value is **UInt.max**, which means no limit. 27 | /// This is not a strict limit—if the cache goes over the limit, some objects in the 28 | /// cache could be evicted later in backgound thread. 29 | var countLimit: UInt = UInt.max 30 | 31 | /// The maximum total cost that the cache can hold before it starts evicting objects. 32 | /// 33 | /// The default value is **UInt.max**, which means no limit. 34 | /// This is not a strict limit—if the cache goes over the limit, some objects in the 35 | /// cache could be evicted later in backgound thread. 36 | var costLimit: UInt = UInt.max 37 | 38 | /// The maximum expiry time of objects in cache. 39 | /// 40 | /// The default value is **Double.greatestFiniteMagnitude**, which means no limit. 41 | /// This is not a strict limit—if an object goes over the limit, the object could 42 | /// be evicted later in backgound thread. 43 | var ageLimit: TimeInterval = TimeInterval(Double.greatestFiniteMagnitude) 44 | 45 | /// The auto trim check time interval in seconds. **Default is 5.0**. 46 | /// 47 | /// The cache holds an internal timer to check whether the cache reaches 48 | /// its limits, and if the limit is reached, it begins to evict objects. 49 | var autoTrimInterval: TimeInterval = 5.0 50 | 51 | #if canImport(UIKit) 52 | /// If **YES**, the cache will remove all objects when the app receives a memory warning. 53 | /// The **default** value is **YES**. 54 | var removeAllObjectsOnMemoryWarning: Bool = true 55 | 56 | /// If **YES**, The cache will remove all objects when the app enter background. 57 | /// The **default** value is **YES**. 58 | var removeAllObjectsWhenEnteringBackground: Bool = true 59 | 60 | /// A closure to be executed when the app receives a memory warning. 61 | /// **The default value is nil**. 62 | var didReceiveMemoryWarning: ((MemoryCache) -> Void)? 63 | 64 | /// A closure to be executed when the app enter background. 65 | /// **The default value is nil**. 66 | var didEnterBackground: ((MemoryCache) -> Void)? 67 | 68 | private var observers: [NSObjectProtocol] = [] 69 | #endif 70 | 71 | /// If **YES**, the key-value pair will be released on main thread, otherwise on background thread. 72 | /// **Default** is **NO**. 73 | /// 74 | /// You may set this value to **YES** if the key-value object contains 75 | /// **the instance which should be released in main thread (such as UIView/CALayer)**. 76 | var releaseOnMainThread: Bool { 77 | set { 78 | lock { lru.releaseOnMainThread = newValue } 79 | } 80 | get { 81 | return lockRead { lru.releaseOnMainThread } 82 | } 83 | } 84 | 85 | /// If **YES**, the key-value pair will be released asynchronously to avoid blocking 86 | /// 87 | /// the access methods, otherwise it will be released in the access method 88 | /// (such as removeObjectForKey:). **Default is YES**. 89 | var releaseAsynchronously: Bool { 90 | set { 91 | lock { lru.releaseAsynchronously = newValue } 92 | } 93 | get { 94 | return self.lockRead { self.lru.releaseAsynchronously } 95 | } 96 | } 97 | 98 | /// The number of objects in the cache (read-only) 99 | var totalCount: UInt { 100 | return self.lockRead { self.lru.totalCount } 101 | } 102 | 103 | /// The total cost of objects in the cache (read-only). 104 | var totalCost: UInt { 105 | return self.lockRead { self.lru.totalCost } 106 | } 107 | 108 | private var mutex: pthread_mutex_t 109 | private let lru: LinkedMap = LinkedMap() 110 | private let queue: DispatchQueue 111 | 112 | /// The constructor 113 | /// - Parameter name: The name of the cache. 114 | /// The default value is **"com.iteatimeteam.ClaretCache.memory.default"** 115 | /// - Parameter costLimit: The maximum total cost that the cache can hold before it starts evicting objects. 116 | /// The default value is **UInt.max**, which means no limit. 117 | /// - Parameter countLimit: The maximum number of objects the cache should hold. 118 | /// The default value is **UInt.max**, which means no limit. 119 | /// - Parameter ageLimit: The maximum expiry time of objects in cache. 120 | /// The default value is **Double.greatestFiniteMagnitude**, which means no limit. 121 | /// - Parameter autoTrimInterval: The auto trim check time interval in seconds. 122 | /// The default value is **5.0**. 123 | init(name: String = "com.iteatimeteam.ClaretCache.memory.default", 124 | costLimit: UInt = UInt.max, 125 | countLimit: UInt = UInt.max, 126 | ageLimit: TimeInterval = TimeInterval(Double.greatestFiniteMagnitude), 127 | autoTrimInterval: TimeInterval = 5.0) { 128 | 129 | self.name = name 130 | self.costLimit = costLimit 131 | self.countLimit = countLimit 132 | self.ageLimit = ageLimit 133 | self.autoTrimInterval = autoTrimInterval 134 | 135 | mutex = .init() 136 | pthread_mutex_init(&mutex, nil) 137 | queue = DispatchQueue(label: "com.iteatimeteam.ClaretCache.memory", qos: DispatchQoS.default) 138 | #if canImport(UIKit) 139 | addNotification() 140 | #endif 141 | trimRecursively() 142 | } 143 | 144 | deinit { 145 | #if canImport(UIKit) 146 | removeNotification() 147 | #endif 148 | lru.removeAll() 149 | pthread_mutex_destroy(&mutex) 150 | } 151 | } 152 | 153 | // MARK: - Access Methods 154 | ///============================================================================= 155 | /// @name Access Methods 156 | ///============================================================================= 157 | public extension MemoryCache { 158 | 159 | /// Returns a Boolean value that indicates whether a given key is in cache. 160 | /// - Parameter atKey: atKey An object identifying the value. If nil, just return **NO**. 161 | /// - Returns: Whether the atKey is in cache. 162 | final func contains(_ atKey: Key) -> Bool { 163 | return lockRead { lru.dic[atKey] != nil } 164 | } 165 | 166 | /// Sets the value of the specified key in the cache, and associates the key-value 167 | /// pair with the specified cost. 168 | /// - Parameter value: The object to store in the cache. If nil, it calls **remove(_ atKey:)**. 169 | /// - Parameter atKey: The atKey with which to associate the value. If nil, this method has no effect. 170 | /// - Parameter cost: The cost with which to associate the key-value pair. 171 | final func set(_ value: Value?, _ atKey: Key, cost: UInt = 0) { 172 | // Delete new if value is nil 173 | // Cache if value is not nil 174 | guard let value = value else { 175 | remove(atKey) 176 | return 177 | } 178 | 179 | lock { 180 | let now = currentTime() 181 | if let node = lru.dic[atKey] { 182 | lru.totalCost -= node.cost 183 | lru.totalCount += cost 184 | node.cost = cost 185 | node.time = now 186 | node.value = value 187 | lru.bring(toHead: node) 188 | } else { 189 | let node = LinkedMap.Node(atKey: atKey, value: value, cost: cost) 190 | node.time = now 191 | lru.insert(atHead: node) 192 | } 193 | 194 | if lru.totalCost > costLimit { 195 | queue.async { 196 | self.trimTo(cost: self.costLimit) 197 | } 198 | } 199 | 200 | if lru.totalCount > countLimit { 201 | if let node = lru.removeTail() { 202 | release { 203 | //hold and release in queue 204 | _ = node.cost 205 | } 206 | } 207 | } 208 | } 209 | } 210 | 211 | /// Removes the value of the specified key in the cache. 212 | /// - Parameter atKey: atKey The atKey identifying the value to be removed. 213 | /// If nil, this method has no effect. 214 | final func remove(_ atKey: Key) { 215 | pthread_mutex_lock(&mutex) 216 | defer { 217 | pthread_mutex_unlock(&mutex) 218 | } 219 | guard let node = lru.dic[atKey] else { return } 220 | lru.remove(node) 221 | release { 222 | _ = node.cost //hold and release in queue 223 | } 224 | } 225 | 226 | /// Empties the cache immediately. 227 | final func removeAll() { 228 | lock { 229 | lru.removeAll() 230 | } 231 | } 232 | 233 | final subscript(_ atKey: Key) -> Value? { 234 | get { 235 | return lockRead { () -> Value? in 236 | guard let node = lru.dic[atKey] else { 237 | return nil 238 | } 239 | node.time = currentTime() 240 | lru.bring(toHead: node) 241 | return node.value 242 | } 243 | } 244 | set { 245 | set(newValue, atKey) 246 | } 247 | } 248 | 249 | /// Removes objects from the cache with LRU, 250 | /// until the **totalCount** is below or equal to the specified value. 251 | /// - Parameter count: The total count allowed to remain after the cache has been trimmed. 252 | final func trimTo(count: UInt) { 253 | guard count > .zero else { 254 | removeAll() 255 | return 256 | } 257 | 258 | trimCount(count) 259 | } 260 | 261 | /// Removes objects from the cache with LRU, until the **totalCost** is or equal to the specified value. 262 | /// - Parameter cost: cost The total cost allowed to remain after the cache has been trimmed. 263 | final func trimTo(cost: UInt) { 264 | trimCost(cost) 265 | } 266 | 267 | /// Removes objects from the cache with LRU, until all expiry objects removed by the specified value. 268 | /// - Parameter age: The maximum age (in seconds) of objects. 269 | final func trimTo(age: TimeInterval) { 270 | trimAge(age) 271 | } 272 | } 273 | 274 | extension MemoryCache: CustomDebugStringConvertible { 275 | public var debugDescription: String { 276 | return "<\(type(of: self)): \(Unmanaged.passUnretained(self).toOpaque())> (\(name))" 277 | } 278 | } 279 | 280 | private extension MemoryCache { 281 | 282 | #if canImport(UIKit) 283 | func addNotification() { 284 | 285 | let memoryWarningObserver = NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: nil) { [weak self] _ in 286 | guard let self = self else { return } 287 | self.didReceiveMemoryWarning?(self) 288 | if self.removeAllObjectsOnMemoryWarning { 289 | self.removeAll() 290 | } 291 | } 292 | 293 | let enterBackgroundObserver = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { [weak self] _ in 294 | guard let self = self else { return } 295 | self.didEnterBackground?(self) 296 | if self.removeAllObjectsWhenEnteringBackground { 297 | self.removeAll() 298 | } 299 | } 300 | observers.append(contentsOf: [memoryWarningObserver, enterBackgroundObserver]) 301 | } 302 | 303 | func removeNotification() { 304 | observers.forEach { 305 | NotificationCenter.default.removeObserver($0) 306 | } 307 | observers.removeAll() 308 | } 309 | #endif 310 | 311 | final func trimLRU(path: KeyPath, UInt>, limit: UInt) { 312 | 313 | var finish = lockRead { () -> Bool in 314 | if limit == .zero { 315 | lru.removeAll() 316 | return true 317 | } else if lru[keyPath: path] <= limit { 318 | return true 319 | } 320 | return false 321 | } 322 | guard !finish else { return } 323 | 324 | var holder: [LinkedMap.Node] = [] 325 | 326 | while !finish { 327 | if pthread_mutex_trylock(&mutex) == .zero { 328 | if lru[keyPath: path] > limit { 329 | if let node = lru.removeTail() { 330 | holder.append(node) 331 | } 332 | } else { 333 | finish = true 334 | } 335 | pthread_mutex_unlock(&mutex) 336 | } else { 337 | usleep(10 * 1000) //10 ms 338 | } 339 | } 340 | 341 | guard !holder.isEmpty else { return } 342 | 343 | release(trimed: true) { 344 | _ = holder.isEmpty // release in queue 345 | } 346 | } 347 | 348 | final func trimCost(_ costLimit: UInt) { 349 | trimLRU(path: \.totalCost, limit: costLimit) 350 | } 351 | 352 | final func trimCount(_ countLimit: UInt) { 353 | trimLRU(path: \.totalCount, limit: countLimit) 354 | } 355 | 356 | final func trimAge(_ ageLimit: TimeInterval) { 357 | let now = currentTime() 358 | var finish = lockRead { () -> Bool in 359 | if ageLimit <= .zero { 360 | lru.removeAll() 361 | return true 362 | } else if lru.tail == nil || (now - (lru.tail?.time ?? 0)) <= ageLimit { 363 | return true 364 | } 365 | return false 366 | } 367 | guard !finish else { return } 368 | 369 | var holder: [LinkedMap.Node] = [] 370 | 371 | while !finish { 372 | if pthread_mutex_trylock(&mutex) == .zero { 373 | if let tail = lru.tail, (now - tail.time) > ageLimit { 374 | if let node = lru.removeTail() { 375 | holder.append(node) 376 | } 377 | } else { 378 | finish = true 379 | } 380 | pthread_mutex_unlock(&mutex) 381 | } else { 382 | usleep(10 * 1000) //10 ms 383 | } 384 | } 385 | 386 | guard !holder.isEmpty else { return } 387 | 388 | release(trimed: true) { 389 | _ = holder.isEmpty // release in queue 390 | } 391 | } 392 | 393 | final func trimRecursively() { 394 | memoryCacheReleaseQueue().asyncAfter(deadline: .now() + autoTrimInterval) { [weak self] in 395 | guard let self = self else { return } 396 | self.trimInBackground() 397 | self.trimRecursively() 398 | } 399 | } 400 | 401 | final func trimInBackground() { 402 | queue.async { [weak self] in 403 | guard let self = self else { return } 404 | self.trimTo(cost: self.costLimit) 405 | self.trimTo(count: self.countLimit) 406 | self.trimTo(age: self.ageLimit) 407 | } 408 | } 409 | 410 | final func lock(execute: (() -> Void)) { 411 | pthread_mutex_lock(&mutex) 412 | defer { 413 | pthread_mutex_unlock(&mutex) 414 | } 415 | execute() 416 | } 417 | 418 | final func lockRead(execute: (() -> V)) -> V { 419 | pthread_mutex_lock(&mutex) 420 | defer { 421 | pthread_mutex_unlock(&mutex) 422 | } 423 | return execute() 424 | } 425 | 426 | final func release(trimed: Bool = false, _ execute: @escaping (() -> Void)) { 427 | if trimed { 428 | let queue = lru.releaseOnMainThread ? DispatchQueue.main : memoryCacheReleaseQueue() 429 | queue.async(execute: execute) 430 | } else { 431 | if lru.releaseAsynchronously { 432 | let queue = lru.releaseOnMainThread ? DispatchQueue.main : memoryCacheReleaseQueue() 433 | queue.async(execute: execute) 434 | } else if lru.releaseOnMainThread && pthread_main_np() != .zero { 435 | DispatchQueue.main.async(execute: execute) 436 | } 437 | } 438 | } 439 | 440 | final func currentTime() -> TimeInterval { 441 | #if canImport(QuartzCore) 442 | return CACurrentMediaTime() 443 | #else 444 | return Date().timeIntervalSince1970 445 | #endif 446 | } 447 | } 448 | 449 | private func memoryCacheReleaseQueue() -> DispatchQueue { 450 | return DispatchQueue.global(qos: .utility) 451 | } 452 | 453 | /** 454 | A linked map used by MemoryCache. 455 | It's not thread-safe and does not validate the parameters. 456 | 457 | Typically, you should not use this class directly. 458 | */ 459 | fileprivate final class LinkedMap where Key: Hashable, Value: Equatable { 460 | var dic: [Key: Node] = [:] 461 | var totalCost: UInt = 0 462 | var totalCount: UInt = 0 463 | var head: Node? // MRU, do not change it directly 464 | var tail: Node? // LRU, do not change it directly 465 | var releaseOnMainThread: Bool = false 466 | var releaseAsynchronously: Bool = true 467 | 468 | final class Node: Equatable where Key: Hashable, Value: Equatable { 469 | var prev: Node? 470 | var next: Node? 471 | let key: Key 472 | var value: Value? 473 | var cost: UInt = 0 474 | var time: TimeInterval = 0.0 475 | 476 | init(atKey: Key, value: Value?, cost: UInt = 0) { 477 | self.key = atKey 478 | self.value = value 479 | self.cost = cost 480 | } 481 | 482 | static func == (lhs: Node, rhs: Node) -> Bool { 483 | return lhs.key == rhs.key && lhs.value == rhs.value 484 | } 485 | 486 | #if ClaretCacheLOG 487 | deinit { 488 | print("[ClaretCache LOG]: cache for key: \(key) release in \(Thread.current)") 489 | } 490 | #endif 491 | } 492 | } 493 | 494 | extension LinkedMap { 495 | 496 | /// Insert a node at head and update the total cost. 497 | /// Node and node.key should not be nil. 498 | final func insert(atHead node: Node) { 499 | dic[node.key] = node 500 | totalCost += node.cost 501 | totalCount += 1 502 | if head != nil { 503 | node.next = head 504 | head?.prev = node 505 | head = node 506 | } else { 507 | tail = node 508 | head = tail 509 | } 510 | } 511 | 512 | /// Bring a inner node to header. 513 | /// Node should already inside the dic. 514 | final func bring(toHead node: Node) { 515 | guard head != node else { return } 516 | 517 | if tail == node { 518 | tail = node.prev 519 | tail?.next = nil 520 | } else { 521 | node.next?.prev = node.prev 522 | node.prev?.next = node.next 523 | } 524 | 525 | node.next = head 526 | node.prev = nil 527 | head?.prev = node 528 | head = node 529 | } 530 | 531 | /// Remove a inner node and update the total cost. 532 | /// Node should already inside the dic. 533 | final func remove(_ node: Node) { 534 | dic.removeValue(forKey: node.key) 535 | totalCost -= node.cost 536 | totalCount -= 1 537 | 538 | if node.next != nil { 539 | node.next?.prev = node.prev 540 | } 541 | 542 | if node.prev != nil { 543 | node.prev?.next = node.next 544 | } 545 | 546 | if head == node { 547 | head = node.next 548 | } 549 | 550 | if tail == node { 551 | tail = node.prev 552 | } 553 | } 554 | 555 | /// Remove tail node if exist. 556 | final func removeTail() -> Node? { 557 | guard let tmpTail = tail else { return nil } 558 | dic.removeValue(forKey: tmpTail.key) 559 | totalCost -= tmpTail.cost 560 | totalCount -= 1 561 | if head == tail { 562 | head = nil 563 | tail = nil 564 | } else { 565 | tail = tail?.prev 566 | tail?.next = nil 567 | } 568 | return tmpTail 569 | } 570 | 571 | /// Remove all node 572 | final func removeAll() { 573 | totalCost = 0 574 | totalCount = 0 575 | head = nil 576 | tail = nil 577 | guard !dic.isEmpty else { return } 578 | 579 | let holder: [Key: Node] = dic 580 | dic = [:] 581 | if releaseAsynchronously { 582 | let queue = releaseOnMainThread ? DispatchQueue.main : memoryCacheReleaseQueue() 583 | queue.async { 584 | // hold and release in specified queue 585 | _ = holder.count 586 | } 587 | } else if releaseOnMainThread && pthread_main_np() != .zero { 588 | DispatchQueue.main.async { 589 | // hold and release in specified queue 590 | _ = holder.count 591 | } 592 | } else { 593 | // nothing 594 | } 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /Swift_Style_Guide.md: -------------------------------------------------------------------------------- 1 | # ClaretCache Swift代码选型规范 2 | 3 | ## 目标 4 | 5 | 本规约旨在: 6 | 7 | * 使代码易读,易理解, 易维护. 8 | * 减少编写代码时的认知负担. 9 | * 使项目成员能更专注讨论代码逻辑而不是代码写法. 10 | 11 | 12 | ## 指导原则 13 | 14 | * 本规约是 [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/) 的扩展,规约内容不应该和官方文档相抵触. 15 | * 如果规约修改了代码格式, 则需要重新自动格式化(使用SwiftLint) 16 | 17 | 18 | ## 目录 19 | 20 | 1. [Xcode 配置](#xcode-配置) 21 | 1. [命名](#命名) 22 | 1. [风格](#风格) 23 | 1. [函数](#函数) 24 | 1. [闭包](#) 25 | 1. [操作符](#操作符) 26 | 1. [最佳实践](#最佳实践) 27 | 1. [与Objc的交互](#与Objc的交互) 28 | 29 | ## Xcode 配置 30 | 31 | * **每行的最大列宽应为100个字符.** (考虑到外接大屏显示器, 我们选择最大列宽超过80个字符) 32 | 33 | * **每行使用2个空格缩进.** 34 | 35 | * **删除所有行尾的空白字符.** 36 | 37 | **[⬆ 返回顶部](#目录)** 38 | 39 | ## 命名 40 | 41 | * **对于类型(值类型或引用类型)和协议, 使用大写驼峰命名法; 其余则使用小写驼峰命名法.** 42 | 43 |
44 | 示例 45 | 46 | ```swift 47 | protocol Person { 48 | // ... 49 | } 50 | 51 | class Teacher: Person { 52 | 53 | enum Gender { 54 | // ... 55 | } 56 | 57 | class Course { 58 | // ... 59 | } 60 | 61 | var courses: [Course] = [] 62 | static let schoolName: String = "Stanford" 63 | 64 | func addCourse(_ course: Course) { 65 | // ... 66 | } 67 | } 68 | 69 | let teacher = Teacher() 70 | ``` 71 | 72 |
73 | 74 | _特例: 给自定义私有属性添加下划线前缀. 当自定义的属性和系统属性冲突时, 并且需要赋予自定义属性更高的权限时. 75 | 76 |
77 | 示例 78 | 79 | ```swift 80 | class DemoViewController: UIViewController { 81 | private lazy var _view = CustomView() 82 | 83 | loadView() { 84 | self.view = _view 85 | } 86 | } 87 | ``` 88 | 89 |
90 | 91 | * **给布尔值命名时请参考 `isTeacher`, `hasCourse`这样的命名方式.** 该方式能更好地体现其是一个布尔类型, 而非其他类型. 92 | 93 | * **名称中的缩略语(例如URL),除非该缩略语是名称的开头,否则一律使用大写.** *(名称: 变量名,函数名)* 94 | 95 |
96 | 示例 97 | 98 | ```swift 99 | //错误示范 100 | class UrlValidator { 101 | 102 | func isValidUrl(_ URL: URL) -> Bool { 103 | // ... 104 | } 105 | 106 | func isUrlReachable(_ URL: URL) -> Bool { 107 | // ... 108 | } 109 | } 110 | 111 | let URLValidator = UrlValidator().isValidUrl(/* some URL */) 112 | 113 | // 正确示范 114 | class URLValidator { 115 | 116 | func isValidURL(_ url: URL) -> Bool { 117 | // ... 118 | } 119 | 120 | func isURLReachable(_ url: URL) -> Bool { 121 | // ... 122 | } 123 | } 124 | 125 | let urlValidator = URLValidator().isValidURL(/* some URL */) 126 | ``` 127 | 128 |
129 | 130 | * **名称应该明确体现其功能. 从左至右顺序应为: 常见部分到具体部分.** 常见部分是指: 最能帮助我们锁定目标的类型名词, 一般粒度较大; 具体部分是指: 粒度最小的部分。 131 | 132 |
133 | 示例 134 | 135 | ```swift 136 | // 错误示范 137 | let rightTitleMargin: CGFloat 138 | let leftTitleMargin: CGFloat 139 | let bodyRightMargin: CGFloat 140 | let bodyLeftMargin: CGFloat 141 | 142 | // 正确示范 143 | let titleMarginRight: CGFloat 144 | let titleMarginLeft: CGFloat 145 | let bodyMarginRight: CGFloat 146 | let bodyMarginLeft: CGFloat 147 | ``` 148 | 149 |
150 | 151 | * **如果名称不明确,请在名称中包含有关类型的提示.** 152 | 153 |
154 | 示例 155 | 156 | ```swift 157 | // 错误示范 158 | let title: String 159 | let cancel: UIButton 160 | 161 | // 正确示范 162 | let titleText: String 163 | let cancelButton: UIButton 164 | ``` 165 | 166 |
167 | 168 | * **事件处理函数命名使用过去时.** 169 | 170 |
171 | 示例 172 | 173 | ```swift 174 | // 错误示例 175 | class ExperiencesViewController { 176 | 177 | private func handleBookButtonTap() { 178 | // ... 179 | } 180 | 181 | private func modelChanged() { 182 | // ... 183 | } 184 | } 185 | 186 | // 正确示例 187 | class ExperiencesViewController { 188 | 189 | private func didTapBookButton() { 190 | // ... 191 | } 192 | 193 | private func modelDidChange() { 194 | // ... 195 | } 196 | } 197 | ``` 198 | 199 |
200 | 201 | * **避免Objective-C的命名前缀.** 202 | 203 |
204 | 示例 205 | 206 | ```swift 207 | // 错误示例 208 | class AIRAccount { 209 | // ... 210 | } 211 | 212 | // 正确示例 213 | class Account { 214 | // ... 215 | } 216 | ``` 217 | 218 |
219 | 220 | **[⬆ 返回顶部](#目录)** 221 | 222 | ## 风格 223 | 224 | * **当编译器可推断变量类型时, 不需要显式为变量添加类型** 225 | 226 |
227 | 示例 228 | 229 | ```swift 230 | // 错误示例 231 | let teacher: Teacher = Teacher() 232 | 233 | // 正确示例 234 | let teacher = Teacher() 235 | ``` 236 | 237 | ```swift 238 | enum Weather { 239 | case sunny 240 | case cloudy 241 | } 242 | 243 | func someWeather() -> Weather { 244 | // WRONG 245 | return Weather.sunny 246 | 247 | // RIGHT 248 | return .sunny 249 | } 250 | ``` 251 | 252 |
253 | 254 | * **不要使用 `self` 关键字,除非产生二义性.** 255 | 256 |
257 | 示例 258 | 259 | ```swift 260 | final class Listing { 261 | 262 | init(capacity: Int, allowsPets: Bool) { 263 | // 错误示例 264 | self.capacity = capacity 265 | self.isFamilyFriendly = !allowsPets // `self.` not required here 266 | 267 | // 正确示例 268 | self.capacity = capacity 269 | isFamilyFriendly = !allowsPets 270 | } 271 | } 272 | ``` 273 | 274 |
275 | 276 | * **当元组作为返回值时, 给每一个成员添加名称,使其含义更为清晰.** 如果元祖包含超过3个成员的话, 则建议使用结构体. 277 | 278 |
279 | 示例 280 | 281 | ```swift 282 | // 错误示例 283 | func numbers() -> (Int, Int) { 284 | return (6, 6) 285 | } 286 | let numbers = numbers() 287 | print(numbers.0) 288 | 289 | // 正确示例 290 | func numbers() -> (x: Int, y: Int) { 291 | return (x: 6, y: 6) 292 | } 293 | 294 | // 替代方案 295 | func numbers2() -> (x: Int, y: Int) { 296 | let x = is 6 297 | let y = 6 298 | return (x, y) 299 | } 300 | 301 | let numbers = numbers() 302 | numbers 303 | numbers 304 | ``` 305 | 306 |
307 | 308 | * **当声明类型或者变量时, 变量名后紧跟冒号,之后添加空格符, 空格符后紧跟类型.** 309 | 310 |
311 | 示例 312 | 313 | ```swift 314 | 315 | // 错误示例 316 | var something : Double = 0 317 | 318 | // 正确示例 319 | var something: Double = 0 320 | ``` 321 | 322 | ```swift 323 | // 错误示例 324 | class MyClass : SuperClass { 325 | // ... 326 | } 327 | 328 | // 正确示例 329 | class MyClass: SuperClass { 330 | // ... 331 | } 332 | ``` 333 | 334 | ```swift 335 | // 错误示例 336 | var dict = [KeyType:ValueType]() 337 | var dict = [KeyType : ValueType]() 338 | 339 | // 正确示例 340 | var dict = [KeyType: ValueType]() 341 | ``` 342 | 343 |
344 | 345 | * **返回箭头两侧添加空格以增加可读性.** 346 | 347 |
348 | 示例 349 | 350 | ```swift 351 | // 错误示例 352 | func doSomething()->String { 353 | // ... 354 | } 355 | 356 | // 正确示例 357 | func doSomething() -> String { 358 | // ... 359 | } 360 | ``` 361 | 362 |
363 | 364 | * **去除不必要的括号.** 365 | 366 |
367 | 示例 368 | 369 | ```swift 370 | // 错误示例 371 | if (userCount > 0) { ... } 372 | switch (someValue) { ... } 373 | let evens = userCounts.filter { (number) in number % 2 == 0 } 374 | let squares = userCounts.map() { $0 * $0 } 375 | 376 | // 正确示例 377 | if userCount > 0 { ... } 378 | switch someValue { ... } 379 | let evens = userCounts.filter { number in number % 2 == 0 } 380 | let squares = userCounts.map { $0 * $0 } 381 | ``` 382 | 383 | ```swift 384 | // 错误示例 385 | if case .done(_) = result { ... } 386 | 387 | switch animal { 388 | case .dog(_, _, _): 389 | ... 390 | } 391 | 392 | // 正确示例 393 | if case .done = result { ... } 394 | 395 | switch animal { 396 | case .dog: 397 | ... 398 | } 399 | ``` 400 | 401 |
402 | 403 | * **当使用`switch`时,默认情况使用 @unknown 来修饰 `default`关键字.** 404 | 405 |
406 | 示例 407 | 408 | ```swift 409 | // 错误示例 410 | let someFruit = .apple 411 | switch someFruit { 412 | case "apple": 413 | print("apple") 414 | default: 415 | print("Some other fruits.") 416 | } 417 | 418 | // 正确示例 419 | switch someFruit { 420 | case .apple: 421 | ... 422 | @unknown default: 423 | print("We don't sell that kind of fruit here.") 424 | } 425 | ``` 426 | 427 |
428 | 429 | ### 函数 430 | 431 | * **函数体积不应超过百行; 并且需要对函数入参进行判断; 其内部应尽量避免使用全局变量来传递数据.** 432 | 433 |
434 | 示例 435 | 436 | ```swift 437 | // 正确示例 438 | func saveRSS(rss: RSS?, store: Store?) { 439 | guard let rss = rss else { return } 440 | 441 | guard let store = store else { return } 442 | 443 | return 444 | } 445 | ``` 446 |
447 | 448 | * **当函数没有返回值时, 不需指定 `Void` 关键字.** 449 | 450 |
451 | 示例 452 | 453 | ```swift 454 | // 错误示例 455 | func doSomething() -> Void { 456 | ... 457 | } 458 | 459 | // 正确示例 460 | func doSomething() { 461 | ... 462 | } 463 | ``` 464 | 465 |
466 | 467 | ### 闭包 468 | 469 | * **使用 `Void` 作为闭包返回值类型(当返回为空时).** 470 |
471 | 示例 472 | 473 | ```swift 474 | // 错误示例 475 | func doSomething(completion: () -> ()) { 476 | ... 477 | } 478 | 479 | // 正确示例 480 | func doSomething(completion: () -> Void) { 481 | ... 482 | } 483 | ``` 484 | 485 |
486 | 487 | * **使用 (`_`) 代替闭包中未被使用的参数.** 488 | 489 |
490 | 示例 491 | 492 | ```swift 493 | // 错误示例 494 | someAsyncThing() { argument1, argument2, argument3 in 495 | print(argument3) 496 | } 497 | 498 | // 正确示例 499 | someAsyncThing() { _, _, argument3 in 500 | print(argument3) 501 | } 502 | ``` 503 | 504 |
505 | 506 | ### 操作符 507 | 508 | * **二元操作符两侧应添加空格.** 该规则不适用于以下操作符 (e.g. `1...6` 或者 `1..<6`) 509 |
510 | 511 | ```swift 512 | 示例 513 | 514 | // 错误示例 515 | let capacity = 1+2 516 | let capacity = currentCapacity ?? 0 517 | let mask = (UIAccessibilityTraitButton|UIAccessibilityTraitSelected) 518 | let capacity=newCapacity 519 | let latitude = region.center.latitude - region.span.latitudeDelta/2.0 520 | 521 | // 正确示例 522 | let capacity = 1 + 2 523 | let capacity = currentCapacity ?? 0 524 | let mask = (UIAccessibilityTraitButton | UIAccessibilityTraitSelected) 525 | let capacity = newCapacity 526 | let latitude = region.center.latitude - (region.span.latitudeDelta / 2.0) 527 | ``` 528 | 529 |
530 | 531 | **[⬆ 返回顶部](#目录)** 532 | 533 | ## 最佳实践 534 | 535 | * **尽可能在初始化函数 `init` 中完成对变量的初始化工作; 避免直接声明强制解包的变量.** 但是UIViewController的 `view `变量不在此考虑范围内. 536 | 537 |
538 | 示例 539 | 540 | ```swift 541 | // 错误示例 542 | class MyClass: NSObject { 543 | 544 | init() { 545 | super.init() 546 | someValue = 5 547 | } 548 | 549 | var someValue: Int! 550 | } 551 | 552 | // 正确示例 553 | class MyClass: NSObject { 554 | 555 | init() { 556 | someValue = 0 557 | super.init() 558 | } 559 | 560 | var someValue: Int 561 | } 562 | ``` 563 | 564 |
565 | 566 | * **避免在 `init()` 中声明一切耗时或产生副作用的操作.** 例如建立数据库连接,读取数据等等. 567 | 568 | * **将属性观察器中的复杂逻辑提取到函数中.** 569 | 570 |
571 | 示例 572 | 573 | ```swift 574 | // 错误示例 575 | class TextField { 576 | var text: String? { 577 | didSet { 578 | guard oldValue != text else { 579 | return 580 | } 581 | 582 | // Do a bunch of text-related side-effects. 583 | } 584 | } 585 | } 586 | 587 | // 正确示例 588 | class TextField { 589 | var text: String? { 590 | didSet { textDidUpdate(from: oldValue) } 591 | } 592 | 593 | private func textDidUpdate(from oldValue: String?) { 594 | guard oldValue != text else { 595 | return 596 | } 597 | 598 | // Do a bunch of text-related side-effects. 599 | } 600 | } 601 | ``` 602 | 603 |
604 | 605 | * **将复杂的回调逻辑代码放入函数**. 这样可以有效减少嵌套和 `weak self` 的使用。如果需要使用 `self` 关键字,则使用 `guard` 将其解包后使用. 606 | 607 |
608 | 示例 609 | 610 | ```swift 611 | //错误示例 612 | class MyClass { 613 | 614 | func request(completion: () -> Void) { 615 | API.request() { [weak self] response in 616 | if let strongSelf = self { 617 | // Processing and side effects 618 | } 619 | completion() 620 | } 621 | } 622 | } 623 | 624 | // 正确示例 625 | class MyClass { 626 | 627 | func request(completion: () -> Void) { 628 | API.request() { [weak self] response in 629 | guard let strongSelf = self else { return } 630 | strongSelf.doSomething(strongSelf.property) 631 | completion() 632 | } 633 | } 634 | 635 | func doSomething(nonOptionalParameter: SomeClass) { 636 | // Processing and side effects 637 | } 638 | } 639 | ``` 640 | 641 |
642 | 643 | * **在一个范围 `Scope` 起始部分使用guard来做逻辑或者是参数检查.** 644 | 645 | * **访问控制符需要有清晰地设定.** 首选 `public, open, private` 而不是 `fileprivate`. 646 | 647 | * **访避免使用全局函数.** 648 | 649 |
650 | 示例 651 | 652 | ```swift 653 | // 错误示例 654 | func age(of person, bornAt timeInterval) -> Int { 655 | // ... 656 | } 657 | 658 | func jump(person: Person) { 659 | // ... 660 | } 661 | 662 | // 正确示例 663 | class Person { 664 | var bornAt: TimeInterval 665 | 666 | var age: Int { 667 | // ... 668 | } 669 | 670 | func jump() { 671 | // ... 672 | } 673 | } 674 | ``` 675 | 676 |
677 | 678 | * **将私有常量放于文件顶部.** 若常量是外部或模块内可见,则将其定义为静态属性. 679 | 680 |
681 | 示例 682 | 683 | ```swift 684 | // 标准示例 685 | private let privateValue = "secret" 686 | 687 | public class MyClass { 688 | 689 | public static let publicValue = "something" 690 | 691 | func doSomething() { 692 | print(privateValue) 693 | print(MyClass.publicValue) 694 | } 695 | } 696 | ``` 697 | 698 |
699 | 700 | * **使用无具体 `case` 的枚举类型来管理 `public, internal`的常量和函数.** 这样做可有效避免命名空间产生的冲突. 701 | 702 |
703 | 示例 704 | 705 | ```swift 706 | // 标准示例 707 | enum Environment { 708 | 709 | enum Earth { 710 | static let gravity = 9.8 711 | } 712 | 713 | enum Moon { 714 | static let gravity = 1.6 715 | } 716 | } 717 | ``` 718 | 719 |
720 | 721 | * **使用Swift枚举自产生值,除非特定业务需要映射到外部资源.** 722 | 723 |
724 | 示例 725 | 726 | ```swift 727 | // 错误示例 728 | enum ErrorType: String { 729 | case error = "error" 730 | case warning = "warning" 731 | } 732 | 733 | enum UserType: String { 734 | case owner 735 | case manager 736 | case member 737 | } 738 | 739 | enum Planet: Int { 740 | case mercury = 0 741 | case venus = 1 742 | case earth = 2 743 | case mars = 3 744 | case jupiter = 4 745 | case saturn = 5 746 | case uranus = 6 747 | case neptune = 7 748 | } 749 | 750 | enum ErrorCode: Int { 751 | case notEnoughMemory 752 | case invalidResource 753 | case timeOut 754 | } 755 | 756 | // 正确实例 757 | enum ErrorType: String { 758 | case error 759 | case warning 760 | } 761 | 762 | /// 特定需要 763 | // swiftlint:disable redundant_string_enum_value 764 | enum UserType: String { 765 | case owner = "owner" 766 | case manager = "manager" 767 | case member = "member" 768 | } 769 | // swiftlint:enable redundant_string_enum_value 770 | 771 | enum Planet: Int { 772 | case mercury 773 | case venus 774 | case earth 775 | case mars 776 | case jupiter 777 | case saturn 778 | case uranus 779 | case neptune 780 | } 781 | ``` 782 | 783 |
784 | 785 | * **默认使用 `static` 作为类型函数** 若支持重写,则要使用 `class` 关键字. 786 | 787 |
788 | 示例 789 | 790 | ```swift 791 | 792 | // 错误示例 793 | class Fruit { 794 | class func eatFruits(_ fruits: [Fruit]) { ... } 795 | } 796 | 797 | // 错误示例 798 | class Fruit { 799 | static func eatFruits(_ fruits: [Fruit]) { ... } 800 | } 801 | ``` 802 | 803 |
804 | 805 | * **默认使用 `final` 修饰 `class`类型.** 该规则会明确告诉编译器取消对该class类型的动态派发优化, 其函数会使用直接派发方式. 806 | 807 |
808 | 示例 809 | 810 | ```swift 811 | // 错误示例 812 | class SettingsRepository { 813 | // ... 814 | } 815 | 816 | // 正确示例 817 | final class SettingsRepository { 818 | // ... 819 | } 820 | ``` 821 | 822 |
823 | 824 | * **在不使用 `optinal binding`值时,则检查其是否为空.** 825 | 826 |
827 | 示例 828 | 829 | ```swift 830 | var thing: Thing? 831 | 832 | // 错误示例 833 | if let _ = thing { 834 | doThing() 835 | } 836 | 837 | // 正确示例 838 | if thing != nil { 839 | doThing() 840 | } 841 | ``` 842 | 843 |
844 | 845 | **[⬆ 返回顶部](#目录)** 846 | 847 | ## 与Objc的交互 848 | 849 | * **在非必要情况下class无需继承 `NSObject`.** 如果需要使用Objc特性, 则按照需添加 `@objc` 修饰. 850 | 851 |
852 | 示例 853 | 854 | ```swift 855 | // 标准示例 856 | class PriceBreakdownViewController { 857 | 858 | private let acceptButton = UIButton() 859 | 860 | private func setUpAcceptButton() { 861 | acceptButton.addTarget( 862 | self, 863 | action: #selector(didTapAcceptButton), 864 | forControlEvents: .TouchUpInside) 865 | } 866 | 867 | @objc 868 | private func didTapAcceptButton() { 869 | // ... 870 | } 871 | } 872 | ``` 873 | 874 |
875 | 876 | **[⬆ 返回顶部](#目录)** -------------------------------------------------------------------------------- /Tests/ClaretCacheTests/ClaretCacheTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import ClaretCache 3 | 4 | final class ClaretCacheTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(ClaretCache().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample) 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Tests/ClaretCacheTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(ClaretCacheTests.allTests) 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import ClaretCacheTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += ClaretCacheTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /configs/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: # rule identifiers to exclude from running 2 | - colon 3 | - comma 4 | - control_statement 5 | - file_length 6 | - type_body_length 7 | opt_in_rules: # some rules are only opt-in 8 | - empty_count 9 | # Find all the available rules by running: 10 | # swiftlint rules 11 | included: # paths to include during linting. `--path` is ignored if present. 12 | - Sources 13 | - ClaretCacheDemoTests 14 | excluded: # paths to ignore during linting. Takes precedence over `included`. 15 | - Carthage 16 | - Pods 17 | - Source/ExcludedFolder 18 | - Source/ExcludedFile.swift 19 | - Source/*/ExcludedFile.swift # Exclude files with a wildcard 20 | analyzer_rules: # Rules run by `swiftlint analyze` (experimental) 21 | - explicit_self 22 | 23 | # configurable rules can be customized from this configuration file 24 | # binary rules can set their severity level 25 | force_cast: warning # implicitly 26 | force_try: 27 | severity: warning # explicitly 28 | # rules that have both warning and error levels, can set just the warning level 29 | # implicitly 30 | line_length: 200 31 | # they can set both implicitly with an array 32 | type_body_length: 33 | - 300 # warning 34 | - 400 # error 35 | # or they can set both explicitly 36 | file_length: 37 | warning: 600 38 | error: 1200 39 | # naming rules can set warnings/errors for min_length and max_length 40 | # additionally they can set excluded names 41 | type_name: 42 | min_length: 4 # only warning 43 | max_length: # warning and error 44 | warning: 40 45 | error: 50 46 | excluded: iPhone # excluded via string 47 | identifier_name: 48 | min_length: # only min_length 49 | error: 2 # only error 50 | excluded: # excluded via string array 51 | - id 52 | - URL 53 | - GlobalAPIKey 54 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown) 55 | --------------------------------------------------------------------------------