diff --git a/Examples/Mantle/Podfile b/Examples/Mantle/Podfile
index 3d6e54d..36a78fc 100644
--- a/Examples/Mantle/Podfile
+++ b/Examples/Mantle/Podfile
@@ -1,4 +1,4 @@
-platform :ios, '8.0'
+platform :ios, '9.0'
use_frameworks!
target 'YMCacheMantleExample' do
diff --git a/Examples/Mantle/Podfile.lock b/Examples/Mantle/Podfile.lock
index 25d33a5..42d4d63 100644
--- a/Examples/Mantle/Podfile.lock
+++ b/Examples/Mantle/Podfile.lock
@@ -2,20 +2,24 @@ PODS:
- Mantle (2.0.3):
- Mantle/extobjc (= 2.0.3)
- Mantle/extobjc (2.0.3)
- - YMCache (2.0.1)
+ - YMCache (2.2.0)
DEPENDENCIES:
- Mantle (~> 2.0)
- YMCache (from `../../`)
+SPEC REPOS:
+ trunk:
+ - Mantle
+
EXTERNAL SOURCES:
YMCache:
- :path: ../../
+ :path: "../../"
SPEC CHECKSUMS:
Mantle: 8d6b14979a082a9ed4994463eb7a4ff576c94632
- YMCache: fbaa0a6b4b894b3c0b6ce30a899f6f413617e91d
+ YMCache: ea1a858871d9dd1cbb2daca41ce87695999e33c8
-PODFILE CHECKSUM: b5436bf716c3b9fafcba05b85523d009ff960ff5
+PODFILE CHECKSUM: 0ad642fd9dd67bb09b8987d7229ab527f1dcd7eb
-COCOAPODS: 1.2.1
+COCOAPODS: 1.10.0
diff --git a/Examples/Mantle/YMCacheMantleExample.xcodeproj/project.pbxproj b/Examples/Mantle/YMCacheMantleExample.xcodeproj/project.pbxproj
index 2044f79..8dea29f 100644
--- a/Examples/Mantle/YMCacheMantleExample.xcodeproj/project.pbxproj
+++ b/Examples/Mantle/YMCacheMantleExample.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 46;
+ objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -157,7 +157,6 @@
FCDDABBE1B701243006C333F /* Frameworks */,
FCDDABBF1B701243006C333F /* Resources */,
119EACCBCD59D3B14FE65668 /* [CP] Embed Pods Frameworks */,
- F435A07B04CB05CBEE059EC2 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -174,7 +173,8 @@
FCDDABB91B701243006C333F /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 0640;
+ CLASSPREFIX = YM;
+ LastUpgradeCheck = 1220;
ORGANIZATIONNAME = "Yahoo, Inc";
TargetAttributes = {
FCDDABC01B701243006C333F = {
@@ -183,10 +183,11 @@
};
};
buildConfigurationList = FCDDABBC1B701243006C333F /* Build configuration list for PBXProject "YMCacheMantleExample" */;
- compatibilityVersion = "Xcode 3.2";
+ compatibilityVersion = "Xcode 12.0";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
+ English,
en,
Base,
);
@@ -220,14 +221,16 @@
buildActionMask = 2147483647;
files = (
);
- inputPaths = (
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-YMCacheMantleExample/Pods-YMCacheMantleExample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
- outputPaths = (
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-YMCacheMantleExample/Pods-YMCacheMantleExample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-YMCacheMantleExample/Pods-YMCacheMantleExample-frameworks.sh\"\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-YMCacheMantleExample/Pods-YMCacheMantleExample-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
F3003163E4700F556166CDD0 /* [CP] Check Pods Manifest.lock */ = {
@@ -236,28 +239,16 @@
files = (
);
inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-YMCacheMantleExample-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- 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";
- showEnvVarsInLog = 0;
- };
- F435A07B04CB05CBEE059EC2 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-YMCacheMantleExample/Pods-YMCacheMantleExample-resources.sh\"\n";
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@@ -302,23 +293,36 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@@ -334,7 +338,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.4;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -345,17 +349,29 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@@ -371,7 +387,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.4;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
@@ -382,9 +398,15 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 8C9405CF2A6A466514E7ED8F /* Pods-YMCacheMantleExample.debug.xcconfig */;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = YMCacheMantleExample/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
@@ -393,9 +415,15 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 4697FDB2E18840AB71844213 /* Pods-YMCacheMantleExample.release.xcconfig */;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = YMCacheMantleExample/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
diff --git a/Examples/Mantle/YMCacheMantleExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Examples/Mantle/YMCacheMantleExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/Examples/Mantle/YMCacheMantleExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Examples/Mantle/YMCacheMantleExample/Info.plist b/Examples/Mantle/YMCacheMantleExample/Info.plist
index cb363dd..116095d 100644
--- a/Examples/Mantle/YMCacheMantleExample/Info.plist
+++ b/Examples/Mantle/YMCacheMantleExample/Info.plist
@@ -7,7 +7,7 @@
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
- com.yahoo.$(PRODUCT_NAME:rfc1034identifier)
+ $(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
@@ -19,7 +19,7 @@
CFBundleSignature
????
CFBundleVersion
- 1
+ $(CURRENT_PROJECT_VERSION)
LSRequiresIPhoneOS
UILaunchStoryboardName
diff --git a/Examples/Mantle/YMCacheMantleExample/TableViewController.m b/Examples/Mantle/YMCacheMantleExample/TableViewController.m
index be93df3..be8b582 100644
--- a/Examples/Mantle/YMCacheMantleExample/TableViewController.m
+++ b/Examples/Mantle/YMCacheMantleExample/TableViewController.m
@@ -25,7 +25,7 @@ - (void)viewDidLoad {
self.formatter = [NSNumberFormatter new];
self.formatter.positivePrefix = @"+";
self.formatter.numberStyle = NSNumberFormatterCurrencyStyle;
-
+
self.cache = [YMMemoryCache memoryCacheWithName:@"dog-cache"];
self.cache.notificationInterval = 0.25;
diff --git a/YMCache.podspec b/YMCache.podspec
index 963445b..ee0396c 100644
--- a/YMCache.podspec
+++ b/YMCache.podspec
@@ -12,5 +12,6 @@ Pod::Spec.new do |s|
s.tvos.deployment_target = '9.0'
s.watchos.deployment_target = '3.0'
- s.source_files = 'YMCache/*.[h,m]'
+ s.source_files = 'YMCache/*.{h,m,swift}'
+ s.swift_versions = [ '5.0', '5.2' ]
end
diff --git a/YMCache.xcodeproj/project.pbxproj b/YMCache.xcodeproj/project.pbxproj
index df63b27..b9e5deb 100644
--- a/YMCache.xcodeproj/project.pbxproj
+++ b/YMCache.xcodeproj/project.pbxproj
@@ -3,10 +3,12 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 46;
+ objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
+ C6F9EE1F290A1691008DEB36 /* YMMemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F9EE1E290A1691008DEB36 /* YMMemoryCache.swift */; };
+ C6F9EE20290A1691008DEB36 /* YMMemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F9EE1E290A1691008DEB36 /* YMMemoryCache.swift */; };
FC4B27521BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = FC4B27511BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m */; };
FC4B27531BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = FC4B27511BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m */; };
FC59B36E1B71156B00093177 /* YMCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC59B3631B71156B00093177 /* YMCache.framework */; };
@@ -80,6 +82,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ C6F9EE1E290A1691008DEB36 /* YMMemoryCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMMemoryCache.swift; sourceTree = ""; };
FC4B27511BA1320C00F870A1 /* YMCachePersistenceControllerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = YMCachePersistenceControllerSpec.m; path = YMCacheTests/YMCachePersistenceControllerSpec.m; sourceTree = SOURCE_ROOT; };
FC59B3631B71156B00093177 /* YMCache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = YMCache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
FC59B36D1B71156B00093177 /* YMCache-MacTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "YMCache-MacTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -98,7 +101,6 @@
FCB05E161B6FE21300C7302B /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; };
FCB05E171B6FE21300C7302B /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
FCB05E181B6FE21300C7302B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
- FCB05E341B6FE93F00C7302B /* Cartfile.private */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile.private; sourceTree = ""; };
FCD3750F1BACF80E0034AD3B /* NSRunLoop+AsyncTestAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSRunLoop+AsyncTestAdditions.h"; path = "YMCacheTests/NSRunLoop+AsyncTestAdditions.h"; sourceTree = SOURCE_ROOT; };
FCD375101BACF80E0034AD3B /* NSRunLoop+AsyncTestAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSRunLoop+AsyncTestAdditions.m"; path = "YMCacheTests/NSRunLoop+AsyncTestAdditions.m"; sourceTree = SOURCE_ROOT; };
FCDDAAEE1B6FFE40006C333F /* YMCache.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; name = YMCache.podspec; path = Example/../YMCache.podspec; sourceTree = ""; };
@@ -212,7 +214,6 @@
FCB05E131B6FE1FA00C7302B /* Project Metadata */ = {
isa = PBXGroup;
children = (
- FCB05E341B6FE93F00C7302B /* Cartfile.private */,
FCB05E151B6FE21300C7302B /* CHANGELOG.md */,
FCB05E161B6FE21300C7302B /* CONTRIBUTING.md */,
FCB05E171B6FE21300C7302B /* LICENSE */,
@@ -257,6 +258,7 @@
FCB05DDB1B6FDFC300C7302B /* YMCachePersistenceController.m */,
FCB05DDD1B6FDFC300C7302B /* YMMemoryCache.h */,
FCB05DDE1B6FDFC300C7302B /* YMMemoryCache.m */,
+ C6F9EE1E290A1691008DEB36 /* YMMemoryCache.swift */,
);
name = Public;
sourceTree = "";
@@ -378,17 +380,19 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = YM;
- LastUpgradeCheck = 1010;
+ LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "Yahoo, Inc";
TargetAttributes = {
FC59B3621B71156B00093177 = {
CreatedOnToolsVersion = 6.4;
+ LastSwiftMigration = 1300;
};
FC59B36C1B71156B00093177 = {
CreatedOnToolsVersion = 6.4;
};
FCDDAB5C1B7008D4006C333F = {
CreatedOnToolsVersion = 6.4;
+ LastSwiftMigration = 1300;
};
FCDDAB661B7008D4006C333F = {
CreatedOnToolsVersion = 6.4;
@@ -396,11 +400,12 @@
};
};
buildConfigurationList = FCB05DA41B6FDF1800C7302B /* Build configuration list for PBXProject "YMCache" */;
- compatibilityVersion = "Xcode 3.2";
- developmentRegion = English;
+ compatibilityVersion = "Xcode 13.0";
+ developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
+ Base,
);
mainGroup = FCB05DA01B6FDF1800C7302B;
productRefGroup = FCB05DAA1B6FDF1800C7302B /* Products */;
@@ -453,6 +458,7 @@
files = (
FC59B37C1B7115FC00093177 /* YMCachePersistenceController.m in Sources */,
FC59B37D1B7115FD00093177 /* YMMemoryCache.m in Sources */,
+ C6F9EE20290A1691008DEB36 /* YMMemoryCache.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -472,6 +478,7 @@
files = (
FCD690821B710F520099E854 /* YMCachePersistenceController.m in Sources */,
FCD690831B710F540099E854 /* YMMemoryCache.m in Sources */,
+ C6F9EE1F290A1691008DEB36 /* YMMemoryCache.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -509,6 +516,7 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
@@ -521,11 +529,13 @@
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR;
CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
@@ -548,6 +558,7 @@
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
+ GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -559,8 +570,8 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
- MACOSX_DEPLOYMENT_TARGET = 10.10;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ MACOSX_DEPLOYMENT_TARGET = 11.3;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -571,6 +582,7 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
+ CLANG_ENABLE_MODULES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
@@ -583,10 +595,16 @@
);
INFOPLIST_FILE = YMCache/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(PROJECT_NAME)";
SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -596,6 +614,7 @@
FC4B27561BA13A2000F870A1 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -607,7 +626,12 @@
"$(inherited)",
);
INFOPLIST_FILE = YMCacheTests/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(PROJECT_DIR)/Carthage/Build/iOS";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ "$(PROJECT_DIR)/Carthage/Build/iOS",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
};
@@ -616,6 +640,7 @@
FC4B27571BA13A2000F870A1 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
@@ -629,11 +654,17 @@
);
INFOPLIST_FILE = YMCache/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(PROJECT_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
@@ -642,6 +673,7 @@
FC4B27581BA13A2000F870A1 /* Test */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
FRAMEWORK_SEARCH_PATHS = (
@@ -655,7 +687,12 @@
"$(inherited)",
);
INFOPLIST_FILE = YMCacheTests/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/Carthage/Build/Mac";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/../Frameworks",
+ "$(PROJECT_DIR)/Carthage/Build/Mac",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
@@ -665,6 +702,7 @@
FC59B3771B71156B00093177 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
@@ -678,11 +716,17 @@
);
INFOPLIST_FILE = YMCache/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(PROJECT_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
@@ -691,6 +735,7 @@
FC59B3781B71156B00093177 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
@@ -699,11 +744,16 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = YMCache/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(PROJECT_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
@@ -712,6 +762,7 @@
FC59B37A1B71156B00093177 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
FRAMEWORK_SEARCH_PATHS = (
@@ -725,7 +776,12 @@
"$(inherited)",
);
INFOPLIST_FILE = YMCacheTests/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/Carthage/Build/Mac";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/../Frameworks",
+ "$(PROJECT_DIR)/Carthage/Build/Mac",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
@@ -735,6 +791,7 @@
FC59B37B1B71156B00093177 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
COMBINE_HIDPI_IMAGES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(DEVELOPER_FRAMEWORKS_DIR)",
@@ -743,7 +800,12 @@
);
GCC_PREFIX_HEADER = "YMCacheTests/Tests-Prefix.pch";
INFOPLIST_FILE = YMCacheTests/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/Carthage/Build/Mac";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/../Frameworks",
+ "$(PROJECT_DIR)/Carthage/Build/Mac",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
@@ -758,6 +820,7 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
@@ -770,11 +833,13 @@
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR;
CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
@@ -797,6 +862,7 @@
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
+ GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -808,8 +874,8 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
- MACOSX_DEPLOYMENT_TARGET = 10.10;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ MACOSX_DEPLOYMENT_TARGET = 11.3;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -824,6 +890,7 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
@@ -836,11 +903,13 @@
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR;
CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
@@ -856,6 +925,7 @@
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
+ GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@@ -867,8 +937,8 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
- MACOSX_DEPLOYMENT_TARGET = 10.10;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
+ MACOSX_DEPLOYMENT_TARGET = 11.3;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
@@ -879,6 +949,7 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
+ CLANG_ENABLE_MODULES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
@@ -891,10 +962,16 @@
);
INFOPLIST_FILE = YMCache/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(PROJECT_NAME)";
SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -905,6 +982,7 @@
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
+ CLANG_ENABLE_MODULES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
@@ -913,10 +991,15 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = YMCache/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(PROJECT_NAME)";
SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -926,6 +1009,7 @@
FCDDAB741B7008D4006C333F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -937,7 +1021,12 @@
"$(inherited)",
);
INFOPLIST_FILE = YMCacheTests/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(PROJECT_DIR)/Carthage/Build/iOS";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ "$(PROJECT_DIR)/Carthage/Build/iOS",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
};
@@ -946,6 +1035,7 @@
FCDDAB751B7008D4006C333F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -953,7 +1043,12 @@
);
GCC_PREFIX_HEADER = "YMCacheTests/Tests-Prefix.pch";
INFOPLIST_FILE = YMCacheTests/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(PROJECT_DIR)/Carthage/Build/iOS";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ "$(PROJECT_DIR)/Carthage/Build/iOS",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
};
diff --git a/YMCache.xcodeproj/xcshareddata/xcschemes/YMCache-Mac.xcscheme b/YMCache.xcodeproj/xcshareddata/xcschemes/YMCache-Mac.xcscheme
index 18e72da..99bc35a 100644
--- a/YMCache.xcodeproj/xcshareddata/xcschemes/YMCache-Mac.xcscheme
+++ b/YMCache.xcodeproj/xcshareddata/xcschemes/YMCache-Mac.xcscheme
@@ -1,6 +1,6 @@
+
+
+
+
@@ -53,17 +62,6 @@
-
-
-
-
-
-
-
-
+
+
+
+
@@ -53,17 +62,6 @@
-
-
-
-
-
-
-
-
+#import
+#import
diff --git a/YMCache/YMCachePersistenceController.m b/YMCache/YMCachePersistenceController.m
index 5a542a8..71a067c 100644
--- a/YMCache/YMCachePersistenceController.m
+++ b/YMCache/YMCachePersistenceController.m
@@ -3,8 +3,9 @@
// Licensed under the terms of the MIT License. See LICENSE file in the project root.
#import "YMCachePersistenceController.h"
-#import "YMMemoryCache.h"
#import "YMLog.h"
+#import
+
#include "TargetConditionals.h"
diff --git a/YMCache/YMMemoryCache.h b/YMCache/YMMemoryCache.h
index c38625c..46cea15 100644
--- a/YMCache/YMMemoryCache.h
+++ b/YMCache/YMMemoryCache.h
@@ -1,5 +1,5 @@
-// Created by Adam Kaplan on 8/1/15.
-// Copyright 2015 Yahoo.
+// Created by Adam Kaplan on 1/4/21.
+// Copyright 2021 Verizon Media.
// Licensed under the terms of the MIT License. See LICENSE file in the project root.
#import
@@ -24,7 +24,7 @@ extern NSString *const kYFCacheUpdatedItemsUserInfoKey;
*/
extern NSString *const kYFCacheRemovedItemsUserInfoKey;
-/** Type of a decider block for determining is an item is evictable.
+ /** Type of a decider block for determining is an item is evictable.
* @param key The key associated with value in the cache.
* @param value The value of the item in the cache.
* @param context Arbitrary user-provided context.
@@ -33,133 +33,4 @@ typedef BOOL (^YMMemoryCacheEvictionDecider)(id key, id value, void *__nullable
typedef __nullable id (^YMMemoryCacheObjectLoader)(void);
-/** The YMMemoryCache class declares a programatic interface to objects that manage ephemeral
- * associations of keys and values, similar to Foundation's NSMutableDictionary. The primary benefit
- * is that YMMemoryCache is designed to be safely accessed and mutated across multiple threads. It
- * offers specific optimizations for use in a multi-threaded environment, and hides certain functionality
- * that may be potentially unsafe or behave in unexpected ways for users accustomed to an NSDictionary.
- *
- * Implementation Notes:
- *
- * In general, non-mutating access (getters) execute synchronously and in parallel (by way of a concurrent
- * Grand Central Dispatch queue).
- * Mutating access (setters), on the other hand, take advantage of dispatch barriers to provide safe,
- * blocking writes while preserving in-band synchronous-like ordering behavior.
- *
- * In other words, getters behave as if they were run on a concurrent dispatch queue with respect to
- * each other. However, setters behave as though they were run on a serial dispatch queue with respect
- * to both getters and other setters.
- *
- * One side effect of this approach is that any access – even a non-mutating getter – may result in a
- * blocking call, though overall latency should be extremely low. Users should plan for this and try
- * to pull out all values at once (via `enumerateKeysAndObjectsUsingBlock`) or in the background.
- */
-@interface YMMemoryCache : NSObject
-
-/** Unique name identifying this cache, for example, in log messages. */
-@property (nonatomic, readonly, nullable) NSString *name;
-
-/** Maximum amount of time between evictions checks. Evictions may occur at any time up to this value.
- * Defaults to 600 seconds, or 10 minutes.
- */
-@property (nonatomic) NSTimeInterval evictionInterval;
-
-/** Maximum amount of time between notification of changes to cached items. After each notificationInterval,
- * the cache will post a notification named `kYFCacheDidChangeNotification` with userInfo that contains
- * `kYFCacheUpdatedItemsUserInfoKey` and `kYFCacheRemovedItemsUserInfoKey`. They keys contain information
- * that is as a complete delta of changes since the last notification.
- *
- * Defaults to 0, disabled.
- */
-@property (nonatomic) NSTimeInterval notificationInterval;
-
-/** Creates and returns a new memory cache using the specified name, but no eviction delegate.
- * @param name A unique name for this cache. Optional, helpful for debugging.
- * @return a new cache identified by `name`
- */
-+ (instancetype)memoryCacheWithName:(nullable NSString *)name;
-
-/** Creates and returns a new memory cache using the specified name, and eviction delegate.
- * @param name A unique name for this cache. Optional, helpful for debugging.
- * @param evictionDecider The eviction decider to use. See initWithName:evictionDecider:`
- * @return a new cache identified by `name`
- */
-+ (instancetype)memoryCacheWithName:(nullable NSString *)name
- evictionDecider:(nullable YMMemoryCacheEvictionDecider)evictionDecider;
-
-- (instancetype)init NS_UNAVAILABLE; // use designated initializer
-
-/** Initializes a newly allocated memory cache using the specified cache name, delegate & queue.
- * @param name (Optional) A unique name for this cache. Helpful for debugging.
- * @param evictionDecider (Optional) A block used to decide if an item (a key-value pair) is evictable.
- * Clients return YES if the item can be evicted, or NO if the item should not be evicted from the cache.
- * A nil evictionDevider is equivalent to returning NO for all items. The decider will execute on an
- * arbitrary thread. The `context` parameter is NULL if the block is called due to the internal eviction
- * timer expiring.
- * @return An initialized memory cache using name, delegate and delegateQueue.
- */
-- (instancetype)initWithName:(nullable NSString *)name
- evictionDecider:(nullable YMMemoryCacheEvictionDecider)evictionDecider NS_DESIGNATED_INITIALIZER;
-
-/** Returns the value associated with a given key.
- * @param key The key for which to return the corresponding value.
- * @return The value associated with `key`, or `nil` if no value is associated with key.
- */
-- (nullable id)objectForKeyedSubscript:(nonnull id)key;
-
-/** Get the value for the key. If value does not exist, invokes defaultLoader(), sets the result as
- the value for key, and returns it. In order to ensure consistency, the cache is locked when
- the defaultLoader block needs to be invoked.
- */
-- (nullable id)objectForKey:(NSString *)key withDefault:(YMMemoryCacheObjectLoader)defaultLoader;
-
-/** Sets the value associated with a given key.
- * @param obj The value for `key`
- * @param key The key for `value`. The key is copied (keys must conform to the NSCopying protocol).
- * If `key` already exists in the cache, `object` takes its place. If `object` is `nil`, key is removed
- * from the cache.
- */
-- (void)setObject:(nullable id)obj forKeyedSubscript:(id)key;
-
-/** Adds to the cache the entries from a dictionary.
- * If the cache contains the same key as the dictionary, the cache's previous value object for that key
- * is replaced with new value object. All entries from dictionary are added to the cache at once such
- * that all of them are accessable by the next cache accessor, even if that accessor is triggered before
- * this method returns.
- *
- * All entries in the dictionary will be part of the next change notification event, even if an identical
- * key-value pair was already present in the cache.
- *
- * @param dictionary The dictionary from which to add entries.
- */
-- (void)addEntriesFromDictionary:(NSDictionary *)dictionary;
-
-/** Empties the cache of its entries.
- * Each key and corresponding value object is sent a release message.
- */
-- (void)removeAllObjects;
-
-/** Removes from the cache entries specified by keys in a given array.
- * If a key in `keys` does not exist, the entry is ignored. This method is more efficient at removing
- * the values for multiple keys than calling `setObject:forKeyedSubscript` multiple times.
- * @param keys An array of objects specifying the keys to remove.
- */
-- (void)removeObjectsForKeys:(NSArray *)keys;
-
-/** Returns a snapshot of all values in the cache.
- * The returned dictionary may differ from the actual cache as soon as it is returned. Because of this,
- * it is recommended to use `enumerateKeysAndObjectsUsingBlock:` for any operations that require a
- * guarantee that all items are operated upon (such as in low-memory situations).
- * @return A copy of the underlying dictionary upon which the cache is built.
- */
-- (NSDictionary *)allItems;
-
-/** Triggers an immediate (synchronous) check for exired items, and releases those items that are expired.
- * This method does nothing if no expirationDecider block was provided during initialization. The
- * evictionDecider block is run on the queue that this method is called on.
- */
-- (void)purgeEvictableItems:(nullable void *)context;
-
-@end
-
NS_ASSUME_NONNULL_END
diff --git a/YMCache/YMMemoryCache.m b/YMCache/YMMemoryCache.m
index e4b0d23..4299cbd 100644
--- a/YMCache/YMMemoryCache.m
+++ b/YMCache/YMMemoryCache.m
@@ -1,330 +1,9 @@
-// Created by Adam Kaplan on 8/1/15.
-// Copyright 2015 Yahoo.
+// Created by Adam Kaplan on 1/4/21.
+// Copyright 2021 Verizon Media.
// Licensed under the terms of the MIT License. See LICENSE file in the project root.
#import "YMMemoryCache.h"
-#define AssertPrivateQueue \
-NSAssert(dispatch_get_specific(kYFPrivateQueueKey) == (__bridge void *)self, @"Wrong Queue")
-
-#define AssertNotPrivateQueue \
-NSAssert(dispatch_get_specific(kYFPrivateQueueKey) != (__bridge void *)self, @"Potential deadlock: blocking call issued from current queue, to current queue")
-
NSString *const kYFCacheDidChangeNotification = @"kYFCacheDidChangeNotification";
NSString *const kYFCacheUpdatedItemsUserInfoKey = @"kYFCacheUpdatedItemsUserInfoKey";
NSString *const kYFCacheRemovedItemsUserInfoKey = @"kYFCacheRemovedItemsUserInfoKey";
-
-
-static const CFStringRef kYFPrivateQueueKey = CFSTR("kYFPrivateQueueKey");
-
-@interface YMMemoryCache ()
-@property (nonatomic) dispatch_queue_t queue;
-@property (nonatomic) dispatch_source_t notificationTimer;
-@property (nonatomic) dispatch_source_t evictionTimer;
-/// All of the key-value pairs stored in the cache
-@property (nonatomic) NSMutableDictionary *items;
-/// The keys (and their current value) that have been added/updated since the last kYFCacheDidChangeNotification
-@property (nonatomic) NSMutableDictionary *updatedPendingNotify;
-/// The keys that have been removed since the last kYFCacheDidChangeNotification
-@property (nonatomic) NSMutableSet *removedPendingNotify;
-
-@property (nonatomic, copy) YMMemoryCacheEvictionDecider evictionDecider;
-@property (nonatomic) dispatch_queue_t evictionDeciderQueue;
-@end
-
-@implementation YMMemoryCache
-
-#pragma mark - Lifecycle
-
-+ (instancetype)memoryCacheWithName:(NSString *)name {
- return [[self alloc] initWithName:name evictionDecider:nil];
-}
-
-+ (instancetype)memoryCacheWithName:(NSString *)name evictionDecider:(YMMemoryCacheEvictionDecider)evictionDecider {
- return [[self alloc] initWithName:name evictionDecider:evictionDecider];
-}
-
-- (instancetype)initWithName:(NSString *)cacheName evictionDecider:(YMMemoryCacheEvictionDecider)evictionDecider {
-
- if (self = [super init]) {
-
- NSString *queueName = @"com.yahoo.cache";
- if (cacheName) {
- _name = cacheName;
- queueName = [queueName stringByAppendingFormat:@" %@", cacheName];
- }
- _queue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_CONCURRENT);
- dispatch_queue_set_specific(_queue, kYFPrivateQueueKey, (__bridge void *)self, NULL);
-
- if (evictionDecider) {
- _evictionDecider = evictionDecider;
- NSString *evictionQueueName = [queueName stringByAppendingString:@" (eviction)"];
- _evictionDeciderQueue = dispatch_queue_create([evictionQueueName UTF8String], DISPATCH_QUEUE_SERIAL);
-
- // Time interval to notify UI. This sets the overall update cadence for the app.
- [self setEvictionInterval:600.0];
- }
-
- [self setNotificationInterval:0.0];
-
- _items = [NSMutableDictionary dictionary];
- }
- return self;
-}
-
-- (void)dealloc {
- self.queue = nil; // kill queue, then kill timers
- self.evictionDeciderQueue = nil;
-
- dispatch_source_t evictionTimer = self.evictionTimer;
- if (evictionTimer && 0 == dispatch_source_testcancel(evictionTimer)) {
- dispatch_source_cancel(evictionTimer);
- }
-
- dispatch_source_t notificationTimer = self.notificationTimer;
- if (notificationTimer && 0 == dispatch_source_testcancel(notificationTimer)) {
- dispatch_source_cancel(notificationTimer);
- }
-}
-
-#pragma mark - Persistence
-
-- (void)addEntriesFromDictionary:(NSDictionary *)dictionary {
- dispatch_barrier_async(self.queue, ^{
- [self.items addEntriesFromDictionary:dictionary];
- [self.updatedPendingNotify addEntriesFromDictionary:dictionary];
- for (id key in dictionary) {
- [self.removedPendingNotify removeObject:key];
- }
- });
-}
-
-- (NSDictionary *)allItems {
- AssertNotPrivateQueue;
-
- __block NSDictionary *items;
- dispatch_sync(self.queue, ^{
- items = [self.items copy];
- });
-
- return items;
-}
-
-#pragma mark - Property Setters
-
-- (void)setEvictionInterval:(NSTimeInterval)evictionInterval {
- if (!self.evictionDeciderQueue) { // abort if this instance was not configured with an evictionDecider
- return;
- }
-
- dispatch_barrier_async(self.evictionDeciderQueue, ^{
- self->_evictionInterval = evictionInterval;
-
- if (self.evictionTimer) {
- dispatch_source_cancel(self.evictionTimer);
- self.evictionTimer = nil;
- }
-
- if (evictionInterval > 0) {
- self.evictionTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.evictionDeciderQueue);
-
- __weak __typeof(self) weakSelf = self;
- dispatch_source_set_event_handler(self.evictionTimer, ^{ [weakSelf purgeEvictableItems:NULL]; });
-
- dispatch_source_set_timer(self.evictionTimer,
- dispatch_time(DISPATCH_TIME_NOW, (SInt64)(evictionInterval * NSEC_PER_SEC)),
- (UInt64)(self.evictionInterval * NSEC_PER_SEC),
- 5 * NSEC_PER_MSEC);
-
- dispatch_resume(self.evictionTimer);
- }
- });
-}
-
-- (void)setNotificationInterval:(NSTimeInterval)notificationInterval {
-
- dispatch_barrier_async(self.queue, ^{
- self->_notificationInterval = notificationInterval;
-
- if (self.notificationTimer) {
- dispatch_source_cancel(self.notificationTimer);
- self.notificationTimer = nil;
- }
-
- if (self.notificationInterval > 0) {
- self.updatedPendingNotify = [NSMutableDictionary dictionary];
- self.removedPendingNotify = [NSMutableSet set];
-
- self.notificationTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue);
-
- __weak __typeof(self) weakSelf = self;
- dispatch_source_set_event_handler(self.notificationTimer, ^{
- [weakSelf sendPendingNotifications];
- });
-
- dispatch_source_set_timer(self.notificationTimer,
- dispatch_time(DISPATCH_TIME_NOW, (SInt64)(self.notificationInterval * NSEC_PER_SEC)),
- (UInt64)(self.notificationInterval * NSEC_PER_SEC),
- 5 * NSEC_PER_MSEC);
-
- dispatch_resume(self.notificationTimer);
- }
- else {
- self.updatedPendingNotify = nil;
- self.removedPendingNotify = nil;
- }
- });
-}
-
-
-- (id)objectForKey:(NSString *)key withDefault:(YMMemoryCacheObjectLoader)defaultLoader
-{
- NSParameterAssert(key);
- AssertNotPrivateQueue;
-
- id item = [self objectForKeyedSubscript:key];
- if (!defaultLoader) {
- return item;
- }
-
- // If default loader is valid and we don't have this object in cache, we should create and save it.
- if (!item) {
- __block id newItem;
- dispatch_barrier_sync(self.queue, ^{
- // In order to ensure that read call are only blocking when they need
- // to be, this mutating block is executed with it's own barrier. This
- // means that we have a potential race condition if this method is called
- // in parallel with the same missing key. Resolve by checking again!
- newItem = self.items[key];
- if (newItem) {
- return; // item was probably added in another loading block that was queued concurrently with this one
- }
-
- newItem = defaultLoader();
- if (newItem) {
- [self.removedPendingNotify removeObject:key];
- self.items[key] = newItem;
- self.updatedPendingNotify[key] = newItem;
- }
- });
-
- item = newItem;
- }
-
- return item;
-}
-
-#pragma mark - Keyed Subscripting
-
-- (id)objectForKeyedSubscript:(id)key {
- AssertNotPrivateQueue;
-
- __block id item;
- dispatch_sync(self.queue, ^{
- item = self.items[key];
- });
- return item;
-}
-
-- (void)setObject:(id)obj forKeyedSubscript:(id)key {
- NSParameterAssert(key); // The collections will assert, but fail earlier to aid in async debugging
-
- __weak __typeof(self) weakSelf = self;
- dispatch_barrier_async(self.queue, ^{
- __strong __typeof(self) strongSelf = weakSelf;
-
- if (obj) {
- [strongSelf.removedPendingNotify removeObject:key];
- strongSelf.items[key] = obj;
- strongSelf.updatedPendingNotify[key] = obj;
- } else if (strongSelf.items[key]) { // removing existing key
- [strongSelf.removedPendingNotify addObject:key];
- [strongSelf.items removeObjectForKey:key];
- [strongSelf.updatedPendingNotify removeObjectForKey:key];
- }
- });
-}
-
-#pragma mark - Key-Value Management
-
-- (void)removeAllObjects {
- AssertNotPrivateQueue;
-
- dispatch_barrier_sync(self.queue, ^{
- for (id key in self.items) {
- [self.updatedPendingNotify removeObjectForKey:key];
- [self.removedPendingNotify addObject:key];
- }
-
- [self.items removeAllObjects];
- });
-}
-
-- (void)removeObjectsForKeys:(NSArray *)keys {
- AssertNotPrivateQueue;
-
- if (!keys.count) {
- return;
- }
-
- dispatch_barrier_sync(self.queue, ^{
- for (id key in keys) {
- if (self.items[key]) {
- [self.removedPendingNotify addObject:key];
- [self.updatedPendingNotify removeObjectForKey:key];
- [self.items removeObjectForKey:key];
- }
- }
- });
-}
-
-#pragma mark - Notification
-
-- (void)sendPendingNotifications {
- AssertPrivateQueue;
-
- NSDictionary *updatedPending = self.updatedPendingNotify ? self.updatedPendingNotify : @{};
- NSSet *removedPending = self.removedPendingNotify ? self.removedPendingNotify : [NSSet set];
- if (!updatedPending.count && !removedPending.count) {
- return;
- }
- self.updatedPendingNotify = [NSMutableDictionary dictionary];
- self.removedPendingNotify = [NSMutableSet set];
-
- dispatch_async(dispatch_get_main_queue(), ^{ // does not require a barrier since setObject: is the only other mutator
- [[NSNotificationCenter defaultCenter] postNotificationName:kYFCacheDidChangeNotification
- object:self
- userInfo:@{ kYFCacheUpdatedItemsUserInfoKey: updatedPending,
- kYFCacheRemovedItemsUserInfoKey: removedPending }];
- });
-}
-
-#pragma mark - Cleanup
-
-- (void)purgeEvictableItems:(void *)context {
- // All external execution must have been dispatched to another queue so as to not leak the private queue
- // though the user-provided evictionDecider block.
- AssertNotPrivateQueue;
-
- // Don't clean up if no expiration decider block
- if (!self.evictionDecider) {
- return;
- }
-
- NSDictionary *items = self.allItems; // sync & safe
- YMMemoryCacheEvictionDecider evictionDecider = self.evictionDecider;
- NSMutableArray *itemKeysToPurge = [NSMutableArray new];
-
- for (id key in items) {
- id value = items[key];
-
- BOOL shouldEvict = evictionDecider(key, value, context);
- if (shouldEvict) {
- [itemKeysToPurge addObject:key];
- }
- }
-
- [self removeObjectsForKeys:itemKeysToPurge];
-}
-
-@end
diff --git a/YMCache/YMMemoryCache.swift b/YMCache/YMMemoryCache.swift
new file mode 100644
index 0000000..f76e345
--- /dev/null
+++ b/YMCache/YMMemoryCache.swift
@@ -0,0 +1,378 @@
+// Created by Adam Kaplan on 1/4/21.
+// Copyright 2021 Verizon Media.
+// Licensed under the terms of the MIT License. See LICENSE file in the project root.
+
+import Foundation
+
+/// The YMMemoryCache class declares a programatic interface to objects that manage ephemeral
+/// associations of keys and values, similar to Foundation's NSMutableDictionary. The primary benefit
+/// is that YMMemoryCache is designed to be safely accessed and mutated across multiple threads. It
+/// offers specific optimizations for use in a multi-threaded environment, and hides certain functionality
+/// that may be potentially unsafe or behave in unexpected ways for users accustomed to an NSDictionary.
+///
+/// Implementation Notes:
+///
+/// In general, non-mutating access (getters) execute synchronously and in parallel (by way of a concurrent
+/// Grand Central Dispatch queue).
+/// Mutating access (setters), on the other hand, take advantage of dispatch barriers to provide safe,
+/// blocking writes while preserving in-band synchronous-like ordering behavior.
+///
+/// In other words, getters behave as if they were run on a concurrent dispatch queue with respect to
+/// each other. However, setters behave as though they were run on a serial dispatch queue with respect
+/// to both getters and other setters.
+///
+/// One side effect of this approach is that any access – even a non-mutating getter – may result in a
+/// blocking call, though overall latency should be extremely low. Users should plan for this and try
+/// to pull out all values at once (via `enumerateKeysAndObjectsUsingBlock`) or in the background.
+@objc public class YMMemoryCache: NSObject {
+
+ /// Unique name identifying this cache, for example, in log messages.
+ @objc public let name: String?
+
+ private var _evictionInterval: TimeInterval = 0
+
+ /// Maximum amount of time between evictions checks. Evictions may occur at any time up to this value.
+ /// Defaults to 600 seconds, or 10 minutes.
+ @objc public var evictionInterval: TimeInterval {
+ set {
+ evictionDeciderQueue.async(flags: .barrier) { [weak self] in
+ guard let self = self else { return }
+ self._evictionInterval = newValue // set shadow value
+
+ let source = self.newSourceTimer(replacing: self.evictionSourceTimer, interval: newValue, queue: self.evictionDeciderQueue) { [weak self] in
+ self?.purgeEvictableItems()
+ }
+
+ // Start source timer
+ self.evictionSourceTimer = source
+ source?.resume()
+ }
+ }
+ get { _evictionInterval }
+ }
+
+ private var _notificationInterval: TimeInterval = 0
+
+ /// Maximum amount of time between notification of changes to cached items. After each notificationInterval,
+ /// the cache will post a notification named `kYFCacheDidChangeNotification` with userInfo that contains
+ /// `kYFCacheUpdatedItemsUserInfoKey` and `kYFCacheRemovedItemsUserInfoKey`. They keys contain information
+ /// that is as a complete delta of changes since the last notification.
+ ///
+ /// Defaults to 0, disabled.
+ @objc public var notificationInterval: TimeInterval {
+ set {
+ queue.async(flags: .barrier) { [weak self] in
+ guard let self = self else { return }
+ self._notificationInterval = newValue
+
+ let source = self.newSourceTimer(replacing: self.notificationSourceTimer, interval: newValue, queue: self.queue) { [weak self] in
+ self?.sendPendingNotifications()
+ }
+ self.notificationSourceTimer = source
+ if let source = source {
+ source.resume()
+ } else {
+ // Re-claim any resources no longer needed if notifications are disabled
+ self.itemsUpdatedPendingNotify.removeAll()
+ self.itemsRemovedPendingNotify.removeAll()
+ }
+ }
+ }
+ get { _notificationInterval }
+ }
+
+ /// Eviction decider block invoked during eviction runs
+ private let evictionDecider: EvictionDecider?
+
+ private let queue: DispatchQueue
+
+ private lazy var evictionDeciderQueue = DispatchQueue(label: "com.yahoo.cache (eviction)")
+ private var evictionSourceTimer: DispatchSourceTimer? = nil
+
+ private var notificationSourceTimer: DispatchSourceTimer? = nil
+
+ private var items = [AnyHashable: Any]()
+ private var itemsUpdatedPendingNotify = [AnyHashable: Any]()
+ private var itemsRemovedPendingNotify = Set()
+
+ private static let kYFPrivateQueueKey = DispatchSpecificKey()
+ private let privateQueueNonce = UUID()
+
+ /// Creates and returns a new memory cache using the specified name, but no eviction delegate.
+ /// - Parameter name: Unique name for this cache. Optional, helpful for debugging.
+ /// - Returns: New cache identified by `name`
+ @objc public class func memoryCache(withName name: String?) -> YMMemoryCache {
+ return memoryCache(withName: name, evictionDecider: nil)
+ }
+
+ /// Creates and returns a new memory cache using the specified name, but no eviction delegate.
+ /// - Parameter name: Unique name for this cache. Optional, helpful for debugging.
+ /// - Parameter evictionDecider: The eviction decider to use. See `evictionDecider` property.
+ /// - Returns: New cache identified by `name`
+ @objc public class func memoryCache(withName name: String?, evictionDecider: EvictionDecider? = nil) -> YMMemoryCache {
+ return YMMemoryCache(withName: name, evictionDecider: evictionDecider)
+ }
+
+ /// Initializes a newly allocated memory cache using the specified cache name, delegate & queue.
+ /// - Parameters:
+ /// - name: Unique name for this cache. Helpful for debugging.
+ /// - evictionDecider: block used to decide if an item (a key-value pair) is evictable. Clients return YES
+ /// if the item can be evicted, or NO if the item should not be evicted from the cache. A nil evictionDevider is
+ /// equivalent to returning NO for all items. The decider will execute on an arbitrary thread. The `context`
+ /// parameter is NULL if the block is called due to the internal eviction timer expiring.
+ @objc public init(withName name: String? = nil, evictionDecider: EvictionDecider? = nil) {
+ self.name = name
+ self.evictionDecider = evictionDecider
+
+ let queueName: String
+ if let name = name {
+ queueName = "com.yahoo.cache \(name)"
+ } else {
+ queueName = "com.yahoo.cache"
+ }
+ queue = DispatchQueue(label: queueName, attributes: .concurrent)
+ queue.setSpecific(key: YMMemoryCache.kYFPrivateQueueKey, value: privateQueueNonce)
+
+ super.init()
+
+ if evictionDecider != nil {
+ // Time interval to notify UI. This sets the overall update cadence for the app.
+ evictionInterval = 600
+ }
+ notificationInterval = 0
+ }
+
+ deinit {
+ if let source = evictionSourceTimer, !source.isCancelled {
+ source.cancel()
+ }
+
+ if let source = notificationSourceTimer, !source.isCancelled {
+ source.cancel()
+ }
+ }
+
+ /// Returns the value associated with a given key
+ @objc public subscript(key: AnyHashable) -> Any? {
+ get { self[key, withDefault: nil] }
+ set { self[key, withDefault: nil] = newValue }
+ }
+
+ /// Get the value for the key. If value does not exist, invokes defaultLoader(), sets the result as
+ /// the value for key, and returns it. In order to ensure consistency, the cache is locked when
+ /// the defaultLoader block needs to be invoked
+ @objc public subscript(key: AnyHashable, withDefault loader: (() -> Any?)? = nil) -> Any? {
+ get {
+ assertNotPrivateQueue()
+
+ var item: Any? = nil
+ queue.sync {
+ item = self.items[key]
+ }
+
+ // Stop here unless a loader was provided and item did not exist.
+ // The loader phase requires more expensive dispatch barrier
+ guard let loader = loader, item == nil else { return item }
+
+ queue.sync(flags: .barrier) {
+ // In order to ensure that read call are only blocking when they need
+ // to be, this mutating block is executed with it's own barrier. This
+ // means that we have a potential race condition if this method is called
+ // in parallel with the same missing key. Resolve by checking again!
+ item = self.items[key]
+ guard item == nil else { return } // item may have been added concurrently in another thread (another loading block).
+
+ if let item = loader() {
+ self.itemsRemovedPendingNotify.remove(key)
+ self.items[key] = item
+ self.itemsUpdatedPendingNotify[key] = item
+ }
+ }
+ return item
+ }
+ set {
+ queue.async(flags: .barrier) { [weak self] in
+ guard let self = self else { return }
+ if let newValue = newValue {
+ self.itemsRemovedPendingNotify.remove(key)
+ self.items[key] = newValue
+ self.itemsUpdatedPendingNotify[key] = newValue
+ } else if self.items[key] != nil { // removing existing entry
+ self.itemsRemovedPendingNotify.insert(key)
+ self.items.removeValue(forKey: key)
+ self.itemsUpdatedPendingNotify.removeValue(forKey: key)
+ }
+ }
+ }
+ }
+
+ /// Adds to the cache the entries from a dictionary.
+ /// If the cache contains the same key as the dictionary, the cache's previous value object for that key
+ /// is replaced with new value object. All entries from dictionary are added to the cache at once such
+ /// that all of them are accessable by the next cache accessor, even if that accessor is triggered
+ /// before this method returns.
+ ///
+ /// All entries in the dictionary will be part of the next change notification event, even if an identical
+ /// key-value pair was already present in the cache.
+ ///
+ /// - Parameters:
+ /// - other: The dictionary from which to add entries
+ @objc public func addEntries(fromDictionary other: [AnyHashable: Any]) {
+ queue.async(flags: .barrier) {
+ self.items.merge(other) { $1 }
+ self.itemsUpdatedPendingNotify.merge(other) { $1 }
+ other.keys.forEach { self.itemsRemovedPendingNotify.remove($0) }
+ }
+ }
+
+ /** Empties the cache of its entries.
+ * Each key and corresponding value object is sent a release message.
+ */
+ @objc public func removeAllObjects() {
+ assertNotPrivateQueue()
+
+ queue.sync(flags: .barrier) {
+ self.items.keys.forEach { key in
+ self.itemsUpdatedPendingNotify.removeValue(forKey: key)
+ self.itemsRemovedPendingNotify.insert(key)
+ }
+ self.items.removeAll()
+ }
+ }
+
+ /// Removes from the cache entries specified by keys in a given array.
+ /// If a key in `keys` does not exist, the entry is ignored. This method is more efficient at removing
+ /// the values for multiple keys than calling `setObject:forKeyedSubscript` multiple times.
+ /// - Parameter keys: An array of objects specifying the keys to remove
+ @objc public func removeObjects(forKeys keys: [AnyHashable]) {
+ assertNotPrivateQueue()
+
+ queue.sync(flags: .barrier) {
+ keys.forEach { key in
+ guard self.items[key] != nil else { return }
+ self.itemsUpdatedPendingNotify.removeValue(forKey: key)
+ self.itemsRemovedPendingNotify.insert(key)
+ self.items.removeValue(forKey: key)
+ }
+ }
+ }
+
+ /// Returns a snapshot of all values in the cache.
+ /// The returned dictionary may differ from the actual cache as soon as it is returned. Because of this,
+ /// it is recommended to use `enumerateKeysAndObjectsUsingBlock:` for any operations that require a
+ /// guarantee that all items are operated upon (such as in low-memory situations).
+ /// - Returns: A copy of the underlying dictionary upon which the cache is built.
+ @objc public func allItems() -> [AnyHashable: Any] {
+ assertNotPrivateQueue()
+
+ var items = [AnyHashable: Any]()
+ queue.sync {
+ items = self.items
+ }
+ return items
+ }
+
+ /// Triggers an immediate (synchronous) check for exired items, and releases those items that are expired.
+ /// This method does nothing if no expirationDecider block was provided during initialization. The
+ /// evictionDecider block is run on the queue that this method is called on.
+ @objc(purgeEvictableItems:) public func purgeEvictableItems(context: UnsafeMutableRawPointer? = nil) {
+ // All external execution must have been dispatched to another queue so as to not leak the private queue
+ // though the user-provided evictionDecider block.
+ assertNotPrivateQueue()
+ guard let evictionDecider = evictionDecider else { return }
+
+ let frozenItems = allItems() // Trampoline to internal queue and copy items
+ let purgableItems = frozenItems.filter { kv in
+ let (key, value) = kv
+ return evictionDecider(key, value, context)
+ }.keys
+ removeObjects(forKeys: Array(purgableItems))
+ }
+}
+
+extension YMMemoryCache {
+
+ /// Throws an assertion if the current queue is not the private queue of this instance
+ private func assertPrivateQueue() {
+ guard let key = DispatchQueue.getSpecific(key: YMMemoryCache.kYFPrivateQueueKey) else {
+ return assertionFailure("Incorrect queue")
+ }
+ assert(key == privateQueueNonce, "Incorrect queue")
+ }
+
+ /// Throws an assertion if the current queue is the private queue of this instance
+ private func assertNotPrivateQueue() {
+ guard let key = DispatchQueue.getSpecific(key: YMMemoryCache.kYFPrivateQueueKey) else { return }
+ assert(key != privateQueueNonce, "Potential deadlock: blocking call issued from current queue to the same queue")
+ }
+
+ /// Create a new Dispatch Source Timer instance.
+ /// - Parameters:
+ /// - oldSource: An existing timer to be canceled
+ /// - interval: Repeat interval for the timer. If not greater than 0, the new timer is not set, but any old timer would be canceled
+ /// - queue: Dispatch queue to use for timer source invocations
+ /// - execute: The block to invoke on `queue` after each `interval`
+ /// - Returns: The new source timer that was created, if any was. Call `resume()` on this timer to start it.
+ private func newSourceTimer(replacing oldSource: DispatchSourceTimer?, interval: TimeInterval, queue: DispatchQueue, execute: @escaping () -> Void) -> DispatchSourceTimer? {
+ // Cancel any scheduled work
+ if let oldSource = oldSource {
+ oldSource.cancel()
+ }
+
+ guard interval > .zero else { return nil } // nothing to schedule
+
+ // Create a new source timer
+ let source = DispatchSource.makeTimerSource(queue: queue)
+ source.setEventHandler(handler: execute)
+
+ // Schedule repeating invocations
+ let usecInterval = Int(interval * Double(USEC_PER_SEC))
+ source.schedule(deadline: .now() + .microseconds(usecInterval), repeating: .microseconds(usecInterval))
+
+ return source
+ }
+
+ /// Dispatch any pending change notifications
+ private func sendPendingNotifications() {
+ assertPrivateQueue()
+ guard !itemsUpdatedPendingNotify.isEmpty && !itemsRemovedPendingNotify.isEmpty else {
+ return
+ }
+
+ let updated = itemsUpdatedPendingNotify
+ let removed = itemsRemovedPendingNotify
+ itemsUpdatedPendingNotify.removeAll()
+ itemsRemovedPendingNotify.removeAll()
+
+ DispatchQueue.main.async { [weak self] in
+ NotificationCenter.default.post(name: YMMemoryCache.CacheDidChangeNotification, object: self, userInfo: [
+ YMMemoryCache.CacheUpdatedItemsUserInfoKey: updated,
+ YMMemoryCache.CacheRemovedItemsUserInfoKey: removed
+ ])
+ }
+ }
+}
+
+@objc public extension YMMemoryCache {
+ /// Cache update notification. The userInfo dictionary in the notification contains two values:
+ /// `kYFCacheUpdatedItemsUserInfoKey` containing key-value pairs that have been added/updated and
+ /// `kYFCacheRemovedItemsUserInfoKey` containing keys that have been removed.
+ /// The notification is essentially a delta between the last notification and the current cache state.
+ @objc static var CacheDidChangeNotification: NSNotification.Name { NSNotification.Name.yfCacheDidChange }
+
+ /// A key whose value is an NSDictionary of key-value pairs
+ /// representing entries that have been added
+ /// to or removed from the cache since the last notification.
+ @objc static var CacheUpdatedItemsUserInfoKey: String { kYFCacheUpdatedItemsUserInfoKey }
+
+ /// A key whose value is an NSSet of cache keys representing entries that have been removed from the
+ /// cache since the last notification.
+ @objc static var CacheRemovedItemsUserInfoKey: String { kYFCacheRemovedItemsUserInfoKey }
+
+ /// Type of a decider block for determining is an item is evictable.
+ /// - Parameters:
+ /// - key: The key associated with value in the cache
+ /// - value: The value of the item in the cache
+ /// - context: Arbitrary user-provided context
+ typealias EvictionDecider = (_ key: Any, _ value: Any, _ context: UnsafeMutableRawPointer?) -> Bool
+}