From a033d0306523b3b9220cad0399b44e5ce201f591 Mon Sep 17 00:00:00 2001 From: Adam Kaplan Date: Wed, 6 Jan 2021 22:14:24 -0500 Subject: [PATCH 1/7] Swift 5 port --- Examples/Mantle/Podfile | 2 +- Examples/Mantle/Podfile.lock | 14 +- .../project.pbxproj | 49 ++- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + YMCache.podspec | 11 +- YMCache/YMCache.h | 1 - YMCache/YMCachePersistenceController.m | 1 - YMCache/YMMemoryCache.h | 165 -------- YMCache/YMMemoryCache.m | 330 ---------------- YMCache/YMMemoryCache.swift | 362 ++++++++++++++++++ 10 files changed, 408 insertions(+), 535 deletions(-) create mode 100644 Examples/Mantle/YMCacheMantleExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 YMCache/YMMemoryCache.h delete mode 100644 YMCache/YMMemoryCache.m create mode 100644 YMCache/YMMemoryCache.swift 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..973c0cd 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,6 +173,7 @@ FCDDABB91B701243006C333F /* Project object */ = { isa = PBXProject; attributes = { + CLASSPREFIX = YM; LastUpgradeCheck = 0640; ORGANIZATIONNAME = "Yahoo, Inc"; TargetAttributes = { @@ -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 */ @@ -334,7 +325,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 = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -371,7 +362,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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; @@ -384,7 +375,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = YMCacheMantleExample/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -395,7 +389,10 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = YMCacheMantleExample/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); 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/YMCache.podspec b/YMCache.podspec index 191c212..baa045d 100644 --- a/YMCache.podspec +++ b/YMCache.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'YMCache' - s.version = '2.1.1' + s.version = '2.2.0' s.summary = 'Fast & simple small object cache. GCD-based and thread-safe.' s.homepage = 'https://github.com/yahoo/YMCache' s.license = 'MIT' @@ -8,12 +8,11 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/yahoo/YMCache.git', :tag => s.version.to_s } s.social_media_url = 'https://twitter.com/adkap' - s.ios.deployment_target = '8.0' + s.ios.deployment_target = '9.0' s.osx.deployment_target = '10.10' s.tvos.deployment_target = '11.0' s.watchos.deployment_target = '3.0' - - s.requires_arc = true - - 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/YMCache.h b/YMCache/YMCache.h index 048a5a9..509c984 100644 --- a/YMCache/YMCache.h +++ b/YMCache/YMCache.h @@ -10,6 +10,5 @@ FOUNDATION_EXPORT double YMCacheVersionNumber; FOUNDATION_EXPORT const unsigned char YMCacheVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import -#import #import diff --git a/YMCache/YMCachePersistenceController.m b/YMCache/YMCachePersistenceController.m index 5a542a8..54fc8ce 100644 --- a/YMCache/YMCachePersistenceController.m +++ b/YMCache/YMCachePersistenceController.m @@ -3,7 +3,6 @@ // Licensed under the terms of the MIT License. See LICENSE file in the project root. #import "YMCachePersistenceController.h" -#import "YMMemoryCache.h" #import "YMLog.h" #include "TargetConditionals.h" diff --git a/YMCache/YMMemoryCache.h b/YMCache/YMMemoryCache.h deleted file mode 100644 index c38625c..0000000 --- a/YMCache/YMMemoryCache.h +++ /dev/null @@ -1,165 +0,0 @@ -// Created by Adam Kaplan on 8/1/15. -// Copyright 2015 Yahoo. -// Licensed under the terms of the MIT License. See LICENSE file in the project root. - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * 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. - */ -extern NSString *const kYFCacheDidChangeNotification; -/** - * 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. - */ -extern NSString *const kYFCacheUpdatedItemsUserInfoKey; -/** - * A key whose value is an NSSet of cache keys representing entries that have been removed from the - * cache since the last notification. - */ -extern NSString *const kYFCacheRemovedItemsUserInfoKey; - -/** 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. - */ -typedef BOOL (^YMMemoryCacheEvictionDecider)(id key, id value, void *__nullable context); - -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 deleted file mode 100644 index e4b0d23..0000000 --- a/YMCache/YMMemoryCache.m +++ /dev/null @@ -1,330 +0,0 @@ -// Created by Adam Kaplan on 8/1/15. -// Copyright 2015 Yahoo. -// 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..54368c9 --- /dev/null +++ b/YMCache/YMMemoryCache.swift @@ -0,0 +1,362 @@ +// Created by Adam Kaplan on 8/1/15. +// Copyright 2021 Verizon Media. +// Licensed under the terms of the MIT License. See LICENSE file in the project root. + +import Foundation + +/// 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. +public let kYFCacheDidChangeNotification = "kYFCacheDidChangeNotification" + +/// 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. +public let kYFCacheUpdatedItemsUserInfoKey = "kYFCacheUpdatedItemsUserInfoKey" + +/// A key whose value is an NSSet of cache keys representing entries that have been removed from the +/// cache since the last notification. +public let kYFCacheRemovedItemsUserInfoKey = "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 +public typealias YMMemoryCacheEvictionDecider = (_ key: Any, _ value: Any, _ context: UnsafeMutableRawPointer?) -> Bool + +/// 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 + let evictionDecider: YMMemoryCacheEvictionDecider? + + 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. + /// - 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: YMMemoryCacheEvictionDecider? = 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: YMMemoryCacheEvictionDecider? = 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. + * @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. + */ + /** 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. + */ + /** 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. + /// + /// - Parameter fromDictionary: 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 private 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)) + } + + 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: NSNotification.Name(rawValue: kYFCacheDidChangeNotification), object: self, userInfo: [ + kYFCacheUpdatedItemsUserInfoKey: updated, + kYFCacheRemovedItemsUserInfoKey: removed + ]) + } + } +} + +extension YMMemoryCache { + private func assertPrivateQueue() { + guard let key = DispatchQueue.getSpecific(key: YMMemoryCache.kYFPrivateQueueKey) else { + return assertionFailure("Incorrect queue") + } + assert(key == privateQueueNonce, "Incorrect queue") + } + + 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") + } + + 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 + } +} From 2e3373c3bc170e04b8893fc60bb73d9905b1b596 Mon Sep 17 00:00:00 2001 From: Adam Kaplan Date: Wed, 6 Jan 2021 23:27:54 -0500 Subject: [PATCH 2/7] Swift interface fixes --- .../project.pbxproj | 37 +++++++++- .../Mantle/YMCacheMantleExample/Info.plist | 4 +- .../TableViewController.m | 2 +- YMCache/YMCache.h | 1 + YMCache/YMCachePersistenceController.m | 1 + YMCache/YMMemoryCache.h | 36 +++++++++ YMCache/YMMemoryCache.m | 9 +++ YMCache/YMMemoryCache.swift | 74 +++++++++++-------- 8 files changed, 128 insertions(+), 36 deletions(-) create mode 100644 YMCache/YMMemoryCache.h create mode 100644 YMCache/YMMemoryCache.m diff --git a/Examples/Mantle/YMCacheMantleExample.xcodeproj/project.pbxproj b/Examples/Mantle/YMCacheMantleExample.xcodeproj/project.pbxproj index 973c0cd..8dea29f 100644 --- a/Examples/Mantle/YMCacheMantleExample.xcodeproj/project.pbxproj +++ b/Examples/Mantle/YMCacheMantleExample.xcodeproj/project.pbxproj @@ -174,7 +174,7 @@ isa = PBXProject; attributes = { CLASSPREFIX = YM; - LastUpgradeCheck = 0640; + LastUpgradeCheck = 1220; ORGANIZATIONNAME = "Yahoo, Inc"; TargetAttributes = { FCDDABC01B701243006C333F = { @@ -293,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; @@ -325,7 +338,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -336,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"; @@ -362,7 +387,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; @@ -373,12 +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", ); + PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -387,12 +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", ); + PRODUCT_BUNDLE_IDENTIFIER = "com.yahoo.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; 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/YMCache.h b/YMCache/YMCache.h index 509c984..765149c 100644 --- a/YMCache/YMCache.h +++ b/YMCache/YMCache.h @@ -11,4 +11,5 @@ FOUNDATION_EXPORT const unsigned char YMCacheVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import #import +#import diff --git a/YMCache/YMCachePersistenceController.m b/YMCache/YMCachePersistenceController.m index 54fc8ce..9e472df 100644 --- a/YMCache/YMCachePersistenceController.m +++ b/YMCache/YMCachePersistenceController.m @@ -4,6 +4,7 @@ #import "YMCachePersistenceController.h" #import "YMLog.h" +#import #include "TargetConditionals.h" diff --git a/YMCache/YMMemoryCache.h b/YMCache/YMMemoryCache.h new file mode 100644 index 0000000..46cea15 --- /dev/null +++ b/YMCache/YMMemoryCache.h @@ -0,0 +1,36 @@ +// 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 + +NS_ASSUME_NONNULL_BEGIN + +/** + * 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. + */ +extern NSString *const kYFCacheDidChangeNotification; +/** + * 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. + */ +extern NSString *const kYFCacheUpdatedItemsUserInfoKey; +/** + * A key whose value is an NSSet of cache keys representing entries that have been removed from the + * cache since the last notification. + */ +extern NSString *const kYFCacheRemovedItemsUserInfoKey; + + /** 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. + */ +typedef BOOL (^YMMemoryCacheEvictionDecider)(id key, id value, void *__nullable context); + +typedef __nullable id (^YMMemoryCacheObjectLoader)(void); + +NS_ASSUME_NONNULL_END diff --git a/YMCache/YMMemoryCache.m b/YMCache/YMMemoryCache.m new file mode 100644 index 0000000..4299cbd --- /dev/null +++ b/YMCache/YMMemoryCache.m @@ -0,0 +1,9 @@ +// 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" + +NSString *const kYFCacheDidChangeNotification = @"kYFCacheDidChangeNotification"; +NSString *const kYFCacheUpdatedItemsUserInfoKey = @"kYFCacheUpdatedItemsUserInfoKey"; +NSString *const kYFCacheRemovedItemsUserInfoKey = @"kYFCacheRemovedItemsUserInfoKey"; diff --git a/YMCache/YMMemoryCache.swift b/YMCache/YMMemoryCache.swift index 54368c9..f55516d 100644 --- a/YMCache/YMMemoryCache.swift +++ b/YMCache/YMMemoryCache.swift @@ -1,31 +1,9 @@ -// Created by Adam Kaplan on 8/1/15. +// 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 -/// 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. -public let kYFCacheDidChangeNotification = "kYFCacheDidChangeNotification" - -/// 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. -public let kYFCacheUpdatedItemsUserInfoKey = "kYFCacheUpdatedItemsUserInfoKey" - -/// A key whose value is an NSSet of cache keys representing entries that have been removed from the -/// cache since the last notification. -public let kYFCacheRemovedItemsUserInfoKey = "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 -public typealias YMMemoryCacheEvictionDecider = (_ key: Any, _ value: Any, _ context: UnsafeMutableRawPointer?) -> Bool - /// 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 @@ -104,7 +82,7 @@ public typealias YMMemoryCacheEvictionDecider = (_ key: Any, _ value: Any, _ con } /// Eviction decider block invoked during eviction runs - let evictionDecider: YMMemoryCacheEvictionDecider? + let evictionDecider: EvictionDecider? private let queue: DispatchQueue @@ -120,11 +98,18 @@ public typealias YMMemoryCacheEvictionDecider = (_ key: Any, _ value: Any, _ con 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: YMMemoryCacheEvictionDecider? = nil) -> YMMemoryCache { + @objc public class func memoryCache(withName name: String?, evictionDecider: EvictionDecider? = nil) -> YMMemoryCache { return YMMemoryCache(withName: name, evictionDecider: evictionDecider) } @@ -135,7 +120,7 @@ public typealias YMMemoryCacheEvictionDecider = (_ key: Any, _ value: Any, _ con /// 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: YMMemoryCacheEvictionDecider? = nil) { + @objc public init(withName name: String? = nil, evictionDecider: EvictionDecider? = nil) { self.name = name self.evictionDecider = evictionDecider @@ -167,6 +152,11 @@ public typealias YMMemoryCacheEvictionDecider = (_ key: Any, _ value: Any, _ con } } + @objc public subscript(key: AnyHashable) -> Any? { + get { self[key, withDefault: nil] } + set { self[key, withDefault: nil] = newValue } + } + /** 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. @@ -294,7 +284,7 @@ public typealias YMMemoryCacheEvictionDecider = (_ key: Any, _ value: Any, _ con /// 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 private func purgeEvictableItems(context: UnsafeMutableRawPointer? = nil) { + @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() @@ -320,9 +310,9 @@ public typealias YMMemoryCacheEvictionDecider = (_ key: Any, _ value: Any, _ con itemsRemovedPendingNotify.removeAll() DispatchQueue.main.async { [weak self] in - NotificationCenter.default.post(name: NSNotification.Name(rawValue: kYFCacheDidChangeNotification), object: self, userInfo: [ - kYFCacheUpdatedItemsUserInfoKey: updated, - kYFCacheRemovedItemsUserInfoKey: removed + NotificationCenter.default.post(name: YMMemoryCache.CacheDidChangeNotification, object: self, userInfo: [ + YMMemoryCache.CacheUpdatedItemsUserInfoKey: updated, + YMMemoryCache.CacheRemovedItemsUserInfoKey: removed ]) } } @@ -360,3 +350,27 @@ extension YMMemoryCache { return source } } + +@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 +} From 941c8771331948dea032914a83e96a6387564829 Mon Sep 17 00:00:00 2001 From: Adam Kaplan Date: Wed, 6 Jan 2021 23:37:37 -0500 Subject: [PATCH 3/7] Docs --- YMCache/YMMemoryCache.swift | 69 +++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/YMCache/YMMemoryCache.swift b/YMCache/YMMemoryCache.swift index f55516d..27dcd68 100644 --- a/YMCache/YMMemoryCache.swift +++ b/YMCache/YMMemoryCache.swift @@ -82,7 +82,7 @@ import Foundation } /// Eviction decider block invoked during eviction runs - let evictionDecider: EvictionDecider? + private let evictionDecider: EvictionDecider? private let queue: DispatchQueue @@ -152,25 +152,15 @@ import Foundation } } + /// 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 } } - /** 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. - */ - /** 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. - */ - /** 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. - */ + /// 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() @@ -297,28 +287,11 @@ import Foundation }.keys removeObjects(forKeys: Array(purgableItems)) } - - 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 - ]) - } - } } 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") @@ -326,11 +299,19 @@ extension YMMemoryCache { 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 { @@ -349,6 +330,26 @@ extension YMMemoryCache { 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 { From 6ff19eb32e56987efe04bc333b66c3dfdff972c9 Mon Sep 17 00:00:00 2001 From: David Grandinetti Date: Tue, 30 Mar 2021 11:25:45 -0400 Subject: [PATCH 4/7] update gem install --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 59a8e4b..0859c2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: xcode_destination: platform=macOS before_install: - - gem install slather --no-rdoc --no-ri --no-document --quiet + - gem install slather --no-document --quiet - brew update || brew update - brew install carthage || brew outdated carthage || brew upgrade carthage - "./script/travis-codesign-setup.sh" From c0e54db86f34994fc41c91fc739e9152d2c9123a Mon Sep 17 00:00:00 2001 From: David Grandinetti Date: Tue, 30 Mar 2021 11:37:16 -0400 Subject: [PATCH 5/7] update to xcode 12.2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0859c2d..cfcfbad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: objective-c os: osx -osx_image: xcode10.1 +osx_image: xcode12.2 xcode_project: YMCache.xcodeproj matrix: From 91714111a0e08b213900a95cd76994e678029088 Mon Sep 17 00:00:00 2001 From: Adam Kaplan Date: Wed, 26 Oct 2022 21:13:12 -0400 Subject: [PATCH 6/7] Bring up to date with Xcode 13.0 --- YMCache.xcodeproj/project.pbxproj | 113 ++++++++++++++---- .../xcschemes/YMCache-Mac.xcscheme | 24 ++-- .../xcschemes/YMCache-iOS.xcscheme | 24 ++-- 3 files changed, 109 insertions(+), 52 deletions(-) diff --git a/YMCache.xcodeproj/project.pbxproj b/YMCache.xcodeproj/project.pbxproj index df63b27..7d8b5b0 100644 --- a/YMCache.xcodeproj/project.pbxproj +++ b/YMCache.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 55; objects = { /* Begin PBXBuildFile section */ @@ -98,7 +98,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 +211,6 @@ FCB05E131B6FE1FA00C7302B /* Project Metadata */ = { isa = PBXGroup; children = ( - FCB05E341B6FE93F00C7302B /* Cartfile.private */, FCB05E151B6FE21300C7302B /* CHANGELOG.md */, FCB05E161B6FE21300C7302B /* CONTRIBUTING.md */, FCB05E171B6FE21300C7302B /* LICENSE */, @@ -378,7 +376,7 @@ isa = PBXProject; attributes = { CLASSPREFIX = YM; - LastUpgradeCheck = 1010; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "Yahoo, Inc"; TargetAttributes = { FC59B3621B71156B00093177 = { @@ -396,11 +394,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 */; @@ -509,6 +508,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 +521,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 +550,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 +562,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; @@ -583,7 +586,11 @@ ); 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; @@ -607,7 +614,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)"; }; @@ -629,7 +641,11 @@ ); 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; @@ -655,7 +671,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; @@ -678,7 +699,11 @@ ); 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; @@ -699,7 +724,11 @@ 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; @@ -725,7 +754,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; @@ -743,7 +777,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 +797,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 +810,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 +839,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 +851,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 +867,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 +880,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 +902,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 +914,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; @@ -891,7 +938,11 @@ ); 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; @@ -913,7 +964,11 @@ 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; @@ -937,7 +992,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)"; }; @@ -953,7 +1013,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 @@ - - - - - - - - Date: Wed, 26 Oct 2022 21:32:34 -0400 Subject: [PATCH 7/7] Fix compile errors --- YMCache.xcodeproj/project.pbxproj | 30 ++++++++++++++++++++++++++ YMCache/YMCachePersistenceController.m | 1 + YMCache/YMMemoryCache.swift | 3 ++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/YMCache.xcodeproj/project.pbxproj b/YMCache.xcodeproj/project.pbxproj index 7d8b5b0..b9e5deb 100644 --- a/YMCache.xcodeproj/project.pbxproj +++ b/YMCache.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ 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; }; @@ -255,6 +258,7 @@ FCB05DDB1B6FDFC300C7302B /* YMCachePersistenceController.m */, FCB05DDD1B6FDFC300C7302B /* YMMemoryCache.h */, FCB05DDE1B6FDFC300C7302B /* YMMemoryCache.m */, + C6F9EE1E290A1691008DEB36 /* YMMemoryCache.swift */, ); name = Public; sourceTree = ""; @@ -381,12 +385,14 @@ TargetAttributes = { FC59B3621B71156B00093177 = { CreatedOnToolsVersion = 6.4; + LastSwiftMigration = 1300; }; FC59B36C1B71156B00093177 = { CreatedOnToolsVersion = 6.4; }; FCDDAB5C1B7008D4006C333F = { CreatedOnToolsVersion = 6.4; + LastSwiftMigration = 1300; }; FCDDAB661B7008D4006C333F = { CreatedOnToolsVersion = 6.4; @@ -452,6 +458,7 @@ files = ( FC59B37C1B7115FC00093177 /* YMCachePersistenceController.m in Sources */, FC59B37D1B7115FD00093177 /* YMMemoryCache.m in Sources */, + C6F9EE20290A1691008DEB36 /* YMMemoryCache.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -471,6 +478,7 @@ files = ( FCD690821B710F520099E854 /* YMCachePersistenceController.m in Sources */, FCD690831B710F540099E854 /* YMMemoryCache.m in Sources */, + C6F9EE1F290A1691008DEB36 /* YMMemoryCache.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -574,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; @@ -594,6 +603,8 @@ 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 = ""; @@ -603,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)", @@ -628,6 +640,7 @@ FC4B27571BA13A2000F870A1 /* Test */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; @@ -650,6 +663,8 @@ PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -658,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 = ( @@ -686,6 +702,7 @@ FC59B3771B71156B00093177 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; @@ -708,6 +725,8 @@ PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -716,6 +735,7 @@ FC59B3781B71156B00093177 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; @@ -733,6 +753,7 @@ PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -741,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 = ( @@ -769,6 +791,7 @@ FC59B37B1B71156B00093177 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", @@ -926,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; @@ -946,6 +970,8 @@ 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 = ""; @@ -956,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; @@ -972,6 +999,7 @@ 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 = ""; @@ -981,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)", @@ -1006,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)", diff --git a/YMCache/YMCachePersistenceController.m b/YMCache/YMCachePersistenceController.m index 9e472df..71a067c 100644 --- a/YMCache/YMCachePersistenceController.m +++ b/YMCache/YMCachePersistenceController.m @@ -6,6 +6,7 @@ #import "YMLog.h" #import + #include "TargetConditionals.h" diff --git a/YMCache/YMMemoryCache.swift b/YMCache/YMMemoryCache.swift index 27dcd68..f76e345 100644 --- a/YMCache/YMMemoryCache.swift +++ b/YMCache/YMMemoryCache.swift @@ -215,7 +215,8 @@ import Foundation /// 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. /// - /// - Parameter fromDictionary: The dictionary from which to add entries + /// - 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 }