pinterest / pincache Goto Github PK
View Code? Open in Web Editor NEWFast, non-deadlocking parallel object cache for iOS, tvOS and OS X
License: Apache License 2.0
Fast, non-deadlocking parallel object cache for iOS, tvOS and OS X
License: Apache License 2.0
Hi,
I am not able to save an object.
The log shows : PINDiskCache.m (303) ERROR: The operation couldn’t be completed. (Cocoa error 4.)
Hi!
First, many things for making your work open!
In PINDiskCache.m, this is a kind request to make the values for PINDiskCachePrefix
and PINDiskCacheSharedName
changeable at build time (i.e. not hard-coded into the repository!).
Suggestions that would all work:
Thanks!!
Drew
Carthage can download prebuilt frameworks if they are attached to a GitHub release.
For example, to prebuild the zip required for 2.0.1:
git checkout 2.0.1
carthage build --no-skip-current
carthage archive PINCache
Attach the resulting PINCache.framework.zip
to the release.
If a key is longer than the filesystem supports in its filename, the object isn't cached properly. At the very least, PINDiskCache should throw an assert when this happens.
Hey guys, is that possible to disable disk cache for PINCache?
The current version (2.1.1) is not used for production at the moment. Maybe a new podspec should created for cocoapods as soon as possible to fix this issues with all new installation of PINRemoteImage and PINCache.
Hi,
I have an issue after updating PINCache.
Object are saved between a given session, however when I kill the app, all objects are nil.
Any idea?
Thanks
We're using PINRemoteImage and asking it to download several very large images (~5k x 4k). When it does this memory spikes up to 1GB and the app gets killed by the system. This seems to be the case even if we set our costLimit to something far below 1GB:
var imagePrefetcher: PINRemoteImageManager = {
let manager = PINRemoteImageManager.sharedImageManager()
manager.cache.diskCache.byteLimit = 20_000_000
manager.cache.memoryCache.costLimit = 300_000_000
return manager
}()
It seems like perhaps the costLimit is simply not being honored? Or is it possible these are ending up in memory somewhere else first?
We're making the download calls serially all on the main thread:
for image in imagesToCache {
imagePrefetcher.downloadImageWithURL(image) { result in
Our crash reporting consistently logs crashes on -[PINDiskCache objectForKey:fileURL:]
. This is one of our highest reported crashes. This is on version 2.1.
Stack traces for the queue the crash occurs on look like the following:
Thread : Crashed: com.pinterest.PINDiskCache Asynchronous Queue
0 Foundation 0x269dea08 -[NSKeyedUnarchiver initForReadingWithData:] + 439
1 Foundation 0x269de991 -[NSKeyedUnarchiver initForReadingWithData:] + 320
2 Foundation 0x26a37305 +[NSKeyedUnarchiver unarchiveObjectWithFile:] + 124
3 Remind101 0x37159f -[PINDiskCache objectForKey:fileURL:] (PINDiskCache.m:605)
4 Remind101 0x3707e1 __35-[PINDiskCache objectForKey:block:]_block_invoke (PINDiskCache.m:433)
5 libdispatch.dylib 0x25dbdb5b _dispatch_call_block_and_release + 10
6 libdispatch.dylib 0x25dc8365 _dispatch_async_redirect_invoke$VARIANT$mp + 1360
7 libdispatch.dylib 0x25dcc921 _dispatch_root_queue_drain + 1560
8 libdispatch.dylib 0x25dcc305 _dispatch_worker_thread3 + 96
9 libsystem_pthread.dylib 0x25f7bb29 _pthread_wqthread + 1024
10 libsystem_pthread.dylib 0x25f7b718 start_wqthread + 8
All other threads are idle and the main thread is simply spinning the runloop when this happens:
Thread : com.apple.main-thread
0 libsystem_kernel.dylib 0x25ec4c24 mach_msg_trap + 20
1 libsystem_kernel.dylib 0x25ec4a29 mach_msg + 40
2 CoreFoundation 0x26207355 __CFRunLoopServiceMachPort + 136
3 CoreFoundation 0x262056dd __CFRunLoopRun + 1036
4 CoreFoundation 0x26158bf9 CFRunLoopRunSpecific + 520
5 CoreFoundation 0x261589e5 CFRunLoopRunInMode + 108
6 GraphicsServices 0x273a4ac9 GSEventRunModal + 160
7 UIKit 0x2a3e8ba1 UIApplicationMain + 144
8 Remind101 0x241703 main (main.m:16)
9 libdispatch.dylib 0x25e07873 (Missing)
There happen to be quite a large number of queues for PINDiskCache
when this occurs though. Their threads look like so:
Thread : com.pinterest.PINDiskCache Asynchronous Queue
0 libsystem_kernel.dylib 0x25ec4c74 semaphore_wait_trap + 8
1 libdispatch.dylib 0x25dcec83 _dispatch_semaphore_wait_slow + 190
2 Remind101 0x3714b1 -[PINDiskCache objectForKey:fileURL:] (PINDiskCache.m:599)
3 Remind101 0x3707e1 __35-[PINDiskCache objectForKey:block:]_block_invoke (PINDiskCache.m:433)
4 libdispatch.dylib 0x25dbdb5b _dispatch_call_block_and_release + 10
5 libdispatch.dylib 0x25dc8365 _dispatch_async_redirect_invoke$VARIANT$mp + 1360
6 libdispatch.dylib 0x25dcc921 _dispatch_root_queue_drain + 1560
7 libdispatch.dylib 0x25dcc305 _dispatch_worker_thread3 + 96
8 libsystem_pthread.dylib 0x25f7bb29 _pthread_wqthread + 1024
9 libsystem_pthread.dylib 0x25f7b718 start_wqthread + 8
Hi, I've created simple project to test PINCache, and the app crashed on dealloc method in either PINMemoryCache or PINDiskCache. Steps to reproduce:
I was able to establish that re-adding files to Pod-PINCache project fixes the problem, but in my opinion that's not proper fix for it. Probably user wouldn't like the idea of updating project file when updating pods.
Thanks.
When adding objects to PINCache
via -setObject:forKey:
those objects are internally stored to a PINMemoryCache
and PINDiskCache
. Trouble is, the value specified for the object stored in the PINMemoryCache
is zero, meaning that trimming the memory cache by setting a cost limit doesn't work. It would be handy if there was some way to specify cost either explicitly when calling -setObject:forKey:
on the outer PINCache
or by having the objects passed in conform to a protocol indicating their cost, something like a PINMemoryCacheCost
protocol.
When an object in PINMemoryCache is replaced, the willRemoveObjectBlock
and the didRemoveObjectBlock
for the old object aren't called.
They are called in most other cases when an object is removed via a call to removeObjectAndExecuteBlocksForKey
. This happens in
removeObjectForKey
And when trimming in
trimMemoryToDate trimToCostLimit trimToCostLimitByDate
But they aren't called in ALL other cases. They are not called in the case of removing ALL objects. In that case willRemoveAllObjectsBlock
and didRemoveAllObjectsBlock
blocks are called instead.
Also worth noting, if trimToDate is given a date of distantPast
(should this be distantFuture
?) it will call removeAll instead of the normal trim hence also skipping calls to the willRemove/didRemove blocks
Anyways, with that background in mind, should the blocks be called in the case when an object is replaced ?
And what about when an object in the cache is replaced with an object it is .equal() to ?
If a caller calls
- (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost
on a PINMemoryCache for an existing key, the cost associated with the currently stored object is never deducted from the the cache's totalCost
It seems like it might even make sense to call
[self removeObjectAndExecuteBlocksForKey:key];
At the beginning of the setObject:forKey:withCost method if the key already exists
I had a discussion with a colleague the other day who was advocating we use NSURLCache
over PINCache
. I was asked: what does PINCache
offer that NSURLCache
doesn't offer? I was rather at a loss.
As someone who hasn't used NSURLCache
I don't know much about it but "NSURLCache provides a composite in-memory and on-disk caching mechanism for URL requests to your application"link. This seems to be pretty much what PINCache is doing too.
One thing I thought of is that PINCache
prunes objects by LRU which is maybe something NSURLCache
doesn't? But I am skeptical.
Would love to hear comments on this so I can help get a better understanding to advocate PINCache
over the ones provided by Foundation...
Because the latest release is from before containsObjectForKey
was added, I am not able to use that API, do you mind adding a new release for it or showing me how to access it?
I am using Swift with the use frameworks Cocoapods option.
from my Podfile:
pod 'PINCache', :git => 'https://github.com/pinterest/PINCache.git'
Thanks btw, I find this cache very useful!
Hi there,
It would be great to be able to purge items that have expired from the cache. For example, I have a service that returns images with a cache-control header. I would like to have the ability to, when inserting into the cache, specify a date to mark them as expired and then have the ability to remove only the expired items from the cache.
Thanks
PINDiskCache.m (756) ERROR: The item couldn’t be saved because the file name “https%3A%2F%2Fupload%2Ewikimedia%2Eorg%2Fwikipedia%2Fcommons%2Fthumb%2F0%2F0b%2F%25E5%2590%258D%25E9%2589%25842201F%25E3%2582%25AE%25E3%2583%25A9%25E3%2583%2586%25E3%2582%25A3%25E3%2583%258A%25E3%2583%25BB%25E3%2582%25B7%25E3%2582%25A7%25E3%2582%25A4%25E3%2583%259F%25E5%258F%25B720080720%2Ejpg%2F700px-%25E5%2590%258D%25E9%2589%25842201F%25E3%2582%25AE%25E3%2583%25A9%25E3%2583%2586%25E3%2582%25A3%25E3%2583%258A%25E3%2583%25BB%25E3%2582%25B7%25E3%2582%25A7%25E3%2582%25A4%25E3%2583%259F%25E5%258F%25B720080720%2Ejpg” is invalid.
PINCache/PINDiskCache.m:148:33: 'CFURLCreateStringByAddingPercentEscapes' is deprecated: first deprecated in iOS 9.0 - Use [NSString stringByAddingPercentEncodingWithAllowedCharacters:] instead, which always uses the recommended UTF-8 encoding, and which encodes for a specific URL component or subcomponent (since each URL component or subcomponent has different rules for what characters are valid).
PINCache/PINDiskCache.m:161:35: 'CFURLCreateStringByReplacingPercentEscapesUsingEncoding' is deprecated: first deprecated in iOS 9.0 - Use [NSString stringByRemovingPercentEncoding] or CFURLCreateStringByReplacingPercentEscapes() instead, which always uses the recommended UTF-8 encoding.
Would be great if you could add watchOS platform support
There is currently no support for watchOS 2.
I have created pull request #45 to solve the issue
Cheers
Tim
I see high rate crash on [PINDiskCache setObject:forKey:fileURL:]
in my crash report. I use 2.2.2 PINCache with AsyncDisplayKit. It seems relative to file protection ? any idea about this?
Fatal Exception: NSInvalidArgumentException
*** -[NSURL initFileURLWithPath:isDirectory:]: nil string parameter
Fatal Exception: NSInvalidArgumentException
0 CoreFoundation 0x18278ae38 __exceptionPreprocess
1 libobjc.A.dylib 0x181deff80 objc_exception_throw
2 CoreFoundation 0x18278ad80 -[NSException initWithCoder:]
3 Foundation 0x18308f68c -[NSURL(NSURL) initFileURLWithPath:isDirectory:]
4 Foundation 0x18308f540 +[NSURL(NSURL) fileURLWithPath:isDirectory:]
5 Foundation 0x183149800 _NSCreateTemporaryFile_Protected
6 Foundation 0x1831336bc +[NSKeyedArchiver archiveRootObject:toFile:]
7 meitian.iOS 0x1003f7068 -[PINDiskCache setObject:forKey:fileURL:] (PINDiskCache.m:699)
8 meitian.iOS 0x1003f5d0c __39-[PINDiskCache setObject:forKey:block:]_block_invoke (PINDiskCache.m:494)
9 libdispatch.dylib 0x1821d54bc _dispatch_call_block_and_release
10 libdispatch.dylib 0x1821d547c _dispatch_client_callout
11 libdispatch.dylib 0x1821dfd50 _dispatch_async_redirect_invoke
12 libdispatch.dylib 0x1821d547c _dispatch_client_callout
13 libdispatch.dylib 0x1821e3914 _dispatch_root_queue_drain
14 libdispatch.dylib 0x1821e30b0 _dispatch_worker_thread3
15 libsystem_pthread.dylib 0x1823ed470 _pthread_wqthread
16 libsystem_pthread.dylib 0x1823ed020 start_wqthread
Release only contains iOS and Mac framework at the moment. Could you add tvOS as well?
There are quite a few local variables that shadow other local variables.
For example, the weakSelf variable in PINCache's objectForKey method.
#36 is breaking the disk cache of PINCache
more explanation is directly in the pull request.
So, using objC it is perfect, but when I wanna cast with swift I had EXC_BAD_ACCESS
When I call objectForKey
- (id <NSCoding>)objectForKey:(NSString *)key;
And I have nothing cached for that key, but Swift understand that the object is an NSConding. So, if I have values into a key, I have no problems.
So, how can I cast this NSCoding result and compare with nil value. I only need to know if the object return from PINCache is nil (never cached).
I've already tried:
private func setupTrucks() {
let trucksCached: AnyObject? = PINDiskCache.sharedCache().objectForKey(TruckCache.key)
if let _truck = trucksCached as? [Truck] { //EXC_BAD_ACCESS here
print(_truck)
}
}
private func setupTrucks() {
let trucksCached = PINDiskCache.sharedCache().objectForKey(TruckCache.key) as [Truck] //EXC_BAD_ACCESS here
if trucksCached.count > 0 {
print(_truck)
}
}
PINDiskCache.m should be compiled with f-objc-arc-exceptions
so that ARC is made exception safe.
In the podspec, you can break those files out into a subspec and make that subspec part of the default spec:
s.subspec 'exception-safe' do |sp|
sp.source_files = 'PINDiskCache.m'
sp.compiler_flags = '-f-objc-arc-exceptions`
end
Please check out Carthage.
I might be missing something obvious. I'm using PINcache to store my images that is async plugged into a custom cell in a collectionView.
How can I check if an image is cached?
Is there any similar method to SDWebImage where you can check if an image is cached on disk like this:
Hi! Thank you for great lib.
In method, that generates file name in disk cache for key you have such code:
- (NSURL *)encodedFileURLForKey:(NSString *)key
{
if (![key length])
return nil;
return [_cacheURL URLByAppendingPathComponent:[self encodedString:key]];
}
- (NSString *)encodedString:(NSString *)string
{
if (![string length])
return @"";
return [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@".:/"]];
}
Why do you leave ".:/"? If I use URL strings as keys (for example link to file as key for NSData) — it generates wrong file names and cannot save file to disk.
BOOL written = [NSKeyedArchiver archiveRootObject:object toFile:[fileURL path]];
archiveRootObject:
returns NO here in - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key fileURL:(NSURL **)outFileURL
Hello! In a perfect world, setting ageLimit
schedules regular trims of the cache, and the the cache is always free of objects that have expired. In reality, you could set an ageLimit
to 5 minutes, add an object to the cache 2.5 minutes later, and then this object will get to live on for 7.5 minutes instead of the 5 minutes maximum set by setting the ageLimit
(because it missed the cache trim at the 5 minute mark, and it won't get cleared again until the 10 minute mark).
To avoid this scenario entirely, methods like objectForKey:
and objectForKey:block:
should only return objects in the cache if they're actually meant to still be in the cache (i.e. return nil if the object has technically expired but hasn't been cleared yet). Another benefit, aside from avoiding the scenario I mentioned earlier, is that this allows the user to reliably write code like this:
[self.someCache objectForKey:myObjectKey block:^(PINDiskCache *cache, NSString *key, id <NSCoding> __nullable object, NSURL * __nullable fileURL) {
if (object) {
// use the object
} else {
// create/fetch a new one of these objects
}
}];
Without this, you first have to get the fileURL
, check the fileURL
file's modification time and compare it against the ageLimit
, if that all checks out, then call objectForKey:block:
. The issue here though is that if you do the fileURL
file modification time checking asynchronously to avoid holding any locks in PINCache, the following objectForKey:block:
call is not guaranteed to return your object as a trimToAgeLimitRecursively
may have been called during that time and cleared your object!
Does this make sense? Thoughts?
Hi,
I'm looking at the code and I'm a bit confused.
Just for example, looking at setObject:forKey:block
in PINDiskCache.m
I can see:
...
dispatch_async(_asyncQueue, ^{
PINDiskCache *strongSelf = weakSelf;
NSURL *fileURL = nil;
[strongSelf setObject:object forKey:key fileURL:&fileURL];
if (block) {
[strongSelf lock];
block(strongSelf, key, object, fileURL);
[strongSelf unlock];
}
});
Why strongSelf lock/unlock
is needed between the return callback call?
No access to files is performed (and in fact inside the setObject:forKey:fileURL:
another lock is made).
Is there any reason I can't understand for this?
It's the same for reading (get) method; is there need to lock a read operation?
Thank you :)
To support notification center and Apple Watch extensions, we'd like to move our cache location to a shared app group container. However this enables multiple processes to simultaneously attempt to read and write on the same files. Does PINCache have any protection against this, by using NSFileCoordinator, for example?
NS_EXTENSION_UNAVAILABLE_IOS
can be used to mark symbols as unavailable to extensions. This should allow us to remove the PIN_APP_EXTENSIONS
define and produce one library for both application and extensions.
Inspired by AFNetworking/AFNetworking#2737.
Is there a reason this doesn't work on iOS 8.0?
�Hi, I wonder if there is a way, when saving cache in disk, to retrieve the file of the cache and extract data. I'm using Pincache for store offline data, but I usually need to get the data that was not synchronised to the server for some reason (probably bugs).
There is a way to get this file and, perhaps, read in another app?
Sorry to create this issue but i didn't know where to ask.
When using Carthage to build the framework and include in a framework that is used by app extensions, I have the following warning:
linking against a dylib which is not safe for use in application extensions
The build setting Require Only App-Extension-Safe API
is set to YES
.
I'm wondering that is related to this PR: #72
Does anyone have an idea about that?
The macro for beginning a background task looks like this:
UIBackgroundTaskIdentifier taskID = UIBackgroundTaskInvalid;
taskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:taskID];
}];
UIBackgroundTaskIdentifier
is a value type, so when the block is created it captures taskID
when it equals UIBackgroundTaskInvalid
. It never captures the actual task identifier, so if the expiration handler pops the actual task isn't properly cancelled.
We use PINCache (previously TMCache) as part of our caching layer, and we use PINDiskCache as an offline store for image assets and other metadata.
A great feature would be to support the caching of existing files provided by an NSURL.
Use case for us is NSURLSession background session. We download > 2000 files using a background session, which provides a callback with a temporary file url. We have implemented a separate system to store offline assets, as it includes videos, ZIP files, PDFs, but would love to have it all wrapped up as part of PINDiskCache.
The symptom we are seeing is that keys above a certain length cause the cache to either not save or miss when accessed. After a quick empirical experiment it seems like the maximum key length is 255 characters (UNIX filename limit) and since special characters are escaped, their presence lowers the maximum limit.
We're using URLs as keys, some of which can be very long.
All files are currently written unencrypted on disk.
For protection of the user's privacy, files should be encrypted when needed if necessary
Can you add tvos to the podspec so tvos apps can use this with cocoapods?
Compile-time warning under Xcode 7 Beta 4:
PINRemoteImage/Example/Pods/PINCache/PINCache/PINDiskCache.m:43:17: Method override for the designated initializer of the superclass '-init' not found
Code crashes here. Why is that? The deployment target is 8.0 so it should use ARC.
(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
dispatch_release(_queue); <-----------------CRASH
dispatch_release(_lockSemaphore);
_queue = nil;
}
Hey there! I'm not sure if you all already had plans for other commits going into the next version bump for PINCache. If not, I'd be happy to make a PR with the CHANGELOG.md update and podspec version bump. We're anxious to integrate #63 into an internal pod we have, and unfortunately the CocoaPods podspec dsl for a pod's dependency doesn't allow us to specify a git sha.
Are you all ready for PINCache 2.2?
While the Disk Cache storing mechanism is great for image and text files, it causing some issues with other media types such as videos (The AVPlayer cannot handle that file type).
The current version of PINCache
is 2.1.2 but the CHANGELOG ends at version 2.0
. It seems as if changes have been documented in GH. I can submit a PR to sync these two docs...
Just added PINCache to my xcode 7 project via cocoapods and get these 2 warnings:
for PINCache and PINDiskCache
Method override for the designated initializer of the superclass '-init' not found
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.