Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: cache clean up #15

Merged
merged 11 commits into from
Dec 3, 2015
32 changes: 32 additions & 0 deletions Pod/Tests/Specs/Storage/DiskStorageSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,38 @@ class DiskStorageSpec: QuickSpec {
}
}

describe("clearExpired") {
it("removes expired objects") {
let expectation1 = self.expectationWithDescription(
"Clear If Expired Expectation")
let expectation2 = self.expectationWithDescription(
"Don't Clear If Not Expired Expectation")

let expiry1: Expiry = .Date(NSDate().dateByAddingTimeInterval(-100000))
let expiry2: Expiry = .Date(NSDate().dateByAddingTimeInterval(100000))

let key1 = "item1"
let key2 = "item2"

storage.add(key1, object: object, expiry: expiry1)
storage.add(key2, object: object, expiry: expiry2)

storage.clearExpired {
storage.object(key1) { (receivedObject: User?) in
expect(receivedObject).to(beNil())
expectation1.fulfill()
}

storage.object(key2) { (receivedObject: User?) in
expect(receivedObject).toNot(beNil())
expectation2.fulfill()
}
}

self.waitForExpectationsWithTimeout(5.0, handler:nil)
}
}

describe("#fileName") {
it("returns a correct file name") {
expect(storage.fileName(key)).to(equal(key.base64()))
Expand Down
32 changes: 32 additions & 0 deletions Pod/Tests/Specs/Storage/MemoryStorageSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,38 @@ class MemoryStorageSpec: QuickSpec {
self.waitForExpectationsWithTimeout(2.0, handler:nil)
}
}

describe("clearExpired") {
it("removes expired objects") {
let expectation1 = self.expectationWithDescription(
"Clear Expired Expectation 1")
let expectation2 = self.expectationWithDescription(
"Clear Expired Expectation 2")

let expiry1: Expiry = .Date(NSDate().dateByAddingTimeInterval(-100000))
let expiry2: Expiry = .Date(NSDate().dateByAddingTimeInterval(100000))

let key1 = "item1"
let key2 = "item2"

storage.add(key1, object: object, expiry: expiry1)
storage.add(key2, object: object, expiry: expiry2)

storage.clearExpired {
storage.object(key1) { (receivedObject: User?) in
expect(receivedObject).to(beNil())
expectation1.fulfill()
}

storage.object(key2) { (receivedObject: User?) in
expect(receivedObject).to(beNil())
expectation2.fulfill()
}
}

self.waitForExpectationsWithTimeout(5.0, handler:nil)
}
}
}
}
}
49 changes: 48 additions & 1 deletion Source/Cache/HybridCache.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Foundation
import UIKit

public class HybridCache {

Expand All @@ -16,6 +16,19 @@ public class HybridCache {

frontStorage = StorageFactory.resolve(name, kind: config.frontKind, maxSize: config.maxSize)
backStorage = StorageFactory.resolve(name, kind: config.backKind, maxSize: config.maxSize)

let notificationCenter = NSNotificationCenter.defaultCenter()

notificationCenter.addObserver(self, selector: "applicationDidReceiveMemoryWarning",
name: UIApplicationDidReceiveMemoryWarningNotification, object: nil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️

notificationCenter.addObserver(self, selector: "applicationWillTerminate",
name: UIApplicationWillTerminateNotification, object: nil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💀

notificationCenter.addObserver(self, selector: "applicationDidEnterBackground",
name: UIApplicationDidEnterBackgroundNotification, object: nil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to remove these notifications at any time?

}

deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}

// MARK: - Caching
Expand Down Expand Up @@ -78,4 +91,38 @@ public class HybridCache {
}
}
}

// MARK: - Notifications

func applicationDidReceiveMemoryWarning() {
frontStorage.clearExpired(nil)
}

func applicationWillTerminate() {
backStorage.clearExpired(nil)
}

func applicationDidEnterBackground() {
let application = UIApplication.sharedApplication()
var backgroundTask: UIBackgroundTaskIdentifier?

backgroundTask = application.beginBackgroundTaskWithExpirationHandler { [weak self] in
guard let weakSelf = self, var backgroundTask = backgroundTask else { return }

weakSelf.endBackgroundTask(&backgroundTask)
}

backStorage.clearExpired { [weak self] in
guard let weakSelf = self, var backgroundTask = backgroundTask else { return }

dispatch_async(dispatch_get_main_queue()) {
weakSelf.endBackgroundTask(&backgroundTask)
}
}
}

func endBackgroundTask(inout task: UIBackgroundTaskIdentifier) {
UIApplication.sharedApplication().endBackgroundTask(task)
task = UIBackgroundTaskInvalid
}
}
84 changes: 79 additions & 5 deletions Source/Storage/DiskStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class DiskStorage: StorageAware {
do {
try weakSelf.fileManager.createDirectoryAtPath(weakSelf.path,
withIntermediateDirectories: true, attributes: nil)
} catch _ {}
} catch {}
}

