├── 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 | [![build-status](https://github.com/yahoo/YMCache/workflows/YMCache%20CI/badge.svg?branch=master)](https://github.com/yahoo/YMCache/actions) 4 | 5 | [![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) 6 | [![CocoaPods Compatible](https://img.shields.io/badge/CocoaPods-compatible-brightgreen.svg)](https://github.com/CocoaPods/CocoaPods) 7 | [![GitHub license](https://img.shields.io/github/license/yahoo/YMCache.svg)](https://raw.githubusercontent.com/yahoo/YMCache/master/LICENSE.md) 8 | [![Supported Platforms](https://img.shields.io/cocoapods/p/YMCache.svg)]() 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 | --------------------------------------------------------------------------------