├── Example.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcshareddata
│ └── xcschemes
│ │ ├── AwesomeCache-tvOS.xcscheme
│ │ ├── AwesomeCache-watchOS.xcscheme
│ │ ├── AwesomeCache-iOS.xcscheme
│ │ └── AwesomeCacheTests.xcscheme
└── project.pbxproj
├── AwesomeCache
├── NSKeyedUnarchiverWrapper.h
├── NSKeyedUnarchiverWrapper.m
├── AwesomeCache.h
├── Info.plist
├── CacheObject.swift
└── Cache.swift
├── .gitignore
├── Example
├── Images.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── ViewController.swift
├── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
├── Info.plist
├── CacheObject.swift
├── AppDelegate.swift
└── Cache.swift
├── AwesomeCacheTests
├── Info.plist
└── AwesomeCacheTests.swift
├── .travis.yml
├── AwesomeCache.podspec
├── LICENSE
└── README.md
/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/AwesomeCache/NSKeyedUnarchiverWrapper.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSKeyedUnarchiverWrapper.h
3 | // Example
4 | //
5 | // Created by Javier Soto on 5/17/16.
6 | // Copyright © 2016 Alexander Schuch. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | extern NSObject * __nullable _awesomeCache_unarchiveObjectSafely(NSString * __nonnull path);
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Xcode
4 | #
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata
15 | *.xccheckout
16 | *.moved-aside
17 | DerivedData
18 | *.hmap
19 | *.ipa
20 | *.xcuserstate
21 |
22 | # Carthage
23 | Carthage/Checkouts
24 | Carthage/Build
25 |
--------------------------------------------------------------------------------
/Example/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "40x40",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "60x60",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "60x60",
21 | "scale" : "3x"
22 | }
23 | ],
24 | "info" : {
25 | "version" : 1,
26 | "author" : "xcode"
27 | }
28 | }
--------------------------------------------------------------------------------
/AwesomeCache/NSKeyedUnarchiverWrapper.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSKeyedUnarchiverWrapper.m
3 | // Example
4 | //
5 | // Created by Javier Soto on 5/17/16.
6 | // Copyright © 2016 Alexander Schuch. All rights reserved.
7 | //
8 |
9 | #import "NSKeyedUnarchiverWrapper.h"
10 |
11 | NSObject * __nullable _awesomeCache_unarchiveObjectSafely(NSString *path) {
12 | @try {
13 | return [NSKeyedUnarchiver unarchiveObjectWithFile:path];
14 | }
15 | @catch (NSException *exception) {
16 | NSLog(@"Caught exception while unarchiving file at path %@: %@", path, exception);
17 | return nil;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/AwesomeCache/AwesomeCache.h:
--------------------------------------------------------------------------------
1 | //
2 | // AwesomeCache.h
3 | // AwesomeCache
4 | //
5 | // Created by Alexander Schuch on 31/01/15.
6 | // Copyright (c) 2015 Alexander Schuch. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for AwesomeCache.
12 | FOUNDATION_EXPORT double AwesomeCacheVersionNumber;
13 |
14 | //! Project version string for AwesomeCache.
15 | FOUNDATION_EXPORT const unsigned char AwesomeCacheVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 | #import
--------------------------------------------------------------------------------
/AwesomeCacheTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Example/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Example
4 | //
5 | // Created by Alexander Schuch on 12/07/14.
6 | // Copyright (c) 2014 Alexander Schuch. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AwesomeCache
11 |
12 | class ViewController: UIViewController {
13 |
14 | @IBOutlet var textView: UITextView!
15 |
16 | let cache = try! Cache(name: "AwesomeCache")
17 |
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 | textView.text = (cache["myText"] as? String) ?? ""
21 | }
22 |
23 | @IBAction func reloadData(_ sender: AnyObject?) {
24 | textView.text = (cache["myText"] as? String) ?? ""
25 | }
26 |
27 | @IBAction func saveInCache(_ sender: AnyObject?) {
28 | cache["myText"] = textView.text as NSString?
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode8
3 | env:
4 | global:
5 | - LC_CTYPE=en_US.UTF-8
6 | - LANG=en_US.UTF-8
7 | - PROJECT=Example.xcodeproj
8 | - IOS_FRAMEWORK_SCHEME="AwesomeCache-iOS"
9 | - TVOS_FRAMEWORK_SCHEME="AwesomeCache-tvOS"
10 | - WATCHOS_FRAMEWORK_SCHEME="AwesomeCache-watchOS"
11 | matrix:
12 | - DESTINATION="OS=10.0,name=iPhone 6" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="iphonesimulator10.0"
13 | # - DESTINATION="OS=9.2,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK"
14 | # - DESTINATION="OS=2.2,name=Apple Watch - 42mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK"
15 | script:
16 | - set -o pipefail
17 | - xcodebuild -version
18 | - xcodebuild -showsdks
19 | - xcodebuild -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c;
20 |
--------------------------------------------------------------------------------
/AwesomeCache/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/AwesomeCache.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "AwesomeCache"
3 | s.version = "5.0"
4 | s.summary = "Delightful on-disk cache (written in Swift)"
5 | s.description = "Delightful on-disk cache (written in Swift). Backed by NSCache for maximum performance and support for expiry of single objects."
6 | s.homepage = "https://github.com/aschuch/AwesomeCache"
7 | s.license = { :type => "MIT", :file => "LICENSE" }
8 | s.author = { "Alexander Schuch" => "alexander@schuch.me" }
9 | s.social_media_url = "http://twitter.com/schuchalexander"
10 | s.ios.deployment_target = "8.0"
11 | s.tvos.deployment_target= "9.0"
12 | s.watchos.deployment_target= "3.0"
13 | s.source = { :git => "https://github.com/aschuch/AwesomeCache.git", :tag => s.version }
14 | s.requires_arc = true
15 | s.source_files = "AwesomeCache/Cache.swift", "AwesomeCache/CacheObject.swift", "AwesomeCache/NSKeyedUnarchiverWrapper.{h,m}"
16 | end
17 |
--------------------------------------------------------------------------------
/Example/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Alexander Schuch (http://schuch.me)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Example/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 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Example/CacheObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CacheObject.swift
3 | // Example
4 | //
5 | // Created by Alexander Schuch on 12/07/14.
6 | // Copyright (c) 2014 Alexander Schuch. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | * This class is a wrapper around an objects that should be cached to disk.
13 | *
14 | * NOTE: It is currently not possible to use generics with a subclass of NSObject
15 | * However, NSKeyedArchiver needs a concrete subclass of NSObject to work correctly
16 | */
17 | class CacheObject : NSObject, NSCoding {
18 | let value: AnyObject
19 | let expiryDate: NSDate
20 |
21 | /**
22 | * Designated initializer.
23 | *
24 | * @param value An object that should be cached
25 | * @param expiryDate The expiry date of the given value
26 | */
27 | init(value: AnyObject, expiryDate: NSDate) {
28 | self.value = value
29 | self.expiryDate = expiryDate
30 | }
31 |
32 | /**
33 | * Returns true if this object is expired.
34 | * Expiry of the object is determined by its expiryDate.
35 | */
36 | func isExpired() -> Bool {
37 | let expires = expiryDate.timeIntervalSinceNow
38 | let now = NSDate().timeIntervalSinceNow
39 |
40 | return now > expires
41 | }
42 |
43 |
44 | /// NSCoding
45 |
46 | init(coder aDecoder: NSCoder!) {
47 | value = aDecoder.decodeObjectForKey("value")
48 | expiryDate = aDecoder.decodeObjectForKey("expiryDate") as NSDate
49 | }
50 |
51 | func encodeWithCoder(aCoder: NSCoder!) {
52 | aCoder.encodeObject(value, forKey: "value")
53 | aCoder.encodeObject(expiryDate, forKey: "expiryDate")
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/AwesomeCache/CacheObject.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// This class is a wrapper around an objects that should be cached to disk.
4 | ///
5 | /// NOTE: It is currently not possible to use generics with a subclass of NSObject
6 | /// However, NSKeyedArchiver needs a concrete subclass of NSObject to work correctly
7 | open class CacheObject: NSObject, NSCoding {
8 | public let value: AnyObject
9 | public let expiryDate: Date
10 |
11 | /// Designated initializer.
12 | ///
13 | /// - parameter value: An object that should be cached
14 | /// - parameter expiryDate: The expiry date of the given value
15 | public init(value: AnyObject, expiryDate: Date) {
16 | self.value = value
17 | self.expiryDate = expiryDate
18 | }
19 |
20 | /// Determines if cached object is expired
21 | ///
22 | /// - returns: True If objects expiry date has passed
23 | func isExpired() -> Bool {
24 | return expiryDate.isInThePast
25 | }
26 |
27 |
28 | /// NSCoding
29 |
30 | required public init?(coder aDecoder: NSCoder) {
31 | guard let val = aDecoder.decodeObject(forKey: "value"),
32 | let expiry = aDecoder.decodeObject(forKey: "expiryDate") as? Date else {
33 | return nil
34 | }
35 |
36 | self.value = val as AnyObject
37 | self.expiryDate = expiry
38 | super.init()
39 | }
40 |
41 | public func encode(with aCoder: NSCoder) {
42 | aCoder.encode(value, forKey: "value")
43 | aCoder.encode(expiryDate, forKey: "expiryDate")
44 | }
45 | }
46 |
47 | extension Date {
48 | var isInThePast: Bool {
49 | return self.timeIntervalSinceNow < 0
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Example
4 | //
5 | // Created by Alexander Schuch on 12/07/14.
6 | // Copyright (c) 2014 Alexander Schuch. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | 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 throttle down OpenGL ES frame rates. 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 inactive 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 | }
45 |
46 |
--------------------------------------------------------------------------------
/Example.xcodeproj/xcshareddata/xcschemes/AwesomeCache-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/Example.xcodeproj/xcshareddata/xcschemes/AwesomeCache-watchOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/Example.xcodeproj/xcshareddata/xcschemes/AwesomeCache-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/Example.xcodeproj/xcshareddata/xcschemes/AwesomeCacheTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Awesome Cache
2 |
3 | [](https://travis-ci.org/aschuch/AwesomeCache)
4 | [](https://img.shields.io/cocoapods/v/AwesomeCache.svg)
5 | 
6 | 
7 | 
8 |
9 | Delightful on-disk cache (written in Swift).
10 | Backed by NSCache for maximum performance and support for expiry of single objects.
11 |
12 |
13 | ## Usage
14 |
15 | ```swift
16 | do {
17 | let cache = try Cache(name: "awesomeCache")
18 |
19 | cache["name"] = "Alex"
20 | let name = cache["name"]
21 | cache["name"] = nil
22 | } catch _ {
23 | print("Something went wrong :(")
24 | }
25 | ```
26 |
27 | ### Sync by design
28 |
29 | AwesomeCache >= 3.0 is designed to have a sync API, making it easy to reason about the actual contents of the cache. This decision has been made based on [feedback from the community](issues/33), to keep the API of AwesomeCache small and easy to use.
30 |
31 | The internals of the cache use a concurrent dispatch queue, that syncs reads and writes for thread safety. In case a particular caching operation blocks your main thread for too long, consider offloading the read and write operations to a different thread.
32 |
33 | ```swift
34 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
35 | cache["name"] = "Alex"
36 | }
37 | ```
38 |
39 | ### Cache expiry
40 |
41 | Objects can also be cached for a certain period of time.
42 |
43 | ```swift
44 | cache.setObject("Alex", forKey: "name", expires: .never) // same as cache["name"] = "Alex"
45 | cache.setObject("Alex", forKey: "name", expires: .seconds(2)) // name expires in 2 seconds
46 | cache.setObject("Alex", forKey: "name", expires: .date(Date(timeIntervalSince1970: 1428364800))) // name expires on 4th of July 2015
47 | ```
48 |
49 | If an object is accessed after its expiry date, it is automatically removed from the cache and deleted from disk.
50 | However, you are responsible to delete expired objects regularly by calling `removeExpiredObjects` (e.g. on app launch).
51 |
52 | ### Awesome API Caching
53 |
54 | API responses are usually cached for a specific period of time. AwesomeCache provides an easy method to cache a block of asynchronous tasks.
55 |
56 | ```swift
57 | cache.setObject(forKey: "name", cacheBlock: { success, failure in
58 | // Perform tasks, e.g. call an API
59 | let response = ...
60 |
61 | success(response, .seconds(300)) // Cache response for 5 minutes
62 | // ... or failure(error)
63 | }, completion: { object, isLoadedFromCache, error in
64 | if object {
65 | // object is now cached
66 | }
67 | })
68 | ```
69 |
70 | If the cache already contains an object, the `completion` block is called with the cached object immediately.
71 |
72 | If no object is found or the cached object is already expired, the `cacheBlock` is called.
73 | You may perform any tasks (e.g. network calls) within this block. Upon completion of these tasks, make sure to call the `success` or `failure` block that is passed to the `cacheBlock`. The cacheBlock will not be re-evaluated until the object is expired or manually deleted.
74 |
75 | The completion block is invoked as soon as the cacheBlock is finished and the object is cached.
76 |
77 | ## Version Compatibility
78 |
79 | Current Swift compatibility breakdown:
80 |
81 | | Swift Version | Framework Version |
82 | | ------------- | ----------------- |
83 | | 3.0 | 5.x |
84 | | 2.3 | 4.x |
85 | | 2.2 | 3.x |
86 |
87 | [all releases]: https://github.com/aschuch/AwesomeCache/releases
88 |
89 | ## Installation
90 |
91 | #### Carthage
92 |
93 | Add the following line to your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile).
94 |
95 | ```
96 | github "aschuch/AwesomeCache"
97 | ```
98 |
99 | Then run `carthage update`.
100 |
101 | #### CocoaPods
102 |
103 | Add the following line to your Podfile.
104 |
105 | ```
106 | pod "AwesomeCache", "~> 3.0"
107 | ```
108 |
109 | Then run `pod install` with CocoaPods 0.36 or newer.
110 |
111 | #### Manually
112 |
113 | Just drag and drop the two `.swift` files as well as the `NSKeyedUnarchiverWrapper.h/.m` files in the `AwesomeCache` folder into your project.
114 | If you are adding AwesomeCache to a Swift project, you also need to add an import for `NSKeyedUnarchiverWrapper.h` to your bridging header.
115 |
116 | ## Tests
117 |
118 | Open the Xcode project and press `⌘-U` to run the tests.
119 |
120 | Alternatively, all tests can be run in the terminal using [xctool](https://github.com/facebook/xctool).
121 |
122 | ```bash
123 | xctool -scheme AwesomeCacheTests -sdk iphonesimulator test
124 | ```
125 |
126 | ## Contributing
127 |
128 | * Create something awesome, make the code better, add some functionality,
129 | whatever (this is the hardest part).
130 | * [Fork it](http://help.github.com/forking/)
131 | * Create new branch to make your changes
132 | * Commit all your changes to your branch
133 | * Submit a [pull request](http://help.github.com/pull-requests/)
134 |
135 |
136 | ## Contact
137 |
138 | Feel free to get in touch.
139 |
140 | * Website:
141 | * Twitter: [@schuchalexander](http://twitter.com/schuchalexander)
142 |
--------------------------------------------------------------------------------
/Example/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 |
26 |
27 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.
28 |
29 |
30 |
31 |
40 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/Example/Cache.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Cache.swift
3 | // Example
4 | //
5 | // Created by Alexander Schuch on 12/07/14.
6 | // Copyright (c) 2014 Alexander Schuch. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | * Represents the expiry of a cached object
13 | */
14 | enum CacheExpiry {
15 | case Never
16 | case InSeconds(NSTimeInterval)
17 | case Date(NSDate)
18 | }
19 |
20 | /**
21 | * A generic cache that persists objects to disk and is backed by a NSCache.
22 | * Supports an expiry date for every cached object. Expired objects are automatically deleted upon their next access via `objectForKey:`.
23 | * If you want to delete expired objects, call `removeAllExpiredObjects`.
24 | *
25 | * Subclassing notes: This class fully supports subclassing.
26 | * The easiest way to implement a subclass is to override `objectForKey` and `setObject:forKey:expires:`, e.g. to modify values prior to reading/writing to the cache.
27 | */
28 | class Cache {
29 | let name: String // @readonly
30 | let directory: String // @readonly
31 |
32 | // @private
33 | let cache = NSCache()
34 | let fileManager = NSFileManager()
35 | let diskQueue: dispatch_queue_t = dispatch_queue_create("com.aschuch.cache.diskQueue", DISPATCH_QUEUE_SERIAL)
36 |
37 |
38 | /// Initializers
39 |
40 | /**
41 | * Designated initializer.
42 | *
43 | * @param name Name of this cache
44 | * @param directory Objects in this cache are persisted to this directory.
45 | * If no directory is specified, a new directory is created in the system's Caches directory
46 | *
47 | * @return A new cache with the given name and directory
48 | *
49 | */
50 | init(name: String, directory: String?) {
51 | // Ensure directory name
52 | var dir: String? = directory
53 | if !dir {
54 | let cacheDirectory = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true)[0] as String
55 | dir = cacheDirectory.stringByAppendingFormat("/com.aschuch.cache/%@", name)
56 | }
57 | self.directory = dir!
58 |
59 | self.name = name
60 | cache.name = name
61 |
62 | // Create directory on disk
63 | if !fileManager.fileExistsAtPath(self.directory) {
64 | fileManager.createDirectoryAtPath(self.directory, withIntermediateDirectories: true, attributes: nil, error: nil)
65 | }
66 | }
67 |
68 | /**
69 | * @param name Name of this cache
70 | *
71 | * @return A new cache with the given name and the default cache directory
72 | */
73 | convenience init(name: String) {
74 | self.init(name: name, directory: nil)
75 | }
76 |
77 |
78 | /// Awesome caching
79 |
80 | /**
81 | * Returns a cached object immediately or evaluates a cacheBlock. The cacheBlock is not re-evaluated until the object is expired or manually deleted.
82 | *
83 | * First looks up an object with the given key. If no object was found or the cached object is already expired, the `cacheBlock` is called.
84 | * You can perform any tasks (e.g. network calls) within this block. Upon completion of these tasks, make sure to call the completion block that is passed to the `cacheBlock`.
85 | * The completion block is called immediately, if the cache already contains an object for the given key. Otherwise it is called as soon as the `cacheBlock` completes and the object is cached.
86 | *
87 | * @param key The key for the cached object
88 | * @param cacheBlock This block gets called if there is no cached object or this object is already expired.
89 | * The supplied block must be called upon completion (with the object to cache and its expiry).
90 | * @param completaion Called as soon as a cached object is available to use. The second parameter is true if the object was already cached.
91 | */
92 | func setObjectForKey(key: String, cacheBlock: ((T, CacheExpiry) -> ()) -> (), completion: (T, Bool) -> ()) {
93 | if let object = objectForKey(key) {
94 | completion(object, true)
95 | } else {
96 | let cacheReturnBlock: (T, CacheExpiry) -> () = { (obj, expires) in
97 | self.setObject(obj, forKey: key, expires: expires)
98 | completion(obj, false)
99 | }
100 | cacheBlock(cacheReturnBlock)
101 | }
102 | }
103 |
104 |
105 | /// Get object
106 |
107 | /**
108 | * Looks up and returns an object with the specified name if it exists.
109 | * If an object is already expired, it is automatically deleted and `nil` will be returned.
110 | *
111 | * @param name The name of the object that should be returned
112 | * @return The cached object for the given name, or nil
113 | */
114 | func objectForKey(key: String) -> T? {
115 | var possibleObject: CacheObject?
116 |
117 | // Check if object exists in local cache
118 | possibleObject = cache.objectForKey(key) as? CacheObject
119 |
120 | if !possibleObject {
121 | // Try to load object from disk (synchronously)
122 | dispatch_sync(diskQueue) {
123 | let path = self._pathForKey(key)
124 | if self.fileManager.fileExistsAtPath(path) {
125 | possibleObject = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? CacheObject
126 | }
127 | }
128 | }
129 |
130 | // Check if object is not already expired and return
131 | // Delete object if expired
132 | if let object = possibleObject {
133 | if !object.isExpired() {
134 | return object.value as? T
135 | } else {
136 | removeObjectForKey(key)
137 | }
138 | }
139 |
140 | return nil
141 | }
142 |
143 |
144 | /// Set object
145 |
146 | /**
147 | * Adds a given object to the cache.
148 | *
149 | * @param object The object that should be cached
150 | * @param forKey A key that represents this object in the cache
151 | */
152 | func setObject(object: T, forKey key: String) {
153 | self.setObject(object, forKey: key, expires: .Never)
154 | }
155 |
156 | /**
157 | * Adds a given object to the cache.
158 | * The object is automatically marked as expired as soon as its expiry date is reached.
159 | *
160 | * @param object The object that should be cached
161 | * @param forKey A key that represents this object in the cache
162 | */
163 | func setObject(object: T, forKey key: String, expires: CacheExpiry) {
164 | let expiryDate = _expiryDateForCacheExpiry(expires)
165 | let cacheObject = CacheObject(value: object, expiryDate: expiryDate)
166 |
167 | // Set object in local cache
168 | cache.setObject(cacheObject, forKey: key)
169 |
170 | // Write object to disk (asyncronously)
171 | dispatch_async(diskQueue) {
172 | let path = self._pathForKey(key)
173 | NSKeyedArchiver.archiveRootObject(cacheObject, toFile: path)
174 | }
175 | }
176 |
177 |
178 | /// Remove objects
179 |
180 | /**
181 | * Removes an object from the cache.
182 | *
183 | * @param key The key of the object that should be removed
184 | */
185 | func removeObjectForKey(key: String) {
186 | cache.removeObjectForKey(key)
187 |
188 | dispatch_async(diskQueue) {
189 | let path = self._pathForKey(key)
190 | self.fileManager.removeItemAtPath(path, error: nil)
191 | }
192 | }
193 |
194 | /**
195 | * Removes all objects from the cache.
196 | */
197 | func removeAllObjects() {
198 | cache.removeAllObjects()
199 |
200 | dispatch_async(diskQueue) {
201 | let paths = self.fileManager.contentsOfDirectoryAtPath(self.directory, error: nil) as [String]
202 | for path in paths {
203 | self.fileManager.removeItemAtPath(path, error: nil)
204 | }
205 | }
206 | }
207 |
208 |
209 | /// Remove Expired Objects
210 |
211 | /**
212 | * Removes all expired objects from the cache.
213 | */
214 | func removeExpiredObjects() {
215 | dispatch_async(diskQueue) {
216 | let paths = self.fileManager.contentsOfDirectoryAtPath(self.directory, error: nil) as [String]
217 | let keys = paths.map { $0.lastPathComponent.stringByDeletingPathExtension }
218 |
219 | for key in keys {
220 | // `objectForKey:` deletes the object if it is expired
221 | self.objectForKey(key)
222 | }
223 | }
224 | }
225 |
226 |
227 | /// Subscripting
228 |
229 | subscript(key: String) -> T? {
230 | get {
231 | return objectForKey(key)
232 | }
233 | set(newValue) {
234 | if let value = newValue {
235 | setObject(value, forKey: key)
236 | } else {
237 | removeObjectForKey(key)
238 | }
239 | }
240 | }
241 |
242 |
243 | /// @private Helper
244 |
245 | /**
246 | * @private
247 | */
248 | func _pathForKey(key: String) -> String {
249 | return directory.stringByAppendingPathComponent(key).stringByAppendingPathExtension("cache")
250 | }
251 |
252 | /**
253 | * @private
254 | */
255 | func _expiryDateForCacheExpiry(expiry: CacheExpiry) -> NSDate {
256 | switch expiry {
257 | case .Never:
258 | return NSDate.distantFuture() as NSDate
259 | case .InSeconds(let seconds):
260 | return NSDate().dateByAddingTimeInterval(seconds)
261 | case .Date(let date):
262 | return date
263 | }
264 | }
265 | }
266 |
267 |
--------------------------------------------------------------------------------
/AwesomeCacheTests/AwesomeCacheTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AwesomeCacheTests.swift
3 | // AwesomeCacheTests
4 | //
5 | // Created by Alexander Schuch on 31/01/15.
6 | // Copyright (c) 2015 Alexander Schuch. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCTest
11 | @testable import AwesomeCache
12 |
13 | class AwesomeCacheTests: XCTestCase {
14 |
15 | func testCustomCachePath() {
16 | let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
17 | let cache = try! Cache(name: "CustomCachePath", directory: url)
18 |
19 | cache.setObject("AddedString", forKey: "add")
20 | XCTAssertNotNil(cache.object(forKey: "add"), "Added object should not be nil")
21 | }
22 |
23 | func testGetterAndSetter() {
24 | let cache = try! Cache(name: "testGetterAndSetter")
25 |
26 | let nilObject = cache.object(forKey: "unavailable")
27 | XCTAssertNil(nilObject, "Getting an unavailable object should return nil")
28 |
29 | cache.setObject("AddedString", forKey: "add")
30 | XCTAssertNotNil(cache.object(forKey: "add"), "Added object should not be nil")
31 | XCTAssertEqual("AddedString", cache.object(forKey: "add")!, "Fetched object should be equal to the inserted object")
32 | }
33 |
34 | func testGetExpiredObjectIfPresent() {
35 | let cache = try! Cache(name: "testGetExpiredObject")
36 |
37 | cache.setObject("AlreadyExpired", forKey: "alreadyExpired", expires: .date(Date().addingTimeInterval(-1)))
38 |
39 | XCTAssertNotNil(cache.object(forKey: "alreadyExpired", returnExpiredObjectIfPresent: true), "Already expired object should be returned when `returnExpiredObjectIfPresent` is true")
40 | XCTAssertNil(cache.object(forKey: "alreadyExpired"), "Already expired object should not be returned when `returnExpiredObjectIfPresent` is not set")
41 | }
42 |
43 | func testRemoveObject() {
44 | let cache = try! Cache(name: "testRemoveObject")
45 |
46 | cache.setObject("AddedString", forKey: "remove")
47 | XCTAssertNotNil(cache.object(forKey: "remove"), "Added object should not be nil")
48 | XCTAssertEqual("AddedString", cache.object(forKey: "remove")!, "Fetched object should be equal to the inserted object")
49 |
50 | cache.removeObject(forKey: "remove")
51 | XCTAssertNil(cache.object(forKey: "remove"), "Removed object should be nil")
52 | }
53 |
54 | func testRemoveAllObjects() {
55 | let cache = try! Cache(name: "testRemoveAllObjects")
56 |
57 | cache.setObject("AddedString 1", forKey: "remove 1")
58 | cache.setObject("AddedString 2", forKey: "remove 2")
59 | XCTAssertNotNil(cache.object(forKey: "remove 1"), "Added object should not be nil")
60 | XCTAssertNotNil(cache.object(forKey: "remove 2"), "Added object should not be nil")
61 |
62 | cache.removeAllObjects()
63 | XCTAssertNil(cache.object(forKey: "remove 1"), "Removed object should be nil")
64 | XCTAssertNil(cache.object(forKey: "remove 2"), "Removed object should be nil")
65 | }
66 |
67 | func testRemoveExpiredObjects() {
68 | let cache = try! Cache(name: "testRemoveExpiredObjects")
69 |
70 | cache.setObject("NeverExpires", forKey: "never", expires: .never)
71 | cache.setObject("ExpiresIn2Seconds", forKey: "2Seconds", expires: .seconds(2))
72 | cache.removeExpiredObjects()
73 |
74 | XCTAssertNotNil(cache.object(forKey: "never"), "Added object should not be nil since it never expires")
75 | XCTAssertNotNil(cache.object(forKey: "2Seconds"), "Added object should not be nil since 2 seconds have not passed")
76 |
77 | sleep(3)
78 |
79 | cache.removeExpiredObjects()
80 | XCTAssertNotNil(cache.object(forKey: "never"), "Object should not be nil since it never expires")
81 | XCTAssertNil(cache.object(forKey: "2Seconds"), "Object should be nil since 2 seconds have passed")
82 | }
83 |
84 | func testSubscripting() {
85 | let cache = try! Cache(name: "testSubscripting")
86 |
87 | cache["addSubscript"] = "AddedString"
88 | XCTAssertNotNil(cache["addSubscript"], "Get non-nil object via subscript")
89 | XCTAssertEqual("AddedString", cache["addSubscript"]!, "Get non-nil object via subscript")
90 |
91 | cache["addSubscript"] = nil
92 | XCTAssertNil(cache["addSubscript"], "Get deleted object via subscript")
93 | }
94 |
95 | func testInvalidKey() {
96 | let cache = try! Cache(name: "testInvalidKey")
97 |
98 | let key = "//$%foobar--893"
99 | cache.setObject("AddedString", forKey: key)
100 | XCTAssertNotNil(cache.object(forKey: key), "Added object should not be nil")
101 | XCTAssertEqual("AddedString", cache.object(forKey: key)!, "Fetched object should be equal to the inserted object")
102 | }
103 |
104 | func testObjectExpiry() {
105 | let cache = try! Cache(name: "testObjectExpiry")
106 |
107 | cache.setObject("NeverExpires", forKey: "never", expires: .never)
108 | cache.setObject("ExpiresIn2Seconds", forKey: "2Seconds", expires: .seconds(2))
109 | cache.setObject("ExpiresAtDate", forKey: "atDate", expires: .date(Date().addingTimeInterval(4)))
110 |
111 | XCTAssertNotNil(cache.object(forKey: "never"), "Never expires")
112 | XCTAssertNotNil(cache.object(forKey: "2Seconds"), "Expires in 2 seconds")
113 | XCTAssertNotNil(cache.object(forKey: "atDate"), "Expires in 4 seconds")
114 |
115 | sleep(2)
116 |
117 | XCTAssertNotNil(cache.object(forKey: "never"), "Never expires")
118 | XCTAssertNil(cache.object(forKey: "2Seconds"), "Expires in 2 seconds")
119 | XCTAssertNotNil(cache.object(forKey: "atDate"), "Expires in 4 seconds")
120 |
121 | sleep(2)
122 |
123 | XCTAssertNotNil(cache.object(forKey: "never"), "Never expires")
124 | XCTAssertNil(cache.object(forKey: "2Seconds"), "Expires in 2 seconds")
125 | XCTAssertNil(cache.object(forKey: "atDate"), "Expires in 3 seconds")
126 | }
127 |
128 | func testAllObjects() {
129 | let cache = try! Cache(name: "testAllObjects")
130 |
131 | cache.setObject("NeverExpires", forKey: "never", expires: .never)
132 | cache.setObject("ExpiresIn2Seconds", forKey: "2Seconds", expires: .seconds(2))
133 | cache.setObject("ExpiresAtDate", forKey: "atDate", expires: .date(Date().addingTimeInterval(4)))
134 |
135 | sleep(2)
136 |
137 | let all = cache.allObjects()
138 |
139 | XCTAssertTrue(all.count == 2, "2 returned objects")
140 | XCTAssertTrue(all.contains("NeverExpires"), "Never expires")
141 | XCTAssertFalse(all.contains("ExpiresIn2Seconds"), "Expires in 2 seconds")
142 | XCTAssertTrue(all.contains("ExpiresAtDate"), "Expires in 4 seconds")
143 |
144 | let expiredIncluded = cache.allObjects(includeExpired: true)
145 | XCTAssertTrue(expiredIncluded.count == 3, "3 returned objects")
146 | XCTAssertTrue(expiredIncluded.contains("NeverExpires"), "Never expires")
147 | XCTAssertTrue(expiredIncluded.contains("ExpiresIn2Seconds"), "Expires in 2 seconds")
148 | XCTAssertTrue(expiredIncluded.contains("ExpiresAtDate"), "Expires in 4 seconds")
149 | }
150 |
151 | func testRemoveAllExpiredObjects() {
152 | let cache = try! Cache(name: "testRemoveAllExpiredObjects")
153 |
154 | cache.setObject("NeverExpires", forKey: "never", expires: .never)
155 | cache.setObject("AlreadyExpired", forKey: "alreadyExpired", expires: .date(Date().addingTimeInterval(-1)))
156 |
157 | cache.cache.removeAllObjects() // Prevent the in-memory cache to return the object when trying to read the expiration date
158 | cache.removeExpiredObjects()
159 |
160 | XCTAssertNotNil(cache.object(forKey: "never"), "Never expires")
161 | XCTAssertNil(cache.object(forKey: "alreadyExpired"), "Already expired")
162 | }
163 |
164 | func testCacheBlockExecuted() {
165 | let cache = try! Cache(name: "testCacheBlockExecuted")
166 | var executed = false
167 |
168 | cache.setObject(forKey: "blockExecuted", cacheBlock: { successBlock, failureBlock in
169 | executed = true
170 | successBlock("AddedString", .never)
171 | }, completion: { object, isLoadedFromCache, error in
172 | XCTAssertNotNil(object, "Cached object not nil")
173 | XCTAssertEqual("AddedString", object!, "Get cached object")
174 |
175 | XCTAssertNotNil(cache.object(forKey: "blockExecuted"), "Get cached object")
176 | XCTAssertTrue(executed, "Block was executed")
177 | XCTAssertFalse(isLoadedFromCache, "Object was not loaded cached")
178 | XCTAssertNil(error, "Error is nil")
179 | })
180 |
181 | // Make sure to always drain the cache
182 | cache.removeAllObjects()
183 | }
184 |
185 | func testCacheBlockNotExecuted() {
186 | let cache = try! Cache(name: "testCacheBlockNotExecuted")
187 | var executed = false
188 |
189 | cache.setObject("AddedString", forKey: "blockNotExecuted")
190 |
191 | cache.setObject(forKey: "blockNotExecuted", cacheBlock: { successBlock, failureBlock in
192 | executed = true
193 | successBlock("SometingElse", .never)
194 | }, completion: { object, isLoadedFromCache, error in
195 | XCTAssertNotNil(object, "Cached object not nil")
196 | XCTAssertEqual("AddedString", object!, "Get cached object")
197 |
198 | XCTAssertNotNil(cache.object(forKey: "blockNotExecuted"), "Get cached object")
199 | XCTAssertEqual("AddedString", cache.object(forKey: "blockNotExecuted")!, "Get cached object")
200 |
201 | XCTAssertFalse(executed, "Block was not executed")
202 | XCTAssertTrue(isLoadedFromCache, "Object was loaded from cached")
203 | XCTAssertNil(error, "Error is nil")
204 | })
205 | }
206 |
207 | func testCacheBlockError() {
208 | let cache = try! Cache(name: "testCacheBlockError")
209 |
210 | cache.setObject(forKey: "blockError", cacheBlock: { successBlock, failureBlock in
211 | let error = NSError(domain: "AwesomeCacheErrorDomain", code: 42, userInfo: nil)
212 | failureBlock(error)
213 | }, completion: { object, isLoadedFromCache, error in
214 | XCTAssertNil(object, "Cached object nil")
215 | XCTAssertNil(cache.object(forKey: "blockError"), "Get cached object")
216 |
217 | XCTAssertFalse(isLoadedFromCache, "Object was loaded from cached")
218 | XCTAssertNotNil(error, "Error is nil")
219 | XCTAssert(error!.domain == "AwesomeCacheErrorDomain", "Error domain")
220 | XCTAssert(error!.code == 42, "Error code")
221 | })
222 | }
223 |
224 | func testDiskPersistance() {
225 | let cache = try! Cache(name: "testDiskPersistance")
226 |
227 | cache.setObject("foobar", forKey: "persistedObject")
228 | let beforeObject = cache.object(forKey: "persistedObject")
229 | XCTAssertNotNil(beforeObject)
230 |
231 | // Remove all objects from internal NSCache
232 | // to force reload from disk
233 | cache.cache.removeAllObjects()
234 |
235 | let afterObject = cache.object(forKey: "persistedObject")
236 | XCTAssertNotNil(afterObject)
237 | }
238 |
239 | }
240 |
--------------------------------------------------------------------------------
/AwesomeCache/Cache.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Represents the expiry of a cached object
4 | public enum CacheExpiry {
5 | case never
6 | case seconds(TimeInterval)
7 | case date(Foundation.Date)
8 | }
9 |
10 | /// A generic cache that persists objects to disk and is backed by a NSCache.
11 | /// Supports an expiry date for every cached object. Expired objects are automatically deleted upon their next access via `objectForKey:`.
12 | /// If you want to delete expired objects, call `removeAllExpiredObjects`.
13 | ///
14 | /// Subclassing notes: This class fully supports subclassing.
15 | /// The easiest way to implement a subclass is to override `objectForKey` and `setObject:forKey:expires:`,
16 | /// e.g. to modify values prior to reading/writing to the cache.
17 | open class Cache {
18 | open let name: String
19 | open let cacheDirectory: URL
20 |
21 | internal let cache = NSCache() // marked internal for testing
22 | fileprivate let fileManager = FileManager()
23 | fileprivate let queue = DispatchQueue(label: "com.aschuch.cache.diskQueue", attributes: DispatchQueue.Attributes.concurrent)
24 |
25 | /// Typealias to define the reusability in declaration of the closures.
26 | public typealias CacheBlockClosure = (T, CacheExpiry) -> Void
27 | public typealias ErrorClosure = (NSError?) -> Void
28 |
29 |
30 | // MARK: Initializers
31 |
32 | /// Designated initializer.
33 | ///
34 | /// - parameter name: Name of this cache
35 | /// - parameter directory: Objects in this cache are persisted to this directory.
36 | /// If no directory is specified, a new directory is created in the system's Caches directory
37 | /// - parameter fileProtection: Needs to be a valid value for `NSFileProtectionKey` (i.e. `NSFileProtectionNone`) and
38 | /// adds the given value as an NSFileManager attribute.
39 | ///
40 | /// - returns: A new cache with the given name and directory
41 | public init(name: String, directory: URL?, fileProtection: String? = nil) throws {
42 | self.name = name
43 | cache.name = name
44 |
45 | if let d = directory {
46 | cacheDirectory = d
47 | } else {
48 | let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
49 | cacheDirectory = url.appendingPathComponent("com.aschuch.cache/\(name)")
50 | }
51 |
52 | // Create directory on disk if needed
53 | try fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true, attributes: nil)
54 |
55 | if let fileProtection = fileProtection {
56 | // Set the correct NSFileProtectionKey
57 | let protection = [FileAttributeKey.protectionKey: fileProtection]
58 | try fileManager.setAttributes(protection, ofItemAtPath: cacheDirectory.path)
59 | }
60 | }
61 |
62 | /// Convenience Initializer
63 | ///
64 | /// - parameter name: Name of this cache
65 | ///
66 | /// - returns A new cache with the given name and the default cache directory
67 | public convenience init(name: String) throws {
68 | try self.init(name: name, directory: nil)
69 | }
70 |
71 |
72 | // MARK: Awesome caching
73 |
74 | /// Returns a cached object immediately or evaluates a cacheBlock.
75 | /// The cacheBlock will not be re-evaluated until the object is expired or manually deleted.
76 | /// If the cache already contains an object, the completion block is called with the cached object immediately.
77 | ///
78 | /// If no object is found or the cached object is already expired, the `cacheBlock` is called.
79 | /// You might perform any tasks (e.g. network calls) within this block. Upon completion of these tasks,
80 | /// make sure to call the `success` or `failure` block that is passed to the `cacheBlock`.
81 | /// The completion block is invoked as soon as the cacheBlock is finished and the object is cached.
82 | ///
83 | /// - parameter key: The key to lookup the cached object
84 | /// - parameter cacheBlock: This block gets called if there is no cached object or the cached object is already expired.
85 | /// The supplied success or failure blocks must be called upon completion.
86 | /// If the error block is called, the object is not cached and the completion block is invoked with this error.
87 | /// - parameter completion: Called as soon as a cached object is available to use. The second parameter is true if the object was already cached.
88 | open func setObject(forKey key: String, cacheBlock: (@escaping CacheBlockClosure, @escaping ErrorClosure) -> Void, completion: @escaping (T?, Bool, NSError?) -> Void) {
89 | if let object = object(forKey: key) {
90 | completion(object, true, nil)
91 | } else {
92 | let successBlock: CacheBlockClosure = { (obj, expires) in
93 | self.setObject(obj, forKey: key, expires: expires)
94 | completion(obj, false, nil)
95 | }
96 |
97 | let failureBlock: ErrorClosure = { (error) in
98 | completion(nil, false, error)
99 | }
100 |
101 | cacheBlock(successBlock, failureBlock)
102 | }
103 | }
104 |
105 |
106 | // MARK: Get object
107 |
108 | /// Looks up and returns an object with the specified name if it exists.
109 | /// If an object is already expired, `nil` will be returned.
110 | ///
111 | /// - parameter key: The name of the object that should be returned
112 | /// - parameter returnExpiredObjectIfPresent: If set to `true`, an expired
113 | /// object may be returned if present. Defaults to `false`.
114 | ///
115 | /// - returns: The cached object for the given name, or nil
116 | open func object(forKey key: String, returnExpiredObjectIfPresent: Bool = false) -> T? {
117 | var object: CacheObject?
118 |
119 | queue.sync {
120 | object = self.read(key)
121 | }
122 |
123 | // Check if object is not already expired and return
124 | if let object = object, !object.isExpired() || returnExpiredObjectIfPresent {
125 | return object.value as? T
126 | }
127 |
128 | return nil
129 | }
130 |
131 | open func allObjects(includeExpired: Bool = false) -> [T] {
132 | var objects = [T]()
133 |
134 | queue.sync {
135 | let keys = self.allKeys()
136 | let all = keys.map(self.read).flatMap { $0 }
137 | let filtered = includeExpired ? all : all.filter { !$0.isExpired() }
138 | objects = filtered.map { $0.value as? T }.flatMap { $0 }
139 | }
140 |
141 | return objects
142 | }
143 |
144 | open func isOnMemory(forKey key: String) -> Bool {
145 | return cache.object(forKey: key as NSString) != nil
146 | }
147 |
148 |
149 | // MARK: Set object
150 |
151 | /// Adds a given object to the cache.
152 | /// The object is automatically marked as expired as soon as its expiry date is reached.
153 | ///
154 | /// - parameter object: The object that should be cached
155 | /// - parameter forKey: A key that represents this object in the cache
156 | /// - parameter expires: The CacheExpiry that indicates when the given object should be expired
157 | open func setObject(_ object: T, forKey key: String, expires: CacheExpiry = .never) {
158 | let expiryDate = expiryDateForCacheExpiry(expires)
159 | let cacheObject = CacheObject(value: object, expiryDate: expiryDate)
160 |
161 | queue.sync(flags: .barrier, execute: {
162 | self.add(cacheObject, key: key)
163 | })
164 | }
165 |
166 | // MARK: Remove objects
167 |
168 | /// Removes an object from the cache.
169 | ///
170 | /// - parameter key: The key of the object that should be removed
171 | open func removeObject(forKey key: String) {
172 | cache.removeObject(forKey: key as NSString)
173 |
174 | queue.sync(flags: .barrier, execute: {
175 | self.removeFromDisk(key)
176 | })
177 | }
178 |
179 | /// Removes all objects from the cache.
180 | open func removeAllObjects() {
181 | cache.removeAllObjects()
182 |
183 | queue.sync(flags: .barrier, execute: {
184 | let keys = self.allKeys()
185 | keys.forEach(self.removeFromDisk)
186 | })
187 | }
188 |
189 | /// Removes all expired objects from the cache.
190 | open func removeExpiredObjects() {
191 | queue.sync(flags: .barrier, execute: {
192 | let keys = self.allKeys()
193 |
194 | for key in keys {
195 | let possibleObject = self.read(key)
196 | if let object = possibleObject , object.isExpired() {
197 | self.cache.removeObject(forKey: key as NSString)
198 | self.removeFromDisk(key)
199 | }
200 | }
201 | })
202 | }
203 |
204 | // MARK: Subscripting
205 |
206 | open subscript(key: String) -> T? {
207 | get {
208 | return object(forKey: key)
209 | }
210 | set(newValue) {
211 | if let value = newValue {
212 | setObject(value, forKey: key)
213 | } else {
214 | removeObject(forKey: key)
215 | }
216 | }
217 | }
218 |
219 | // MARK: Private Helper (not thread safe)
220 |
221 | fileprivate func add(_ object: CacheObject, key: String) {
222 | // Set object in local cache
223 | cache.setObject(object, forKey: key as NSString)
224 |
225 | // Write object to disk
226 | let path = urlForKey(key).path
227 | NSKeyedArchiver.archiveRootObject(object, toFile: path)
228 | }
229 |
230 | fileprivate func read(_ key: String) -> CacheObject? {
231 | // Check if object exists in local cache
232 | if let object = cache.object(forKey: key as NSString) {
233 | return object
234 | }
235 |
236 | // Otherwise, read from disk
237 | let path = urlForKey(key).path
238 | if fileManager.fileExists(atPath: path) {
239 | return _awesomeCache_unarchiveObjectSafely(path) as? CacheObject
240 | }
241 |
242 | return nil
243 | }
244 |
245 | // Deletes an object from disk
246 | fileprivate func removeFromDisk(_ key: String) {
247 | let url = self.urlForKey(key)
248 | _ = try? self.fileManager.removeItem(at: url)
249 | }
250 |
251 |
252 | // MARK: Private Helper
253 |
254 | fileprivate func allKeys() -> [String] {
255 | let urls = try? self.fileManager.contentsOfDirectory(at: self.cacheDirectory, includingPropertiesForKeys: nil, options: [])
256 | return urls?.flatMap { $0.deletingPathExtension().lastPathComponent } ?? []
257 | }
258 |
259 | fileprivate func urlForKey(_ key: String) -> URL {
260 | let k = sanitizedKey(key)
261 | return cacheDirectory
262 | .appendingPathComponent(k)
263 | .appendingPathExtension("cache")
264 | }
265 |
266 | fileprivate func sanitizedKey(_ key: String) -> String {
267 | return key.replacingOccurrences(of: "[^a-zA-Z0-9_]+", with: "-", options: .regularExpression, range: nil)
268 | }
269 |
270 | fileprivate func expiryDateForCacheExpiry(_ expiry: CacheExpiry) -> Date {
271 | switch expiry {
272 | case .never:
273 | return Date.distantFuture
274 | case .seconds(let seconds):
275 | return Date().addingTimeInterval(seconds)
276 | case .date(let date):
277 | return date
278 | }
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/Example.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4D137EB519C1BD3300AC1050 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4D137EB319C1BD3300AC1050 /* LaunchScreen.xib */; };
11 | 4D44D28D1971627700EC5FDB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D44D28C1971627700EC5FDB /* AppDelegate.swift */; };
12 | 4D44D28F1971627700EC5FDB /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D44D28E1971627700EC5FDB /* ViewController.swift */; };
13 | 4D44D2921971627700EC5FDB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4D44D2901971627700EC5FDB /* Main.storyboard */; };
14 | 4D44D2941971627700EC5FDB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4D44D2931971627700EC5FDB /* Images.xcassets */; };
15 | 4D81D4A61C417012009A66A7 /* AwesomeCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC91F9C1A7C54B200C81E10 /* AwesomeCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
16 | 4D81D4A71C417019009A66A7 /* AwesomeCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC91F9C1A7C54B200C81E10 /* AwesomeCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
17 | 4D81D4AE1C417085009A66A7 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC91FB81A7C552900C81E10 /* Cache.swift */; };
18 | 4D81D4AF1C417085009A66A7 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC91FB81A7C552900C81E10 /* Cache.swift */; };
19 | 4D81D4B01C417088009A66A7 /* CacheObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC91FB91A7C552900C81E10 /* CacheObject.swift */; };
20 | 4D81D4B11C417088009A66A7 /* CacheObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC91FB91A7C552900C81E10 /* CacheObject.swift */; };
21 | 4D9EB67A1D8E963300EB0B76 /* NSKeyedUnarchiverWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = BF482C3C1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.m */; };
22 | 4DC91F9D1A7C54B200C81E10 /* AwesomeCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DC91F9C1A7C54B200C81E10 /* AwesomeCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
23 | 4DC91FA31A7C54B200C81E10 /* AwesomeCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DC91F981A7C54B200C81E10 /* AwesomeCache.framework */; };
24 | 4DC91FAC1A7C54B200C81E10 /* AwesomeCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC91FAB1A7C54B200C81E10 /* AwesomeCacheTests.swift */; };
25 | 4DC91FAF1A7C54B200C81E10 /* AwesomeCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DC91F981A7C54B200C81E10 /* AwesomeCache.framework */; };
26 | 4DC91FB01A7C54B200C81E10 /* AwesomeCache.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4DC91F981A7C54B200C81E10 /* AwesomeCache.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
27 | 4DC91FBA1A7C552900C81E10 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC91FB81A7C552900C81E10 /* Cache.swift */; };
28 | 4DC91FBB1A7C552900C81E10 /* CacheObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC91FB91A7C552900C81E10 /* CacheObject.swift */; };
29 | BF482C3D1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = BF482C3B1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
30 | BF482C3E1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = BF482C3B1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
31 | BF482C3F1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = BF482C3C1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.m */; };
32 | BF482C401CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = BF482C3C1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.m */; };
33 | BF482C411CEBE75A002B0C32 /* NSKeyedUnarchiverWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = BF482C3B1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
34 | /* End PBXBuildFile section */
35 |
36 | /* Begin PBXContainerItemProxy section */
37 | 4DC91FA41A7C54B200C81E10 /* PBXContainerItemProxy */ = {
38 | isa = PBXContainerItemProxy;
39 | containerPortal = 4D44D27F1971627700EC5FDB /* Project object */;
40 | proxyType = 1;
41 | remoteGlobalIDString = 4DC91F971A7C54B200C81E10;
42 | remoteInfo = AwesomeCache;
43 | };
44 | 4DC91FA61A7C54B200C81E10 /* PBXContainerItemProxy */ = {
45 | isa = PBXContainerItemProxy;
46 | containerPortal = 4D44D27F1971627700EC5FDB /* Project object */;
47 | proxyType = 1;
48 | remoteGlobalIDString = 4D44D2861971627700EC5FDB;
49 | remoteInfo = Example;
50 | };
51 | 4DC91FAD1A7C54B200C81E10 /* PBXContainerItemProxy */ = {
52 | isa = PBXContainerItemProxy;
53 | containerPortal = 4D44D27F1971627700EC5FDB /* Project object */;
54 | proxyType = 1;
55 | remoteGlobalIDString = 4DC91F971A7C54B200C81E10;
56 | remoteInfo = AwesomeCache;
57 | };
58 | /* End PBXContainerItemProxy section */
59 |
60 | /* Begin PBXCopyFilesBuildPhase section */
61 | 4DC91FB61A7C54B200C81E10 /* Embed Frameworks */ = {
62 | isa = PBXCopyFilesBuildPhase;
63 | buildActionMask = 2147483647;
64 | dstPath = "";
65 | dstSubfolderSpec = 10;
66 | files = (
67 | 4DC91FB01A7C54B200C81E10 /* AwesomeCache.framework in Embed Frameworks */,
68 | );
69 | name = "Embed Frameworks";
70 | runOnlyForDeploymentPostprocessing = 0;
71 | };
72 | /* End PBXCopyFilesBuildPhase section */
73 |
74 | /* Begin PBXFileReference section */
75 | 4D137EB419C1BD3300AC1050 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
76 | 4D44D2871971627700EC5FDB /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
77 | 4D44D28B1971627700EC5FDB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
78 | 4D44D28C1971627700EC5FDB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
79 | 4D44D28E1971627700EC5FDB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
80 | 4D44D2911971627700EC5FDB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
81 | 4D44D2931971627700EC5FDB /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
82 | 4D81D4821C416F3C009A66A7 /* AwesomeCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AwesomeCache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
83 | 4D81D48F1C416F6F009A66A7 /* AwesomeCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AwesomeCache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
84 | 4DC91F981A7C54B200C81E10 /* AwesomeCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AwesomeCache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
85 | 4DC91F9B1A7C54B200C81E10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
86 | 4DC91F9C1A7C54B200C81E10 /* AwesomeCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AwesomeCache.h; sourceTree = ""; };
87 | 4DC91FA21A7C54B200C81E10 /* AwesomeCacheTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AwesomeCacheTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
88 | 4DC91FAA1A7C54B200C81E10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
89 | 4DC91FAB1A7C54B200C81E10 /* AwesomeCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AwesomeCacheTests.swift; sourceTree = ""; };
90 | 4DC91FB81A7C552900C81E10 /* Cache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = ""; };
91 | 4DC91FB91A7C552900C81E10 /* CacheObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheObject.swift; sourceTree = ""; };
92 | BF482C3B1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSKeyedUnarchiverWrapper.h; sourceTree = ""; };
93 | BF482C3C1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSKeyedUnarchiverWrapper.m; sourceTree = ""; };
94 | /* End PBXFileReference section */
95 |
96 | /* Begin PBXFrameworksBuildPhase section */
97 | 4D44D2841971627700EC5FDB /* Frameworks */ = {
98 | isa = PBXFrameworksBuildPhase;
99 | buildActionMask = 2147483647;
100 | files = (
101 | 4DC91FAF1A7C54B200C81E10 /* AwesomeCache.framework in Frameworks */,
102 | );
103 | runOnlyForDeploymentPostprocessing = 0;
104 | };
105 | 4D81D47E1C416F3C009A66A7 /* Frameworks */ = {
106 | isa = PBXFrameworksBuildPhase;
107 | buildActionMask = 2147483647;
108 | files = (
109 | );
110 | runOnlyForDeploymentPostprocessing = 0;
111 | };
112 | 4D81D48B1C416F6F009A66A7 /* Frameworks */ = {
113 | isa = PBXFrameworksBuildPhase;
114 | buildActionMask = 2147483647;
115 | files = (
116 | );
117 | runOnlyForDeploymentPostprocessing = 0;
118 | };
119 | 4DC91F941A7C54B200C81E10 /* Frameworks */ = {
120 | isa = PBXFrameworksBuildPhase;
121 | buildActionMask = 2147483647;
122 | files = (
123 | );
124 | runOnlyForDeploymentPostprocessing = 0;
125 | };
126 | 4DC91F9F1A7C54B200C81E10 /* Frameworks */ = {
127 | isa = PBXFrameworksBuildPhase;
128 | buildActionMask = 2147483647;
129 | files = (
130 | 4DC91FA31A7C54B200C81E10 /* AwesomeCache.framework in Frameworks */,
131 | );
132 | runOnlyForDeploymentPostprocessing = 0;
133 | };
134 | /* End PBXFrameworksBuildPhase section */
135 |
136 | /* Begin PBXGroup section */
137 | 4D44D27E1971627700EC5FDB = {
138 | isa = PBXGroup;
139 | children = (
140 | 4D44D2891971627700EC5FDB /* Example */,
141 | 4DC91F991A7C54B200C81E10 /* AwesomeCache */,
142 | 4DC91FA81A7C54B200C81E10 /* AwesomeCacheTests */,
143 | 4D44D2881971627700EC5FDB /* Products */,
144 | );
145 | sourceTree = "";
146 | };
147 | 4D44D2881971627700EC5FDB /* Products */ = {
148 | isa = PBXGroup;
149 | children = (
150 | 4D44D2871971627700EC5FDB /* Example.app */,
151 | 4DC91F981A7C54B200C81E10 /* AwesomeCache.framework */,
152 | 4DC91FA21A7C54B200C81E10 /* AwesomeCacheTests.xctest */,
153 | 4D81D4821C416F3C009A66A7 /* AwesomeCache.framework */,
154 | 4D81D48F1C416F6F009A66A7 /* AwesomeCache.framework */,
155 | );
156 | name = Products;
157 | sourceTree = "";
158 | };
159 | 4D44D2891971627700EC5FDB /* Example */ = {
160 | isa = PBXGroup;
161 | children = (
162 | 4D44D28C1971627700EC5FDB /* AppDelegate.swift */,
163 | 4D44D28E1971627700EC5FDB /* ViewController.swift */,
164 | 4D44D2901971627700EC5FDB /* Main.storyboard */,
165 | 4D137EB319C1BD3300AC1050 /* LaunchScreen.xib */,
166 | 4D44D2931971627700EC5FDB /* Images.xcassets */,
167 | 4D44D28A1971627700EC5FDB /* Supporting Files */,
168 | );
169 | path = Example;
170 | sourceTree = "";
171 | };
172 | 4D44D28A1971627700EC5FDB /* Supporting Files */ = {
173 | isa = PBXGroup;
174 | children = (
175 | 4D44D28B1971627700EC5FDB /* Info.plist */,
176 | );
177 | name = "Supporting Files";
178 | sourceTree = "";
179 | };
180 | 4DC91F991A7C54B200C81E10 /* AwesomeCache */ = {
181 | isa = PBXGroup;
182 | children = (
183 | 4DC91FB81A7C552900C81E10 /* Cache.swift */,
184 | 4DC91FB91A7C552900C81E10 /* CacheObject.swift */,
185 | 4DC91F9C1A7C54B200C81E10 /* AwesomeCache.h */,
186 | BF482C3B1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.h */,
187 | BF482C3C1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.m */,
188 | 4DC91F9A1A7C54B200C81E10 /* Supporting Files */,
189 | );
190 | path = AwesomeCache;
191 | sourceTree = "";
192 | };
193 | 4DC91F9A1A7C54B200C81E10 /* Supporting Files */ = {
194 | isa = PBXGroup;
195 | children = (
196 | 4DC91F9B1A7C54B200C81E10 /* Info.plist */,
197 | );
198 | name = "Supporting Files";
199 | sourceTree = "";
200 | };
201 | 4DC91FA81A7C54B200C81E10 /* AwesomeCacheTests */ = {
202 | isa = PBXGroup;
203 | children = (
204 | 4DC91FAB1A7C54B200C81E10 /* AwesomeCacheTests.swift */,
205 | 4DC91FA91A7C54B200C81E10 /* Supporting Files */,
206 | );
207 | path = AwesomeCacheTests;
208 | sourceTree = "";
209 | };
210 | 4DC91FA91A7C54B200C81E10 /* Supporting Files */ = {
211 | isa = PBXGroup;
212 | children = (
213 | 4DC91FAA1A7C54B200C81E10 /* Info.plist */,
214 | );
215 | name = "Supporting Files";
216 | sourceTree = "";
217 | };
218 | /* End PBXGroup section */
219 |
220 | /* Begin PBXHeadersBuildPhase section */
221 | 4D81D47F1C416F3C009A66A7 /* Headers */ = {
222 | isa = PBXHeadersBuildPhase;
223 | buildActionMask = 2147483647;
224 | files = (
225 | 4D81D4A71C417019009A66A7 /* AwesomeCache.h in Headers */,
226 | BF482C3E1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.h in Headers */,
227 | );
228 | runOnlyForDeploymentPostprocessing = 0;
229 | };
230 | 4D81D48C1C416F6F009A66A7 /* Headers */ = {
231 | isa = PBXHeadersBuildPhase;
232 | buildActionMask = 2147483647;
233 | files = (
234 | 4D81D4A61C417012009A66A7 /* AwesomeCache.h in Headers */,
235 | BF482C411CEBE75A002B0C32 /* NSKeyedUnarchiverWrapper.h in Headers */,
236 | );
237 | runOnlyForDeploymentPostprocessing = 0;
238 | };
239 | 4DC91F951A7C54B200C81E10 /* Headers */ = {
240 | isa = PBXHeadersBuildPhase;
241 | buildActionMask = 2147483647;
242 | files = (
243 | 4DC91F9D1A7C54B200C81E10 /* AwesomeCache.h in Headers */,
244 | BF482C3D1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.h in Headers */,
245 | );
246 | runOnlyForDeploymentPostprocessing = 0;
247 | };
248 | /* End PBXHeadersBuildPhase section */
249 |
250 | /* Begin PBXNativeTarget section */
251 | 4D44D2861971627700EC5FDB /* Example */ = {
252 | isa = PBXNativeTarget;
253 | buildConfigurationList = 4D44D2A31971627700EC5FDB /* Build configuration list for PBXNativeTarget "Example" */;
254 | buildPhases = (
255 | 4D44D2831971627700EC5FDB /* Sources */,
256 | 4D44D2841971627700EC5FDB /* Frameworks */,
257 | 4D44D2851971627700EC5FDB /* Resources */,
258 | 4DC91FB61A7C54B200C81E10 /* Embed Frameworks */,
259 | );
260 | buildRules = (
261 | );
262 | dependencies = (
263 | 4DC91FAE1A7C54B200C81E10 /* PBXTargetDependency */,
264 | );
265 | name = Example;
266 | productName = Example;
267 | productReference = 4D44D2871971627700EC5FDB /* Example.app */;
268 | productType = "com.apple.product-type.application";
269 | };
270 | 4D81D4811C416F3C009A66A7 /* AwesomeCache-watchOS */ = {
271 | isa = PBXNativeTarget;
272 | buildConfigurationList = 4D81D4871C416F3C009A66A7 /* Build configuration list for PBXNativeTarget "AwesomeCache-watchOS" */;
273 | buildPhases = (
274 | 4D81D47D1C416F3C009A66A7 /* Sources */,
275 | 4D81D47E1C416F3C009A66A7 /* Frameworks */,
276 | 4D81D47F1C416F3C009A66A7 /* Headers */,
277 | 4D81D4801C416F3C009A66A7 /* Resources */,
278 | );
279 | buildRules = (
280 | );
281 | dependencies = (
282 | );
283 | name = "AwesomeCache-watchOS";
284 | productName = "AwesomeCache-watchOS";
285 | productReference = 4D81D4821C416F3C009A66A7 /* AwesomeCache.framework */;
286 | productType = "com.apple.product-type.framework";
287 | };
288 | 4D81D48E1C416F6F009A66A7 /* AwesomeCache-tvOS */ = {
289 | isa = PBXNativeTarget;
290 | buildConfigurationList = 4D81D4A01C416F6F009A66A7 /* Build configuration list for PBXNativeTarget "AwesomeCache-tvOS" */;
291 | buildPhases = (
292 | 4D81D48A1C416F6F009A66A7 /* Sources */,
293 | 4D81D48B1C416F6F009A66A7 /* Frameworks */,
294 | 4D81D48C1C416F6F009A66A7 /* Headers */,
295 | 4D81D48D1C416F6F009A66A7 /* Resources */,
296 | );
297 | buildRules = (
298 | );
299 | dependencies = (
300 | );
301 | name = "AwesomeCache-tvOS";
302 | productName = "AwesomeCache-tvOS";
303 | productReference = 4D81D48F1C416F6F009A66A7 /* AwesomeCache.framework */;
304 | productType = "com.apple.product-type.framework";
305 | };
306 | 4DC91F971A7C54B200C81E10 /* AwesomeCache-iOS */ = {
307 | isa = PBXNativeTarget;
308 | buildConfigurationList = 4DC91FB51A7C54B200C81E10 /* Build configuration list for PBXNativeTarget "AwesomeCache-iOS" */;
309 | buildPhases = (
310 | 4DC91F931A7C54B200C81E10 /* Sources */,
311 | 4DC91F941A7C54B200C81E10 /* Frameworks */,
312 | 4DC91F951A7C54B200C81E10 /* Headers */,
313 | 4DC91F961A7C54B200C81E10 /* Resources */,
314 | );
315 | buildRules = (
316 | );
317 | dependencies = (
318 | );
319 | name = "AwesomeCache-iOS";
320 | productName = AwesomeCache;
321 | productReference = 4DC91F981A7C54B200C81E10 /* AwesomeCache.framework */;
322 | productType = "com.apple.product-type.framework";
323 | };
324 | 4DC91FA11A7C54B200C81E10 /* AwesomeCacheTests */ = {
325 | isa = PBXNativeTarget;
326 | buildConfigurationList = 4DC91FB71A7C54B200C81E10 /* Build configuration list for PBXNativeTarget "AwesomeCacheTests" */;
327 | buildPhases = (
328 | 4DC91F9E1A7C54B200C81E10 /* Sources */,
329 | 4DC91F9F1A7C54B200C81E10 /* Frameworks */,
330 | 4DC91FA01A7C54B200C81E10 /* Resources */,
331 | );
332 | buildRules = (
333 | );
334 | dependencies = (
335 | 4DC91FA51A7C54B200C81E10 /* PBXTargetDependency */,
336 | 4DC91FA71A7C54B200C81E10 /* PBXTargetDependency */,
337 | );
338 | name = AwesomeCacheTests;
339 | productName = AwesomeCacheTests;
340 | productReference = 4DC91FA21A7C54B200C81E10 /* AwesomeCacheTests.xctest */;
341 | productType = "com.apple.product-type.bundle.unit-test";
342 | };
343 | /* End PBXNativeTarget section */
344 |
345 | /* Begin PBXProject section */
346 | 4D44D27F1971627700EC5FDB /* Project object */ = {
347 | isa = PBXProject;
348 | attributes = {
349 | LastSwiftMigration = 0700;
350 | LastSwiftUpdateCheck = 0730;
351 | LastUpgradeCheck = 0800;
352 | ORGANIZATIONNAME = "Alexander Schuch";
353 | TargetAttributes = {
354 | 4D44D2861971627700EC5FDB = {
355 | CreatedOnToolsVersion = 6.0;
356 | LastSwiftMigration = 0800;
357 | };
358 | 4D81D4811C416F3C009A66A7 = {
359 | CreatedOnToolsVersion = 7.2;
360 | };
361 | 4D81D48E1C416F6F009A66A7 = {
362 | CreatedOnToolsVersion = 7.2;
363 | };
364 | 4DC91F971A7C54B200C81E10 = {
365 | CreatedOnToolsVersion = 6.2;
366 | LastSwiftMigration = 0800;
367 | };
368 | 4DC91FA11A7C54B200C81E10 = {
369 | CreatedOnToolsVersion = 6.2;
370 | LastSwiftMigration = 0800;
371 | TestTargetID = 4D44D2861971627700EC5FDB;
372 | };
373 | };
374 | };
375 | buildConfigurationList = 4D44D2821971627700EC5FDB /* Build configuration list for PBXProject "Example" */;
376 | compatibilityVersion = "Xcode 3.2";
377 | developmentRegion = English;
378 | hasScannedForEncodings = 0;
379 | knownRegions = (
380 | en,
381 | Base,
382 | );
383 | mainGroup = 4D44D27E1971627700EC5FDB;
384 | productRefGroup = 4D44D2881971627700EC5FDB /* Products */;
385 | projectDirPath = "";
386 | projectRoot = "";
387 | targets = (
388 | 4D44D2861971627700EC5FDB /* Example */,
389 | 4DC91F971A7C54B200C81E10 /* AwesomeCache-iOS */,
390 | 4DC91FA11A7C54B200C81E10 /* AwesomeCacheTests */,
391 | 4D81D4811C416F3C009A66A7 /* AwesomeCache-watchOS */,
392 | 4D81D48E1C416F6F009A66A7 /* AwesomeCache-tvOS */,
393 | );
394 | };
395 | /* End PBXProject section */
396 |
397 | /* Begin PBXResourcesBuildPhase section */
398 | 4D44D2851971627700EC5FDB /* Resources */ = {
399 | isa = PBXResourcesBuildPhase;
400 | buildActionMask = 2147483647;
401 | files = (
402 | 4D44D2921971627700EC5FDB /* Main.storyboard in Resources */,
403 | 4D137EB519C1BD3300AC1050 /* LaunchScreen.xib in Resources */,
404 | 4D44D2941971627700EC5FDB /* Images.xcassets in Resources */,
405 | );
406 | runOnlyForDeploymentPostprocessing = 0;
407 | };
408 | 4D81D4801C416F3C009A66A7 /* Resources */ = {
409 | isa = PBXResourcesBuildPhase;
410 | buildActionMask = 2147483647;
411 | files = (
412 | );
413 | runOnlyForDeploymentPostprocessing = 0;
414 | };
415 | 4D81D48D1C416F6F009A66A7 /* Resources */ = {
416 | isa = PBXResourcesBuildPhase;
417 | buildActionMask = 2147483647;
418 | files = (
419 | );
420 | runOnlyForDeploymentPostprocessing = 0;
421 | };
422 | 4DC91F961A7C54B200C81E10 /* Resources */ = {
423 | isa = PBXResourcesBuildPhase;
424 | buildActionMask = 2147483647;
425 | files = (
426 | );
427 | runOnlyForDeploymentPostprocessing = 0;
428 | };
429 | 4DC91FA01A7C54B200C81E10 /* Resources */ = {
430 | isa = PBXResourcesBuildPhase;
431 | buildActionMask = 2147483647;
432 | files = (
433 | );
434 | runOnlyForDeploymentPostprocessing = 0;
435 | };
436 | /* End PBXResourcesBuildPhase section */
437 |
438 | /* Begin PBXSourcesBuildPhase section */
439 | 4D44D2831971627700EC5FDB /* Sources */ = {
440 | isa = PBXSourcesBuildPhase;
441 | buildActionMask = 2147483647;
442 | files = (
443 | 4D44D28F1971627700EC5FDB /* ViewController.swift in Sources */,
444 | 4D44D28D1971627700EC5FDB /* AppDelegate.swift in Sources */,
445 | );
446 | runOnlyForDeploymentPostprocessing = 0;
447 | };
448 | 4D81D47D1C416F3C009A66A7 /* Sources */ = {
449 | isa = PBXSourcesBuildPhase;
450 | buildActionMask = 2147483647;
451 | files = (
452 | 4D81D4AE1C417085009A66A7 /* Cache.swift in Sources */,
453 | 4D81D4B01C417088009A66A7 /* CacheObject.swift in Sources */,
454 | BF482C401CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.m in Sources */,
455 | );
456 | runOnlyForDeploymentPostprocessing = 0;
457 | };
458 | 4D81D48A1C416F6F009A66A7 /* Sources */ = {
459 | isa = PBXSourcesBuildPhase;
460 | buildActionMask = 2147483647;
461 | files = (
462 | 4D81D4AF1C417085009A66A7 /* Cache.swift in Sources */,
463 | 4D81D4B11C417088009A66A7 /* CacheObject.swift in Sources */,
464 | 4D9EB67A1D8E963300EB0B76 /* NSKeyedUnarchiverWrapper.m in Sources */,
465 | );
466 | runOnlyForDeploymentPostprocessing = 0;
467 | };
468 | 4DC91F931A7C54B200C81E10 /* Sources */ = {
469 | isa = PBXSourcesBuildPhase;
470 | buildActionMask = 2147483647;
471 | files = (
472 | 4DC91FBA1A7C552900C81E10 /* Cache.swift in Sources */,
473 | 4DC91FBB1A7C552900C81E10 /* CacheObject.swift in Sources */,
474 | BF482C3F1CEBE4A2002B0C32 /* NSKeyedUnarchiverWrapper.m in Sources */,
475 | );
476 | runOnlyForDeploymentPostprocessing = 0;
477 | };
478 | 4DC91F9E1A7C54B200C81E10 /* Sources */ = {
479 | isa = PBXSourcesBuildPhase;
480 | buildActionMask = 2147483647;
481 | files = (
482 | 4DC91FAC1A7C54B200C81E10 /* AwesomeCacheTests.swift in Sources */,
483 | );
484 | runOnlyForDeploymentPostprocessing = 0;
485 | };
486 | /* End PBXSourcesBuildPhase section */
487 |
488 | /* Begin PBXTargetDependency section */
489 | 4DC91FA51A7C54B200C81E10 /* PBXTargetDependency */ = {
490 | isa = PBXTargetDependency;
491 | target = 4DC91F971A7C54B200C81E10 /* AwesomeCache-iOS */;
492 | targetProxy = 4DC91FA41A7C54B200C81E10 /* PBXContainerItemProxy */;
493 | };
494 | 4DC91FA71A7C54B200C81E10 /* PBXTargetDependency */ = {
495 | isa = PBXTargetDependency;
496 | target = 4D44D2861971627700EC5FDB /* Example */;
497 | targetProxy = 4DC91FA61A7C54B200C81E10 /* PBXContainerItemProxy */;
498 | };
499 | 4DC91FAE1A7C54B200C81E10 /* PBXTargetDependency */ = {
500 | isa = PBXTargetDependency;
501 | target = 4DC91F971A7C54B200C81E10 /* AwesomeCache-iOS */;
502 | targetProxy = 4DC91FAD1A7C54B200C81E10 /* PBXContainerItemProxy */;
503 | };
504 | /* End PBXTargetDependency section */
505 |
506 | /* Begin PBXVariantGroup section */
507 | 4D137EB319C1BD3300AC1050 /* LaunchScreen.xib */ = {
508 | isa = PBXVariantGroup;
509 | children = (
510 | 4D137EB419C1BD3300AC1050 /* Base */,
511 | );
512 | name = LaunchScreen.xib;
513 | sourceTree = "";
514 | };
515 | 4D44D2901971627700EC5FDB /* Main.storyboard */ = {
516 | isa = PBXVariantGroup;
517 | children = (
518 | 4D44D2911971627700EC5FDB /* Base */,
519 | );
520 | name = Main.storyboard;
521 | sourceTree = "";
522 | };
523 | /* End PBXVariantGroup section */
524 |
525 | /* Begin XCBuildConfiguration section */
526 | 4D44D2A11971627700EC5FDB /* Debug */ = {
527 | isa = XCBuildConfiguration;
528 | buildSettings = {
529 | ALWAYS_SEARCH_USER_PATHS = NO;
530 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
531 | CLANG_CXX_LIBRARY = "libc++";
532 | CLANG_ENABLE_MODULES = YES;
533 | CLANG_ENABLE_OBJC_ARC = YES;
534 | CLANG_WARN_BOOL_CONVERSION = YES;
535 | CLANG_WARN_CONSTANT_CONVERSION = YES;
536 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
537 | CLANG_WARN_EMPTY_BODY = YES;
538 | CLANG_WARN_ENUM_CONVERSION = YES;
539 | CLANG_WARN_INFINITE_RECURSION = YES;
540 | CLANG_WARN_INT_CONVERSION = YES;
541 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
542 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
543 | CLANG_WARN_UNREACHABLE_CODE = YES;
544 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
545 | CODE_SIGN_IDENTITY = "iPhone Developer";
546 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
547 | COPY_PHASE_STRIP = NO;
548 | ENABLE_STRICT_OBJC_MSGSEND = YES;
549 | ENABLE_TESTABILITY = YES;
550 | GCC_C_LANGUAGE_STANDARD = gnu99;
551 | GCC_DYNAMIC_NO_PIC = NO;
552 | GCC_NO_COMMON_BLOCKS = YES;
553 | GCC_OPTIMIZATION_LEVEL = 0;
554 | GCC_PREPROCESSOR_DEFINITIONS = (
555 | "DEBUG=1",
556 | "$(inherited)",
557 | );
558 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
559 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
560 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
561 | GCC_WARN_UNDECLARED_SELECTOR = YES;
562 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
563 | GCC_WARN_UNUSED_FUNCTION = YES;
564 | GCC_WARN_UNUSED_VARIABLE = YES;
565 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
566 | MTL_ENABLE_DEBUG_INFO = YES;
567 | ONLY_ACTIVE_ARCH = YES;
568 | SDKROOT = iphoneos;
569 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
570 | SWIFT_VERSION = 3.0;
571 | };
572 | name = Debug;
573 | };
574 | 4D44D2A21971627700EC5FDB /* Release */ = {
575 | isa = XCBuildConfiguration;
576 | buildSettings = {
577 | ALWAYS_SEARCH_USER_PATHS = NO;
578 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
579 | CLANG_CXX_LIBRARY = "libc++";
580 | CLANG_ENABLE_MODULES = YES;
581 | CLANG_ENABLE_OBJC_ARC = YES;
582 | CLANG_WARN_BOOL_CONVERSION = YES;
583 | CLANG_WARN_CONSTANT_CONVERSION = YES;
584 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
585 | CLANG_WARN_EMPTY_BODY = YES;
586 | CLANG_WARN_ENUM_CONVERSION = YES;
587 | CLANG_WARN_INFINITE_RECURSION = YES;
588 | CLANG_WARN_INT_CONVERSION = YES;
589 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
590 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
591 | CLANG_WARN_UNREACHABLE_CODE = YES;
592 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
593 | CODE_SIGN_IDENTITY = "iPhone Developer";
594 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
595 | COPY_PHASE_STRIP = YES;
596 | ENABLE_NS_ASSERTIONS = NO;
597 | ENABLE_STRICT_OBJC_MSGSEND = YES;
598 | GCC_C_LANGUAGE_STANDARD = gnu99;
599 | GCC_NO_COMMON_BLOCKS = YES;
600 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
601 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
602 | GCC_WARN_UNDECLARED_SELECTOR = YES;
603 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
604 | GCC_WARN_UNUSED_FUNCTION = YES;
605 | GCC_WARN_UNUSED_VARIABLE = YES;
606 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
607 | MTL_ENABLE_DEBUG_INFO = NO;
608 | SDKROOT = iphoneos;
609 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
610 | SWIFT_VERSION = 3.0;
611 | VALIDATE_PRODUCT = YES;
612 | };
613 | name = Release;
614 | };
615 | 4D44D2A41971627700EC5FDB /* Debug */ = {
616 | isa = XCBuildConfiguration;
617 | buildSettings = {
618 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
619 | INFOPLIST_FILE = Example/Info.plist;
620 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
621 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
622 | PRODUCT_BUNDLE_IDENTIFIER = "com.aschuch.${PRODUCT_NAME:rfc1034identifier}";
623 | PRODUCT_NAME = "$(TARGET_NAME)";
624 | SWIFT_VERSION = 3.0;
625 | };
626 | name = Debug;
627 | };
628 | 4D44D2A51971627700EC5FDB /* Release */ = {
629 | isa = XCBuildConfiguration;
630 | buildSettings = {
631 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
632 | INFOPLIST_FILE = Example/Info.plist;
633 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
634 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
635 | PRODUCT_BUNDLE_IDENTIFIER = "com.aschuch.${PRODUCT_NAME:rfc1034identifier}";
636 | PRODUCT_NAME = "$(TARGET_NAME)";
637 | SWIFT_VERSION = 3.0;
638 | };
639 | name = Release;
640 | };
641 | 4D81D4881C416F3C009A66A7 /* Debug */ = {
642 | isa = XCBuildConfiguration;
643 | buildSettings = {
644 | APPLICATION_EXTENSION_API_ONLY = YES;
645 | CLANG_ENABLE_MODULES = YES;
646 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
647 | CURRENT_PROJECT_VERSION = 1;
648 | DEBUG_INFORMATION_FORMAT = dwarf;
649 | DEFINES_MODULE = YES;
650 | DYLIB_COMPATIBILITY_VERSION = 1;
651 | DYLIB_CURRENT_VERSION = 1;
652 | DYLIB_INSTALL_NAME_BASE = "@rpath";
653 | GCC_NO_COMMON_BLOCKS = YES;
654 | INFOPLIST_FILE = AwesomeCache/Info.plist;
655 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
656 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
657 | PRODUCT_BUNDLE_IDENTIFIER = "com.aschuch.AwesomeCache-watchOS";
658 | PRODUCT_NAME = AwesomeCache;
659 | SDKROOT = watchos;
660 | SKIP_INSTALL = YES;
661 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
662 | TARGETED_DEVICE_FAMILY = 4;
663 | VERSIONING_SYSTEM = "apple-generic";
664 | VERSION_INFO_PREFIX = "";
665 | WATCHOS_DEPLOYMENT_TARGET = 2.0;
666 | };
667 | name = Debug;
668 | };
669 | 4D81D4891C416F3C009A66A7 /* Release */ = {
670 | isa = XCBuildConfiguration;
671 | buildSettings = {
672 | APPLICATION_EXTENSION_API_ONLY = YES;
673 | CLANG_ENABLE_MODULES = YES;
674 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
675 | COPY_PHASE_STRIP = NO;
676 | CURRENT_PROJECT_VERSION = 1;
677 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
678 | DEFINES_MODULE = YES;
679 | DYLIB_COMPATIBILITY_VERSION = 1;
680 | DYLIB_CURRENT_VERSION = 1;
681 | DYLIB_INSTALL_NAME_BASE = "@rpath";
682 | GCC_NO_COMMON_BLOCKS = YES;
683 | INFOPLIST_FILE = AwesomeCache/Info.plist;
684 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
685 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
686 | PRODUCT_BUNDLE_IDENTIFIER = "com.aschuch.AwesomeCache-watchOS";
687 | PRODUCT_NAME = AwesomeCache;
688 | SDKROOT = watchos;
689 | SKIP_INSTALL = YES;
690 | TARGETED_DEVICE_FAMILY = 4;
691 | VERSIONING_SYSTEM = "apple-generic";
692 | VERSION_INFO_PREFIX = "";
693 | WATCHOS_DEPLOYMENT_TARGET = 2.0;
694 | };
695 | name = Release;
696 | };
697 | 4D81D4A11C416F6F009A66A7 /* Debug */ = {
698 | isa = XCBuildConfiguration;
699 | buildSettings = {
700 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
701 | CURRENT_PROJECT_VERSION = 1;
702 | DEBUG_INFORMATION_FORMAT = dwarf;
703 | DEFINES_MODULE = YES;
704 | DYLIB_COMPATIBILITY_VERSION = 1;
705 | DYLIB_CURRENT_VERSION = 1;
706 | DYLIB_INSTALL_NAME_BASE = "@rpath";
707 | GCC_NO_COMMON_BLOCKS = YES;
708 | INFOPLIST_FILE = AwesomeCache/Info.plist;
709 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
710 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
711 | PRODUCT_BUNDLE_IDENTIFIER = "com.aschuch.AwesomeCache-tvOS";
712 | PRODUCT_NAME = AwesomeCache;
713 | SDKROOT = appletvos;
714 | SKIP_INSTALL = YES;
715 | TARGETED_DEVICE_FAMILY = 3;
716 | TVOS_DEPLOYMENT_TARGET = 9.1;
717 | VERSIONING_SYSTEM = "apple-generic";
718 | VERSION_INFO_PREFIX = "";
719 | };
720 | name = Debug;
721 | };
722 | 4D81D4A21C416F6F009A66A7 /* Release */ = {
723 | isa = XCBuildConfiguration;
724 | buildSettings = {
725 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
726 | COPY_PHASE_STRIP = NO;
727 | CURRENT_PROJECT_VERSION = 1;
728 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
729 | DEFINES_MODULE = YES;
730 | DYLIB_COMPATIBILITY_VERSION = 1;
731 | DYLIB_CURRENT_VERSION = 1;
732 | DYLIB_INSTALL_NAME_BASE = "@rpath";
733 | GCC_NO_COMMON_BLOCKS = YES;
734 | INFOPLIST_FILE = AwesomeCache/Info.plist;
735 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
736 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
737 | PRODUCT_BUNDLE_IDENTIFIER = "com.aschuch.AwesomeCache-tvOS";
738 | PRODUCT_NAME = AwesomeCache;
739 | SDKROOT = appletvos;
740 | SKIP_INSTALL = YES;
741 | TARGETED_DEVICE_FAMILY = 3;
742 | TVOS_DEPLOYMENT_TARGET = 9.1;
743 | VERSIONING_SYSTEM = "apple-generic";
744 | VERSION_INFO_PREFIX = "";
745 | };
746 | name = Release;
747 | };
748 | 4DC91FB11A7C54B200C81E10 /* Debug */ = {
749 | isa = XCBuildConfiguration;
750 | buildSettings = {
751 | CLANG_ENABLE_MODULES = YES;
752 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
753 | CURRENT_PROJECT_VERSION = 1;
754 | DEFINES_MODULE = YES;
755 | DYLIB_COMPATIBILITY_VERSION = 1;
756 | DYLIB_CURRENT_VERSION = 1;
757 | DYLIB_INSTALL_NAME_BASE = "@rpath";
758 | GCC_PREPROCESSOR_DEFINITIONS = (
759 | "DEBUG=1",
760 | "$(inherited)",
761 | );
762 | INFOPLIST_FILE = AwesomeCache/Info.plist;
763 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
764 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
765 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
766 | PRODUCT_BUNDLE_IDENTIFIER = "com.aschuch.$(PRODUCT_NAME:rfc1034identifier)";
767 | PRODUCT_NAME = AwesomeCache;
768 | SKIP_INSTALL = YES;
769 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
770 | SWIFT_VERSION = 3.0;
771 | TARGETED_DEVICE_FAMILY = "1,2";
772 | VERSIONING_SYSTEM = "apple-generic";
773 | VERSION_INFO_PREFIX = "";
774 | };
775 | name = Debug;
776 | };
777 | 4DC91FB21A7C54B200C81E10 /* Release */ = {
778 | isa = XCBuildConfiguration;
779 | buildSettings = {
780 | CLANG_ENABLE_MODULES = YES;
781 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
782 | COPY_PHASE_STRIP = NO;
783 | CURRENT_PROJECT_VERSION = 1;
784 | DEFINES_MODULE = YES;
785 | DYLIB_COMPATIBILITY_VERSION = 1;
786 | DYLIB_CURRENT_VERSION = 1;
787 | DYLIB_INSTALL_NAME_BASE = "@rpath";
788 | INFOPLIST_FILE = AwesomeCache/Info.plist;
789 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
790 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
791 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
792 | PRODUCT_BUNDLE_IDENTIFIER = "com.aschuch.$(PRODUCT_NAME:rfc1034identifier)";
793 | PRODUCT_NAME = AwesomeCache;
794 | SKIP_INSTALL = YES;
795 | SWIFT_VERSION = 3.0;
796 | TARGETED_DEVICE_FAMILY = "1,2";
797 | VERSIONING_SYSTEM = "apple-generic";
798 | VERSION_INFO_PREFIX = "";
799 | };
800 | name = Release;
801 | };
802 | 4DC91FB31A7C54B200C81E10 /* Debug */ = {
803 | isa = XCBuildConfiguration;
804 | buildSettings = {
805 | FRAMEWORK_SEARCH_PATHS = "$(inherited)";
806 | GCC_PREPROCESSOR_DEFINITIONS = (
807 | "DEBUG=1",
808 | "$(inherited)",
809 | );
810 | INFOPLIST_FILE = AwesomeCacheTests/Info.plist;
811 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
812 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
813 | PRODUCT_BUNDLE_IDENTIFIER = "com.aschuch.$(PRODUCT_NAME:rfc1034identifier)";
814 | PRODUCT_NAME = "$(TARGET_NAME)";
815 | SWIFT_VERSION = 3.0;
816 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example";
817 | };
818 | name = Debug;
819 | };
820 | 4DC91FB41A7C54B200C81E10 /* Release */ = {
821 | isa = XCBuildConfiguration;
822 | buildSettings = {
823 | COPY_PHASE_STRIP = NO;
824 | FRAMEWORK_SEARCH_PATHS = "$(inherited)";
825 | INFOPLIST_FILE = AwesomeCacheTests/Info.plist;
826 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
827 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
828 | PRODUCT_BUNDLE_IDENTIFIER = "com.aschuch.$(PRODUCT_NAME:rfc1034identifier)";
829 | PRODUCT_NAME = "$(TARGET_NAME)";
830 | SWIFT_VERSION = 3.0;
831 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example";
832 | };
833 | name = Release;
834 | };
835 | /* End XCBuildConfiguration section */
836 |
837 | /* Begin XCConfigurationList section */
838 | 4D44D2821971627700EC5FDB /* Build configuration list for PBXProject "Example" */ = {
839 | isa = XCConfigurationList;
840 | buildConfigurations = (
841 | 4D44D2A11971627700EC5FDB /* Debug */,
842 | 4D44D2A21971627700EC5FDB /* Release */,
843 | );
844 | defaultConfigurationIsVisible = 0;
845 | defaultConfigurationName = Release;
846 | };
847 | 4D44D2A31971627700EC5FDB /* Build configuration list for PBXNativeTarget "Example" */ = {
848 | isa = XCConfigurationList;
849 | buildConfigurations = (
850 | 4D44D2A41971627700EC5FDB /* Debug */,
851 | 4D44D2A51971627700EC5FDB /* Release */,
852 | );
853 | defaultConfigurationIsVisible = 0;
854 | defaultConfigurationName = Release;
855 | };
856 | 4D81D4871C416F3C009A66A7 /* Build configuration list for PBXNativeTarget "AwesomeCache-watchOS" */ = {
857 | isa = XCConfigurationList;
858 | buildConfigurations = (
859 | 4D81D4881C416F3C009A66A7 /* Debug */,
860 | 4D81D4891C416F3C009A66A7 /* Release */,
861 | );
862 | defaultConfigurationIsVisible = 0;
863 | defaultConfigurationName = Release;
864 | };
865 | 4D81D4A01C416F6F009A66A7 /* Build configuration list for PBXNativeTarget "AwesomeCache-tvOS" */ = {
866 | isa = XCConfigurationList;
867 | buildConfigurations = (
868 | 4D81D4A11C416F6F009A66A7 /* Debug */,
869 | 4D81D4A21C416F6F009A66A7 /* Release */,
870 | );
871 | defaultConfigurationIsVisible = 0;
872 | defaultConfigurationName = Release;
873 | };
874 | 4DC91FB51A7C54B200C81E10 /* Build configuration list for PBXNativeTarget "AwesomeCache-iOS" */ = {
875 | isa = XCConfigurationList;
876 | buildConfigurations = (
877 | 4DC91FB11A7C54B200C81E10 /* Debug */,
878 | 4DC91FB21A7C54B200C81E10 /* Release */,
879 | );
880 | defaultConfigurationIsVisible = 0;
881 | defaultConfigurationName = Release;
882 | };
883 | 4DC91FB71A7C54B200C81E10 /* Build configuration list for PBXNativeTarget "AwesomeCacheTests" */ = {
884 | isa = XCConfigurationList;
885 | buildConfigurations = (
886 | 4DC91FB31A7C54B200C81E10 /* Debug */,
887 | 4DC91FB41A7C54B200C81E10 /* Release */,
888 | );
889 | defaultConfigurationIsVisible = 0;
890 | defaultConfigurationName = Release;
891 | };
892 | /* End XCConfigurationList section */
893 | };
894 | rootObject = 4D44D27F1971627700EC5FDB /* Project object */;
895 | }
896 |
--------------------------------------------------------------------------------