do {
Expand All @@ -52,7 +52,7 @@ public class DiskStorage: StorageAware {
try weakSelf.fileManager.setAttributes(
[NSFileModificationDate : expiry.date],
ofItemAtPath: filePath)
} catch _ {}
} catch {}

completion?()
}
Expand Down Expand Up @@ -85,7 +85,7 @@ public class DiskStorage: StorageAware {

do {
try weakSelf.fileManager.removeItemAtPath(weakSelf.filePath(key))
} catch _ {}
} catch {}

completion?()
}
Expand All @@ -106,7 +106,7 @@ public class DiskStorage: StorageAware {
where expiryDate.inThePast {
try weakSelf.fileManager.removeItemAtPath(weakSelf.filePath(key))
}
} catch _ {}
} catch {}

completion?()
}
Expand All @@ -121,7 +121,81 @@ public class DiskStorage: StorageAware {

do {
try weakSelf.fileManager.removeItemAtPath(weakSelf.path)
} catch _ {}
} catch {}

completion?()
}
}

public func clearExpired(completion: (() -> Void)? = nil) {
dispatch_async(writeQueue) { [weak self] in
guard let weakSelf = self else {
completion?()
return
}

let URL = NSURL(fileURLWithPath: weakSelf.path)
let resourceKeys = [NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]
var objects = [(URL: NSURL, resourceValues: [NSObject: AnyObject])]()
var URLsToDelete = [NSURL]()
var totalSize: UInt = 0

guard let fileEnumerator = weakSelf.fileManager.enumeratorAtURL(URL, includingPropertiesForKeys: resourceKeys,
options: .SkipsHiddenFiles, errorHandler: nil), URLs = fileEnumerator.allObjects as? [NSURL] else {
completion?()
return
}

for fileURL in URLs {
do {
let resourceValues = try fileURL.resourceValuesForKeys(resourceKeys)

guard (resourceValues[NSURLIsDirectoryKey] as? NSNumber)?.boolValue == false else {
continue
}

if let expiryDate = resourceValues[NSURLContentModificationDateKey] as? NSDate
where expiryDate.inThePast {
URLsToDelete.append(fileURL)
continue
}

if let fileSize = resourceValues[NSURLTotalFileAllocatedSizeKey] as? NSNumber {
totalSize += fileSize.unsignedLongValue
objects.append((URL: fileURL, resourceValues: resourceValues))
}
} catch {}
}

for fileURL in URLsToDelete {
do {
try weakSelf.fileManager.removeItemAtURL(fileURL)
} catch {}
}

if weakSelf.maxSize > 0 && totalSize > weakSelf.maxSize {
let targetSize = weakSelf.maxSize / 2

let sortedFiles = objects.sort({
let time1 = ($0.resourceValues[NSURLContentModificationDateKey] as? NSDate)?.timeIntervalSince1970
let time2 = ($1.resourceValues[NSURLContentModificationDateKey] as? NSDate)?.timeIntervalSince1970
return time1 > time2
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can remove ()


for file in sortedFiles {
do {
try weakSelf.fileManager.removeItemAtURL(file.URL)
} catch {}

if let fileSize = file.resourceValues[NSURLTotalFileAllocatedSizeKey] as? NSNumber {
totalSize -= fileSize.unsignedLongValue
}

if totalSize < targetSize {
break
}
}
}

completion?()
}
Expand Down
6 changes: 5 additions & 1 deletion Source/Storage/MemoryStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ public class MemoryStorage: StorageAware {
}
}

public func clearExpired(completion: (() -> Void)? = nil) {
clear(completion)
}

// MARK: - Helpers

func removeIfExpired(key: String, capsule: Capsule, completion: (() -> Void)? = nil) {
Expand All @@ -107,4 +111,4 @@ public class MemoryStorage: StorageAware {
completion?()
}
}
}
}
1 change: 1 addition & 0 deletions Source/Storage/StorageAware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public protocol CacheAware {
func remove(key: String, completion: (() -> Void)?)
func removeIfExpired(key: String, completion: (() -> Void)?)
func clear(completion: (() -> Void)?)
func clearExpired(completion: (() -> Void)?)
}

public protocol StorageAware: CacheAware {
Expand Down