├── YMCache
├── .gitkeep
├── YMCache.h
├── YMLog.h
├── Info.plist
├── YMCachePersistenceController.h
├── YMMemoryCache.h
├── YMCachePersistenceController.m
└── YMMemoryCache.m
├── Examples
└── Mantle
│ ├── .gitignore
│ ├── Podfile
│ ├── YMCacheMantleExample.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── project.pbxproj
│ ├── YMCacheMantleExample
│ ├── YMMantleSerializer.h
│ ├── TableViewController.h
│ ├── AppDelegate.h
│ ├── main.m
│ ├── stock.json
│ ├── Stock.h
│ ├── Stock.m
│ ├── Images.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── YMCachePersistenceController+MantleSupport.h
│ ├── YMCachePersistenceController+MantleSupport.m
│ ├── Info.plist
│ ├── YMMantleSerializer.m
│ ├── AppDelegate.m
│ ├── Base.lproj
│ │ ├── LaunchScreen.xib
│ │ └── Main.storyboard
│ └── TableViewController.m
│ ├── YMCacheMantleExample.xcworkspace
│ └── contents.xcworkspacedata
│ └── Podfile.lock
├── YMCache.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ ├── YMCache w
│ │ └── Mantle Support.xcscheme
│ │ ├── YMCache-Mac.xcscheme
│ │ └── YMCache-iOS.xcscheme
└── project.pbxproj
├── YMCacheTests
├── Tests-Prefix.pch
├── NSRunLoop+AsyncTestAdditions.h
├── Info.plist
├── NSRunLoop+AsyncTestAdditions.m
├── YMCachePersistenceControllerSpec.m
└── YMMemoryCacheSpec.m
├── YMCache.podspec
├── .gitignore
├── Package.swift
├── .github
└── workflows
│ └── CI.yml
├── LICENSE
├── CONTRIBUTING.md
├── CHANGELOG.md
└── README.md
/YMCache/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Examples/Mantle/.gitignore:
--------------------------------------------------------------------------------
1 | Pods/
2 |
--------------------------------------------------------------------------------
/Examples/Mantle/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '8.0'
2 | use_frameworks!
3 |
4 | target 'YMCacheMantleExample' do
5 | pod "Mantle", "~> 2.0"
6 | pod "YMCache", :path => "../../"
7 | end
8 |
9 |
--------------------------------------------------------------------------------
/YMCache.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/YMCacheTests/Tests-Prefix.pch:
--------------------------------------------------------------------------------
1 | // The contents of this file are implicitly included at the beginning of every test case source file.
2 |
3 | #ifdef __OBJC__
4 |
5 | #import
6 | #import
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/YMMantleSerializer.h:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | @import YMCache;
6 |
7 | @interface YMMantleSerializer : NSObject
8 |
9 | @end
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/TableViewController.h:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | @import UIKit;
6 |
7 | @interface TableViewController : UITableViewController
8 |
9 | @end
10 |
11 |
--------------------------------------------------------------------------------
/YMCache.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/AppDelegate.h:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import
6 |
7 | @interface AppDelegate : UIResponder
8 |
9 | @property (strong, nonatomic) UIWindow *window;
10 |
11 | @end
12 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/main.m:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import
6 | #import "AppDelegate.h"
7 |
8 | int main(int argc, char * argv[]) {
9 | @autoreleasepool {
10 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/YMCache/YMCache.h:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/1/15.
2 | // Copyright 2015 Yahoo.
3 |
4 | #import
5 |
6 | //! Project version number for YMCache.
7 | FOUNDATION_EXPORT double YMCacheVersionNumber;
8 |
9 | //! Project version string for YMCache.
10 | FOUNDATION_EXPORT const unsigned char YMCacheVersionString[];
11 |
12 | #import "YMMemoryCache.h"
13 | #import "YMCachePersistenceController.h"
14 | #import "YMLog.h"
15 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/stock.json:
--------------------------------------------------------------------------------
1 | {
2 | "YHOO": {
3 | "symbol": "YHOO",
4 | "name": "Yahoo",
5 | "last": 35.00
6 | },
7 | "AAPL": {
8 | "symbol": "AAPL",
9 | "name": "Apple",
10 | "last": 40.00
11 | },
12 | "GOOG": {
13 | "symbol": "GOOG",
14 | "name": "Google",
15 | "last": 50.00
16 | },
17 | "MSFT": {
18 | "symbol": "MSFT",
19 | "name": "Microsoft",
20 | "last": 30.00
21 | },
22 | }
--------------------------------------------------------------------------------
/Examples/Mantle/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Mantle (2.0.3):
3 | - Mantle/extobjc (= 2.0.3)
4 | - Mantle/extobjc (2.0.3)
5 | - YMCache (2.0.1)
6 |
7 | DEPENDENCIES:
8 | - Mantle (~> 2.0)
9 | - YMCache (from `../../`)
10 |
11 | EXTERNAL SOURCES:
12 | YMCache:
13 | :path: ../../
14 |
15 | SPEC CHECKSUMS:
16 | Mantle: 8d6b14979a082a9ed4994463eb7a4ff576c94632
17 | YMCache: fbaa0a6b4b894b3c0b6ce30a899f6f413617e91d
18 |
19 | PODFILE CHECKSUM: b5436bf716c3b9fafcba05b85523d009ff960ff5
20 |
21 | COCOAPODS: 1.2.1
22 |
--------------------------------------------------------------------------------
/YMCacheTests/NSRunLoop+AsyncTestAdditions.h:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 |
6 | #import
7 |
8 | @interface NSRunLoop (AsyncTestAdditions)
9 |
10 | - (void)runContinuouslyForInterval:(NSTimeInterval)timeout;
11 |
12 | - (void)runUntilNonNil:(id *)objPtr timeout:(NSTimeInterval)timeout;
13 |
14 | - (void)runUntilTrue:(BOOL(^)())block timeout:(NSTimeInterval)timeout;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/Stock.h:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import
6 |
7 | @interface Stock : MTLModel
8 |
9 | @property (nonatomic, readonly) NSString *symbol;
10 | @property (nonatomic, readonly) NSString *name;
11 | @property (nonatomic, readonly) NSNumber *last;
12 |
13 | - (instancetype)initWithSymbol:(NSString *)symbol name:(NSString *)name last:(NSNumber *)last;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/Stock.m:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import "Stock.h"
6 |
7 | @implementation Stock
8 |
9 | + (NSDictionary *)JSONKeyPathsByPropertyKey {
10 | return [NSDictionary mtl_identityPropertyMapWithModel:self];
11 | }
12 |
13 | - (instancetype)initWithSymbol:(NSString *)symbol name:(NSString *)name last:(NSNumber *)last {
14 | self = [super init];
15 | if (self) {
16 | _symbol = symbol;
17 | _name = name;
18 | _last = last;
19 | }
20 | return self;
21 | }
22 |
23 | @end
24 |
--------------------------------------------------------------------------------
/YMCache.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'YMCache'
3 | s.version = '2.2.1'
4 | s.summary = 'Fast & simple small object cache. GCD-based and thread-safe.'
5 | s.homepage = 'https://github.com/yahoo/YMCache'
6 | s.license = 'MIT'
7 | s.author = { 'adamkaplan' => 'adamkaplan@yahoo-inc.com' }
8 | s.source = { :git => 'https://github.com/yahoo/YMCache.git', :tag => s.version.to_s }
9 |
10 | s.ios.deployment_target = '9.0'
11 | s.osx.deployment_target = '10.10'
12 | s.tvos.deployment_target = '9.0'
13 | s.watchos.deployment_target = '3.0'
14 |
15 | s.source_files = 'YMCache/*.[h,m]'
16 | end
17 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/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" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
22 | # Bundler
23 | .bundle
24 |
25 | Carthage
26 |
27 | # Slather
28 | html/
29 |
30 | # We recommend against adding the Pods directory to your .gitignore. However
31 | # you should judge for yourself, the pros and cons are mentioned at:
32 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
33 | #
34 | # Note: if you ignore the Pods directory, make sure to uncomment
35 | # `pod install` in .travis.yml
36 | #
37 | # Pods/
38 |
39 | # Swift Package Manager
40 | .build/
41 | .swiftpm/
42 | Package.resolved
43 |
--------------------------------------------------------------------------------
/YMCache/YMLog.h:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/1/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | //compile out any logging for production builds
6 | #ifndef DEBUG
7 |
8 | // no-op both YFLog in release mode
9 | #define YMLog_(level, fmt, ...)
10 |
11 | #else
12 |
13 | // define a logging macro that should be used instead of NSLog
14 | #define YMLOG_ENABLED // use when logging requires non-trivial computation
15 |
16 | #define YMLOG_PREFIX(level) "[" level "]"
17 |
18 | #define YMLog_(level, fmt, ...) NSLog((@YMLOG_PREFIX(level) " %s " fmt), __PRETTY_FUNCTION__, ##__VA_ARGS__)
19 |
20 | #endif
21 |
22 |
23 | #define YMLog(fmt, ...) YMLog_("INFO", fmt, ##__VA_ARGS__)
24 | #define YMWarn(fmt, ...) YMLog_("WARN", fmt, ##__VA_ARGS__)
25 | #define YMError(fmt, ...) YMLog_("ERROR",fmt, ##__VA_ARGS__)
26 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/YMCachePersistenceController+MantleSupport.h:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | @import YMCache;
6 |
7 | @class YMMantleSerializer;
8 | @class YMMemoryCache;
9 |
10 | @interface YMCachePersistenceController (MantleSupport)
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | + (YMMantleSerializer *)mantleSerializer;
15 |
16 | - (nullable instancetype)initWithCache:(YMMemoryCache *)cache
17 | mantleModelClass:(Class)modelClass
18 | fileURL:(NSURL *)cacheFileURL;
19 |
20 | - (nullable instancetype)initWithCache:(YMMemoryCache *)cache
21 | mantleModelClass:(Class)modelClass
22 | name:(NSString *)cacheName;
23 |
24 | NS_ASSUME_NONNULL_END
25 |
26 | @end
27 |
--------------------------------------------------------------------------------
/YMCacheTests/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 |
--------------------------------------------------------------------------------
/YMCache/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 | NSHumanReadableCopyright
24 | Copyright © 2015 Yahoo, Inc. All rights reserved.
25 | NSPrincipalClass
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "YMCache",
7 | platforms: [
8 | .iOS(.v9),
9 | .macOS(.v10_10),
10 | .tvOS(.v9),
11 | .watchOS(.v3)
12 | ],
13 | products: [
14 | .library(
15 | name: "YMCache",
16 | targets: ["YMCache"]),
17 | ],
18 | dependencies: [],
19 | targets: [
20 | .target(
21 | name: "YMCache",
22 | path: "YMCache",
23 | exclude: ["Info.plist"],
24 | publicHeadersPath: "."),
25 | .testTarget(
26 | name: "YMCacheTests",
27 | dependencies: ["YMCache"],
28 | path: "YMCacheTests",
29 | exclude: [
30 | "Info.plist",
31 | "Tests-Prefix.pch",
32 | "YMCachePersistenceControllerSpec.m", // TODO: Un-specta-ify
33 | "YMMemoryCacheSpec.m" // TODO: Un-specta-ify
34 | ]),
35 | ]
36 | )
37 |
--------------------------------------------------------------------------------
/.github/workflows/CI.yml:
--------------------------------------------------------------------------------
1 | name: "YMCache CI"
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - '*'
10 |
11 | jobs:
12 | Pods:
13 | name: Cocoapods Lint (Latest Stable Xcode)
14 | runs-on: macOS-11
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v2
18 |
19 | - name: Setup Xcode version
20 | uses: maxim-lobanov/setup-xcode@v1.4.0
21 | with:
22 | xcode-version: latest-stable
23 |
24 | - name: Run pod lib lint
25 | run: pod lib lint --fail-fast
26 |
27 | SwiftPM:
28 | name: SwiftPM (Latest Stable Xcode)
29 | runs-on: macOS-11
30 | steps:
31 | - name: Checkout
32 | uses: actions/checkout@v2
33 |
34 | - name: Setup Xcode version
35 | uses: maxim-lobanov/setup-xcode@v1.4.0
36 | with:
37 | xcode-version: latest-stable
38 |
39 | - name: Build
40 | run: swift build
41 |
42 | - name: Run tests
43 | run: swift test
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Yahoo, Inc. All rights reserved.
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/YMCachePersistenceController+MantleSupport.m:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import "YMCachePersistenceController+MantleSupport.h"
6 | #import "YMMantleSerializer.h"
7 |
8 | @implementation YMCachePersistenceController (MantleSupport)
9 |
10 | + (nonnull YMMantleSerializer *)mantleSerializer {
11 | static YMMantleSerializer *mantleSerializer;
12 | static dispatch_once_t onceToken;
13 | dispatch_once(&onceToken, ^{
14 | mantleSerializer = [YMMantleSerializer new];
15 | });
16 | return mantleSerializer;
17 | }
18 |
19 | - (nullable instancetype)initWithCache:(YMMemoryCache *)cache
20 | mantleModelClass:(Class)modelClass
21 | fileURL:(NSURL *)cacheFileURL {
22 | return [self initWithCache:cache modelClass:modelClass delegate:[[self class] mantleSerializer] fileURL:cacheFileURL];
23 | }
24 |
25 | - (nullable instancetype)initWithCache:(YMMemoryCache *)cache
26 | mantleModelClass:(Class)modelClass
27 | name:(NSString *)cacheName {
28 | return [self initWithCache:cache modelClass:modelClass delegate:[[self class] mantleSerializer] name:cacheName];
29 | }
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | com.yahoo.$(PRODUCT_NAME:rfc1034identifier)
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 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/YMCacheTests/NSRunLoop+AsyncTestAdditions.m:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import "NSRunLoop+AsyncTestAdditions.h"
6 |
7 | @implementation NSRunLoop (AsyncTestAdditions)
8 |
9 | - (void)runContinuouslyForInterval:(NSTimeInterval)timeout {
10 | id neverTrue = nil;
11 |
12 | [self runUntilNonNil:&neverTrue timeout:timeout];
13 | }
14 |
15 | - (void)runUntilNonNil:(id __autoreleasing *)objPtr timeout:(NSTimeInterval)timeout {
16 | NSParameterAssert(objPtr);
17 | if (!objPtr) {
18 | return;
19 | }
20 |
21 | NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:timeout];
22 |
23 | NSTimeInterval stopInterval = stopDate.timeIntervalSinceReferenceDate;
24 |
25 | while (!*objPtr && stopInterval >= [NSDate date].timeIntervalSinceReferenceDate) {
26 | [self runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.15]];
27 | }
28 | }
29 |
30 | - (void)runUntilTrue:(BOOL(^)())block timeout:(NSTimeInterval)timeout {
31 | NSParameterAssert(block);
32 | if (!block) {
33 | return;
34 | }
35 |
36 | NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:timeout];
37 |
38 | NSTimeInterval stopInterval = stopDate.timeIntervalSinceReferenceDate;
39 |
40 | while (!block() && stopInterval >= [NSDate date].timeIntervalSinceReferenceDate) {
41 | [self runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.15]];
42 | }
43 | }
44 |
45 | @end
46 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guidelines
2 |
3 | ## General Guidelines
4 |
5 | - **Min iOS SDK**: 7.0
6 | - **Language**: Swift-compatible Objective-C.
7 | - **Tests**: Yes, please
8 |
9 | #### Architecture guidelines
10 |
11 | - Avoid singletons that don't encapsulate a finite resource
12 | - Never expose mutable state
13 | - Public API designed to be called safely from any thread
14 | - Keep classes/methods sharply focused
15 | - Stay generic
16 |
17 | ## Style Guide
18 |
19 | #### Base style:
20 |
21 | Please add new code to this project based on the following style guidelines:
22 |
23 | - [Apple's Coding Guidelines for Cocoa](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html)
24 | - [NYTimes Objective C Style Guidelines](https://github.com/NYTimes/objective-c-style-guide)
25 |
26 | Among other things, these guidelines call for:
27 |
28 | - Open braces on the same line; close braces on their own line
29 | - Always using braces for `if` statements, even with single-liners
30 | - No spaces in method signatures except after the scope (-/+) and between parameter segments
31 | - Use dot-notation, not `setXXX`, for properties (e.g. `self.enabled = YES`)
32 | - Asterisk should touch variable name, not type (e.g. `NSString *myString`)
33 | - Prefer `static const` (or `static Type *const`) over `#define` for compile-time constants
34 | - Prefer private properties to ‘naked’ instance variables wherever possible
35 | - Prefer accessor methods over direct struct access (e.g. CGGeometry methods like `CGRectGetMinX()`)
36 |
37 | #### Additions:
38 |
39 | - Prefix all class names with `YM`
40 | - Prefix all constants with `kYM`
41 | - Group related methods with `#pragma mark`
42 | - Keep as much of the API private as is practically possible
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/YMMantleSerializer.m:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import "YMMantleSerializer.h"
6 |
7 | #import
8 | #import
9 | #import
10 |
11 | // Mantle 2 – unable to resolve 2.0-only version of the method.
12 | // If you do not need Mantle 1.0 support, call -JSONDictionaryFromModel:error: directly.
13 | typedef NSDictionary *(*mtl2_msgSend_t)(__strong id, SEL, __strong MTLModel *, NSError *__autoreleasing*);
14 | mtl2_msgSend_t dictFromModel = (mtl2_msgSend_t)objc_msgSend;
15 |
16 | static NSString *const kYMMantleTypeError = @"Mantle model object must be of type MTLModel";
17 |
18 | @implementation YMMantleSerializer
19 |
20 | - (nullable id)persistenceController:(YMCachePersistenceController *)controller
21 | modelFromJSONDictionary:(NSDictionary *)value
22 | error:(NSError *__nullable*__nullable)error {
23 | NSParameterAssert(controller.modelClass);
24 | NSParameterAssert(value);
25 |
26 | return [MTLJSONAdapter modelOfClass:controller.modelClass fromJSONDictionary:value error:error];
27 | }
28 |
29 | - (nullable NSDictionary *)persistenceController:(YMCachePersistenceController *)controller
30 | JSONDictionaryFromModel:(id)value
31 | error:(NSError *__nullable*__nullable)error {
32 | NSParameterAssert(controller);
33 | NSParameterAssert(value);
34 |
35 | if ([value isKindOfClass:[MTLModel class]] && [value conformsToProtocol:@protocol(MTLJSONSerializing)]) {
36 | static SEL mtl2sel = NULL;
37 | if (!mtl2sel) {
38 | mtl2sel = NSSelectorFromString(@"JSONDictionaryFromModel:error:");
39 | }
40 |
41 | // Mantle 2
42 | if ([MTLJSONAdapter respondsToSelector:mtl2sel]) {
43 | NSDictionary *jsonDict = dictFromModel([MTLJSONAdapter class], mtl2sel, value, error);
44 | return jsonDict;
45 | }
46 |
47 | // Mantle 1
48 | #pragma clang diagnostic push
49 | #pragma clang diagnostic ignored "-Wdeprecated-declarations"
50 | return [MTLJSONAdapter JSONDictionaryFromModel:value];
51 | #pragma clang diagnostic pop
52 | }
53 |
54 | if (error) {
55 | *error = [NSError errorWithDomain:@"YMMantleSerializer"
56 | code:0
57 | userInfo:@{ NSLocalizedDescriptionKey: kYMMantleTypeError }];
58 | }
59 | return nil;
60 | }
61 |
62 | @end
--------------------------------------------------------------------------------
/YMCache.xcodeproj/xcshareddata/xcschemes/YMCache w/Mantle Support.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
51 |
52 |
53 |
54 |
60 |
61 |
63 |
64 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | CHANGELOG
2 | ==================
3 |
4 | 2.2.1 (2023-2-13)
5 | ==================
6 | * Fix a warning in Swift Package about not including something in the umbrella
7 |
8 | 2.2.0 (2021-11-30)
9 | ==================
10 | * [Feature] Add Swift Package Manager (SPM) support
11 | * [Pod] Bump minimum iOS version to 9.0
12 |
13 | 2.1.1 (2018-12-31)
14 | ==================
15 | * [Feature] Support for tvOS and watchOS Latest
16 | * [Fix] Eliminate new pod lint warnings
17 |
18 | 2.0.1 (2017-04-20)
19 | ==================
20 | * [Fix] Reduce new warnings in Xcode 8.3
21 | * [Fix] Incorrect AppleDoc comments
22 |
23 | 2.0.0 (2016-09-13)
24 | ==================
25 | * [Fix] The method `loadMemoryCache` now returns NSUInteger, in line with it's documented behavior.
26 | * [Fix] Persistence manager initializer throws exception when the required parameters are nil. Previous implementation returned nil in release-mode, effectively swallowing the errors.
27 | * [Pod] Bump minimum version for iOS to 8.0, OSX to 10.10. No known breaking changes on the old versions, we just don't test against them.
28 | * [Refactor] Enable many more warnings and patch code to fix resulting warnings
29 | * [Refactor] Remove deprecated methods from 1.x
30 |
31 | 1.3.1 (2016-03-06)
32 | ==================
33 | * [Feature] Remove use of `@import` in order to support ObjC++
34 | * [Pod] Reduce minimum OS X version to 10.8
35 |
36 | 1.3.0 (2015-10-19)
37 | ==================
38 | * [Feature] Expose `NSDataWritingOptions` to clients (adds support for encryption on iOS)
39 |
40 | 1.2.2 (2015-10-12)
41 | ==================
42 | * [Bug] iOS 7 crasher upon removing any single value from the cache (@e28eta)
43 |
44 | 1.2.1 (2015-10-09)
45 | ==================
46 | * [Bug] Unable to clear value using nil value with keyed subscripting
47 | * [Travis] Fix travis secret variable format
48 |
49 | 1.2.0 (2015-10-05)
50 | ==================
51 | * [Feature] Delegate callbacks for auto-save pre/post/fail
52 | * [Refactor] Reduce log messages and fix log message format
53 | * [Travis] Log expiration of code signing certificate on build
54 |
55 | 1.1.0 (2015-09-21)
56 | ==================
57 | * [Feature] Added support for tracking item removal through a new change notification. Deprecated existing notification.
58 | * XCode 7 support
59 |
60 | 1.0.2 (2015-09-09)
61 | ==================
62 | * [Bug] Changes are no longer broadcast repeatedly with the same items
63 | * [Test] Increased code coverage in YMMemoryCache.m to 100%
64 |
65 | 1.0.1 (2015-08-10)
66 | ==================
67 | * [Refactor] Tightened up notification/eviction timing and timer setup.
68 | * [Test] Increased code coverage in YMMemoryCache.m to 99%.
69 | * Improved the README with examples.
70 |
71 | 1.0.0 (2015-08-01)
72 | ==================
73 | * Initial public release of library (@adamkaplan)
74 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/AppDelegate.m:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import "AppDelegate.h"
6 |
7 | @interface AppDelegate ()
8 |
9 | @end
10 |
11 | @implementation AppDelegate
12 |
13 |
14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
15 | // Override point for customization after application launch.
16 | NSFileManager *manager =[NSFileManager defaultManager];
17 | NSArray *docUrls = [manager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
18 | NSURL *destinationUrl = [docUrls.firstObject URLByAppendingPathComponent:@"stock.json"];
19 |
20 | if ([manager fileExistsAtPath:[NSString stringWithCString:[destinationUrl fileSystemRepresentation] encoding:NSUTF8StringEncoding]]) {
21 | return YES;
22 | }
23 |
24 | NSURL *sourceUrl = [[NSBundle mainBundle] URLForResource:@"stock" withExtension:@"json"];
25 | NSError *error;
26 |
27 | [manager copyItemAtURL:sourceUrl toURL:destinationUrl error:&error];
28 | if (error) {
29 | NSAssert(false, @"%@", error);
30 | }
31 | return YES;
32 | }
33 |
34 | - (void)applicationWillResignActive:(UIApplication *)application {
35 | // 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.
36 | // 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.
37 | }
38 |
39 | - (void)applicationDidEnterBackground:(UIApplication *)application {
40 | // 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.
41 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
42 | }
43 |
44 | - (void)applicationWillEnterForeground:(UIApplication *)application {
45 | // 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.
46 | }
47 |
48 | - (void)applicationDidBecomeActive:(UIApplication *)application {
49 | // 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.
50 | }
51 |
52 | - (void)applicationWillTerminate:(UIApplication *)application {
53 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
54 | }
55 |
56 | @end
57 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/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 |
28 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/YMCache.xcodeproj/xcshareddata/xcschemes/YMCache-Mac.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
67 |
68 |
78 |
79 |
85 |
86 |
87 |
88 |
89 |
90 |
96 |
97 |
103 |
104 |
105 |
106 |
108 |
109 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/YMCache.xcodeproj/xcshareddata/xcschemes/YMCache-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
67 |
68 |
78 |
79 |
85 |
86 |
87 |
88 |
89 |
90 |
96 |
97 |
103 |
104 |
105 |
106 |
108 |
109 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample/TableViewController.m:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import "TableViewController.h"
6 | #import "Stock.h"
7 | #import "YMCachePersistenceController+MantleSupport.h"
8 |
9 | static NSString *const kCacheName = @"stock.json";
10 |
11 | @interface TableViewController ()
12 | @property (nonatomic) YMMemoryCache *cache;
13 | @property (nonatomic) YMCachePersistenceController *cacheController;
14 | @property (nonatomic) NSArray *keys;
15 | @property (nonatomic) dispatch_source_t timer;
16 | @property (nonatomic) NSNumberFormatter *formatter;
17 | @end
18 |
19 | @implementation TableViewController
20 |
21 | - (void)viewDidLoad {
22 | [super viewDidLoad];
23 | // Do any additional setup after loading the view, typically from a nib.
24 |
25 | self.formatter = [NSNumberFormatter new];
26 | self.formatter.positivePrefix = @"+";
27 | self.formatter.numberStyle = NSNumberFormatterCurrencyStyle;
28 |
29 | self.cache = [YMMemoryCache memoryCacheWithName:@"dog-cache"];
30 | self.cache.notificationInterval = 0.25;
31 |
32 | self.cacheController =[[YMCachePersistenceController alloc] initWithCache:self.cache
33 | mantleModelClass:[Stock class]
34 | name:kCacheName];
35 |
36 | // Create randomizer using a repeating timer on a background thread
37 | dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
38 | self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
39 | dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC, 0.05 * NSEC_PER_SEC);
40 |
41 | srandom([NSDate timeIntervalSinceReferenceDate]);
42 | __weak typeof(self) weakSelf = self;
43 | dispatch_source_set_event_handler(self.timer, ^{
44 | NSUInteger index = random() % weakSelf.keys.count;
45 |
46 | NSString *symbol = weakSelf.keys[index];
47 | Stock *old = weakSelf.cache[symbol];
48 | double last = [old.last doubleValue] + (random() % 100) / 100.0;
49 | Stock *new = [[Stock alloc] initWithSymbol:old.symbol name:old.name last:@(last)];
50 | weakSelf.cache[symbol] = new;
51 | });
52 | }
53 |
54 | - (void)viewWillAppear:(BOOL)animated {
55 | [super viewWillAppear:animated];
56 |
57 | // load the cache (synchronously)
58 | NSError *error;
59 | [self.cacheController loadMemoryCache:&error];
60 | if (error) {
61 | NSLog(@"Memory cache error! %@", error);
62 | }
63 |
64 | self.keys = self.cache.allItems.allKeys;
65 | }
66 |
67 | - (void)viewDidAppear:(BOOL)animated {
68 | [super viewDidAppear:animated];
69 |
70 | [[NSNotificationCenter defaultCenter] addObserver:self
71 | selector:@selector(cacheUpdated:)
72 | name:kYFCacheDidChangeNotification
73 | object:self.cache];
74 |
75 | dispatch_resume(self.timer);
76 | }
77 |
78 | - (void)viewWillDisappear:(BOOL)animated {
79 | [super viewWillDisappear:animated];
80 |
81 | dispatch_suspend(self.timer);
82 | [[NSNotificationCenter defaultCenter] removeObserver:self];
83 | }
84 |
85 | - (void)didReceiveMemoryWarning {
86 | [super didReceiveMemoryWarning];
87 |
88 | // Trigger immediate synchronous cache cleanup
89 | [self.cache purgeEvictableItems:nil];
90 | }
91 |
92 | #pragma mark -
93 |
94 |
95 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
96 | return 1;
97 | }
98 |
99 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
100 | return self.keys.count;
101 | }
102 |
103 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
104 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ReuseID" forIndexPath:indexPath];
105 |
106 | NSString *key = self.keys[indexPath.row];
107 | Stock *stock = self.cache[key];
108 | cell.textLabel.text = stock.name;
109 | cell.detailTextLabel.text = [self.formatter stringFromNumber:stock.last];
110 |
111 | return cell;
112 | }
113 |
114 | #pragma mark -
115 |
116 | - (void)cacheUpdated:(NSNotification *)notification {
117 | NSDictionary *symbolUpdates = notification.userInfo;
118 |
119 | NSMutableArray *indexPaths = [NSMutableArray array];
120 | for (NSString *symbol in symbolUpdates) {
121 | NSUInteger row = [self.keys indexOfObject:symbol];
122 | NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:0];
123 | [indexPaths addObject:path];
124 | }
125 | [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
126 | }
127 |
128 | @end
129 |
--------------------------------------------------------------------------------
/YMCache/YMCachePersistenceController.h:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/1/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import
6 |
7 | NS_ASSUME_NONNULL_BEGIN
8 |
9 | @class YMCachePersistenceController;
10 |
11 | @protocol YMSerializationDelegate
12 |
13 | - (nullable id)persistenceController:(YMCachePersistenceController *)controller modelFromJSONDictionary:(NSDictionary *)value
14 | error:(NSError * _Nullable * _Nullable)error;
15 |
16 | - (nullable NSDictionary *)persistenceController:(YMCachePersistenceController *)controller JSONDictionaryFromModel:(id)value
17 | error:(NSError * _Nullable * _Nullable)error;
18 |
19 | @optional
20 |
21 | /**
22 | * Optional. Invoked immediately prior to saving the memory cache to disk. This method will only be
23 | * invoked due to expiration of the `saveInterval`, not if `saveMemoryCache:` is called.
24 | *
25 | * @param controller Cache persistence controller
26 | */
27 | - (void)persistenceControllerWillSaveMemoryCache:(YMCachePersistenceController *)controller;
28 |
29 | /**
30 | * Optional. Invoked immediately after successfully saving the memory cache to disk. This method will
31 | * only be invoked due to expiration of the `saveInterval`, not if `saveMemoryCache:` is called.
32 | *
33 | * @param controller Cache persistence controller
34 | */
35 | - (void)persistenceControllerDidSaveMemoryCache:(YMCachePersistenceController *)controller;
36 |
37 | /**
38 | * Optional. Invoked immediately after failing to save the memory cache to disk. This method will
39 | * only be invoked due to expiration of the `saveInterval`, not if `saveMemoryCache:` is called.
40 | *
41 | * @param controller Cache persistence controller
42 | * @param error The first error that was encountered
43 | */
44 | - (void)persistenceController:(YMCachePersistenceController *)controller didFailToSaveMemoryCacheWithError:(NSError *)error;
45 |
46 | @end
47 |
48 | @class YMMemoryCache;
49 |
50 | @interface YMCachePersistenceController : NSObject
51 |
52 | /** The cache from which data will be loaded into or persisted from. */
53 | @property (nonatomic, readonly) YMMemoryCache *cache;
54 |
55 | /** The class of the items contained in the cache. */
56 | @property (nonatomic, readonly) Class modelClass;
57 |
58 | /** The instance used to serialize and de-serialize models */
59 | @property (nonatomic, readonly) idserializionDelegate;
60 |
61 | /** The URL of the file on disk from which cache data will be loaded from or written into. */
62 | @property (nonatomic, readonly) NSURL *cacheFileURL;
63 |
64 | /** The last error, if any, encountered during an automatic save operation. */
65 | @property (nonatomic, readonly) NSError *lastSaveError;
66 |
67 | /** The interval after which `saveMemoryCache` will be automatically called. Set to 0 or negative to disable automatic saving. */
68 | @property (nonatomic) NSTimeInterval saveInterval;
69 |
70 | /** The options to pass to `-[NSData writeToFile:options:error:]` when saving the cache to disk. Default
71 | * is `NSDataWritingAtomic`.
72 | */
73 | @property (nonatomic) NSDataWritingOptions fileWritingOptions;
74 |
75 | - (instancetype)init NS_UNAVAILABLE;
76 |
77 | /** Returns a directory suitable for cache file operations.
78 | * @return A directory suitable for cache file operations, or nil if such a directory could not be located.
79 | */
80 | + (NSURL *)defaultCacheDirectory;
81 |
82 | /** Creates and returns a new cache persistence manager using `cacheDirectoryURL` as the file storage
83 | * URL for all load/save operations.
84 | * @param modelClass Required. The class of the items that will be stored in the cache. Currently only
85 | * homogenously typed caches are supported.
86 | * @param cache Required. The cache instance from which data shall be loaded into or saved from.
87 | * @param serializionDelegate Required. the object responsible for serializing and de-serializing models
88 | * @param cacheFileURL Required. The url on disk to use for saving and loading caches.
89 | * @return a new cache persistence manager or nil if `cacheDirectoryURL` was omitted and no suitable
90 | * cache directory could be located.
91 | */
92 | - (nullable instancetype)initWithCache:(YMMemoryCache *)cache
93 | modelClass:(Class)modelClass
94 | delegate:(id)serializionDelegate
95 | fileURL:(NSURL *)cacheFileURL NS_DESIGNATED_INITIALIZER;
96 |
97 | /** Creates and returns a new cache persistence manager using `cacheName` as the file name in the default
98 | * storage directory for all load/save operations.
99 | * @param modelClass Required. The class of the items that will be stored in the cache. Currently only
100 | * homogenously typed caches are supported.
101 | * @param cache Required. The cache instance from which data shall be loaded into or saved from.
102 | * @param serializionDelegate the object responsible for serializing and de-serializing models
103 | * @param cacheName Required. The name of the cache file on disk to use for saving and loading.
104 | * @return a new cache persistence manager or nil if `cacheDirectoryURL` was omitted and no suitable
105 | * cache directory could be located.
106 | * @see `defaultCacheDirectory`
107 | */
108 | - (nullable instancetype)initWithCache:(YMMemoryCache *)cache
109 | modelClass:(Class)modelClass
110 | delegate:(id)serializionDelegate
111 | name:(NSString *)cacheName;
112 |
113 | /** Loads the specified cache file from disk, parses it, and adds all items to the provided memory cache.
114 | * @param error If there is an error loading the data, upon return contains an NSError object that
115 | * describes the problem.
116 | * @return The number of items loaded from the cache file and added to the memory cache. Zero if
117 | * memoryCache or filename are not valid.
118 | */
119 | - (NSUInteger)loadMemoryCache:(NSError * __autoreleasing *)error;
120 |
121 | /** Saves the memory cache to the specified location on disk.
122 | * @param error If there is an error writing the data, upon return contains an NSError object that
123 | * describes the problem.
124 | * @return True if the memory cache was written to disk. False if an error occurred or if either
125 | * memoryCache or filename are not valid.
126 | */
127 | - (BOOL)saveMemoryCache:(NSError * __autoreleasing *)error;
128 |
129 | @end
130 |
131 | NS_ASSUME_NONNULL_END
132 |
--------------------------------------------------------------------------------
/YMCacheTests/YMCachePersistenceControllerSpec.m:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 9/9/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | @import YMCache;
6 |
7 | @interface TestDelegate : NSObject
8 | @end
9 | @implementation TestDelegate
10 | - (id)persistenceController:(YMCachePersistenceController *)controller
11 | modelFromJSONDictionary:(NSDictionary *)value
12 | error:(NSError * __autoreleasing *)error {
13 | return nil;
14 | }
15 |
16 | - (NSDictionary *)persistenceController:(YMCachePersistenceController *)controller
17 | JSONDictionaryFromModel:(id)value
18 | error:(NSError * __autoreleasing *)error {
19 | return nil;
20 | }
21 | @end
22 |
23 | SpecBegin(YMCachePersistenceControllerSpec)
24 |
25 | describe(@"YMCachePersistenceControllerSpec", ^{
26 |
27 | __block YMCachePersistenceController *controller;
28 | __block YMMemoryCache *cache;
29 | __block Class modelClass;
30 | __block id delegate;
31 | __block NSString *fileName;
32 |
33 | beforeEach(^{
34 | cache = [[YMMemoryCache alloc] initWithName:@"CacheName" evictionDecider:nil];
35 | modelClass = [NSObject class];
36 | delegate = [TestDelegate new];
37 | fileName = @"test-cache";
38 |
39 | controller = [[YMCachePersistenceController alloc] initWithCache:cache
40 | modelClass:modelClass
41 | delegate:delegate
42 | name:fileName];
43 | });
44 |
45 | afterEach(^{});
46 |
47 | context(@"Default Cache Directory", ^{
48 | NSURL *gotUrl = [YMCachePersistenceController defaultCacheDirectory];
49 | NSURL *expectUrl = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
50 | inDomains:NSUserDomainMask].firstObject;
51 | expect(expectUrl).to.equal(gotUrl);
52 | });
53 |
54 | describe(@"Default initializer", ^{
55 |
56 | __block NSURL *fileUrl;
57 |
58 | beforeEach(^{
59 | cache = [[YMMemoryCache alloc] initWithName:@"Name" evictionDecider:nil];
60 | modelClass = [NSObject class];
61 | fileUrl = [NSURL URLWithString:@"file:///"];
62 | });
63 |
64 | context(@"required args", ^{
65 | __block id unused;
66 |
67 | it(@"throws exception if cache is nil", ^{
68 | cache = nil;
69 | expect(^{
70 | unused = [[YMCachePersistenceController alloc] initWithCache:cache
71 | modelClass:modelClass
72 | delegate:delegate
73 | fileURL:fileUrl];
74 | }).to.raiseWithReason(@"InvalidParameterException",
75 | @"A cache is required to use cache persistence controller.");
76 | });
77 |
78 | it(@"throws exception if delegate is nil", ^{
79 | delegate = nil;
80 | expect(^{
81 | unused = [[YMCachePersistenceController alloc] initWithCache:cache
82 | modelClass:modelClass
83 | delegate:delegate
84 | fileURL:fileUrl];
85 | }).to.raiseWithReason(@"InvalidParameterException",
86 | @"Serialization delegate is required for the persistence controller to "
87 | @"map between representations of the cache data between file and memory.");
88 | });
89 |
90 | it(@"throws exception if fileURL is nil", ^{
91 | fileUrl = nil;
92 | expect(^{
93 | unused = [[YMCachePersistenceController alloc] initWithCache:cache
94 | modelClass:modelClass
95 | delegate:delegate
96 | fileURL:fileUrl];
97 | }).to.raiseWithReason(@"InvalidParameterException",
98 | @"The cache file URL is required to use cache persistence controller."
99 | @" If the cache is not meant to be stored in a file, do not use a"
100 | @" persistence controller.");
101 | });
102 | });
103 |
104 | context(@"correctly set initial values", ^{
105 |
106 | it(@"sets all values", ^{
107 | YMCachePersistenceController *con = [[YMCachePersistenceController alloc] initWithCache:cache
108 | modelClass:modelClass
109 | delegate:delegate
110 | fileURL:fileUrl];
111 | expect(con.cache).to.beIdenticalTo(cache);
112 | expect(con.modelClass).to.beIdenticalTo(modelClass);
113 | expect(con.serializionDelegate).to.beIdenticalTo(delegate);
114 | expect(con.cacheFileURL).to.equal(fileUrl);
115 | });
116 |
117 | it(@"sets minimum values", ^{
118 | modelClass = nil;
119 |
120 | YMCachePersistenceController *con = [[YMCachePersistenceController alloc] initWithCache:cache
121 | modelClass:modelClass
122 | delegate:delegate
123 | fileURL:fileUrl];
124 | expect(con.cache).to.beIdenticalTo(cache);
125 | expect(con.modelClass).to.beNil();
126 | expect(con.serializionDelegate).to.beIdenticalTo(delegate);
127 | expect(con.cacheFileURL).to.equal(fileUrl);
128 | });
129 |
130 | });
131 |
132 | });
133 |
134 | context(@"Convienance initializer", ^{
135 |
136 | it(@"return nil if name argument is not valid ", ^{
137 | NSString *name = nil;
138 |
139 | id obj = [[YMCachePersistenceController alloc] initWithCache:cache
140 | modelClass:[NSObject class]
141 | delegate:[TestDelegate new]
142 | name:name];
143 | expect(obj).to.beNil();
144 | });
145 |
146 | it(@"correctly set all initial values", ^{
147 | expect(controller.cache).to.beIdenticalTo(cache);
148 | expect(controller.modelClass).to.beIdenticalTo(modelClass);
149 | expect(controller.serializionDelegate).to.beIdenticalTo(delegate);
150 |
151 | NSURL *url = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
152 | inDomains:NSUserDomainMask].firstObject;
153 | url = [url URLByAppendingPathComponent:fileName];
154 | expect(controller.cacheFileURL).to.equal(url);
155 | });
156 |
157 | });
158 |
159 | context(@"Saving Cache", ^{
160 |
161 | //it(@"Write ", <#^(void)block#>)
162 |
163 | });
164 |
165 |
166 | context(@"Restoring Cache", ^{
167 | //
168 | });
169 |
170 | });
171 |
172 | SpecEnd
173 |
--------------------------------------------------------------------------------
/YMCache/YMMemoryCache.h:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/1/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import
6 |
7 | NS_ASSUME_NONNULL_BEGIN
8 |
9 | /**
10 | * Cache update notification. The userInfo dictionary in the notification contains two values:
11 | * `kYFCacheUpdatedItemsUserInfoKey` containing key-value pairs that have been added/updated and
12 | * `kYFCacheRemovedItemsUserInfoKey` containing keys that have been removed.
13 | * The notification is essentially a delta between the last notification and the current cache state.
14 | */
15 | extern NSString *const kYFCacheDidChangeNotification;
16 | /**
17 | * A key whose value is an NSDictionary of key-value pairs representing entries that have been added
18 | * to or removed from the cache since the last notification.
19 | */
20 | extern NSString *const kYFCacheUpdatedItemsUserInfoKey;
21 | /**
22 | * A key whose value is an NSSet of cache keys representing entries that have been removed from the
23 | * cache since the last notification.
24 | */
25 | extern NSString *const kYFCacheRemovedItemsUserInfoKey;
26 |
27 | /** Type of a decider block for determining is an item is evictable.
28 | * @param key The key associated with value in the cache.
29 | * @param value The value of the item in the cache.
30 | * @param context Arbitrary user-provided context.
31 | */
32 | typedef BOOL (^YMMemoryCacheEvictionDecider)(id key, id value, void *__nullable context);
33 |
34 | typedef __nullable id (^YMMemoryCacheObjectLoader)(void);
35 |
36 | /** The YMMemoryCache class declares a programatic interface to objects that manage ephemeral
37 | * associations of keys and values, similar to Foundation's NSMutableDictionary. The primary benefit
38 | * is that YMMemoryCache is designed to be safely accessed and mutated across multiple threads. It
39 | * offers specific optimizations for use in a multi-threaded environment, and hides certain functionality
40 | * that may be potentially unsafe or behave in unexpected ways for users accustomed to an NSDictionary.
41 | *
42 | * Implementation Notes:
43 | *
44 | * In general, non-mutating access (getters) execute synchronously and in parallel (by way of a concurrent
45 | * Grand Central Dispatch queue).
46 | * Mutating access (setters), on the other hand, take advantage of dispatch barriers to provide safe,
47 | * blocking writes while preserving in-band synchronous-like ordering behavior.
48 | *
49 | * In other words, getters behave as if they were run on a concurrent dispatch queue with respect to
50 | * each other. However, setters behave as though they were run on a serial dispatch queue with respect
51 | * to both getters and other setters.
52 | *
53 | * One side effect of this approach is that any access – even a non-mutating getter – may result in a
54 | * blocking call, though overall latency should be extremely low. Users should plan for this and try
55 | * to pull out all values at once (via `enumerateKeysAndObjectsUsingBlock`) or in the background.
56 | */
57 | @interface YMMemoryCache : NSObject
58 |
59 | /** Unique name identifying this cache, for example, in log messages. */
60 | @property (nonatomic, readonly, nullable) NSString *name;
61 |
62 | /** Maximum amount of time between evictions checks. Evictions may occur at any time up to this value.
63 | * Defaults to 600 seconds, or 10 minutes.
64 | */
65 | @property (nonatomic) NSTimeInterval evictionInterval;
66 |
67 | /** Maximum amount of time between notification of changes to cached items. After each notificationInterval,
68 | * the cache will post a notification named `kYFCacheDidChangeNotification` with userInfo that contains
69 | * `kYFCacheUpdatedItemsUserInfoKey` and `kYFCacheRemovedItemsUserInfoKey`. They keys contain information
70 | * that is as a complete delta of changes since the last notification.
71 | *
72 | * Defaults to 0, disabled.
73 | */
74 | @property (nonatomic) NSTimeInterval notificationInterval;
75 |
76 | /** Creates and returns a new memory cache using the specified name, but no eviction delegate.
77 | * @param name A unique name for this cache. Optional, helpful for debugging.
78 | * @return a new cache identified by `name`
79 | */
80 | + (instancetype)memoryCacheWithName:(nullable NSString *)name;
81 |
82 | /** Creates and returns a new memory cache using the specified name, and eviction delegate.
83 | * @param name A unique name for this cache. Optional, helpful for debugging.
84 | * @param evictionDecider The eviction decider to use. See initWithName:evictionDecider:`
85 | * @return a new cache identified by `name`
86 | */
87 | + (instancetype)memoryCacheWithName:(nullable NSString *)name
88 | evictionDecider:(nullable YMMemoryCacheEvictionDecider)evictionDecider;
89 |
90 | - (instancetype)init NS_UNAVAILABLE; // use designated initializer
91 |
92 | /** Initializes a newly allocated memory cache using the specified cache name, delegate & queue.
93 | * @param name (Optional) A unique name for this cache. Helpful for debugging.
94 | * @param evictionDecider (Optional) A block used to decide if an item (a key-value pair) is evictable.
95 | * Clients return YES if the item can be evicted, or NO if the item should not be evicted from the cache.
96 | * A nil evictionDevider is equivalent to returning NO for all items. The decider will execute on an
97 | * arbitrary thread. The `context` parameter is NULL if the block is called due to the internal eviction
98 | * timer expiring.
99 | * @return An initialized memory cache using name, delegate and delegateQueue.
100 | */
101 | - (instancetype)initWithName:(nullable NSString *)name
102 | evictionDecider:(nullable YMMemoryCacheEvictionDecider)evictionDecider NS_DESIGNATED_INITIALIZER;
103 |
104 | /** Returns the value associated with a given key.
105 | * @param key The key for which to return the corresponding value.
106 | * @return The value associated with `key`, or `nil` if no value is associated with key.
107 | */
108 | - (nullable id)objectForKeyedSubscript:(nonnull id)key;
109 |
110 | /** Get the value for the key. If value does not exist, invokes defaultLoader(), sets the result as
111 | the value for key, and returns it. In order to ensure consistency, the cache is locked when
112 | the defaultLoader block needs to be invoked.
113 | */
114 | - (nullable id)objectForKey:(NSString *)key withDefault:(YMMemoryCacheObjectLoader)defaultLoader;
115 |
116 | /** Sets the value associated with a given key.
117 | * @param obj The value for `key`
118 | * @param key The key for `value`. The key is copied (keys must conform to the NSCopying protocol).
119 | * If `key` already exists in the cache, `object` takes its place. If `object` is `nil`, key is removed
120 | * from the cache.
121 | */
122 | - (void)setObject:(nullable id)obj forKeyedSubscript:(id)key;
123 |
124 | /** Adds to the cache the entries from a dictionary.
125 | * If the cache contains the same key as the dictionary, the cache's previous value object for that key
126 | * is replaced with new value object. All entries from dictionary are added to the cache at once such
127 | * that all of them are accessable by the next cache accessor, even if that accessor is triggered before
128 | * this method returns.
129 | *
130 | * All entries in the dictionary will be part of the next change notification event, even if an identical
131 | * key-value pair was already present in the cache.
132 | *
133 | * @param dictionary The dictionary from which to add entries.
134 | */
135 | - (void)addEntriesFromDictionary:(NSDictionary *)dictionary;
136 |
137 | /** Empties the cache of its entries.
138 | * Each key and corresponding value object is sent a release message.
139 | */
140 | - (void)removeAllObjects;
141 |
142 | /** Removes from the cache entries specified by keys in a given array.
143 | * If a key in `keys` does not exist, the entry is ignored. This method is more efficient at removing
144 | * the values for multiple keys than calling `setObject:forKeyedSubscript` multiple times.
145 | * @param keys An array of objects specifying the keys to remove.
146 | */
147 | - (void)removeObjectsForKeys:(NSArray *)keys;
148 |
149 | /** Returns a snapshot of all values in the cache.
150 | * The returned dictionary may differ from the actual cache as soon as it is returned. Because of this,
151 | * it is recommended to use `enumerateKeysAndObjectsUsingBlock:` for any operations that require a
152 | * guarantee that all items are operated upon (such as in low-memory situations).
153 | * @return A copy of the underlying dictionary upon which the cache is built.
154 | */
155 | - (NSDictionary *)allItems;
156 |
157 | /** Triggers an immediate (synchronous) check for exired items, and releases those items that are expired.
158 | * This method does nothing if no expirationDecider block was provided during initialization. The
159 | * evictionDecider block is run on the queue that this method is called on.
160 | */
161 | - (void)purgeEvictableItems:(nullable void *)context;
162 |
163 | @end
164 |
165 | NS_ASSUME_NONNULL_END
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # YMCache
2 |
3 | [](https://github.com/yahoo/YMCache/actions)
4 |
5 | [](https://github.com/apple/swift-package-manager)
6 | [](https://github.com/CocoaPods/CocoaPods)
7 | [](https://raw.githubusercontent.com/yahoo/YMCache/master/LICENSE.md)
8 | []()
9 |
10 | ---
11 |
12 | YMCache is a lightweight object caching solution for iOS and macOS that is designed for highly parallel access scenarios. YMCache presents a familiar interface emulating `NSMutableDictionary`, while internally leveraging Apple's [Grand Central Dispatch](https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html) technology to strike a balance between performance and consistency.
13 |
14 | The Yahoo Finance iOS team uses YMCache to multiplex access to it's database of thousands of real-time stocks, which change in unpredictable ways, with an unpredictable cadence. YMCache helps relieve the complexity of multi-thread access to a central data store by providing a set of easy to understand [reader-writer](https://en.wikipedia.org/wiki/Readers–writer_lock) access semantics.
15 |
16 | ### Parallel Access
17 |
18 | 1. All read operations occur synchronously on the calling thread, but concurrently across all readers (as system resources allow).
19 | 2. All write operations occur asynchronously
20 | 3. Read operations initiated after a write operation will wait for the write to complete
21 |
22 | The above rules allow for multiple readers, but a single writer. A nice result of this approach is that reads are serialized with respect to writes, enforcing a sensible order: you may read with the confidence that the expected data has been fully written.
23 |
24 | ### Features
25 |
26 | - **Persistence**: save/load a cache from disk once, or at a defined interval
27 | - **Eviction**: handle low memory situations in-band, using whatever logic suits your needs
28 | - **Serialization**: arbitrary model transformations comes for free. You can use Mantle, straight NSJSONSerialization or any other format you can think up!
29 | - **Bulk operations**: efficient multi-value reads/writes. (Bulk operations follow the [Parallel Access](#ParallelAccess) rules, but count as a single operation)
30 |
31 | ## SETUP
32 |
33 | We support distribution through [CocoaPods](http://github.com/CocoaPods/CocoaPods) and [Swift Package Manager](https://swift.org/package-manager/).
34 |
35 | ### CocoaPods
36 |
37 | 1. Add YMCache to your project's `Podfile`:
38 |
39 | ```ruby
40 | target :MyApp do
41 | pod 'YMCache', '~> 1.0'
42 | end
43 | ```
44 |
45 | 2. Run `pod update` or `pod install` in your project directory.
46 |
47 | ### SwiftPM
48 |
49 | Add `.package(url: "https://github.com/yahoo/YMCache.git", from: "2.2.0")` to your `package.swift`
50 |
51 | ## Usage
52 |
53 | #### Synopsis
54 | ```objc
55 | YMMemoryCache *cache = [YMMemoryCache memoryCacheWithName:@"my-object-cache"];
56 | cache[@"Key1"] = valueA;
57 | MyVal *valueA = cache[@"Key1"];
58 | [cache addEntriesFromDictionary:@{ @"Key2": value2 }];
59 | NSDictionary *allItems = [cache allItems];
60 | [cache removeAllObjects];
61 | // cache = empty; allItems = @{ @"Key1": value1, @"Key2": value2 }
62 | ```
63 | This cache is essentially a completely thread-safe NSDictionary with read-write order guarantees.
64 |
65 | #### Eviction
66 |
67 | ##### Manual eviction
68 | ```objc
69 | // Create memory cache with an eviction decider block, which will be triggered for each item in the cache whenever
70 | // you call `-purgeEvictableItems:`.
71 | YMMemoryCache *cache = [YMMemoryCache memoryCacheWithName:@"my-object-cache"
72 | evictionDecider:^(NSString *key, NewsStory *value, void *context) {
73 | return value.publishDate > [NSDate dateWithTimeIntervalSinceNow:-300];
74 | }];
75 |
76 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
77 | [cache purgeEvictableItems:nil];
78 | });
79 | ```
80 |
81 | This example cache includes an eviction block which is called once after a 10 second delay. You are responsible for implementing the logic to decide which items are safe to evict. In this case, `NewsStory` models published more than 5 minutes ago will be purged from the cache. In this case, the eviction decider will be invoked on the main queue, because that is where `-purgeEvictableItems:` is called from.
82 |
83 | ##### Time-based eviction
84 | ```objc
85 | YMMemoryCache *cache = [YMMemoryCache memoryCacheWithName:@"my-object-cache"
86 | evictionDecider:^(NSString *key, NewsStory *value, void *context) {
87 | return value.publishDate > [NSDate dateWithTimeIntervalSinceNow:-300];
88 | }];
89 |
90 | cache.evictionInterval = 60.0; // trigger eviction every 60 seconds
91 | ```
92 | This creates a cache with periodic time-based cache evictions every 60 seconds. Note that automatic invocations of the eviction decider execute on an arbitrary background thread. This approach can be combined with other manual eviction calls to provide a situations in which cache eviction is triggered on-demand, but at lease every N minutes.
93 |
94 | ##### Automatic eviction on low memory
95 | ```objc
96 | // Create memory cache with an eviction decider block, which will be triggered for each item in the cache whenever
97 | // you call `-purgeEvictableItems:`.
98 | YMMemoryCache *cache = [YMMemoryCache memoryCacheWithName:@"my-object-cache"
99 | evictionDecider:^(NSString *key, NewsStory *value, void *context) {
100 | return value.publishDate > [NSDate dateWithTimeIntervalSinceNow:-300];
101 | }];
102 |
103 | // Trigger in-band cache eviction during low memory events.
104 | [[NSNotificationCenter defaultCenter] addObserver:cache
105 | selector:@selector(purgeEvictableItems:)
106 | name:UIApplicationDidReceiveMemoryWarningNotification
107 | object:nil];
108 |
109 | // or, more commonly
110 |
111 | - (void)didReceiveMemoryWarning {
112 | [super didReceiveMemoryWarning];
113 |
114 | // Trigger immediate synchronous cache cleanup
115 | [self.cache purgeEvictableItems:nil];
116 | }
117 | ```
118 | The eviction decider blocks that react to low memory situations will execute on the main thread because that is the only thread that sends low memory notifications or calls `-didReceiveMemoryWarning`.
119 |
120 | #### Observing Changes
121 | ```objc
122 | YMMemoryCache *cache = [YMMemoryCache memoryCacheWithName:@"my-object-cache"];
123 |
124 | cache.notificationInterval = 0.5;
125 |
126 | [[NSNotificationCenter defaultCenter] addObserver:self
127 | selector:@selector(cacheUpdated:)
128 | name:kYFCacheDidChangeNotification
129 | object:cache];
130 |
131 | // from any thread, such as a network client on a background thread
132 | cache[@"Key"] = value;
133 |
134 | // within 0.5s (as per configuration) a notification will fire and call this:
135 | - (void)cacheUpdated:(NSNotification *)notification {
136 | // Get a snapshot of all values that were added or replaced since the last notification
137 | NSDictionary *addedOrUpdated = notification.userInfo[kYFCacheUpdatedItemsUserInfoKey];
138 | // Get a set of all keys that were removed since the last notification
139 | NSSet *removedKeys = notification.userInfo[kYFCacheRemovedItemsUserInfoKey];
140 | }
141 | ```
142 |
143 | ### File Encryption (iOS) and writing options
144 |
145 | The `YMCachePersistenceManager` uses `NSData` to read and write data to disk. By default, we write atomically. You may control write options by setting the persistence manager's `fileWritingOptions` property before the next write.
146 |
147 | ### Examples
148 |
149 | To run the example projects, clone the repo, and run `pod install` from one of the directories in Example.
150 |
151 | #### Example: Mantle Serialization
152 |
153 | It's very easy to use Mantle – version 1 or 2 – to serialize your cache to disk! Check out the pre-built, production-ready example in [Examples/Mantle](https://github.com/yahoo/YMCache/tree/master/Examples/Mantle).
154 |
155 | ## Support & Contributing
156 |
157 | Report any bugs or send feature requests to the GitHub issues. Pull requests are very much welcomed. See [CONTRIBUTING](https://github.com/yahoo/YMCache/blob/master/CONTRIBUTING.md) for details.
158 |
159 | ## License
160 |
161 | MIT license. See the [LICENSE](https://github.com/yahoo/YMCache/blob/master/LICENSE) file for details.
162 |
--------------------------------------------------------------------------------
/YMCache/YMCachePersistenceController.m:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/1/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import "YMCachePersistenceController.h"
6 | #import "YMMemoryCache.h"
7 | #import "YMLog.h"
8 |
9 | #include "TargetConditionals.h"
10 |
11 |
12 | static NSString *const kYFCachePersistenceErrorDomain = @"YFCachePersistenceErrorDomain";
13 |
14 | @interface YMCachePersistenceController ()
15 | @property (nonatomic) dispatch_source_t updateTimer;
16 | @property (nonatomic) dispatch_queue_t updateQueue;
17 | @end
18 |
19 | @implementation YMCachePersistenceController
20 |
21 | + (NSURL *)defaultCacheDirectory {
22 | NSArray *urls = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
23 | if (!urls.count) {
24 | NSAssert(false, @"%@", @"Unable to find suitable cache directory URL");
25 | return nil;
26 | }
27 | return [urls lastObject];
28 | }
29 |
30 | - (instancetype)initWithCache:(YMMemoryCache *)cache
31 | modelClass:(Class)modelClass
32 | delegate:(id)serializionDelegate
33 | fileURL:(NSURL *)cacheFileURL {
34 | NSParameterAssert(cache);
35 | NSParameterAssert(serializionDelegate);
36 | NSParameterAssert(cacheFileURL);
37 | if (!cache || !cacheFileURL || !serializionDelegate) {
38 | // If any of these variables are nil, the persistence controller cannot do it's job.
39 | NSString *exceptionReason;
40 | if (!cache) {
41 | exceptionReason = @"A cache is required to use cache persistence controller.";
42 | }
43 |
44 | if (!cacheFileURL) {
45 | exceptionReason = @"The cache file URL is required to use cache persistence controller."
46 | @" If the cache is not meant to be stored in a file, do not use a persistence controller.";
47 | }
48 |
49 | if (!serializionDelegate) {
50 | exceptionReason = @"Serialization delegate is required for the persistence controller to "
51 | @"map between representations of the cache data between file and memory.";
52 | }
53 |
54 | @throw [NSException exceptionWithName:@"InvalidParameterException"
55 | reason:exceptionReason
56 | userInfo:nil];
57 |
58 | return nil;
59 | }
60 |
61 | self = [super init];
62 | if (self) {
63 | _cache = cache;
64 | _modelClass = modelClass;
65 | _cacheFileURL = cacheFileURL;
66 | _serializionDelegate = serializionDelegate;
67 | _fileWritingOptions = NSDataWritingAtomic;
68 |
69 | NSString *queueName = @"com.yahoo.persist";
70 | if (cache.name) {
71 | [queueName stringByAppendingFormat:@"%@ ", cache.name];
72 | }
73 | _updateQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_SERIAL);
74 | }
75 | return self;
76 | }
77 |
78 | - (instancetype)initWithCache:(YMMemoryCache *)cache
79 | modelClass:(Class)modelClass
80 | delegate:(id)serializionDelegate
81 | name:(NSString *)cacheName {
82 | NSParameterAssert(cacheName.length);
83 | if (!cacheName.length) {
84 | return nil;
85 | }
86 |
87 | NSURL *defaultCacheDirectoryURL = [[self class] defaultCacheDirectory];
88 | NSURL *cacheFileURL = [defaultCacheDirectoryURL URLByAppendingPathComponent:cacheName isDirectory:NO];
89 | return [self initWithCache:cache modelClass:modelClass delegate:serializionDelegate fileURL:cacheFileURL];
90 | }
91 |
92 | - (void)setSaveInterval:(NSTimeInterval)saveInterval {
93 | // Explicitely not checking to see if saveInterval has actually changed. There may be some expectation
94 | // about timing from a client's perspective. For example, if at once point saveInterval is set to 10s,
95 | // and then 5s later it is once again set to 10s, when should it fire?
96 | // A) With a change check: 10s after the first call, 5s after the second call – second caller is confused
97 | // B) Without a change check: 20s after the first call, 10s after the second call – first caller is confused
98 |
99 | dispatch_sync(self.updateQueue, ^{
100 | self->_saveInterval = saveInterval;
101 |
102 | // Invalidate existing source timer.
103 | if (self.updateTimer) {
104 | dispatch_source_cancel(self.updateTimer);
105 | self.updateTimer = nil;
106 | }
107 |
108 | // Create new timer if interval is positive
109 | if (saveInterval > 0) {
110 | //YMLog(@"Setting cache (%@) auto-save interval to %0.4fs", _cache.name, saveInterval);
111 |
112 | dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.updateQueue);
113 | dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, (UInt64)(saveInterval * NSEC_PER_SEC), NSEC_PER_SEC / 10);
114 | __weak __typeof(self) weakSelf = self;
115 | dispatch_source_set_event_handler(timer, ^{
116 | __strong __typeof(self) strongSelf = weakSelf;
117 |
118 | if ([strongSelf.serializionDelegate respondsToSelector:@selector(persistenceControllerWillSaveMemoryCache:)]) {
119 | [strongSelf.serializionDelegate persistenceControllerWillSaveMemoryCache:strongSelf];
120 | }
121 |
122 | NSError *error;
123 | [strongSelf saveMemoryCache:&error];
124 |
125 | if (error) {
126 | if ([strongSelf.serializionDelegate respondsToSelector:@selector(persistenceController:didFailToSaveMemoryCacheWithError:)]) {
127 | [strongSelf.serializionDelegate persistenceController:strongSelf didFailToSaveMemoryCacheWithError:error];
128 | }
129 | return;
130 | }
131 |
132 | if ([strongSelf.serializionDelegate respondsToSelector:@selector(persistenceControllerDidSaveMemoryCache:)]) {
133 | [strongSelf.serializionDelegate persistenceControllerDidSaveMemoryCache:strongSelf];
134 | }
135 | });
136 |
137 | self.updateTimer = timer;
138 | dispatch_resume(timer);
139 | }
140 | else {
141 | //YMLog(@"Disabling cache (%@) auto-save", _cache.name);
142 | }
143 | });
144 | }
145 |
146 | - (NSUInteger)loadMemoryCache:(NSError * __autoreleasing *)error {
147 | // NSJSONSerialization and NSValueTransformers can be rather agressive in their exceptions.
148 | // Reading the pre-existing cache is not critical. If an exception is thrown, we'll swallow it here and wrap
149 | // it in an error. The app will act as though it did not have any cached data.
150 | @try {
151 | return [self p_loadMemoryCacheUnsafe:error];
152 | }
153 | @catch (NSException *exception) {
154 | if (error) {
155 | *error = [self errorFromException:exception context:@"Cache Save"];
156 | }
157 | return 0;
158 | }
159 | }
160 |
161 | - (NSUInteger)p_loadMemoryCacheUnsafe:(NSError * __autoreleasing *)error {
162 | if (error) {
163 | *error = nil;
164 | }
165 |
166 | BOOL cacheFileExists = [[NSFileManager defaultManager] fileExistsAtPath:self.cacheFileURL.path isDirectory:nil];
167 | if (!cacheFileExists) {
168 | return 0;
169 | }
170 |
171 | // Find, open, and load raw JSON into a dictionary
172 | NSInputStream *is = [NSInputStream inputStreamWithURL:self.cacheFileURL];
173 | [is open];
174 | if ([is streamError]) {
175 | if (error) {
176 | *error = [is streamError];
177 | //YMLog(@"Cache read stream error: %@", *error);
178 | }
179 | return 0;
180 | }
181 |
182 | NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithStream:is options:NSJSONReadingMutableContainers error:error];
183 | [is close];
184 |
185 | if (dict && ![dict isKindOfClass:[NSMutableDictionary class]]) {
186 | //YMLog(@"Invalid cache file format, not a dictionary in %@", self.cacheFileURL);
187 |
188 | if (error && !*error) { // set error if JSON parsing didn't fail, but the JSON root element was unexpected.
189 | NSString *errorStr = [NSString stringWithFormat:@"Invalid JSON format. Expected root element to be NSMutableDictionary, got %@", [dict class]];
190 | *error = [NSError errorWithDomain:@"YFCachePersistence" code:0 userInfo:@{ NSLocalizedDescriptionKey: errorStr }];
191 | }
192 |
193 | // Remove error can happen, but we can only return one error, so log it.
194 | NSError *removeError;
195 | [[NSFileManager defaultManager] removeItemAtURL:self.cacheFileURL error:&removeError];
196 | if (removeError) {
197 | YMLog(@"Error while attempting to delete corrupt cache file %@", removeError);
198 | }
199 | return 0;
200 | }
201 |
202 | // De-serialize model entries
203 | for (id key in dict.allKeys) {
204 | NSDictionary *value = dict[key];
205 | id model = [self.serializionDelegate persistenceController:self modelFromJSONDictionary:value error:error];
206 | if (!model) {
207 | NSError *logError;
208 | if (error) {
209 | logError = *error;
210 | error = nil;
211 | }
212 | YMLog(@"Error de-serializing model at %@ of type %@ from JSON value %@ – %@", key, self.modelClass, value, logError);
213 | continue;
214 | }
215 | dict[key] = model;
216 | }
217 |
218 | // Load into cache
219 | [self.cache addEntriesFromDictionary:dict];
220 | return dict.count;
221 | }
222 |
223 | - (BOOL)saveMemoryCache:(NSError * __autoreleasing *)error {
224 | // NSJSONSerialization and NSValueTransformers can be rather agressive in their exceptions.
225 | // Writing the cache is not critical. If an exception is thrown, we'll swallow it here and wrap
226 | // it in an error
227 | @try {
228 | return [self p_saveMemoryCacheUnsafe:error];
229 | }
230 | @catch (NSException *exception) {
231 | if (error) {
232 | *error = [self errorFromException:exception context:@"Cache Save"];
233 | }
234 |
235 | return NO;
236 | }
237 | }
238 |
239 | - (BOOL)p_saveMemoryCacheUnsafe:(NSError * __autoreleasing *)error {
240 | if (error) {
241 | *error = nil;
242 | }
243 |
244 | NSMutableDictionary *cacheEntries = [self.cache.allItems mutableCopy];
245 | for (id key in cacheEntries.allKeys) {
246 | id value = cacheEntries[key];
247 |
248 | NSDictionary *dict = [self.serializionDelegate persistenceController:self JSONDictionaryFromModel:value error:error];
249 | if (dict) {
250 | cacheEntries[key] = dict;
251 | }
252 | }
253 |
254 | NSError *localError;
255 | NSData *data = [NSJSONSerialization dataWithJSONObject:cacheEntries options:0 error:&localError];
256 | if (localError) {
257 | if (error) {
258 | *error = localError;
259 | }
260 | //YMLog(@"Error serializing cache to json: %@", localError.localizedDescription);
261 | return NO;
262 | }
263 |
264 | BOOL success = [data writeToURL:self.cacheFileURL options:self.fileWritingOptions error:&localError];
265 | if (localError) {
266 | if (error) {
267 | *error = localError;
268 | }
269 | //YMLog(@"Error writing cache to file '%@' %@", self.cacheFileURL, localError.localizedDescription);
270 | }
271 | return success;
272 | }
273 |
274 | - (NSError *)errorFromException:(NSException *)exception context:(NSString *)contextString {
275 | NSParameterAssert(contextString);
276 | NSParameterAssert(exception);
277 |
278 | NSString *reason = [NSString stringWithFormat:@"[%@] %@: %@", contextString, exception.name, exception.reason];
279 | NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
280 | if (exception.userInfo) {
281 | [userInfo addEntriesFromDictionary:exception.userInfo];
282 | }
283 |
284 | if (exception.callStackSymbols) {
285 | userInfo[@"CallStack"] = exception.callStackSymbols;
286 | }
287 | userInfo[NSLocalizedDescriptionKey] = reason;
288 |
289 | NSError *error = [NSError errorWithDomain:kYFCachePersistenceErrorDomain
290 | code:1
291 | userInfo:userInfo];
292 | return error;
293 | }
294 |
295 | @end
296 |
--------------------------------------------------------------------------------
/YMCache/YMMemoryCache.m:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/1/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | #import "YMMemoryCache.h"
6 |
7 | #define AssertPrivateQueue \
8 | NSAssert(dispatch_get_specific(kYFPrivateQueueKey) == (__bridge void *)self, @"Wrong Queue")
9 |
10 | #define AssertNotPrivateQueue \
11 | NSAssert(dispatch_get_specific(kYFPrivateQueueKey) != (__bridge void *)self, @"Potential deadlock: blocking call issued from current queue, to current queue")
12 |
13 | NSString *const kYFCacheDidChangeNotification = @"kYFCacheDidChangeNotification";
14 | NSString *const kYFCacheUpdatedItemsUserInfoKey = @"kYFCacheUpdatedItemsUserInfoKey";
15 | NSString *const kYFCacheRemovedItemsUserInfoKey = @"kYFCacheRemovedItemsUserInfoKey";
16 |
17 |
18 | static const CFStringRef kYFPrivateQueueKey = CFSTR("kYFPrivateQueueKey");
19 |
20 | @interface YMMemoryCache ()
21 | @property (nonatomic) dispatch_queue_t queue;
22 | @property (nonatomic) dispatch_source_t notificationTimer;
23 | @property (nonatomic) dispatch_source_t evictionTimer;
24 | /// All of the key-value pairs stored in the cache
25 | @property (nonatomic) NSMutableDictionary *items;
26 | /// The keys (and their current value) that have been added/updated since the last kYFCacheDidChangeNotification
27 | @property (nonatomic) NSMutableDictionary *updatedPendingNotify;
28 | /// The keys that have been removed since the last kYFCacheDidChangeNotification
29 | @property (nonatomic) NSMutableSet *removedPendingNotify;
30 |
31 | @property (nonatomic, copy) YMMemoryCacheEvictionDecider evictionDecider;
32 | @property (nonatomic) dispatch_queue_t evictionDeciderQueue;
33 | @end
34 |
35 | @implementation YMMemoryCache
36 |
37 | #pragma mark - Lifecycle
38 |
39 | + (instancetype)memoryCacheWithName:(NSString *)name {
40 | return [[self alloc] initWithName:name evictionDecider:nil];
41 | }
42 |
43 | + (instancetype)memoryCacheWithName:(NSString *)name evictionDecider:(YMMemoryCacheEvictionDecider)evictionDecider {
44 | return [[self alloc] initWithName:name evictionDecider:evictionDecider];
45 | }
46 |
47 | - (instancetype)initWithName:(NSString *)cacheName evictionDecider:(YMMemoryCacheEvictionDecider)evictionDecider {
48 |
49 | if (self = [super init]) {
50 |
51 | NSString *queueName = @"com.yahoo.cache";
52 | if (cacheName) {
53 | _name = cacheName;
54 | queueName = [queueName stringByAppendingFormat:@" %@", cacheName];
55 | }
56 | _queue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_CONCURRENT);
57 | dispatch_queue_set_specific(_queue, kYFPrivateQueueKey, (__bridge void *)self, NULL);
58 |
59 | if (evictionDecider) {
60 | _evictionDecider = evictionDecider;
61 | NSString *evictionQueueName = [queueName stringByAppendingString:@" (eviction)"];
62 | _evictionDeciderQueue = dispatch_queue_create([evictionQueueName UTF8String], DISPATCH_QUEUE_SERIAL);
63 |
64 | // Time interval to notify UI. This sets the overall update cadence for the app.
65 | [self setEvictionInterval:600.0];
66 | }
67 |
68 | [self setNotificationInterval:0.0];
69 |
70 | _items = [NSMutableDictionary dictionary];
71 | }
72 | return self;
73 | }
74 |
75 | - (void)dealloc {
76 | self.queue = nil; // kill queue, then kill timers
77 | self.evictionDeciderQueue = nil;
78 |
79 | dispatch_source_t evictionTimer = self.evictionTimer;
80 | if (evictionTimer && 0 == dispatch_source_testcancel(evictionTimer)) {
81 | dispatch_source_cancel(evictionTimer);
82 | }
83 |
84 | dispatch_source_t notificationTimer = self.notificationTimer;
85 | if (notificationTimer && 0 == dispatch_source_testcancel(notificationTimer)) {
86 | dispatch_source_cancel(notificationTimer);
87 | }
88 | }
89 |
90 | #pragma mark - Persistence
91 |
92 | - (void)addEntriesFromDictionary:(NSDictionary *)dictionary {
93 | dispatch_barrier_async(self.queue, ^{
94 | [self.items addEntriesFromDictionary:dictionary];
95 | [self.updatedPendingNotify addEntriesFromDictionary:dictionary];
96 | for (id key in dictionary) {
97 | [self.removedPendingNotify removeObject:key];
98 | }
99 | });
100 | }
101 |
102 | - (NSDictionary *)allItems {
103 | AssertNotPrivateQueue;
104 |
105 | __block NSDictionary *items;
106 | dispatch_sync(self.queue, ^{
107 | items = [self.items copy];
108 | });
109 |
110 | return items;
111 | }
112 |
113 | #pragma mark - Property Setters
114 |
115 | - (void)setEvictionInterval:(NSTimeInterval)evictionInterval {
116 | if (!self.evictionDeciderQueue) { // abort if this instance was not configured with an evictionDecider
117 | return;
118 | }
119 |
120 | dispatch_barrier_async(self.evictionDeciderQueue, ^{
121 | self->_evictionInterval = evictionInterval;
122 |
123 | if (self.evictionTimer) {
124 | dispatch_source_cancel(self.evictionTimer);
125 | self.evictionTimer = nil;
126 | }
127 |
128 | if (evictionInterval > 0) {
129 | self.evictionTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.evictionDeciderQueue);
130 |
131 | __weak __typeof(self) weakSelf = self;
132 | dispatch_source_set_event_handler(self.evictionTimer, ^{ [weakSelf purgeEvictableItems:NULL]; });
133 |
134 | dispatch_source_set_timer(self.evictionTimer,
135 | dispatch_time(DISPATCH_TIME_NOW, (SInt64)(evictionInterval * NSEC_PER_SEC)),
136 | (UInt64)(self.evictionInterval * NSEC_PER_SEC),
137 | 5 * NSEC_PER_MSEC);
138 |
139 | dispatch_resume(self.evictionTimer);
140 | }
141 | });
142 | }
143 |
144 | - (void)setNotificationInterval:(NSTimeInterval)notificationInterval {
145 |
146 | dispatch_barrier_async(self.queue, ^{
147 | self->_notificationInterval = notificationInterval;
148 |
149 | if (self.notificationTimer) {
150 | dispatch_source_cancel(self.notificationTimer);
151 | self.notificationTimer = nil;
152 | }
153 |
154 | if (self.notificationInterval > 0) {
155 | self.updatedPendingNotify = [NSMutableDictionary dictionary];
156 | self.removedPendingNotify = [NSMutableSet set];
157 |
158 | self.notificationTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue);
159 |
160 | __weak __typeof(self) weakSelf = self;
161 | dispatch_source_set_event_handler(self.notificationTimer, ^{
162 | [weakSelf sendPendingNotifications];
163 | });
164 |
165 | dispatch_source_set_timer(self.notificationTimer,
166 | dispatch_time(DISPATCH_TIME_NOW, (SInt64)(self.notificationInterval * NSEC_PER_SEC)),
167 | (UInt64)(self.notificationInterval * NSEC_PER_SEC),
168 | 5 * NSEC_PER_MSEC);
169 |
170 | dispatch_resume(self.notificationTimer);
171 | }
172 | else {
173 | self.updatedPendingNotify = nil;
174 | self.removedPendingNotify = nil;
175 | }
176 | });
177 | }
178 |
179 |
180 | - (id)objectForKey:(NSString *)key withDefault:(YMMemoryCacheObjectLoader)defaultLoader
181 | {
182 | NSParameterAssert(key);
183 | AssertNotPrivateQueue;
184 |
185 | id item = [self objectForKeyedSubscript:key];
186 | if (!defaultLoader) {
187 | return item;
188 | }
189 |
190 | // If default loader is valid and we don't have this object in cache, we should create and save it.
191 | if (!item) {
192 | __block id newItem;
193 | dispatch_barrier_sync(self.queue, ^{
194 | // In order to ensure that read call are only blocking when they need
195 | // to be, this mutating block is executed with it's own barrier. This
196 | // means that we have a potential race condition if this method is called
197 | // in parallel with the same missing key. Resolve by checking again!
198 | newItem = self.items[key];
199 | if (newItem) {
200 | return; // item was probably added in another loading block that was queued concurrently with this one
201 | }
202 |
203 | newItem = defaultLoader();
204 | if (newItem) {
205 | [self.removedPendingNotify removeObject:key];
206 | self.items[key] = newItem;
207 | self.updatedPendingNotify[key] = newItem;
208 | }
209 | });
210 |
211 | item = newItem;
212 | }
213 |
214 | return item;
215 | }
216 |
217 | #pragma mark - Keyed Subscripting
218 |
219 | - (id)objectForKeyedSubscript:(id)key {
220 | AssertNotPrivateQueue;
221 |
222 | __block id item;
223 | dispatch_sync(self.queue, ^{
224 | item = self.items[key];
225 | });
226 | return item;
227 | }
228 |
229 | - (void)setObject:(id)obj forKeyedSubscript:(id)key {
230 | NSParameterAssert(key); // The collections will assert, but fail earlier to aid in async debugging
231 |
232 | __weak __typeof(self) weakSelf = self;
233 | dispatch_barrier_async(self.queue, ^{
234 | __strong __typeof(self) strongSelf = weakSelf;
235 |
236 | if (obj) {
237 | [strongSelf.removedPendingNotify removeObject:key];
238 | strongSelf.items[key] = obj;
239 | strongSelf.updatedPendingNotify[key] = obj;
240 | } else if (strongSelf.items[key]) { // removing existing key
241 | [strongSelf.removedPendingNotify addObject:key];
242 | [strongSelf.items removeObjectForKey:key];
243 | [strongSelf.updatedPendingNotify removeObjectForKey:key];
244 | }
245 | });
246 | }
247 |
248 | #pragma mark - Key-Value Management
249 |
250 | - (void)removeAllObjects {
251 | AssertNotPrivateQueue;
252 |
253 | dispatch_barrier_sync(self.queue, ^{
254 | for (id key in self.items) {
255 | [self.updatedPendingNotify removeObjectForKey:key];
256 | [self.removedPendingNotify addObject:key];
257 | }
258 |
259 | [self.items removeAllObjects];
260 | });
261 | }
262 |
263 | - (void)removeObjectsForKeys:(NSArray *)keys {
264 | AssertNotPrivateQueue;
265 |
266 | if (!keys.count) {
267 | return;
268 | }
269 |
270 | dispatch_barrier_sync(self.queue, ^{
271 | for (id key in keys) {
272 | if (self.items[key]) {
273 | [self.removedPendingNotify addObject:key];
274 | [self.updatedPendingNotify removeObjectForKey:key];
275 | [self.items removeObjectForKey:key];
276 | }
277 | }
278 | });
279 | }
280 |
281 | #pragma mark - Notification
282 |
283 | - (void)sendPendingNotifications {
284 | AssertPrivateQueue;
285 |
286 | NSDictionary *updatedPending = self.updatedPendingNotify ? self.updatedPendingNotify : @{};
287 | NSSet *removedPending = self.removedPendingNotify ? self.removedPendingNotify : [NSSet set];
288 | if (!updatedPending.count && !removedPending.count) {
289 | return;
290 | }
291 | self.updatedPendingNotify = [NSMutableDictionary dictionary];
292 | self.removedPendingNotify = [NSMutableSet set];
293 |
294 | dispatch_async(dispatch_get_main_queue(), ^{ // does not require a barrier since setObject: is the only other mutator
295 | [[NSNotificationCenter defaultCenter] postNotificationName:kYFCacheDidChangeNotification
296 | object:self
297 | userInfo:@{ kYFCacheUpdatedItemsUserInfoKey: updatedPending,
298 | kYFCacheRemovedItemsUserInfoKey: removedPending }];
299 | });
300 | }
301 |
302 | #pragma mark - Cleanup
303 |
304 | - (void)purgeEvictableItems:(void *)context {
305 | // All external execution must have been dispatched to another queue so as to not leak the private queue
306 | // though the user-provided evictionDecider block.
307 | AssertNotPrivateQueue;
308 |
309 | // Don't clean up if no expiration decider block
310 | if (!self.evictionDecider) {
311 | return;
312 | }
313 |
314 | NSDictionary *items = self.allItems; // sync & safe
315 | YMMemoryCacheEvictionDecider evictionDecider = self.evictionDecider;
316 | NSMutableArray *itemKeysToPurge = [NSMutableArray new];
317 |
318 | for (id key in items) {
319 | id value = items[key];
320 |
321 | BOOL shouldEvict = evictionDecider(key, value, context);
322 | if (shouldEvict) {
323 | [itemKeysToPurge addObject:key];
324 | }
325 | }
326 |
327 | [self removeObjectsForKeys:itemKeysToPurge];
328 | }
329 |
330 | @end
331 |
--------------------------------------------------------------------------------
/Examples/Mantle/YMCacheMantleExample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1A05FE20A2A725C3775CF3FF /* Pods_YMCacheMantleExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EAAF3A13B6D02E10BC145DD /* Pods_YMCacheMantleExample.framework */; };
11 | FCDDABC71B701243006C333F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = FCDDABC61B701243006C333F /* main.m */; };
12 | FCDDABCA1B701243006C333F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = FCDDABC91B701243006C333F /* AppDelegate.m */; };
13 | FCDDABCD1B701243006C333F /* TableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FCDDABCC1B701243006C333F /* TableViewController.m */; };
14 | FCDDABD21B701243006C333F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FCDDABD11B701243006C333F /* Images.xcassets */; };
15 | FCDDABEE1B7012BE006C333F /* YMCachePersistenceController+MantleSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = FCDDABEB1B7012BE006C333F /* YMCachePersistenceController+MantleSupport.m */; };
16 | FCDDABEF1B7012BE006C333F /* YMMantleSerializer.m in Sources */ = {isa = PBXBuildFile; fileRef = FCDDABED1B7012BE006C333F /* YMMantleSerializer.m */; };
17 | FCDDABF21B701360006C333F /* Stock.m in Sources */ = {isa = PBXBuildFile; fileRef = FCDDABF11B701360006C333F /* Stock.m */; };
18 | FCDDABF81B701677006C333F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FCDDABCE1B701243006C333F /* Main.storyboard */; };
19 | FCDDABF91B70167A006C333F /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = FCDDABD31B701243006C333F /* LaunchScreen.xib */; };
20 | FCDDABFB1B701691006C333F /* stock.json in Resources */ = {isa = PBXBuildFile; fileRef = FCDDABF61B70143A006C333F /* stock.json */; };
21 | /* End PBXBuildFile section */
22 |
23 | /* Begin PBXFileReference section */
24 | 4697FDB2E18840AB71844213 /* Pods-YMCacheMantleExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YMCacheMantleExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-YMCacheMantleExample/Pods-YMCacheMantleExample.release.xcconfig"; sourceTree = ""; };
25 | 6EAAF3A13B6D02E10BC145DD /* Pods_YMCacheMantleExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_YMCacheMantleExample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
26 | 8C9405CF2A6A466514E7ED8F /* Pods-YMCacheMantleExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-YMCacheMantleExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-YMCacheMantleExample/Pods-YMCacheMantleExample.debug.xcconfig"; sourceTree = ""; };
27 | FCDDABC11B701243006C333F /* YMCacheMantleExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YMCacheMantleExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
28 | FCDDABC51B701243006C333F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
29 | FCDDABC61B701243006C333F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
30 | FCDDABC81B701243006C333F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
31 | FCDDABC91B701243006C333F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
32 | FCDDABCB1B701243006C333F /* TableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TableViewController.h; sourceTree = ""; };
33 | FCDDABCC1B701243006C333F /* TableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TableViewController.m; sourceTree = ""; };
34 | FCDDABCF1B701243006C333F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
35 | FCDDABD11B701243006C333F /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
36 | FCDDABD41B701243006C333F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
37 | FCDDABEA1B7012BE006C333F /* YMCachePersistenceController+MantleSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "YMCachePersistenceController+MantleSupport.h"; sourceTree = ""; };
38 | FCDDABEB1B7012BE006C333F /* YMCachePersistenceController+MantleSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "YMCachePersistenceController+MantleSupport.m"; sourceTree = ""; };
39 | FCDDABEC1B7012BE006C333F /* YMMantleSerializer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YMMantleSerializer.h; sourceTree = ""; };
40 | FCDDABED1B7012BE006C333F /* YMMantleSerializer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YMMantleSerializer.m; sourceTree = ""; };
41 | FCDDABF01B701360006C333F /* Stock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Stock.h; sourceTree = ""; };
42 | FCDDABF11B701360006C333F /* Stock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Stock.m; sourceTree = ""; };
43 | FCDDABF61B70143A006C333F /* stock.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = stock.json; sourceTree = ""; };
44 | /* End PBXFileReference section */
45 |
46 | /* Begin PBXFrameworksBuildPhase section */
47 | FCDDABBE1B701243006C333F /* Frameworks */ = {
48 | isa = PBXFrameworksBuildPhase;
49 | buildActionMask = 2147483647;
50 | files = (
51 | 1A05FE20A2A725C3775CF3FF /* Pods_YMCacheMantleExample.framework in Frameworks */,
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | 0D99D23B6907BE548A06A73F /* Pods */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 8C9405CF2A6A466514E7ED8F /* Pods-YMCacheMantleExample.debug.xcconfig */,
62 | 4697FDB2E18840AB71844213 /* Pods-YMCacheMantleExample.release.xcconfig */,
63 | );
64 | name = Pods;
65 | sourceTree = "";
66 | };
67 | D245D262AD0DBAF134CE7D7C /* Frameworks */ = {
68 | isa = PBXGroup;
69 | children = (
70 | 6EAAF3A13B6D02E10BC145DD /* Pods_YMCacheMantleExample.framework */,
71 | );
72 | name = Frameworks;
73 | sourceTree = "";
74 | };
75 | FCDDABB81B701243006C333F = {
76 | isa = PBXGroup;
77 | children = (
78 | FCDDABC31B701243006C333F /* YMCacheMantleExample */,
79 | FCDDABC21B701243006C333F /* Products */,
80 | 0D99D23B6907BE548A06A73F /* Pods */,
81 | D245D262AD0DBAF134CE7D7C /* Frameworks */,
82 | );
83 | sourceTree = "";
84 | };
85 | FCDDABC21B701243006C333F /* Products */ = {
86 | isa = PBXGroup;
87 | children = (
88 | FCDDABC11B701243006C333F /* YMCacheMantleExample.app */,
89 | );
90 | name = Products;
91 | sourceTree = "";
92 | };
93 | FCDDABC31B701243006C333F /* YMCacheMantleExample */ = {
94 | isa = PBXGroup;
95 | children = (
96 | FCDDABF41B70136B006C333F /* UI */,
97 | FCDDABF51B701373006C333F /* Data Layer */,
98 | FCDDABC81B701243006C333F /* AppDelegate.h */,
99 | FCDDABC91B701243006C333F /* AppDelegate.m */,
100 | FCDDABF61B70143A006C333F /* stock.json */,
101 | FCDDABCE1B701243006C333F /* Main.storyboard */,
102 | FCDDABD11B701243006C333F /* Images.xcassets */,
103 | FCDDABD31B701243006C333F /* LaunchScreen.xib */,
104 | FCDDABC41B701243006C333F /* Supporting Files */,
105 | );
106 | path = YMCacheMantleExample;
107 | sourceTree = "";
108 | };
109 | FCDDABC41B701243006C333F /* Supporting Files */ = {
110 | isa = PBXGroup;
111 | children = (
112 | FCDDABC51B701243006C333F /* Info.plist */,
113 | FCDDABC61B701243006C333F /* main.m */,
114 | );
115 | name = "Supporting Files";
116 | sourceTree = "";
117 | };
118 | FCDDABF31B701365006C333F /* Model */ = {
119 | isa = PBXGroup;
120 | children = (
121 | FCDDABF01B701360006C333F /* Stock.h */,
122 | FCDDABF11B701360006C333F /* Stock.m */,
123 | );
124 | name = Model;
125 | sourceTree = "";
126 | };
127 | FCDDABF41B70136B006C333F /* UI */ = {
128 | isa = PBXGroup;
129 | children = (
130 | FCDDABCB1B701243006C333F /* TableViewController.h */,
131 | FCDDABCC1B701243006C333F /* TableViewController.m */,
132 | );
133 | name = UI;
134 | sourceTree = "";
135 | };
136 | FCDDABF51B701373006C333F /* Data Layer */ = {
137 | isa = PBXGroup;
138 | children = (
139 | FCDDABF31B701365006C333F /* Model */,
140 | FCDDABEA1B7012BE006C333F /* YMCachePersistenceController+MantleSupport.h */,
141 | FCDDABEB1B7012BE006C333F /* YMCachePersistenceController+MantleSupport.m */,
142 | FCDDABEC1B7012BE006C333F /* YMMantleSerializer.h */,
143 | FCDDABED1B7012BE006C333F /* YMMantleSerializer.m */,
144 | );
145 | name = "Data Layer";
146 | sourceTree = "";
147 | };
148 | /* End PBXGroup section */
149 |
150 | /* Begin PBXNativeTarget section */
151 | FCDDABC01B701243006C333F /* YMCacheMantleExample */ = {
152 | isa = PBXNativeTarget;
153 | buildConfigurationList = FCDDABE41B701243006C333F /* Build configuration list for PBXNativeTarget "YMCacheMantleExample" */;
154 | buildPhases = (
155 | F3003163E4700F556166CDD0 /* [CP] Check Pods Manifest.lock */,
156 | FCDDABBD1B701243006C333F /* Sources */,
157 | FCDDABBE1B701243006C333F /* Frameworks */,
158 | FCDDABBF1B701243006C333F /* Resources */,
159 | 119EACCBCD59D3B14FE65668 /* [CP] Embed Pods Frameworks */,
160 | F435A07B04CB05CBEE059EC2 /* [CP] Copy Pods Resources */,
161 | );
162 | buildRules = (
163 | );
164 | dependencies = (
165 | );
166 | name = YMCacheMantleExample;
167 | productName = YMCacheMantleExample;
168 | productReference = FCDDABC11B701243006C333F /* YMCacheMantleExample.app */;
169 | productType = "com.apple.product-type.application";
170 | };
171 | /* End PBXNativeTarget section */
172 |
173 | /* Begin PBXProject section */
174 | FCDDABB91B701243006C333F /* Project object */ = {
175 | isa = PBXProject;
176 | attributes = {
177 | LastUpgradeCheck = 0640;
178 | ORGANIZATIONNAME = "Yahoo, Inc";
179 | TargetAttributes = {
180 | FCDDABC01B701243006C333F = {
181 | CreatedOnToolsVersion = 6.4;
182 | };
183 | };
184 | };
185 | buildConfigurationList = FCDDABBC1B701243006C333F /* Build configuration list for PBXProject "YMCacheMantleExample" */;
186 | compatibilityVersion = "Xcode 3.2";
187 | developmentRegion = English;
188 | hasScannedForEncodings = 0;
189 | knownRegions = (
190 | en,
191 | Base,
192 | );
193 | mainGroup = FCDDABB81B701243006C333F;
194 | productRefGroup = FCDDABC21B701243006C333F /* Products */;
195 | projectDirPath = "";
196 | projectRoot = "";
197 | targets = (
198 | FCDDABC01B701243006C333F /* YMCacheMantleExample */,
199 | );
200 | };
201 | /* End PBXProject section */
202 |
203 | /* Begin PBXResourcesBuildPhase section */
204 | FCDDABBF1B701243006C333F /* Resources */ = {
205 | isa = PBXResourcesBuildPhase;
206 | buildActionMask = 2147483647;
207 | files = (
208 | FCDDABFB1B701691006C333F /* stock.json in Resources */,
209 | FCDDABD21B701243006C333F /* Images.xcassets in Resources */,
210 | FCDDABF81B701677006C333F /* Main.storyboard in Resources */,
211 | FCDDABF91B70167A006C333F /* LaunchScreen.xib in Resources */,
212 | );
213 | runOnlyForDeploymentPostprocessing = 0;
214 | };
215 | /* End PBXResourcesBuildPhase section */
216 |
217 | /* Begin PBXShellScriptBuildPhase section */
218 | 119EACCBCD59D3B14FE65668 /* [CP] Embed Pods Frameworks */ = {
219 | isa = PBXShellScriptBuildPhase;
220 | buildActionMask = 2147483647;
221 | files = (
222 | );
223 | inputPaths = (
224 | );
225 | name = "[CP] Embed Pods Frameworks";
226 | outputPaths = (
227 | );
228 | runOnlyForDeploymentPostprocessing = 0;
229 | shellPath = /bin/sh;
230 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-YMCacheMantleExample/Pods-YMCacheMantleExample-frameworks.sh\"\n";
231 | showEnvVarsInLog = 0;
232 | };
233 | F3003163E4700F556166CDD0 /* [CP] Check Pods Manifest.lock */ = {
234 | isa = PBXShellScriptBuildPhase;
235 | buildActionMask = 2147483647;
236 | files = (
237 | );
238 | inputPaths = (
239 | );
240 | name = "[CP] Check Pods Manifest.lock";
241 | outputPaths = (
242 | );
243 | runOnlyForDeploymentPostprocessing = 0;
244 | shellPath = /bin/sh;
245 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
246 | showEnvVarsInLog = 0;
247 | };
248 | F435A07B04CB05CBEE059EC2 /* [CP] Copy Pods Resources */ = {
249 | isa = PBXShellScriptBuildPhase;
250 | buildActionMask = 2147483647;
251 | files = (
252 | );
253 | inputPaths = (
254 | );
255 | name = "[CP] Copy Pods Resources";
256 | outputPaths = (
257 | );
258 | runOnlyForDeploymentPostprocessing = 0;
259 | shellPath = /bin/sh;
260 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-YMCacheMantleExample/Pods-YMCacheMantleExample-resources.sh\"\n";
261 | showEnvVarsInLog = 0;
262 | };
263 | /* End PBXShellScriptBuildPhase section */
264 |
265 | /* Begin PBXSourcesBuildPhase section */
266 | FCDDABBD1B701243006C333F /* Sources */ = {
267 | isa = PBXSourcesBuildPhase;
268 | buildActionMask = 2147483647;
269 | files = (
270 | FCDDABF21B701360006C333F /* Stock.m in Sources */,
271 | FCDDABCD1B701243006C333F /* TableViewController.m in Sources */,
272 | FCDDABEF1B7012BE006C333F /* YMMantleSerializer.m in Sources */,
273 | FCDDABCA1B701243006C333F /* AppDelegate.m in Sources */,
274 | FCDDABC71B701243006C333F /* main.m in Sources */,
275 | FCDDABEE1B7012BE006C333F /* YMCachePersistenceController+MantleSupport.m in Sources */,
276 | );
277 | runOnlyForDeploymentPostprocessing = 0;
278 | };
279 | /* End PBXSourcesBuildPhase section */
280 |
281 | /* Begin PBXVariantGroup section */
282 | FCDDABCE1B701243006C333F /* Main.storyboard */ = {
283 | isa = PBXVariantGroup;
284 | children = (
285 | FCDDABCF1B701243006C333F /* Base */,
286 | );
287 | name = Main.storyboard;
288 | sourceTree = "";
289 | };
290 | FCDDABD31B701243006C333F /* LaunchScreen.xib */ = {
291 | isa = PBXVariantGroup;
292 | children = (
293 | FCDDABD41B701243006C333F /* Base */,
294 | );
295 | name = LaunchScreen.xib;
296 | sourceTree = "";
297 | };
298 | /* End PBXVariantGroup section */
299 |
300 | /* Begin XCBuildConfiguration section */
301 | FCDDABE21B701243006C333F /* Debug */ = {
302 | isa = XCBuildConfiguration;
303 | buildSettings = {
304 | ALWAYS_SEARCH_USER_PATHS = NO;
305 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
306 | CLANG_CXX_LIBRARY = "libc++";
307 | CLANG_ENABLE_MODULES = YES;
308 | CLANG_ENABLE_OBJC_ARC = YES;
309 | CLANG_WARN_BOOL_CONVERSION = YES;
310 | CLANG_WARN_CONSTANT_CONVERSION = YES;
311 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
312 | CLANG_WARN_EMPTY_BODY = YES;
313 | CLANG_WARN_ENUM_CONVERSION = YES;
314 | CLANG_WARN_INT_CONVERSION = YES;
315 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
316 | CLANG_WARN_UNREACHABLE_CODE = YES;
317 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
318 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
319 | COPY_PHASE_STRIP = NO;
320 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
321 | ENABLE_STRICT_OBJC_MSGSEND = YES;
322 | GCC_C_LANGUAGE_STANDARD = gnu99;
323 | GCC_DYNAMIC_NO_PIC = NO;
324 | GCC_NO_COMMON_BLOCKS = YES;
325 | GCC_OPTIMIZATION_LEVEL = 0;
326 | GCC_PREPROCESSOR_DEFINITIONS = (
327 | "DEBUG=1",
328 | "$(inherited)",
329 | );
330 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
331 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
332 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
333 | GCC_WARN_UNDECLARED_SELECTOR = YES;
334 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
335 | GCC_WARN_UNUSED_FUNCTION = YES;
336 | GCC_WARN_UNUSED_VARIABLE = YES;
337 | IPHONEOS_DEPLOYMENT_TARGET = 8.4;
338 | MTL_ENABLE_DEBUG_INFO = YES;
339 | ONLY_ACTIVE_ARCH = YES;
340 | SDKROOT = iphoneos;
341 | };
342 | name = Debug;
343 | };
344 | FCDDABE31B701243006C333F /* Release */ = {
345 | isa = XCBuildConfiguration;
346 | buildSettings = {
347 | ALWAYS_SEARCH_USER_PATHS = NO;
348 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
349 | CLANG_CXX_LIBRARY = "libc++";
350 | CLANG_ENABLE_MODULES = YES;
351 | CLANG_ENABLE_OBJC_ARC = YES;
352 | CLANG_WARN_BOOL_CONVERSION = YES;
353 | CLANG_WARN_CONSTANT_CONVERSION = YES;
354 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
355 | CLANG_WARN_EMPTY_BODY = YES;
356 | CLANG_WARN_ENUM_CONVERSION = YES;
357 | CLANG_WARN_INT_CONVERSION = YES;
358 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
359 | CLANG_WARN_UNREACHABLE_CODE = YES;
360 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
361 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
362 | COPY_PHASE_STRIP = NO;
363 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
364 | ENABLE_NS_ASSERTIONS = NO;
365 | ENABLE_STRICT_OBJC_MSGSEND = YES;
366 | GCC_C_LANGUAGE_STANDARD = gnu99;
367 | GCC_NO_COMMON_BLOCKS = YES;
368 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
369 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
370 | GCC_WARN_UNDECLARED_SELECTOR = YES;
371 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
372 | GCC_WARN_UNUSED_FUNCTION = YES;
373 | GCC_WARN_UNUSED_VARIABLE = YES;
374 | IPHONEOS_DEPLOYMENT_TARGET = 8.4;
375 | MTL_ENABLE_DEBUG_INFO = NO;
376 | SDKROOT = iphoneos;
377 | VALIDATE_PRODUCT = YES;
378 | };
379 | name = Release;
380 | };
381 | FCDDABE51B701243006C333F /* Debug */ = {
382 | isa = XCBuildConfiguration;
383 | baseConfigurationReference = 8C9405CF2A6A466514E7ED8F /* Pods-YMCacheMantleExample.debug.xcconfig */;
384 | buildSettings = {
385 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
386 | INFOPLIST_FILE = YMCacheMantleExample/Info.plist;
387 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
388 | PRODUCT_NAME = "$(TARGET_NAME)";
389 | };
390 | name = Debug;
391 | };
392 | FCDDABE61B701243006C333F /* Release */ = {
393 | isa = XCBuildConfiguration;
394 | baseConfigurationReference = 4697FDB2E18840AB71844213 /* Pods-YMCacheMantleExample.release.xcconfig */;
395 | buildSettings = {
396 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
397 | INFOPLIST_FILE = YMCacheMantleExample/Info.plist;
398 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
399 | PRODUCT_NAME = "$(TARGET_NAME)";
400 | };
401 | name = Release;
402 | };
403 | /* End XCBuildConfiguration section */
404 |
405 | /* Begin XCConfigurationList section */
406 | FCDDABBC1B701243006C333F /* Build configuration list for PBXProject "YMCacheMantleExample" */ = {
407 | isa = XCConfigurationList;
408 | buildConfigurations = (
409 | FCDDABE21B701243006C333F /* Debug */,
410 | FCDDABE31B701243006C333F /* Release */,
411 | );
412 | defaultConfigurationIsVisible = 0;
413 | defaultConfigurationName = Release;
414 | };
415 | FCDDABE41B701243006C333F /* Build configuration list for PBXNativeTarget "YMCacheMantleExample" */ = {
416 | isa = XCConfigurationList;
417 | buildConfigurations = (
418 | FCDDABE51B701243006C333F /* Debug */,
419 | FCDDABE61B701243006C333F /* Release */,
420 | );
421 | defaultConfigurationIsVisible = 0;
422 | defaultConfigurationName = Release;
423 | };
424 | /* End XCConfigurationList section */
425 | };
426 | rootObject = FCDDABB91B701243006C333F /* Project object */;
427 | }
428 |
--------------------------------------------------------------------------------
/YMCacheTests/YMMemoryCacheSpec.m:
--------------------------------------------------------------------------------
1 | // Created by Adam Kaplan on 8/2/15.
2 | // Copyright 2015 Yahoo.
3 | // Licensed under the terms of the MIT License. See LICENSE file in the project root.
4 |
5 | @import YMCache;
6 | #import "NSRunLoop+AsyncTestAdditions.h"
7 |
8 | SpecBegin(YMMemoryCacheSpec)
9 |
10 | describe(@"YMMemoryCache", ^{
11 |
12 | NSDictionary *const cacheValues = @{ @"Key0": @"Value0",
13 | @"Key1": @"Value1",
14 | @"Key2": @"Value2" };
15 |
16 | static NSString *const cacheName = @"TestCache";
17 |
18 | __block YMMemoryCacheEvictionDecider decider;
19 | __block YMMemoryCache *emptyCache;
20 | __block YMMemoryCache *populatedCache;
21 |
22 | beforeEach(^{
23 | emptyCache = [YMMemoryCache memoryCacheWithName:cacheName];
24 | emptyCache.notificationInterval = 0.25;
25 |
26 | decider = ^BOOL(id key, id value, void *ctx) {
27 | return NO;
28 | };
29 |
30 | populatedCache = [YMMemoryCache memoryCacheWithName:cacheName evictionDecider:^BOOL(id key, id value, void *context) {
31 | if (!decider) { // afterEach is brutal in it's cleanup.
32 | return NO;
33 | }
34 | return decider(key, value, context); // tests can swap this value out as needed.
35 | }];
36 | [populatedCache addEntriesFromDictionary:cacheValues];
37 | });
38 |
39 | afterEach(^{
40 | // Only YOU can prevent memory leaks!
41 | emptyCache = nil;
42 | populatedCache = nil;
43 | decider = NULL;
44 | });
45 |
46 | // Single Setter
47 |
48 | context(@"Single setter", ^{
49 |
50 | it(@"Should set value via keyed subscripting", ^{
51 | emptyCache[@"key"] = @"Value!";
52 | expect(emptyCache[@"key"]).to.beIdenticalTo(@"Value!");
53 | });
54 |
55 | it(@"Should unset value via keyed subscripting", ^{
56 | id key = cacheValues.allKeys.firstObject;
57 | populatedCache[key] = nil;
58 | expect(populatedCache[key]).to.beNil();
59 | });
60 |
61 | });
62 |
63 | // Single Getter
64 |
65 | context(@"Getter", ^{
66 |
67 | it(@"Should return value", ^{
68 | id key = cacheValues.allKeys.firstObject;
69 | expect(populatedCache[key]).to.equal(cacheValues[key]);
70 | });
71 |
72 | it(@"Should return nil when a key has no value", ^{
73 | expect(emptyCache[@"key"]).to.beNil();
74 | });
75 |
76 | });
77 |
78 | // Default-Loading Getter
79 |
80 | describe(@"Getter with loader block", ^{
81 |
82 | context(@"Without default block", ^{
83 |
84 | id(^nullBlock)() = NULL;
85 |
86 | it(@"returns value", ^{
87 | id key = cacheValues.allKeys.firstObject;
88 | expect([populatedCache objectForKey:key withDefault:nullBlock]).to.equal(cacheValues[key]);
89 | });
90 |
91 | it(@"returns nil for missing value", ^{
92 | expect([emptyCache objectForKey:@"key" withDefault:nullBlock]).to.beNil();
93 | });
94 |
95 | });
96 |
97 | context(@"With default block", ^{
98 |
99 | it(@"returns value when present", ^{
100 | id key = cacheValues.allKeys.firstObject;
101 |
102 | __block BOOL invoked = NO;
103 | id val = [populatedCache objectForKey:key withDefault:^id _Nullable{
104 | // failure case, the value should not be returned, and the block
105 | // should not be invoked. The later is a performance test.
106 | invoked = YES;
107 | return @"foo";
108 | }];
109 |
110 | expect(val).to.equal(cacheValues[key]);
111 | expect(invoked).to.beFalsy();
112 | });
113 |
114 | it(@"loads and returns value when missing", ^{
115 | id val = [populatedCache objectForKey:@"MISSING_KEY" withDefault:^id _Nullable{
116 | return @"abcdef";
117 | }];
118 |
119 | expect(val).to.equal(@"abcdef");
120 | });
121 |
122 | it(@"loads and returns nil when missing and loader nil", ^{
123 | __block BOOL invoked = NO;
124 | id val = [populatedCache objectForKey:@"MISSING_KEY" withDefault:^id _Nullable{
125 | invoked = YES;
126 | return nil;
127 | }];
128 |
129 | expect(val).to.beNil();
130 | expect(invoked).to.beTruthy();
131 | });
132 |
133 | });
134 |
135 |
136 | });
137 |
138 | context(@"Bulk setter", ^{
139 |
140 | it(@"Should do nothing on empty value dictionary", ^{
141 | emptyCache[@"Key"] = @"Wheee!";
142 | NSDictionary *dict; // can't pass nil directly; compiler warning due to __nonnull annotation
143 | [emptyCache addEntriesFromDictionary:dict];
144 | expect(emptyCache[@"Key"]).to.equal(@"Wheee!");
145 | });
146 |
147 | it(@"Should set all new values from a dictionary", ^{
148 | [emptyCache addEntriesFromDictionary:cacheValues];
149 |
150 | for (id key in cacheValues) {
151 | expect(emptyCache[key]).to.beIdenticalTo(emptyCache[key]);
152 | }
153 | });
154 |
155 | it(@"Should set all values from a dictionary, overriding existing", ^{
156 | NSMutableDictionary *updatesDict = [cacheValues mutableCopy];
157 | updatesDict[@"Key0"] = @"BORK!";
158 | updatesDict[@"Key2"] = @"ZONK!";
159 | updatesDict[@"Key3"] = @"Value3";
160 |
161 | [populatedCache addEntriesFromDictionary:updatesDict];
162 |
163 | for (id key in updatesDict) {
164 | expect(populatedCache[key]).to.beIdenticalTo(updatesDict[key]);
165 | }
166 | });
167 | });
168 |
169 | context(@"-removeAllObjects", ^{
170 |
171 | it(@"Should do nothing (not crash) on empty cache", ^{
172 | [emptyCache removeAllObjects];
173 | });
174 |
175 | it(@"Should remove all items", ^{
176 | [populatedCache removeAllObjects];
177 |
178 | for (id key in cacheValues) {
179 | expect(populatedCache[key]).to.beNil();
180 | }
181 | });
182 |
183 | });
184 |
185 | context(@"-removeObjectsForKeys:", ^{
186 |
187 | NSArray *nilArray; // can't pass nil directly; compiler warning due to __nonnull annotation
188 |
189 | it(@"Should do nothing (not crash) on empty cache", ^{
190 | [emptyCache removeObjectsForKeys:@[@"Key"]];
191 | });
192 |
193 | it(@"Should do nothing (not crash) on empty cache & empty key array", ^{
194 | [emptyCache removeObjectsForKeys:nilArray];
195 | });
196 |
197 | it(@"Should do nothing (not crash) on empty key array", ^{
198 | [populatedCache removeObjectsForKeys:nilArray];
199 |
200 | for (id key in cacheValues) {
201 | expect(populatedCache[key]).to.beIdenticalTo(cacheValues[key]);
202 | }
203 | });
204 |
205 | it(@"Should remove: single key in single-value cache", ^{
206 | id key = @"Key";
207 | emptyCache[key] = @"Val";
208 |
209 | [emptyCache removeObjectsForKeys:@[key]];
210 |
211 | expect(emptyCache[key]).to.beNil();
212 | });
213 |
214 | it(@"Should remove: single key in multi-value cache", ^{
215 | NSString *removedKey = cacheValues.allKeys.firstObject;
216 | [populatedCache removeObjectsForKeys:@[removedKey]];
217 |
218 | NSMutableDictionary *newValues = [cacheValues mutableCopy];
219 | [newValues removeObjectForKey:removedKey];
220 |
221 | expect(populatedCache[removedKey]).to.beNil();
222 | for (id key in newValues) {
223 | expect(populatedCache[key]).to.beIdenticalTo(cacheValues[key]);
224 | }
225 | });
226 |
227 | it(@"Should remove: multiple keys in multi-value cache", ^{
228 | [populatedCache removeObjectsForKeys:cacheValues.allKeys];
229 |
230 | for (id key in cacheValues) {
231 | expect(populatedCache[key]).to.beNil();
232 | }
233 | });
234 | });
235 |
236 | context(@"-allItems", ^{
237 |
238 | it(@"Should return empty dictionary from empty cache", ^{
239 | expect(emptyCache.allItems).to.beEmpty();
240 | });
241 |
242 | it(@"Should return all items from cache", ^{
243 | expect(populatedCache.allItems).to.equal(cacheValues);
244 | });
245 | });
246 |
247 | context(@"-purgeEvictableItems", ^{
248 |
249 | it(@"Should do nothing (not crash) on empty cache without decider", ^{
250 | [emptyCache purgeEvictableItems:NULL];
251 | });
252 |
253 | it(@"Should do nothing (not crash) on non-empty cache without decider", ^{
254 | for (id key in cacheValues) {
255 | emptyCache[key] = cacheValues[key];
256 | }
257 | [emptyCache purgeEvictableItems:NULL];
258 | for (id key in cacheValues) {
259 | expect(emptyCache[key]).to.beIdenticalTo(cacheValues[key]);
260 | }
261 | });
262 |
263 | it(@"Should do nothing on empty cache with decider", ^{
264 | __block BOOL deciderCalled = NO;
265 | decider = ^BOOL(id key, id value, void *ctx) {
266 | deciderCalled = YES;
267 | return NO;
268 | };
269 | [emptyCache purgeEvictableItems:NULL];
270 | expect(deciderCalled).to.beFalsy();
271 | });
272 |
273 | it(@"Should do nothing on non-empty cache with default (always NO) decider", ^{
274 | [populatedCache purgeEvictableItems:NULL];
275 | expect(populatedCache.allItems).to.equal(cacheValues);
276 | });
277 |
278 | it(@"Should remove all items from cache with an always YES decider", ^{
279 | decider = ^BOOL(id key, id value, void *ctx) {
280 | return YES;
281 | };
282 | [populatedCache purgeEvictableItems:NULL];
283 | expect(populatedCache.allItems).to.beEmpty();
284 | });
285 |
286 | it(@"Should remove some items from cache based on decider", ^{
287 | NSArray *keys = cacheValues.allKeys;
288 | NSArray *keysToRemove = [keys subarrayWithRange:NSMakeRange(0, 2)]; // 2 of 3
289 | NSArray *keysToKeep = [keys subarrayWithRange:NSMakeRange(2, keys.count - 2)];
290 | decider = ^BOOL(id key, id value, void *ctx) {
291 | return [keysToRemove containsObject:key];
292 | };
293 | [populatedCache purgeEvictableItems:NULL];
294 |
295 | for (id key in keysToKeep) { // test kept
296 | expect(populatedCache[key]).to.beIdenticalTo(cacheValues[key]);
297 | }
298 |
299 | for (id key in keysToRemove) { // test removed
300 | expect(populatedCache[key]).to.beNil();
301 | }
302 | });
303 |
304 | it(@"Should pass the eviction contexxt", ^{ // easy dispatch_time bug
305 | __block NSString *context;
306 | decider = ^BOOL(id key, id value, void *ctx) {
307 | context = (__bridge NSString *)(ctx);
308 | return NO;
309 | };
310 |
311 | NSString *notNullPtr = @"Not Null";
312 | [populatedCache purgeEvictableItems:(__bridge void * __nullable)(notNullPtr)];
313 | expect(context).will.beIdenticalTo(notNullPtr);
314 | });
315 | });
316 |
317 | context(@"-evictionInterval", ^{
318 |
319 | it(@"Should return default value if not set", ^{
320 | expect(populatedCache.evictionInterval).will.equal(600.0);
321 | });
322 |
323 | it(@"Should not evict after upon initialization", ^{ // easy dispatch_time bug
324 | __block BOOL deciderCalled = NO;
325 | decider = ^BOOL(id key, id value, void *ctx) {
326 | deciderCalled = YES;
327 | return NO;
328 | };
329 |
330 | [NSThread sleepForTimeInterval:0.25];
331 | expect(deciderCalled).to.beFalsy;
332 | });
333 |
334 | it(@"Should ignore evictionInterval if no decider block set", ^{
335 | NSTimeInterval originalInterval = emptyCache.evictionInterval;
336 | emptyCache.evictionInterval = 123.0;
337 | expect(emptyCache.evictionInterval).after(0.25).equal(originalInterval);
338 | });
339 |
340 | it(@"Should evict after interval", ^{
341 | __block BOOL deciderCalled = NO;
342 | decider = ^BOOL(id key, id value, void *ctx) {
343 | deciderCalled = YES;
344 | return NO;
345 | };
346 |
347 | populatedCache.evictionInterval = 0.01;
348 | expect(deciderCalled).after(0.25).beTruthy;
349 | });
350 |
351 | it(@"Should disable eviction property", ^{
352 | __block BOOL deciderCalled = NO;
353 | decider = ^BOOL(id key, id value, void *ctx) {
354 | deciderCalled = YES;
355 | return NO;
356 | };
357 |
358 | NSTimeInterval interval = 0.5;
359 | populatedCache.evictionInterval = interval;
360 | dispatch_after((dispatch_time_t)((interval / 4.0) * NSEC_PER_SEC),
361 | dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
362 | populatedCache.evictionInterval = 0;
363 | });
364 |
365 | expect(deciderCalled).after(interval * 2.0).beFalsy;
366 | });
367 |
368 | it(@"Should pass NULL for eviction context", ^{ // easy dispatch_time bug
369 | __block void *context = "Not Null";
370 | decider = ^BOOL(id key, id value, void *ctx) {
371 | context = ctx;
372 | return NO;
373 | };
374 |
375 | NSTimeInterval interval = 0.01;
376 | populatedCache.evictionInterval = interval;
377 | expect(context).after(0.25).beNull;
378 | });
379 | });
380 |
381 | describe(@"-sendPendingNotifications", ^{
382 |
383 | it(@"Should NOT send notification if there were no changes", ^{
384 | __block NSNotification *notification;
385 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
386 | object:populatedCache
387 | queue:[NSOperationQueue mainQueue]
388 | usingBlock:^(NSNotification *note) {
389 | notification = note;
390 | }];
391 | populatedCache.notificationInterval = 0.01;
392 | id all = populatedCache.allItems; all = nil; // force block until the interval was set (since it is queue barrier)
393 |
394 | [[NSRunLoop currentRunLoop] runContinuouslyForInterval:0.5];
395 |
396 | expect(notification).to.beNil(); // expect 0 notifications here
397 |
398 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
399 | });
400 |
401 | context(@"bulk addition", ^{
402 |
403 | it(@"Should send notification shortly after -addEntriesFromDictionary", ^{
404 | __block NSNotification *notification;
405 | __block id observer;
406 | waitUntilTimeout(emptyCache.notificationInterval + 1.0, ^(DoneCallback done) {
407 | observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
408 | object:emptyCache
409 | queue:[NSOperationQueue mainQueue]
410 | usingBlock:^(NSNotification *note) {
411 | notification = note;
412 | done();
413 | }];
414 | [emptyCache addEntriesFromDictionary:cacheValues];
415 | });
416 |
417 | NSDictionary *expectDict = @{ kYFCacheUpdatedItemsUserInfoKey: cacheValues,
418 | kYFCacheRemovedItemsUserInfoKey: [NSSet set] };
419 |
420 | expect(notification.userInfo).to.equal(expectDict);
421 |
422 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
423 | });
424 |
425 | it(@"Should send notification including multiple changes during the same period", ^{
426 | __block NSNotification *notification;
427 | __block id observer;
428 | waitUntilTimeout(emptyCache.notificationInterval + 1.0, ^(DoneCallback done) {
429 | observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
430 | object:emptyCache
431 | queue:[NSOperationQueue mainQueue]
432 | usingBlock:^(NSNotification *note) {
433 | notification = note;
434 | done();
435 | }];
436 |
437 | [emptyCache addEntriesFromDictionary:cacheValues];
438 | emptyCache[@"Key"] = @"Value";
439 | });
440 |
441 | NSMutableDictionary *d = [cacheValues mutableCopy];
442 | d[@"Key"] = @"Value";
443 |
444 | NSDictionary *expectDict = @{ kYFCacheUpdatedItemsUserInfoKey: [d copy],
445 | kYFCacheRemovedItemsUserInfoKey: [NSSet set] };
446 |
447 | expect(notification.userInfo).to.equal(expectDict);
448 |
449 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
450 | });
451 |
452 | });
453 |
454 | context(@"bulk removal", ^{
455 |
456 | context(@"-removeAllObjects", ^{
457 |
458 | it(@"Should send notification after -removeAllObjects", ^{
459 | __block NSNotification *notification;
460 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
461 | object:populatedCache
462 | queue:[NSOperationQueue mainQueue]
463 | usingBlock:^(NSNotification *note) {
464 | notification = note;
465 | }];
466 | populatedCache.notificationInterval = 0.01;
467 | NSDictionary *allItems = populatedCache.allItems;
468 | [populatedCache removeAllObjects];
469 |
470 | [[NSRunLoop currentRunLoop] runUntilTrue:^BOOL{
471 | return notification != nil;
472 | } timeout:1.0];
473 |
474 | NSDictionary *expectDict = @{ kYFCacheUpdatedItemsUserInfoKey: @{},
475 | kYFCacheRemovedItemsUserInfoKey: [NSSet setWithArray:allItems.allKeys] };
476 |
477 | expect(notification.userInfo).to.equal(expectDict);
478 |
479 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
480 | });
481 |
482 | it(@"Should send notification after -removeObjectsForKeys: (all keys)", ^{
483 | __block NSNotification *notification;
484 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
485 | object:populatedCache
486 | queue:[NSOperationQueue mainQueue]
487 | usingBlock:^(NSNotification *note) {
488 | notification = note;
489 | }];
490 | populatedCache.notificationInterval = 0.01;
491 | [populatedCache removeObjectsForKeys:cacheValues.allKeys];
492 |
493 | [[NSRunLoop currentRunLoop] runUntilTrue:^BOOL{
494 | return notification != nil;
495 | } timeout:1.0];
496 |
497 | NSDictionary *expectDict = @{ kYFCacheUpdatedItemsUserInfoKey: @{},
498 | kYFCacheRemovedItemsUserInfoKey: [NSSet setWithArray:cacheValues.allKeys] };
499 |
500 | expect(notification.userInfo).to.equal(expectDict);
501 |
502 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
503 | });
504 |
505 | it(@"Should send notification after -removeObjectsForKeys: (one key)", ^{
506 | __block NSNotification *notification;
507 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
508 | object:populatedCache
509 | queue:[NSOperationQueue mainQueue]
510 | usingBlock:^(NSNotification *note) {
511 | notification = note;
512 | }];
513 | populatedCache.notificationInterval = 0.01;
514 | NSString *keyToRemove = cacheValues.allKeys.firstObject;
515 | [populatedCache removeObjectsForKeys:@[keyToRemove]];
516 |
517 | [[NSRunLoop currentRunLoop] runUntilTrue:^BOOL{
518 | return notification != nil;
519 | } timeout:1.0];
520 |
521 | NSDictionary *expectDict = @{ kYFCacheUpdatedItemsUserInfoKey: @{},
522 | kYFCacheRemovedItemsUserInfoKey: [NSSet setWithObject:keyToRemove] };
523 |
524 | expect(notification.userInfo).to.equal(expectDict);
525 |
526 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
527 | });
528 |
529 | it(@"de-dupes added key-val pairs after removal in same period", ^{ // this was a bug: YMCache #8
530 | __block NSNotification *notification;
531 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
532 | object:populatedCache
533 | queue:[NSOperationQueue mainQueue]
534 | usingBlock:^(NSNotification *note) {
535 | notification = note;
536 | }];
537 | populatedCache.notificationInterval = 0.10;
538 |
539 | NSString *key = cacheValues.allKeys.firstObject;
540 | [populatedCache removeObjectsForKeys:@[key]];
541 | [populatedCache addEntriesFromDictionary:@{ key: @"ONE" }];
542 | [populatedCache removeObjectsForKeys:@[key]];
543 |
544 | // wait for the pending notifications to flush.
545 | [[NSRunLoop currentRunLoop] runUntilTrue:^BOOL{
546 | return notification != nil;
547 | } timeout:1.0];
548 |
549 | expect(notification.userInfo).to.equal(@{ kYFCacheUpdatedItemsUserInfoKey: @{},
550 | kYFCacheRemovedItemsUserInfoKey: [NSSet setWithObject:key] });
551 |
552 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
553 | });
554 |
555 | it(@"NOT send notification after removing a key that did not exist", ^{ // this was a bug: YMCache #8
556 | __block NSNotification *notification;
557 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
558 | object:populatedCache
559 | queue:[NSOperationQueue mainQueue]
560 | usingBlock:^(NSNotification *note) {
561 | notification = note;
562 | }];
563 | populatedCache.notificationInterval = 0.10;
564 |
565 | [populatedCache removeObjectsForKeys:@[@"Something"]]; // key not in dict, no-op
566 |
567 | // wait for the pending notifications to flush.
568 | [[NSRunLoop currentRunLoop] runContinuouslyForInterval:0.25];
569 |
570 | expect(notification).to.beNil(); // expect 0 notifications here
571 |
572 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
573 | });
574 |
575 | });
576 |
577 | context(@"keyed subscripting", ^{
578 |
579 | it(@"Sends notification after setting an object", ^{
580 | __block NSNotification *notification;
581 | __block id observer;
582 | waitUntilTimeout(emptyCache.notificationInterval + 1.0, ^(DoneCallback done) {
583 | observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
584 | object:emptyCache
585 | queue:[NSOperationQueue mainQueue]
586 | usingBlock:^(NSNotification *note) {
587 | notification = note;
588 | done();
589 | }];
590 | emptyCache[@"Key"] = @"Value";
591 | });
592 |
593 | NSDictionary *expectDict = @{ kYFCacheUpdatedItemsUserInfoKey: @{@"Key": @"Value"},
594 | kYFCacheRemovedItemsUserInfoKey: [NSSet set] };
595 |
596 | expect(notification.userInfo).to.equal(expectDict);
597 |
598 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
599 | });
600 |
601 | it(@"Sends notification after un-setting an object", ^{
602 | __block NSNotification *notification;
603 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
604 | object:populatedCache
605 | queue:[NSOperationQueue mainQueue]
606 | usingBlock:^(NSNotification *note) {
607 | notification = note;
608 | }];
609 | populatedCache.notificationInterval = 0.01;
610 | NSString *keyToRemove = cacheValues.allKeys.firstObject;
611 | populatedCache[keyToRemove] = nil;
612 |
613 | [[NSRunLoop currentRunLoop] runUntilTrue:^BOOL{
614 | return notification != nil;
615 | } timeout:1.0];
616 |
617 | NSDictionary *expectDict = @{ kYFCacheUpdatedItemsUserInfoKey: @{},
618 | kYFCacheRemovedItemsUserInfoKey: [NSSet setWithObject:keyToRemove] };
619 |
620 | expect(notification.userInfo).to.equal(expectDict);
621 |
622 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
623 | });
624 |
625 | it(@"NOT send notification if there were no changes AFTER a change", ^{ // this was a bug: YMCache #8
626 | populatedCache.notificationInterval = 0.01;
627 | id all = populatedCache.allItems; all = nil; // force block until the interval was set (since it is queue barrier)
628 |
629 | __block NSNotification *notification;
630 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
631 | object:populatedCache
632 | queue:[NSOperationQueue mainQueue]
633 | usingBlock:^(NSNotification *note) {
634 | notification = note;
635 | }];
636 |
637 | populatedCache[@"Something"] = @"ONE";
638 |
639 | // wait for the pending notifications to flush.
640 | [[NSRunLoop currentRunLoop] runUntilTrue:^BOOL{
641 | return notification != nil;
642 | } timeout:1.0];
643 |
644 | expect(notification).toNot.beNil(); // expect 0 notifications here
645 | notification = nil;
646 |
647 | [[NSRunLoop currentRunLoop] runContinuouslyForInterval:0.25];
648 |
649 | expect(notification).to.beNil(); // expect 0 notifications here
650 |
651 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
652 | });
653 |
654 | it(@"de-dupes added key-val pairs after removal in same period", ^{ // this was a bug: YMCache #8
655 | populatedCache.notificationInterval = 0.1;
656 | id all = populatedCache.allItems; all = nil; // force block until the interval was set (since it is queue barrier)
657 |
658 | __block NSNotification *notification;
659 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
660 | object:populatedCache
661 | queue:[NSOperationQueue mainQueue]
662 | usingBlock:^(NSNotification *note) {
663 | notification = note;
664 | }];
665 |
666 | NSString *key = @"Something";
667 | populatedCache[key] = @"ONE";
668 | populatedCache[key] = nil;
669 |
670 | // wait for the pending notifications to flush.
671 | [[NSRunLoop currentRunLoop] runUntilTrue:^BOOL{
672 | return notification != nil;
673 | } timeout:1.0];
674 |
675 | expect(notification.userInfo).to.equal(@{ kYFCacheUpdatedItemsUserInfoKey: @{},
676 | kYFCacheRemovedItemsUserInfoKey: [NSSet setWithObject:key] });
677 |
678 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
679 | });
680 |
681 | it(@"NOT send notification after unset a key that was not set", ^{ // this was a bug: YMCache #8
682 | populatedCache.notificationInterval = 0.1;
683 | id all = populatedCache.allItems; all = nil; // force block until the interval was set (since it is queue barrier)
684 |
685 | __block NSNotification *notification;
686 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
687 | object:populatedCache
688 | queue:[NSOperationQueue mainQueue]
689 | usingBlock:^(NSNotification *note) {
690 | notification = note;
691 | }];
692 |
693 | populatedCache[@"Something"] = nil; // key not in dict, no-op
694 |
695 | // wait for the pending notifications to flush.
696 | [[NSRunLoop currentRunLoop] runContinuouslyForInterval:0.25];
697 |
698 | expect(notification).to.beNil(); // expect 0 notifications here
699 |
700 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
701 | });
702 |
703 | });
704 |
705 | });
706 |
707 | });
708 |
709 | context(@"-notificationInterval", ^{
710 |
711 | it(@"Should return the default value after initalization", ^{
712 | expect(populatedCache.notificationInterval).to.equal(0.0);
713 | });
714 |
715 | it(@"Should return the new value after setting", ^{
716 | emptyCache.notificationInterval = 500.0;
717 | expect(emptyCache.notificationInterval).will.equal(500.0);
718 | });
719 |
720 | it(@"Should NOT send notification when interval is 0", ^{
721 | __block NSNotification *notification;
722 | id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
723 | object:populatedCache
724 | queue:[NSOperationQueue mainQueue]
725 | usingBlock:^(NSNotification *note) {
726 | notification = note;
727 | }];
728 | populatedCache.notificationInterval = 0.2;
729 | populatedCache.notificationInterval = 0.0;
730 | [populatedCache removeAllObjects];
731 | populatedCache.notificationInterval = 0.2;
732 |
733 | [[NSRunLoop currentRunLoop] runContinuouslyForInterval:0.5];
734 |
735 | expect(notification).to.beNil();
736 |
737 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
738 | });
739 |
740 | it(@"Should not include in notification changes that were made while notifications were disabled", ^{
741 | __block NSNotification *notification;
742 | __block id observer;
743 | waitUntil(^(DoneCallback done) {
744 | observer = [[NSNotificationCenter defaultCenter] addObserverForName:kYFCacheDidChangeNotification
745 | object:populatedCache
746 | queue:[NSOperationQueue mainQueue]
747 | usingBlock:^(NSNotification *note) {
748 | notification = note;
749 | done();
750 | }];
751 |
752 | populatedCache.notificationInterval = 0.0;
753 | [populatedCache removeAllObjects];
754 |
755 | populatedCache.notificationInterval = 0.01;
756 | populatedCache[@"Key"] = @"Value";
757 | });
758 |
759 | NSDictionary *expectDict = @{ kYFCacheUpdatedItemsUserInfoKey: @{ @"Key": @"Value" },
760 | kYFCacheRemovedItemsUserInfoKey: [NSSet set] };
761 |
762 | [[NSRunLoop currentRunLoop] runUntilTrue:^BOOL{
763 | return notification != nil;
764 | } timeout:1.0];
765 |
766 | expect(notification.userInfo).will.equal(expectDict);
767 |
768 | [[NSNotificationCenter defaultCenter] removeObserver:observer];
769 | });
770 |
771 | // Notification interval timing precision is pretty well covered by the -sendNotifications tests.
772 | });
773 | });
774 |
775 | SpecEnd
776 |
--------------------------------------------------------------------------------
/YMCache.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | FC4B27521BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = FC4B27511BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m */; };
11 | FC4B27531BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = FC4B27511BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m */; };
12 | FC59B36E1B71156B00093177 /* YMCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC59B3631B71156B00093177 /* YMCache.framework */; };
13 | FC59B37C1B7115FC00093177 /* YMCachePersistenceController.m in Sources */ = {isa = PBXBuildFile; fileRef = FCB05DDB1B6FDFC300C7302B /* YMCachePersistenceController.m */; };
14 | FC59B37D1B7115FD00093177 /* YMMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = FCB05DDE1B6FDFC300C7302B /* YMMemoryCache.m */; };
15 | FC59B37E1B71160100093177 /* YMCache.h in Headers */ = {isa = PBXBuildFile; fileRef = FCDDAB611B7008D4006C333F /* YMCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
16 | FC59B37F1B71160600093177 /* YMCachePersistenceController.h in Headers */ = {isa = PBXBuildFile; fileRef = FCB05DDA1B6FDFC300C7302B /* YMCachePersistenceController.h */; settings = {ATTRIBUTES = (Public, ); }; };
17 | FC59B3801B71160900093177 /* YMMemoryCache.h in Headers */ = {isa = PBXBuildFile; fileRef = FCB05DDD1B6FDFC300C7302B /* YMMemoryCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
18 | FC59B3811B71160B00093177 /* YMLog.h in Headers */ = {isa = PBXBuildFile; fileRef = FCB05DDC1B6FDFC300C7302B /* YMLog.h */; };
19 | FC59B3821B712AAA00093177 /* YMMemoryCacheSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = FCDDAB831B700DB7006C333F /* YMMemoryCacheSpec.m */; };
20 | FC59B4131B71359700093177 /* Expecta.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC59B4111B71359700093177 /* Expecta.framework */; };
21 | FC59B4141B71359700093177 /* Specta.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC59B4121B71359700093177 /* Specta.framework */; };
22 | FC59B4171B7135A200093177 /* Expecta.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC59B4151B7135A200093177 /* Expecta.framework */; };
23 | FC59B4181B7135A200093177 /* Specta.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC59B4161B7135A200093177 /* Specta.framework */; };
24 | FC59B4551B713FC800093177 /* Expecta.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = FC59B4111B71359700093177 /* Expecta.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
25 | FC59B4561B713FC800093177 /* Specta.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = FC59B4121B71359700093177 /* Specta.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
26 | FC59B4581B713FF000093177 /* Expecta.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = FC59B4151B7135A200093177 /* Expecta.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
27 | FC59B4591B713FF000093177 /* Specta.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = FC59B4161B7135A200093177 /* Specta.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
28 | FCD375111BACF80E0034AD3B /* NSRunLoop+AsyncTestAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = FCD375101BACF80E0034AD3B /* NSRunLoop+AsyncTestAdditions.m */; };
29 | FCD375121BACF80E0034AD3B /* NSRunLoop+AsyncTestAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = FCD375101BACF80E0034AD3B /* NSRunLoop+AsyncTestAdditions.m */; };
30 | FCD690821B710F520099E854 /* YMCachePersistenceController.m in Sources */ = {isa = PBXBuildFile; fileRef = FCB05DDB1B6FDFC300C7302B /* YMCachePersistenceController.m */; };
31 | FCD690831B710F540099E854 /* YMMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = FCB05DDE1B6FDFC300C7302B /* YMMemoryCache.m */; };
32 | FCD690841B710F5C0099E854 /* YMCache.h in Headers */ = {isa = PBXBuildFile; fileRef = FCDDAB611B7008D4006C333F /* YMCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
33 | FCD690851B710F5E0099E854 /* YMCachePersistenceController.h in Headers */ = {isa = PBXBuildFile; fileRef = FCB05DDA1B6FDFC300C7302B /* YMCachePersistenceController.h */; settings = {ATTRIBUTES = (Public, ); }; };
34 | FCD690861B710F600099E854 /* YMMemoryCache.h in Headers */ = {isa = PBXBuildFile; fileRef = FCB05DDD1B6FDFC300C7302B /* YMMemoryCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
35 | FCD690871B710F6E0099E854 /* YMLog.h in Headers */ = {isa = PBXBuildFile; fileRef = FCB05DDC1B6FDFC300C7302B /* YMLog.h */; };
36 | FCDDAB681B7008D4006C333F /* YMCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCDDAB5D1B7008D4006C333F /* YMCache.framework */; };
37 | FCDDAB851B700DBA006C333F /* YMMemoryCacheSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = FCDDAB831B700DB7006C333F /* YMMemoryCacheSpec.m */; };
38 | /* End PBXBuildFile section */
39 |
40 | /* Begin PBXContainerItemProxy section */
41 | FC59B36F1B71156B00093177 /* PBXContainerItemProxy */ = {
42 | isa = PBXContainerItemProxy;
43 | containerPortal = FCB05DA11B6FDF1800C7302B /* Project object */;
44 | proxyType = 1;
45 | remoteGlobalIDString = FC59B3621B71156B00093177;
46 | remoteInfo = "YMCache-Mac";
47 | };
48 | FCDDAB691B7008D4006C333F /* PBXContainerItemProxy */ = {
49 | isa = PBXContainerItemProxy;
50 | containerPortal = FCB05DA11B6FDF1800C7302B /* Project object */;
51 | proxyType = 1;
52 | remoteGlobalIDString = FCDDAB5C1B7008D4006C333F;
53 | remoteInfo = YMCache;
54 | };
55 | /* End PBXContainerItemProxy section */
56 |
57 | /* Begin PBXCopyFilesBuildPhase section */
58 | FC59B4541B713FC100093177 /* CopyFiles */ = {
59 | isa = PBXCopyFilesBuildPhase;
60 | buildActionMask = 2147483647;
61 | dstPath = "";
62 | dstSubfolderSpec = 10;
63 | files = (
64 | FC59B4551B713FC800093177 /* Expecta.framework in CopyFiles */,
65 | FC59B4561B713FC800093177 /* Specta.framework in CopyFiles */,
66 | );
67 | runOnlyForDeploymentPostprocessing = 0;
68 | };
69 | FC59B4571B713FE700093177 /* CopyFiles */ = {
70 | isa = PBXCopyFilesBuildPhase;
71 | buildActionMask = 2147483647;
72 | dstPath = "";
73 | dstSubfolderSpec = 10;
74 | files = (
75 | FC59B4581B713FF000093177 /* Expecta.framework in CopyFiles */,
76 | FC59B4591B713FF000093177 /* Specta.framework in CopyFiles */,
77 | );
78 | runOnlyForDeploymentPostprocessing = 0;
79 | };
80 | /* End PBXCopyFilesBuildPhase section */
81 |
82 | /* Begin PBXFileReference section */
83 | FC4B27511BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = YMCachePersistenceControllerSpec.m; path = YMCacheTests/YMCachePersistenceControllerSpec.m; sourceTree = SOURCE_ROOT; };
84 | FC59B3631B71156B00093177 /* YMCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = YMCache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
85 | FC59B36D1B71156B00093177 /* YMCache-MacTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "YMCache-MacTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
86 | FC59B4111B71359700093177 /* Expecta.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Expecta.framework; path = Carthage/Build/Mac/Expecta.framework; sourceTree = ""; };
87 | FC59B4121B71359700093177 /* Specta.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Specta.framework; path = Carthage/Build/Mac/Specta.framework; sourceTree = ""; };
88 | FC59B4151B7135A200093177 /* Expecta.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Expecta.framework; path = Carthage/Build/iOS/Expecta.framework; sourceTree = ""; };
89 | FC59B4161B7135A200093177 /* Specta.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Specta.framework; path = Carthage/Build/iOS/Specta.framework; sourceTree = ""; };
90 | FCB05DBA1B6FDF1800C7302B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = YMCacheTests/Info.plist; sourceTree = ""; };
91 | FCB05DDA1B6FDFC300C7302B /* YMCachePersistenceController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YMCachePersistenceController.h; sourceTree = ""; };
92 | FCB05DDB1B6FDFC300C7302B /* YMCachePersistenceController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YMCachePersistenceController.m; sourceTree = ""; };
93 | FCB05DDC1B6FDFC300C7302B /* YMLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YMLog.h; sourceTree = ""; };
94 | FCB05DDD1B6FDFC300C7302B /* YMMemoryCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YMMemoryCache.h; sourceTree = ""; };
95 | FCB05DDE1B6FDFC300C7302B /* YMMemoryCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YMMemoryCache.m; sourceTree = ""; };
96 | FCB05E101B6FE1D600C7302B /* Tests-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Tests-Prefix.pch"; path = "YMCacheTests/Tests-Prefix.pch"; sourceTree = ""; };
97 | FCB05E151B6FE21300C7302B /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; };
98 | FCB05E161B6FE21300C7302B /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; };
99 | FCB05E171B6FE21300C7302B /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
100 | FCB05E181B6FE21300C7302B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
101 | FCB05E341B6FE93F00C7302B /* Cartfile.private */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile.private; sourceTree = ""; };
102 | FCD3750F1BACF80E0034AD3B /* NSRunLoop+AsyncTestAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSRunLoop+AsyncTestAdditions.h"; path = "YMCacheTests/NSRunLoop+AsyncTestAdditions.h"; sourceTree = SOURCE_ROOT; };
103 | FCD375101BACF80E0034AD3B /* NSRunLoop+AsyncTestAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSRunLoop+AsyncTestAdditions.m"; path = "YMCacheTests/NSRunLoop+AsyncTestAdditions.m"; sourceTree = SOURCE_ROOT; };
104 | FCDDAAEE1B6FFE40006C333F /* YMCache.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; name = YMCache.podspec; path = Example/../YMCache.podspec; sourceTree = ""; };
105 | FCDDAB421B7008B2006C333F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
106 | FCDDAB5D1B7008D4006C333F /* YMCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = YMCache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
107 | FCDDAB611B7008D4006C333F /* YMCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YMCache.h; sourceTree = ""; };
108 | FCDDAB671B7008D4006C333F /* YMCache-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "YMCache-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
109 | FCDDAB831B700DB7006C333F /* YMMemoryCacheSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = YMMemoryCacheSpec.m; path = YMCacheTests/YMMemoryCacheSpec.m; sourceTree = SOURCE_ROOT; };
110 | /* End PBXFileReference section */
111 |
112 | /* Begin PBXFrameworksBuildPhase section */
113 | FC59B35F1B71156B00093177 /* Frameworks */ = {
114 | isa = PBXFrameworksBuildPhase;
115 | buildActionMask = 2147483647;
116 | files = (
117 | );
118 | runOnlyForDeploymentPostprocessing = 0;
119 | };
120 | FC59B36A1B71156B00093177 /* Frameworks */ = {
121 | isa = PBXFrameworksBuildPhase;
122 | buildActionMask = 2147483647;
123 | files = (
124 | FC59B36E1B71156B00093177 /* YMCache.framework in Frameworks */,
125 | FC59B4141B71359700093177 /* Specta.framework in Frameworks */,
126 | FC59B4131B71359700093177 /* Expecta.framework in Frameworks */,
127 | );
128 | runOnlyForDeploymentPostprocessing = 0;
129 | };
130 | FCDDAB591B7008D4006C333F /* Frameworks */ = {
131 | isa = PBXFrameworksBuildPhase;
132 | buildActionMask = 2147483647;
133 | files = (
134 | );
135 | runOnlyForDeploymentPostprocessing = 0;
136 | };
137 | FCDDAB641B7008D4006C333F /* Frameworks */ = {
138 | isa = PBXFrameworksBuildPhase;
139 | buildActionMask = 2147483647;
140 | files = (
141 | FCDDAB681B7008D4006C333F /* YMCache.framework in Frameworks */,
142 | FC59B4181B7135A200093177 /* Specta.framework in Frameworks */,
143 | FC59B4171B7135A200093177 /* Expecta.framework in Frameworks */,
144 | );
145 | runOnlyForDeploymentPostprocessing = 0;
146 | };
147 | /* End PBXFrameworksBuildPhase section */
148 |
149 | /* Begin PBXGroup section */
150 | FC59B40F1B71357E00093177 /* iOS */ = {
151 | isa = PBXGroup;
152 | children = (
153 | FC59B4151B7135A200093177 /* Expecta.framework */,
154 | FC59B4161B7135A200093177 /* Specta.framework */,
155 | );
156 | name = iOS;
157 | sourceTree = "";
158 | };
159 | FC59B4101B71358300093177 /* Mac */ = {
160 | isa = PBXGroup;
161 | children = (
162 | FC59B4111B71359700093177 /* Expecta.framework */,
163 | FC59B4121B71359700093177 /* Specta.framework */,
164 | );
165 | name = Mac;
166 | sourceTree = "";
167 | };
168 | FCB05DA01B6FDF1800C7302B = {
169 | isa = PBXGroup;
170 | children = (
171 | FCB05E131B6FE1FA00C7302B /* Project Metadata */,
172 | FCD690881B7110010099E854 /* YMCache */,
173 | FCB05DB81B6FDF1800C7302B /* Tests */,
174 | FCB05E1E1B6FE38700C7302B /* Frameworks */,
175 | FCB05DAA1B6FDF1800C7302B /* Products */,
176 | );
177 | sourceTree = "";
178 | };
179 | FCB05DAA1B6FDF1800C7302B /* Products */ = {
180 | isa = PBXGroup;
181 | children = (
182 | FCDDAB5D1B7008D4006C333F /* YMCache.framework */,
183 | FCDDAB671B7008D4006C333F /* YMCache-iOSTests.xctest */,
184 | FC59B3631B71156B00093177 /* YMCache.framework */,
185 | FC59B36D1B71156B00093177 /* YMCache-MacTests.xctest */,
186 | );
187 | name = Products;
188 | sourceTree = "";
189 | };
190 | FCB05DB81B6FDF1800C7302B /* Tests */ = {
191 | isa = PBXGroup;
192 | children = (
193 | FC4B27511BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m */,
194 | FCDDAB831B700DB7006C333F /* YMMemoryCacheSpec.m */,
195 | FCD3750F1BACF80E0034AD3B /* NSRunLoop+AsyncTestAdditions.h */,
196 | FCD375101BACF80E0034AD3B /* NSRunLoop+AsyncTestAdditions.m */,
197 | FCB05DB91B6FDF1800C7302B /* Supporting Files */,
198 | );
199 | path = Tests;
200 | sourceTree = "";
201 | };
202 | FCB05DB91B6FDF1800C7302B /* Supporting Files */ = {
203 | isa = PBXGroup;
204 | children = (
205 | FCB05E101B6FE1D600C7302B /* Tests-Prefix.pch */,
206 | FCB05DBA1B6FDF1800C7302B /* Info.plist */,
207 | );
208 | name = "Supporting Files";
209 | path = "../YMCacheTests/Supporting Files";
210 | sourceTree = "";
211 | };
212 | FCB05E131B6FE1FA00C7302B /* Project Metadata */ = {
213 | isa = PBXGroup;
214 | children = (
215 | FCB05E341B6FE93F00C7302B /* Cartfile.private */,
216 | FCB05E151B6FE21300C7302B /* CHANGELOG.md */,
217 | FCB05E161B6FE21300C7302B /* CONTRIBUTING.md */,
218 | FCB05E171B6FE21300C7302B /* LICENSE */,
219 | FCB05E181B6FE21300C7302B /* README.md */,
220 | FCDDAAEE1B6FFE40006C333F /* YMCache.podspec */,
221 | );
222 | name = "Project Metadata";
223 | sourceTree = "";
224 | };
225 | FCB05E1E1B6FE38700C7302B /* Frameworks */ = {
226 | isa = PBXGroup;
227 | children = (
228 | FC59B40F1B71357E00093177 /* iOS */,
229 | FC59B4101B71358300093177 /* Mac */,
230 | );
231 | name = Frameworks;
232 | sourceTree = "";
233 | };
234 | FCD690881B7110010099E854 /* YMCache */ = {
235 | isa = PBXGroup;
236 | children = (
237 | FCDDAB611B7008D4006C333F /* YMCache.h */,
238 | FCD6908A1B7110DF0099E854 /* Public */,
239 | FCD6908B1B7110ED0099E854 /* Private */,
240 | FCD690891B71106E0099E854 /* Supporting Files */,
241 | );
242 | path = YMCache;
243 | sourceTree = "";
244 | };
245 | FCD690891B71106E0099E854 /* Supporting Files */ = {
246 | isa = PBXGroup;
247 | children = (
248 | FCDDAB421B7008B2006C333F /* Info.plist */,
249 | );
250 | name = "Supporting Files";
251 | sourceTree = "";
252 | };
253 | FCD6908A1B7110DF0099E854 /* Public */ = {
254 | isa = PBXGroup;
255 | children = (
256 | FCB05DDA1B6FDFC300C7302B /* YMCachePersistenceController.h */,
257 | FCB05DDB1B6FDFC300C7302B /* YMCachePersistenceController.m */,
258 | FCB05DDD1B6FDFC300C7302B /* YMMemoryCache.h */,
259 | FCB05DDE1B6FDFC300C7302B /* YMMemoryCache.m */,
260 | );
261 | name = Public;
262 | sourceTree = "";
263 | };
264 | FCD6908B1B7110ED0099E854 /* Private */ = {
265 | isa = PBXGroup;
266 | children = (
267 | FCB05DDC1B6FDFC300C7302B /* YMLog.h */,
268 | );
269 | name = Private;
270 | sourceTree = "";
271 | };
272 | /* End PBXGroup section */
273 |
274 | /* Begin PBXHeadersBuildPhase section */
275 | FC59B3601B71156B00093177 /* Headers */ = {
276 | isa = PBXHeadersBuildPhase;
277 | buildActionMask = 2147483647;
278 | files = (
279 | FC59B37E1B71160100093177 /* YMCache.h in Headers */,
280 | FC59B37F1B71160600093177 /* YMCachePersistenceController.h in Headers */,
281 | FC59B3801B71160900093177 /* YMMemoryCache.h in Headers */,
282 | FC59B3811B71160B00093177 /* YMLog.h in Headers */,
283 | );
284 | runOnlyForDeploymentPostprocessing = 0;
285 | };
286 | FCDDAB5A1B7008D4006C333F /* Headers */ = {
287 | isa = PBXHeadersBuildPhase;
288 | buildActionMask = 2147483647;
289 | files = (
290 | FCD690841B710F5C0099E854 /* YMCache.h in Headers */,
291 | FCD690851B710F5E0099E854 /* YMCachePersistenceController.h in Headers */,
292 | FCD690861B710F600099E854 /* YMMemoryCache.h in Headers */,
293 | FCD690871B710F6E0099E854 /* YMLog.h in Headers */,
294 | );
295 | runOnlyForDeploymentPostprocessing = 0;
296 | };
297 | /* End PBXHeadersBuildPhase section */
298 |
299 | /* Begin PBXNativeTarget section */
300 | FC59B3621B71156B00093177 /* YMCache-Mac */ = {
301 | isa = PBXNativeTarget;
302 | buildConfigurationList = FC59B3761B71156B00093177 /* Build configuration list for PBXNativeTarget "YMCache-Mac" */;
303 | buildPhases = (
304 | FC59B35E1B71156B00093177 /* Sources */,
305 | FC59B35F1B71156B00093177 /* Frameworks */,
306 | FC59B3601B71156B00093177 /* Headers */,
307 | FC59B3611B71156B00093177 /* Resources */,
308 | );
309 | buildRules = (
310 | );
311 | dependencies = (
312 | );
313 | name = "YMCache-Mac";
314 | productName = "YMCache-Mac";
315 | productReference = FC59B3631B71156B00093177 /* YMCache.framework */;
316 | productType = "com.apple.product-type.framework";
317 | };
318 | FC59B36C1B71156B00093177 /* YMCache-MacTests */ = {
319 | isa = PBXNativeTarget;
320 | buildConfigurationList = FC59B3791B71156B00093177 /* Build configuration list for PBXNativeTarget "YMCache-MacTests" */;
321 | buildPhases = (
322 | FC59B3691B71156B00093177 /* Sources */,
323 | FC59B36A1B71156B00093177 /* Frameworks */,
324 | FC59B36B1B71156B00093177 /* Resources */,
325 | FC59B4541B713FC100093177 /* CopyFiles */,
326 | );
327 | buildRules = (
328 | );
329 | dependencies = (
330 | FC59B3701B71156B00093177 /* PBXTargetDependency */,
331 | );
332 | name = "YMCache-MacTests";
333 | productName = "YMCache-MacTests";
334 | productReference = FC59B36D1B71156B00093177 /* YMCache-MacTests.xctest */;
335 | productType = "com.apple.product-type.bundle.unit-test";
336 | };
337 | FCDDAB5C1B7008D4006C333F /* YMCache-iOS */ = {
338 | isa = PBXNativeTarget;
339 | buildConfigurationList = FCDDAB701B7008D4006C333F /* Build configuration list for PBXNativeTarget "YMCache-iOS" */;
340 | buildPhases = (
341 | FCDDAB581B7008D4006C333F /* Sources */,
342 | FCDDAB591B7008D4006C333F /* Frameworks */,
343 | FCDDAB5A1B7008D4006C333F /* Headers */,
344 | FCDDAB5B1B7008D4006C333F /* Resources */,
345 | );
346 | buildRules = (
347 | );
348 | dependencies = (
349 | );
350 | name = "YMCache-iOS";
351 | productName = YMCache;
352 | productReference = FCDDAB5D1B7008D4006C333F /* YMCache.framework */;
353 | productType = "com.apple.product-type.framework";
354 | };
355 | FCDDAB661B7008D4006C333F /* YMCache-iOSTests */ = {
356 | isa = PBXNativeTarget;
357 | buildConfigurationList = FCDDAB731B7008D4006C333F /* Build configuration list for PBXNativeTarget "YMCache-iOSTests" */;
358 | buildPhases = (
359 | FCDDAB631B7008D4006C333F /* Sources */,
360 | FCDDAB641B7008D4006C333F /* Frameworks */,
361 | FCDDAB651B7008D4006C333F /* Resources */,
362 | FC59B4571B713FE700093177 /* CopyFiles */,
363 | );
364 | buildRules = (
365 | );
366 | dependencies = (
367 | FCDDAB6A1B7008D4006C333F /* PBXTargetDependency */,
368 | );
369 | name = "YMCache-iOSTests";
370 | productName = YMCacheTests;
371 | productReference = FCDDAB671B7008D4006C333F /* YMCache-iOSTests.xctest */;
372 | productType = "com.apple.product-type.bundle.unit-test";
373 | };
374 | /* End PBXNativeTarget section */
375 |
376 | /* Begin PBXProject section */
377 | FCB05DA11B6FDF1800C7302B /* Project object */ = {
378 | isa = PBXProject;
379 | attributes = {
380 | CLASSPREFIX = YM;
381 | LastUpgradeCheck = 1010;
382 | ORGANIZATIONNAME = "Yahoo, Inc";
383 | TargetAttributes = {
384 | FC59B3621B71156B00093177 = {
385 | CreatedOnToolsVersion = 6.4;
386 | };
387 | FC59B36C1B71156B00093177 = {
388 | CreatedOnToolsVersion = 6.4;
389 | };
390 | FCDDAB5C1B7008D4006C333F = {
391 | CreatedOnToolsVersion = 6.4;
392 | };
393 | FCDDAB661B7008D4006C333F = {
394 | CreatedOnToolsVersion = 6.4;
395 | };
396 | };
397 | };
398 | buildConfigurationList = FCB05DA41B6FDF1800C7302B /* Build configuration list for PBXProject "YMCache" */;
399 | compatibilityVersion = "Xcode 3.2";
400 | developmentRegion = English;
401 | hasScannedForEncodings = 0;
402 | knownRegions = (
403 | en,
404 | );
405 | mainGroup = FCB05DA01B6FDF1800C7302B;
406 | productRefGroup = FCB05DAA1B6FDF1800C7302B /* Products */;
407 | projectDirPath = "";
408 | projectRoot = "";
409 | targets = (
410 | FCDDAB5C1B7008D4006C333F /* YMCache-iOS */,
411 | FCDDAB661B7008D4006C333F /* YMCache-iOSTests */,
412 | FC59B3621B71156B00093177 /* YMCache-Mac */,
413 | FC59B36C1B71156B00093177 /* YMCache-MacTests */,
414 | );
415 | };
416 | /* End PBXProject section */
417 |
418 | /* Begin PBXResourcesBuildPhase section */
419 | FC59B3611B71156B00093177 /* Resources */ = {
420 | isa = PBXResourcesBuildPhase;
421 | buildActionMask = 2147483647;
422 | files = (
423 | );
424 | runOnlyForDeploymentPostprocessing = 0;
425 | };
426 | FC59B36B1B71156B00093177 /* Resources */ = {
427 | isa = PBXResourcesBuildPhase;
428 | buildActionMask = 2147483647;
429 | files = (
430 | );
431 | runOnlyForDeploymentPostprocessing = 0;
432 | };
433 | FCDDAB5B1B7008D4006C333F /* Resources */ = {
434 | isa = PBXResourcesBuildPhase;
435 | buildActionMask = 2147483647;
436 | files = (
437 | );
438 | runOnlyForDeploymentPostprocessing = 0;
439 | };
440 | FCDDAB651B7008D4006C333F /* Resources */ = {
441 | isa = PBXResourcesBuildPhase;
442 | buildActionMask = 2147483647;
443 | files = (
444 | );
445 | runOnlyForDeploymentPostprocessing = 0;
446 | };
447 | /* End PBXResourcesBuildPhase section */
448 |
449 | /* Begin PBXSourcesBuildPhase section */
450 | FC59B35E1B71156B00093177 /* Sources */ = {
451 | isa = PBXSourcesBuildPhase;
452 | buildActionMask = 2147483647;
453 | files = (
454 | FC59B37C1B7115FC00093177 /* YMCachePersistenceController.m in Sources */,
455 | FC59B37D1B7115FD00093177 /* YMMemoryCache.m in Sources */,
456 | );
457 | runOnlyForDeploymentPostprocessing = 0;
458 | };
459 | FC59B3691B71156B00093177 /* Sources */ = {
460 | isa = PBXSourcesBuildPhase;
461 | buildActionMask = 2147483647;
462 | files = (
463 | FCD375121BACF80E0034AD3B /* NSRunLoop+AsyncTestAdditions.m in Sources */,
464 | FC59B3821B712AAA00093177 /* YMMemoryCacheSpec.m in Sources */,
465 | FC4B27531BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m in Sources */,
466 | );
467 | runOnlyForDeploymentPostprocessing = 0;
468 | };
469 | FCDDAB581B7008D4006C333F /* Sources */ = {
470 | isa = PBXSourcesBuildPhase;
471 | buildActionMask = 2147483647;
472 | files = (
473 | FCD690821B710F520099E854 /* YMCachePersistenceController.m in Sources */,
474 | FCD690831B710F540099E854 /* YMMemoryCache.m in Sources */,
475 | );
476 | runOnlyForDeploymentPostprocessing = 0;
477 | };
478 | FCDDAB631B7008D4006C333F /* Sources */ = {
479 | isa = PBXSourcesBuildPhase;
480 | buildActionMask = 2147483647;
481 | files = (
482 | FCD375111BACF80E0034AD3B /* NSRunLoop+AsyncTestAdditions.m in Sources */,
483 | FCDDAB851B700DBA006C333F /* YMMemoryCacheSpec.m in Sources */,
484 | FC4B27521BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m in Sources */,
485 | );
486 | runOnlyForDeploymentPostprocessing = 0;
487 | };
488 | /* End PBXSourcesBuildPhase section */
489 |
490 | /* Begin PBXTargetDependency section */
491 | FC59B3701B71156B00093177 /* PBXTargetDependency */ = {
492 | isa = PBXTargetDependency;
493 | target = FC59B3621B71156B00093177 /* YMCache-Mac */;
494 | targetProxy = FC59B36F1B71156B00093177 /* PBXContainerItemProxy */;
495 | };
496 | FCDDAB6A1B7008D4006C333F /* PBXTargetDependency */ = {
497 | isa = PBXTargetDependency;
498 | target = FCDDAB5C1B7008D4006C333F /* YMCache-iOS */;
499 | targetProxy = FCDDAB691B7008D4006C333F /* PBXContainerItemProxy */;
500 | };
501 | /* End PBXTargetDependency section */
502 |
503 | /* Begin XCBuildConfiguration section */
504 | FC4B27541BA13A2000F870A1 /* Test */ = {
505 | isa = XCBuildConfiguration;
506 | buildSettings = {
507 | ALWAYS_SEARCH_USER_PATHS = NO;
508 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
509 | CLANG_CXX_LIBRARY = "libc++";
510 | CLANG_ENABLE_MODULES = YES;
511 | CLANG_ENABLE_OBJC_ARC = YES;
512 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
513 | CLANG_WARN_BOOL_CONVERSION = YES;
514 | CLANG_WARN_COMMA = YES;
515 | CLANG_WARN_CONSTANT_CONVERSION = YES;
516 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
517 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
518 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
519 | CLANG_WARN_EMPTY_BODY = YES;
520 | CLANG_WARN_ENUM_CONVERSION = YES;
521 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
522 | CLANG_WARN_INFINITE_RECURSION = YES;
523 | CLANG_WARN_INT_CONVERSION = YES;
524 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
525 | CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
526 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
527 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
528 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
529 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
530 | CLANG_WARN_STRICT_PROTOTYPES = YES;
531 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
532 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
533 | CLANG_WARN_UNREACHABLE_CODE = YES;
534 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
535 | COPY_PHASE_STRIP = NO;
536 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
537 | ENABLE_STRICT_OBJC_MSGSEND = YES;
538 | GCC_C_LANGUAGE_STANDARD = gnu99;
539 | GCC_DYNAMIC_NO_PIC = NO;
540 | GCC_GENERATE_TEST_COVERAGE_FILES = YES;
541 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
542 | GCC_NO_COMMON_BLOCKS = YES;
543 | GCC_OPTIMIZATION_LEVEL = 0;
544 | GCC_PREPROCESSOR_DEFINITIONS = (
545 | "DEBUG=1",
546 | "NS_BLOCK_ASSERTIONS=1",
547 | "$(inherited)",
548 | );
549 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
550 | GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
551 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
552 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
553 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
554 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
555 | GCC_WARN_SHADOW = YES;
556 | GCC_WARN_STRICT_SELECTOR_MATCH = YES;
557 | GCC_WARN_UNDECLARED_SELECTOR = YES;
558 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
559 | GCC_WARN_UNUSED_FUNCTION = YES;
560 | GCC_WARN_UNUSED_LABEL = YES;
561 | GCC_WARN_UNUSED_VARIABLE = YES;
562 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
563 | MACOSX_DEPLOYMENT_TARGET = 10.10;
564 | MTL_ENABLE_DEBUG_INFO = YES;
565 | ONLY_ACTIVE_ARCH = YES;
566 | SDKROOT = iphoneos;
567 | };
568 | name = Test;
569 | };
570 | FC4B27551BA13A2000F870A1 /* Test */ = {
571 | isa = XCBuildConfiguration;
572 | buildSettings = {
573 | APPLICATION_EXTENSION_API_ONLY = YES;
574 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
575 | CURRENT_PROJECT_VERSION = 1;
576 | DEFINES_MODULE = YES;
577 | DYLIB_COMPATIBILITY_VERSION = 1;
578 | DYLIB_CURRENT_VERSION = 1;
579 | DYLIB_INSTALL_NAME_BASE = "@rpath";
580 | GCC_PREPROCESSOR_DEFINITIONS = (
581 | "DEBUG=1",
582 | "$(inherited)",
583 | );
584 | INFOPLIST_FILE = YMCache/Info.plist;
585 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
586 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
587 | PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
588 | PRODUCT_NAME = "$(PROJECT_NAME)";
589 | SKIP_INSTALL = YES;
590 | TARGETED_DEVICE_FAMILY = "1,2";
591 | VERSIONING_SYSTEM = "apple-generic";
592 | VERSION_INFO_PREFIX = "";
593 | };
594 | name = Test;
595 | };
596 | FC4B27561BA13A2000F870A1 /* Test */ = {
597 | isa = XCBuildConfiguration;
598 | buildSettings = {
599 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
600 | FRAMEWORK_SEARCH_PATHS = (
601 | "$(inherited)",
602 | "$(PROJECT_DIR)/Carthage/Build/iOS/**",
603 | );
604 | GCC_PREFIX_HEADER = "YMCacheTests/Tests-Prefix.pch";
605 | GCC_PREPROCESSOR_DEFINITIONS = (
606 | "DEBUG=1",
607 | "$(inherited)",
608 | );
609 | INFOPLIST_FILE = YMCacheTests/Info.plist;
610 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(PROJECT_DIR)/Carthage/Build/iOS";
611 | PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
612 | PRODUCT_NAME = "$(TARGET_NAME)";
613 | };
614 | name = Test;
615 | };
616 | FC4B27571BA13A2000F870A1 /* Test */ = {
617 | isa = XCBuildConfiguration;
618 | buildSettings = {
619 | COMBINE_HIDPI_IMAGES = YES;
620 | CURRENT_PROJECT_VERSION = 1;
621 | DEBUG_INFORMATION_FORMAT = dwarf;
622 | DEFINES_MODULE = YES;
623 | DYLIB_COMPATIBILITY_VERSION = 1;
624 | DYLIB_CURRENT_VERSION = 1;
625 | DYLIB_INSTALL_NAME_BASE = "@rpath";
626 | GCC_PREPROCESSOR_DEFINITIONS = (
627 | "DEBUG=1",
628 | "$(inherited)",
629 | );
630 | INFOPLIST_FILE = YMCache/Info.plist;
631 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
632 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
633 | PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
634 | PRODUCT_NAME = "$(PROJECT_NAME)";
635 | SDKROOT = macosx;
636 | SKIP_INSTALL = YES;
637 | VERSIONING_SYSTEM = "apple-generic";
638 | VERSION_INFO_PREFIX = "";
639 | };
640 | name = Test;
641 | };
642 | FC4B27581BA13A2000F870A1 /* Test */ = {
643 | isa = XCBuildConfiguration;
644 | buildSettings = {
645 | COMBINE_HIDPI_IMAGES = YES;
646 | DEBUG_INFORMATION_FORMAT = dwarf;
647 | FRAMEWORK_SEARCH_PATHS = (
648 | "$(DEVELOPER_FRAMEWORKS_DIR)",
649 | "$(inherited)",
650 | "$(PROJECT_DIR)/Carthage/Build/Mac",
651 | );
652 | GCC_PREFIX_HEADER = "YMCacheTests/Tests-Prefix.pch";
653 | GCC_PREPROCESSOR_DEFINITIONS = (
654 | "DEBUG=1",
655 | "$(inherited)",
656 | );
657 | INFOPLIST_FILE = YMCacheTests/Info.plist;
658 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/Carthage/Build/Mac";
659 | PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
660 | PRODUCT_NAME = "$(TARGET_NAME)";
661 | SDKROOT = macosx;
662 | };
663 | name = Test;
664 | };
665 | FC59B3771B71156B00093177 /* Debug */ = {
666 | isa = XCBuildConfiguration;
667 | buildSettings = {
668 | COMBINE_HIDPI_IMAGES = YES;
669 | CURRENT_PROJECT_VERSION = 1;
670 | DEBUG_INFORMATION_FORMAT = dwarf;
671 | DEFINES_MODULE = YES;
672 | DYLIB_COMPATIBILITY_VERSION = 1;
673 | DYLIB_CURRENT_VERSION = 1;
674 | DYLIB_INSTALL_NAME_BASE = "@rpath";
675 | GCC_PREPROCESSOR_DEFINITIONS = (
676 | "DEBUG=1",
677 | "$(inherited)",
678 | );
679 | INFOPLIST_FILE = YMCache/Info.plist;
680 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
681 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
682 | PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
683 | PRODUCT_NAME = "$(PROJECT_NAME)";
684 | SDKROOT = macosx;
685 | SKIP_INSTALL = YES;
686 | VERSIONING_SYSTEM = "apple-generic";
687 | VERSION_INFO_PREFIX = "";
688 | };
689 | name = Debug;
690 | };
691 | FC59B3781B71156B00093177 /* Release */ = {
692 | isa = XCBuildConfiguration;
693 | buildSettings = {
694 | COMBINE_HIDPI_IMAGES = YES;
695 | CURRENT_PROJECT_VERSION = 1;
696 | DEFINES_MODULE = YES;
697 | DYLIB_COMPATIBILITY_VERSION = 1;
698 | DYLIB_CURRENT_VERSION = 1;
699 | DYLIB_INSTALL_NAME_BASE = "@rpath";
700 | INFOPLIST_FILE = YMCache/Info.plist;
701 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
702 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
703 | PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
704 | PRODUCT_NAME = "$(PROJECT_NAME)";
705 | SDKROOT = macosx;
706 | SKIP_INSTALL = YES;
707 | VERSIONING_SYSTEM = "apple-generic";
708 | VERSION_INFO_PREFIX = "";
709 | };
710 | name = Release;
711 | };
712 | FC59B37A1B71156B00093177 /* Debug */ = {
713 | isa = XCBuildConfiguration;
714 | buildSettings = {
715 | COMBINE_HIDPI_IMAGES = YES;
716 | DEBUG_INFORMATION_FORMAT = dwarf;
717 | FRAMEWORK_SEARCH_PATHS = (
718 | "$(DEVELOPER_FRAMEWORKS_DIR)",
719 | "$(inherited)",
720 | "$(PROJECT_DIR)/Carthage/Build/Mac",
721 | );
722 | GCC_PREFIX_HEADER = "YMCacheTests/Tests-Prefix.pch";
723 | GCC_PREPROCESSOR_DEFINITIONS = (
724 | "DEBUG=1",
725 | "$(inherited)",
726 | );
727 | INFOPLIST_FILE = YMCacheTests/Info.plist;
728 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/Carthage/Build/Mac";
729 | PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
730 | PRODUCT_NAME = "$(TARGET_NAME)";
731 | SDKROOT = macosx;
732 | };
733 | name = Debug;
734 | };
735 | FC59B37B1B71156B00093177 /* Release */ = {
736 | isa = XCBuildConfiguration;
737 | buildSettings = {
738 | COMBINE_HIDPI_IMAGES = YES;
739 | FRAMEWORK_SEARCH_PATHS = (
740 | "$(DEVELOPER_FRAMEWORKS_DIR)",
741 | "$(inherited)",
742 | "$(PROJECT_DIR)/Carthage/Build/Mac",
743 | );
744 | GCC_PREFIX_HEADER = "YMCacheTests/Tests-Prefix.pch";
745 | INFOPLIST_FILE = YMCacheTests/Info.plist;
746 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/Carthage/Build/Mac";
747 | PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
748 | PRODUCT_NAME = "$(TARGET_NAME)";
749 | SDKROOT = macosx;
750 | };
751 | name = Release;
752 | };
753 | FCB05DBB1B6FDF1800C7302B /* Debug */ = {
754 | isa = XCBuildConfiguration;
755 | buildSettings = {
756 | ALWAYS_SEARCH_USER_PATHS = NO;
757 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
758 | CLANG_CXX_LIBRARY = "libc++";
759 | CLANG_ENABLE_MODULES = YES;
760 | CLANG_ENABLE_OBJC_ARC = YES;
761 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
762 | CLANG_WARN_BOOL_CONVERSION = YES;
763 | CLANG_WARN_COMMA = YES;
764 | CLANG_WARN_CONSTANT_CONVERSION = YES;
765 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
766 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
767 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
768 | CLANG_WARN_EMPTY_BODY = YES;
769 | CLANG_WARN_ENUM_CONVERSION = YES;
770 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
771 | CLANG_WARN_INFINITE_RECURSION = YES;
772 | CLANG_WARN_INT_CONVERSION = YES;
773 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
774 | CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
775 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
776 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
777 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
778 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
779 | CLANG_WARN_STRICT_PROTOTYPES = YES;
780 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
781 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
782 | CLANG_WARN_UNREACHABLE_CODE = YES;
783 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
784 | COPY_PHASE_STRIP = NO;
785 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
786 | ENABLE_STRICT_OBJC_MSGSEND = YES;
787 | ENABLE_TESTABILITY = YES;
788 | GCC_C_LANGUAGE_STANDARD = gnu99;
789 | GCC_DYNAMIC_NO_PIC = NO;
790 | GCC_GENERATE_TEST_COVERAGE_FILES = YES;
791 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
792 | GCC_NO_COMMON_BLOCKS = YES;
793 | GCC_OPTIMIZATION_LEVEL = 0;
794 | GCC_PREPROCESSOR_DEFINITIONS = (
795 | "DEBUG=1",
796 | "$(inherited)",
797 | );
798 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
799 | GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
800 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
801 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
802 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
803 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
804 | GCC_WARN_SHADOW = YES;
805 | GCC_WARN_STRICT_SELECTOR_MATCH = YES;
806 | GCC_WARN_UNDECLARED_SELECTOR = YES;
807 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
808 | GCC_WARN_UNUSED_FUNCTION = YES;
809 | GCC_WARN_UNUSED_LABEL = YES;
810 | GCC_WARN_UNUSED_VARIABLE = YES;
811 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
812 | MACOSX_DEPLOYMENT_TARGET = 10.10;
813 | MTL_ENABLE_DEBUG_INFO = YES;
814 | ONLY_ACTIVE_ARCH = YES;
815 | SDKROOT = iphoneos;
816 | };
817 | name = Debug;
818 | };
819 | FCB05DBC1B6FDF1800C7302B /* Release */ = {
820 | isa = XCBuildConfiguration;
821 | buildSettings = {
822 | ALWAYS_SEARCH_USER_PATHS = NO;
823 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
824 | CLANG_CXX_LIBRARY = "libc++";
825 | CLANG_ENABLE_MODULES = YES;
826 | CLANG_ENABLE_OBJC_ARC = YES;
827 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
828 | CLANG_WARN_BOOL_CONVERSION = YES;
829 | CLANG_WARN_COMMA = YES;
830 | CLANG_WARN_CONSTANT_CONVERSION = YES;
831 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
832 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
833 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
834 | CLANG_WARN_EMPTY_BODY = YES;
835 | CLANG_WARN_ENUM_CONVERSION = YES;
836 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
837 | CLANG_WARN_INFINITE_RECURSION = YES;
838 | CLANG_WARN_INT_CONVERSION = YES;
839 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
840 | CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
841 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
842 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
843 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
844 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
845 | CLANG_WARN_STRICT_PROTOTYPES = YES;
846 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
847 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
848 | CLANG_WARN_UNREACHABLE_CODE = YES;
849 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
850 | COPY_PHASE_STRIP = NO;
851 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
852 | ENABLE_NS_ASSERTIONS = NO;
853 | ENABLE_STRICT_OBJC_MSGSEND = YES;
854 | GCC_C_LANGUAGE_STANDARD = gnu99;
855 | GCC_GENERATE_TEST_COVERAGE_FILES = YES;
856 | GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
857 | GCC_NO_COMMON_BLOCKS = YES;
858 | GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
859 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
860 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
861 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
862 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
863 | GCC_WARN_SHADOW = YES;
864 | GCC_WARN_STRICT_SELECTOR_MATCH = YES;
865 | GCC_WARN_UNDECLARED_SELECTOR = YES;
866 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
867 | GCC_WARN_UNUSED_FUNCTION = YES;
868 | GCC_WARN_UNUSED_LABEL = YES;
869 | GCC_WARN_UNUSED_VARIABLE = YES;
870 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
871 | MACOSX_DEPLOYMENT_TARGET = 10.10;
872 | MTL_ENABLE_DEBUG_INFO = NO;
873 | SDKROOT = iphoneos;
874 | VALIDATE_PRODUCT = YES;
875 | };
876 | name = Release;
877 | };
878 | FCDDAB711B7008D4006C333F /* Debug */ = {
879 | isa = XCBuildConfiguration;
880 | buildSettings = {
881 | APPLICATION_EXTENSION_API_ONLY = YES;
882 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
883 | CURRENT_PROJECT_VERSION = 1;
884 | DEFINES_MODULE = YES;
885 | DYLIB_COMPATIBILITY_VERSION = 1;
886 | DYLIB_CURRENT_VERSION = 1;
887 | DYLIB_INSTALL_NAME_BASE = "@rpath";
888 | GCC_PREPROCESSOR_DEFINITIONS = (
889 | "DEBUG=1",
890 | "$(inherited)",
891 | );
892 | INFOPLIST_FILE = YMCache/Info.plist;
893 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
894 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
895 | PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
896 | PRODUCT_NAME = "$(PROJECT_NAME)";
897 | SKIP_INSTALL = YES;
898 | TARGETED_DEVICE_FAMILY = "1,2";
899 | VERSIONING_SYSTEM = "apple-generic";
900 | VERSION_INFO_PREFIX = "";
901 | };
902 | name = Debug;
903 | };
904 | FCDDAB721B7008D4006C333F /* Release */ = {
905 | isa = XCBuildConfiguration;
906 | buildSettings = {
907 | APPLICATION_EXTENSION_API_ONLY = YES;
908 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
909 | CURRENT_PROJECT_VERSION = 1;
910 | DEFINES_MODULE = YES;
911 | DYLIB_COMPATIBILITY_VERSION = 1;
912 | DYLIB_CURRENT_VERSION = 1;
913 | DYLIB_INSTALL_NAME_BASE = "@rpath";
914 | INFOPLIST_FILE = YMCache/Info.plist;
915 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
916 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
917 | PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
918 | PRODUCT_NAME = "$(PROJECT_NAME)";
919 | SKIP_INSTALL = YES;
920 | TARGETED_DEVICE_FAMILY = "1,2";
921 | VERSIONING_SYSTEM = "apple-generic";
922 | VERSION_INFO_PREFIX = "";
923 | };
924 | name = Release;
925 | };
926 | FCDDAB741B7008D4006C333F /* Debug */ = {
927 | isa = XCBuildConfiguration;
928 | buildSettings = {
929 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
930 | FRAMEWORK_SEARCH_PATHS = (
931 | "$(inherited)",
932 | "$(PROJECT_DIR)/Carthage/Build/iOS/**",
933 | );
934 | GCC_PREFIX_HEADER = "YMCacheTests/Tests-Prefix.pch";
935 | GCC_PREPROCESSOR_DEFINITIONS = (
936 | "DEBUG=1",
937 | "$(inherited)",
938 | );
939 | INFOPLIST_FILE = YMCacheTests/Info.plist;
940 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(PROJECT_DIR)/Carthage/Build/iOS";
941 | PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
942 | PRODUCT_NAME = "$(TARGET_NAME)";
943 | };
944 | name = Debug;
945 | };
946 | FCDDAB751B7008D4006C333F /* Release */ = {
947 | isa = XCBuildConfiguration;
948 | buildSettings = {
949 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
950 | FRAMEWORK_SEARCH_PATHS = (
951 | "$(inherited)",
952 | "$(PROJECT_DIR)/Carthage/Build/iOS/**",
953 | );
954 | GCC_PREFIX_HEADER = "YMCacheTests/Tests-Prefix.pch";
955 | INFOPLIST_FILE = YMCacheTests/Info.plist;
956 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(PROJECT_DIR)/Carthage/Build/iOS";
957 | PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
958 | PRODUCT_NAME = "$(TARGET_NAME)";
959 | };
960 | name = Release;
961 | };
962 | /* End XCBuildConfiguration section */
963 |
964 | /* Begin XCConfigurationList section */
965 | FC59B3761B71156B00093177 /* Build configuration list for PBXNativeTarget "YMCache-Mac" */ = {
966 | isa = XCConfigurationList;
967 | buildConfigurations = (
968 | FC59B3771B71156B00093177 /* Debug */,
969 | FC4B27571BA13A2000F870A1 /* Test */,
970 | FC59B3781B71156B00093177 /* Release */,
971 | );
972 | defaultConfigurationIsVisible = 0;
973 | defaultConfigurationName = Release;
974 | };
975 | FC59B3791B71156B00093177 /* Build configuration list for PBXNativeTarget "YMCache-MacTests" */ = {
976 | isa = XCConfigurationList;
977 | buildConfigurations = (
978 | FC59B37A1B71156B00093177 /* Debug */,
979 | FC4B27581BA13A2000F870A1 /* Test */,
980 | FC59B37B1B71156B00093177 /* Release */,
981 | );
982 | defaultConfigurationIsVisible = 0;
983 | defaultConfigurationName = Release;
984 | };
985 | FCB05DA41B6FDF1800C7302B /* Build configuration list for PBXProject "YMCache" */ = {
986 | isa = XCConfigurationList;
987 | buildConfigurations = (
988 | FCB05DBB1B6FDF1800C7302B /* Debug */,
989 | FC4B27541BA13A2000F870A1 /* Test */,
990 | FCB05DBC1B6FDF1800C7302B /* Release */,
991 | );
992 | defaultConfigurationIsVisible = 0;
993 | defaultConfigurationName = Release;
994 | };
995 | FCDDAB701B7008D4006C333F /* Build configuration list for PBXNativeTarget "YMCache-iOS" */ = {
996 | isa = XCConfigurationList;
997 | buildConfigurations = (
998 | FCDDAB711B7008D4006C333F /* Debug */,
999 | FC4B27551BA13A2000F870A1 /* Test */,
1000 | FCDDAB721B7008D4006C333F /* Release */,
1001 | );
1002 | defaultConfigurationIsVisible = 0;
1003 | defaultConfigurationName = Release;
1004 | };
1005 | FCDDAB731B7008D4006C333F /* Build configuration list for PBXNativeTarget "YMCache-iOSTests" */ = {
1006 | isa = XCConfigurationList;
1007 | buildConfigurations = (
1008 | FCDDAB741B7008D4006C333F /* Debug */,
1009 | FC4B27561BA13A2000F870A1 /* Test */,
1010 | FCDDAB751B7008D4006C333F /* Release */,
1011 | );
1012 | defaultConfigurationIsVisible = 0;
1013 | defaultConfigurationName = Release;
1014 | };
1015 | /* End XCConfigurationList section */
1016 | };
1017 | rootObject = FCB05DA11B6FDF1800C7302B /* Project object */;
1018 | }
1019 |
--------------------------------------------------------------------------------