diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 75a4a8b..898d2ca 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,9 +3,9 @@ name: CI
on: pull_request
jobs:
- pod-lint-10_15:
+ pod-lint:
name: Pod lint
- runs-on: macos-10.15
+ runs-on: macos-13
steps:
- name: Checkout
@@ -23,13 +23,13 @@ jobs:
run: bundle exec pod lib lint --verbose --fail-fast --swift-version=5.5
shell: bash
- test-no-iOS-host-10_15:
- name: Run swift tests that don't require iOS host on MacOS 10.15
- runs-on: macos-10.15
+ test-no-iOS-host-13:
+ name: Run swift tests that don't require iOS host on MacOS 13
+ runs-on: macos-13
strategy:
matrix:
- # latest 11 and all available versions 12
- xcodeVersions: ['11.7', '12.0', '12.1', '12.2', '12.3', '12.4']
+ # all available versions of XCode14
+ xcodeVersions: ['14.1', '14.2', '14.3']
steps:
- name: Checkout
@@ -44,64 +44,9 @@ jobs:
run: swift test -c debug -Xswiftc -enable-testing
shell: bash
- test-no-iOS-host-11:
- name: Run swift tests that don't require iOS host on MacOS 11
- runs-on: macos-11
- strategy:
- matrix:
- # latest 12 and all available versions 13
- xcodeVersions: ['12.5', '13.0', '13.1']
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Set up XCode Version
- uses: maxim-lobanov/setup-xcode@v1
- with:
- xcode-version: ${{ matrix.xcodeVersions }}
-
- - name: Run tests that don't require iOS host
- run: swift test -c debug -Xswiftc -enable-testing
- shell: bash
-
- test-keychain-iOS-host-10_15-XCode11:
- name: Run swift tests that require keychain entitlement on XCode 11
- runs-on: macos-10.15
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Set up XCode Version
- uses: maxim-lobanov/setup-xcode@v1
- with:
- xcode-version: '11.7'
-
- - name: Run tests that require iOS host for keychain entitlement
- run: xcodebuild -project SecureStorageTestsHostApp/SecureStorageTestsHostApp.xcodeproj -scheme SecureStorageTestsHostApp -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 11,OS=13.7" test
- shell: bash
-
- test-keychain-iOS-host-11-XCode12:
- name: Run swift tests that require keychain entitlement on XCode 12
- runs-on: macos-11
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Set up XCode Version
- uses: maxim-lobanov/setup-xcode@v1
- with:
- xcode-version: '12.5'
-
- - name: Run tests that require iOS host for keychain entitlement
- run: xcodebuild -project SecureStorageTestsHostApp/SecureStorageTestsHostApp.xcodeproj -scheme SecureStorageTestsHostApp -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 12,OS=14.5" test
- shell: bash
-
- test-keychain-iOS-host-11-XCode13:
- name: Run swift tests that require keychain entitlement on XCode 13
- runs-on: macos-11
+ test-keychain-iOS-host-13-XCode14:
+ name: Run swift tests that require keychain entitlement on XCode 14
+ runs-on: macos-13
steps:
- name: Checkout
@@ -113,5 +58,5 @@ jobs:
xcode-version: latest-stable
- name: Run tests that require iOS host for keychain entitlement
- run: xcodebuild -project SecureStorageTestsHostApp/SecureStorageTestsHostApp.xcodeproj -scheme SecureStorageTestsHostApp -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 13,OS=15.0" test
+ run: xcodebuild -project SecureStorageTestsHostApp/SecureStorageTestsHostApp.xcodeproj -scheme SecureStorageTestsHostApp -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 14,OS=16.4" test
shell: bash
\ No newline at end of file
diff --git a/GKStorageKit.podspec b/GKStorageKit.podspec
index 7b5ff8f..ad3d931 100644
--- a/GKStorageKit.podspec
+++ b/GKStorageKit.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = 'GKStorageKit'
- s.version = '2.0.0'
+ s.version = '2.2.0'
s.summary = 'GKStorageKit framework.'
s.description = <<-DESC
* GKStorageKit framework
@@ -10,8 +10,8 @@ Pod::Spec.new do |s|
s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' }
s.author = { 'Gligor Kotushevski' => 'gligorkot@gmail.com' }
s.social_media_url = 'https://twitter.com/gligor_nz'
- s.platform = :ios, '9.0'
- s.ios.deployment_target = '9.0'
+ s.platform = :ios, '11.0'
+ s.ios.deployment_target = '11.0'
s.source = { :git => 'https://github.com/gligorkot/GKStorageKit.git', :tag => s.version.to_s }
s.source_files = 'Sources/GKStorageKit/**/*'
diff --git a/Gemfile.lock b/Gemfile.lock
index 34c39e1..56f3335 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,50 +1,50 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (3.0.5)
+ CFPropertyList (3.0.6)
rexml
- activesupport (5.2.6)
+ activesupport (7.0.4.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
- i18n (>= 0.7, < 2)
- minitest (~> 5.1)
- tzinfo (~> 1.1)
- addressable (2.8.0)
- public_suffix (>= 2.0.2, < 5.0)
+ i18n (>= 1.6, < 2)
+ minitest (>= 5.1)
+ tzinfo (~> 2.0)
+ addressable (2.8.4)
+ public_suffix (>= 2.0.2, < 6.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
atomos (0.1.3)
- claide (1.0.3)
- cocoapods (1.10.2)
- addressable (~> 2.6)
+ claide (1.1.0)
+ cocoapods (1.12.1)
+ addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
- cocoapods-core (= 1.10.2)
+ cocoapods-core (= 1.12.1)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
- cocoapods-downloader (>= 1.4.0, < 2.0)
+ cocoapods-downloader (>= 1.6.0, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
- cocoapods-trunk (>= 1.4.0, < 2.0)
+ cocoapods-trunk (>= 1.6.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
fourflusher (>= 2.3.0, < 3.0)
gh_inspector (~> 1.0)
- molinillo (~> 0.6.6)
+ molinillo (~> 0.8.0)
nap (~> 1.0)
- ruby-macho (~> 1.4)
- xcodeproj (>= 1.19.0, < 2.0)
- cocoapods-core (1.10.2)
- activesupport (> 5.0, < 6)
- addressable (~> 2.6)
+ ruby-macho (>= 2.3.0, < 3.0)
+ xcodeproj (>= 1.21.0, < 2.0)
+ cocoapods-core (1.12.1)
+ activesupport (>= 5.0, < 8)
+ addressable (~> 2.8)
algoliasearch (~> 1.0)
concurrent-ruby (~> 1.1)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
netrc (~> 0.11)
- public_suffix
+ public_suffix (~> 4.0)
typhoeus (~> 1.0)
cocoapods-deintegrate (1.0.5)
- cocoapods-downloader (1.5.1)
+ cocoapods-downloader (1.6.3)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.1)
@@ -53,32 +53,31 @@ GEM
netrc (~> 0.11)
cocoapods-try (1.2.0)
colored2 (3.1.2)
- concurrent-ruby (1.1.9)
+ concurrent-ruby (1.2.2)
escape (0.0.4)
- ethon (0.15.0)
+ ethon (0.16.0)
ffi (>= 1.15.0)
- ffi (1.15.4)
+ ffi (1.15.5)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
httpclient (2.8.3)
- i18n (1.8.11)
+ i18n (1.13.0)
concurrent-ruby (~> 1.0)
- json (2.6.1)
- minitest (5.14.4)
- molinillo (0.6.6)
+ json (2.6.3)
+ minitest (5.18.0)
+ molinillo (0.8.0)
nanaimo (0.3.0)
nap (1.1.0)
netrc (0.11.0)
- public_suffix (4.0.6)
+ public_suffix (4.0.7)
rexml (3.2.5)
- ruby-macho (1.4.0)
- thread_safe (0.3.6)
+ ruby-macho (2.5.1)
typhoeus (1.4.0)
ethon (>= 0.9.0)
- tzinfo (1.2.9)
- thread_safe (~> 0.1)
- xcodeproj (1.21.0)
+ tzinfo (2.0.6)
+ concurrent-ruby (~> 1.0)
+ xcodeproj (1.22.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
@@ -93,4 +92,4 @@ DEPENDENCIES
cocoapods (~> 1.10)
BUNDLED WITH
- 2.0.2
+ 2.2.3
diff --git a/Package.swift b/Package.swift
index f6f6756..a3041bd 100644
--- a/Package.swift
+++ b/Package.swift
@@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "GKStorageKit",
platforms: [
- .iOS(.v9),
+ .iOS(.v11),
.macOS(.v10_11)
],
products: [
diff --git a/Package@swift-5.2.swift b/Package@swift-5.2.swift
index adb93aa..6243862 100644
--- a/Package@swift-5.2.swift
+++ b/Package@swift-5.2.swift
@@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "GKStorageKit",
platforms: [
- .iOS(.v9),
+ .iOS(.v11),
.macOS(.v10_11)
],
products: [
diff --git a/Package@swift-5.3.swift b/Package@swift-5.3.swift
index a47b3c0..c62feba 100644
--- a/Package@swift-5.3.swift
+++ b/Package@swift-5.3.swift
@@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "GKStorageKit",
platforms: [
- .iOS(.v9),
+ .iOS(.v11),
.macOS(.v10_11)
],
products: [
diff --git a/Package@swift-5.4.swift b/Package@swift-5.4.swift
index e92ce15..97c32bd 100644
--- a/Package@swift-5.4.swift
+++ b/Package@swift-5.4.swift
@@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "GKStorageKit",
platforms: [
- .iOS(.v9),
+ .iOS(.v11),
.macOS(.v10_11)
],
products: [
diff --git a/SecureStorageTestsHostApp/SecureStorageTestsHostApp.xcodeproj/project.pbxproj b/SecureStorageTestsHostApp/SecureStorageTestsHostApp.xcodeproj/project.pbxproj
index 383f4c5..c85a4be 100644
--- a/SecureStorageTestsHostApp/SecureStorageTestsHostApp.xcodeproj/project.pbxproj
+++ b/SecureStorageTestsHostApp/SecureStorageTestsHostApp.xcodeproj/project.pbxproj
@@ -423,7 +423,7 @@
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -451,7 +451,7 @@
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -473,7 +473,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -497,7 +497,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
diff --git a/SecureStorageTestsHostApp/SecureStorageTestsHostApp/Info.plist b/SecureStorageTestsHostApp/SecureStorageTestsHostApp/Info.plist
index 1dac20b..6c40a6c 100644
--- a/SecureStorageTestsHostApp/SecureStorageTestsHostApp/Info.plist
+++ b/SecureStorageTestsHostApp/SecureStorageTestsHostApp/Info.plist
@@ -2,21 +2,21 @@
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- BNDL
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
diff --git a/Sources/GKStorageKit/Storage/ObjectStorageService.swift b/Sources/GKStorageKit/Storage/ObjectStorageService.swift
index 9025c30..7b18c2f 100644
--- a/Sources/GKStorageKit/Storage/ObjectStorageService.swift
+++ b/Sources/GKStorageKit/Storage/ObjectStorageService.swift
@@ -42,6 +42,38 @@ final class ObjectStorageService: ObjectStorageInterface, StorageKitDecorator {
onSuccess(nil)
}
}
+
+ func storePerishableObject(_ value: T, forKey key: String, expireAfter timeInterval: TimeInterval, onSuccess: () -> ()) {
+ let storableTimestampItem = PerishableItem(expireOn: Date().timeIntervalSince1970 + timeInterval, item: value)
+ storeObject(storableTimestampItem, forKey: key, onSuccess: onSuccess)
+ }
+
+ func getPerishableObject(forKey key: String, onSuccess: (T) -> (), expired: () -> ()) {
+ getObject(forKey: key) { (storable: PerishableItem?) in
+ if let storable = storable, storable.expireOn > Date().timeIntervalSince1970 {
+ // if cache exists and timestamp is not older than timeInterval, return item
+ onSuccess(storable.item)
+ } else {
+ expired()
+ }
+ }
+ }
+
+ func storePerishableCollection(_ collection: Array, forKey key: String, expireAfter timeInterval: TimeInterval, onSuccess: () -> ()) {
+ let storableTimestampCollection = PerishableCollection(expireOn: Date().timeIntervalSince1970 + timeInterval, collection: collection)
+ storeObject(storableTimestampCollection, forKey: key, onSuccess: onSuccess)
+ }
+
+ func getPerishableCollection(forKey key: String, onSuccess: ([T]?) -> (), expired: () -> ()) {
+ getObject(forKey: key) { (storable: PerishableCollection?) in
+ if let storable = storable, storable.expireOn > Date().timeIntervalSince1970 {
+ // if cache exists and timestamp is not older than timeInterval, return collection
+ onSuccess(storable.collection)
+ } else {
+ expired()
+ }
+ }
+ }
func removeValue(forKey key: String, onSuccess: () -> ()) {
objectStorage.removeObject(forKey: key)
diff --git a/Sources/GKStorageKit/Storage/StorableTimestampItem.swift b/Sources/GKStorageKit/Storage/StorableTimestampItem.swift
new file mode 100644
index 0000000..fe733dc
--- /dev/null
+++ b/Sources/GKStorageKit/Storage/StorableTimestampItem.swift
@@ -0,0 +1,19 @@
+//
+// PerishableItem.swift
+// GKStorageKit
+//
+// Created by Gligor Kotushevski on 4/07/19.
+// Copyright © 2019 Gligor Kotushevski. All rights reserved.
+//
+
+import Foundation
+
+struct PerishableItem: Codable {
+ let expireOn: TimeInterval
+ let item: T
+}
+
+struct PerishableCollection: Codable {
+ let expireOn: TimeInterval
+ let collection: Array
+}
diff --git a/Sources/GKStorageKit/StorageKit.swift b/Sources/GKStorageKit/StorageKit.swift
index 3a59756..c046639 100644
--- a/Sources/GKStorageKit/StorageKit.swift
+++ b/Sources/GKStorageKit/StorageKit.swift
@@ -23,6 +23,10 @@ public protocol ObjectStorageInterface {
func getCollection(forKey key: String, onSuccess: ([T]?) -> ())
func storeObject(_ value: T, forKey key: String, onSuccess: () -> ())
func getObject(forKey key: String, onSuccess: (T?) -> ())
+ func storePerishableObject(_ value: T, forKey key: String, expireAfter timeInterval: TimeInterval, onSuccess: () -> ())
+ func getPerishableObject(forKey key: String, onSuccess: (T) -> (), expired: () -> ())
+ func storePerishableCollection(_ collection: Array, forKey key: String, expireAfter timeInterval: TimeInterval, onSuccess: () -> ())
+ func getPerishableCollection(forKey key: String, onSuccess: ([T]?) -> (), expired: () -> ())
func removeValue(forKey key: String, onSuccess: () -> ())
func cleanStorage(onSuccess: () -> ())
}
diff --git a/Tests/GKStorageKitTests/Storage/ObjectStorageServiceTests.swift b/Tests/GKStorageKitTests/Storage/ObjectStorageServiceTests.swift
index 9e446a2..592f71a 100644
--- a/Tests/GKStorageKitTests/Storage/ObjectStorageServiceTests.swift
+++ b/Tests/GKStorageKitTests/Storage/ObjectStorageServiceTests.swift
@@ -201,4 +201,93 @@ class ObjectStorageServiceTests: XCTestCase {
}
}
+ func test_storageServiceSuccessfullyStorePerishableObjectAndRetrieveIt() {
+ let ex = expectation(description: "test_storageServiceSuccessfullyStorePerishableObjectAndRetrieveIt")
+ let key = "key"
+ let storedValue = CodableValueClass(id: 123, firstName: "firstName", lastName: "lastName")
+ var extractedValue: CodableValueClass?
+
+ objectStorageService.storePerishableObject(storedValue, forKey: key, expireAfter: 1, onSuccess: {
+ self.objectStorageService.getPerishableObject(forKey: key, onSuccess: { (value: CodableValueClass?) in
+ extractedValue = value
+ ex.fulfill()
+ }, expired: {
+ XCTFail()
+ })
+ })
+
+ waitForExpectations(timeout: defaultTimeout) { (_) in
+ XCTAssertNotNil(extractedValue)
+ XCTAssertEqual(extractedValue?.id, storedValue.id)
+ XCTAssertEqual(extractedValue?.firstName, storedValue.firstName)
+ XCTAssertEqual(extractedValue?.lastName, storedValue.lastName)
+ }
+ }
+
+ func test_storageServiceStorePerishableObjectAndItExpires() {
+ let ex = expectation(description: "test_storageServiceStorePerishableObjectAndItExpires")
+ let key = "key"
+ let storedValue = CodableValueClass(id: 123, firstName: "firstName", lastName: "lastName")
+ var expired = false
+
+ objectStorageService.storePerishableObject(storedValue, forKey: key, expireAfter: -1, onSuccess: {
+ self.objectStorageService.getPerishableObject(forKey: key, onSuccess: { (value: CodableValueClass?) in
+ XCTFail()
+ }, expired: {
+ expired = true
+ ex.fulfill()
+ })
+ })
+
+ waitForExpectations(timeout: defaultTimeout) { (_) in
+ XCTAssert(expired)
+ }
+ }
+
+ func test_storageServiceSuccessfullyStorePerishableCollectionAndRetrieveIt() {
+ let ex = expectation(description: "test_storageServiceSuccessfullyStorePerishableCollectionAndRetrieveIt")
+ let key = "key"
+ let storable = CodableValueClass(id: 123, firstName: "firstName", lastName: "lastName")
+ let storedValue = [storable]
+ var extractedValue: [Codable]?
+
+ objectStorageService.storePerishableCollection(storedValue, forKey: key, expireAfter: 1, onSuccess: {
+ self.objectStorageService.getPerishableCollection(forKey: key, onSuccess: { (value: [CodableValueClass]?) in
+ extractedValue = value
+ ex.fulfill()
+ }, expired: {
+ XCTFail()
+ })
+ })
+
+ waitForExpectations(timeout: defaultTimeout) { (_) in
+ XCTAssertEqual(extractedValue?.count, storedValue.count)
+ if let firstCaller = extractedValue?.first as? CodableValueClass {
+ XCTAssertEqual(firstCaller.id, storedValue.first!.id)
+ } else {
+ XCTFail()
+ }
+ }
+ }
+
+ func test_storageServiceStorePerishableCollectionAndItExpires() {
+ let ex = expectation(description: "test_storageServiceStorePerishableCollectionAndItExpires")
+ let key = "key"
+ let storable = CodableValueClass(id: 123, firstName: "firstName", lastName: "lastName")
+ let storedValue = [storable]
+ var expired = false
+
+ objectStorageService.storePerishableCollection(storedValue, forKey: key, expireAfter: -1, onSuccess: {
+ self.objectStorageService.getPerishableCollection(forKey: key, onSuccess: { (value: [CodableValueClass]?) in
+ XCTFail()
+ }, expired: {
+ expired = true
+ ex.fulfill()
+ })
+ })
+
+ waitForExpectations(timeout: defaultTimeout) { (_) in
+ XCTAssert(expired)
+ }
+ }
}