diff --git a/KeyClip.xcodeproj/project.pbxproj b/KeyClip.xcodeproj/project.pbxproj
index c6a8e05..3943192 100644
--- a/KeyClip.xcodeproj/project.pbxproj
+++ b/KeyClip.xcodeproj/project.pbxproj
@@ -360,7 +360,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0800;
- LastUpgradeCheck = 0820;
+ LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "Shinichiro Aska";
TargetAttributes = {
38E53E0E1D880BF800D7F626 = {
@@ -374,8 +374,6 @@
};
38E53E251D880C1300D7F626 = {
CreatedOnToolsVersion = 8.0;
- DevelopmentTeam = ERYSSE5R77;
- ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.Keychain = {
enabled = 1;
@@ -408,7 +406,7 @@
};
buildConfigurationList = 6A6127191A299E6400C18E1D /* Build configuration list for PBXProject "KeyClip" */;
compatibilityVersion = "Xcode 3.2";
- developmentRegion = English;
+ developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
@@ -610,7 +608,6 @@
PRODUCT_BUNDLE_IDENTIFIER = "pw.aska.TestApp-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
- SWIFT_VERSION = 3.0;
};
name = Debug;
};
@@ -633,7 +630,6 @@
PRODUCT_BUNDLE_IDENTIFIER = "pw.aska.TestApp-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 3.0;
};
name = Release;
};
@@ -646,10 +642,8 @@
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CODE_SIGN_ENTITLEMENTS = "TestApp Mac/TestApp Mac.entitlements";
- CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
- DEVELOPMENT_TEAM = ERYSSE5R77;
INFOPLIST_FILE = "TestApp Mac/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
@@ -657,7 +651,6 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
- SWIFT_VERSION = 3.0;
};
name = Debug;
};
@@ -670,11 +663,9 @@
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
CODE_SIGN_ENTITLEMENTS = "TestApp Mac/TestApp Mac.entitlements";
- CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- DEVELOPMENT_TEAM = ERYSSE5R77;
INFOPLIST_FILE = "TestApp Mac/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
@@ -682,7 +673,6 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 3.0;
};
name = Release;
};
@@ -690,18 +680,27 @@
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_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;
@@ -730,6 +729,7 @@
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -740,18 +740,27 @@
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_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;
@@ -771,6 +780,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
+ SWIFT_VERSION = 4.2;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
@@ -781,6 +791,7 @@
6A6127361A299E6400C18E1D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
@@ -797,13 +808,13 @@
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
- SWIFT_VERSION = 3.0;
};
name = Debug;
};
6A6127371A299E6400C18E1D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
@@ -821,7 +832,6 @@
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 3.0;
};
name = Release;
};
@@ -841,7 +851,6 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
- SWIFT_VERSION = 3.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApp iOS.app/TestApp iOS";
};
name = Debug;
@@ -859,7 +868,6 @@
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 3.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApp iOS.app/TestApp iOS";
};
name = Release;
@@ -886,7 +894,6 @@
PRODUCT_NAME = "$(PROJECT_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
- SWIFT_VERSION = 3.0;
};
name = Debug;
};
@@ -910,7 +917,6 @@
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 3.0;
};
name = Release;
};
@@ -930,7 +936,6 @@
PRODUCT_BUNDLE_IDENTIFIER = "pw.aska.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
- SWIFT_VERSION = 3.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApp Mac.app/Contents/MacOS/TestApp Mac";
};
name = Debug;
@@ -949,7 +954,6 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_VERSION = 3.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApp Mac.app/Contents/MacOS/TestApp Mac";
};
name = Release;
diff --git a/KeyClip.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/KeyClip.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/KeyClip.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac Tests.xcscheme b/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac Tests.xcscheme
index ae8c426..ce8dc80 100644
--- a/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac Tests.xcscheme
+++ b/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac Tests.xcscheme
@@ -1,6 +1,6 @@
-
-
-
-
diff --git a/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac.xcscheme b/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac.xcscheme
index c8cdac3..42dced1 100644
--- a/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac.xcscheme
+++ b/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac.xcscheme
@@ -1,6 +1,6 @@
Void)? = nil) -> Bool {
- return Static.instance.exists(key, failure: failure)
- }
-
- open class func save(_ key: String, data: Data, failure: ((NSError) -> Void)? = nil) -> Bool {
- return Static.instance.save(key, data: data, failure: failure)
- }
-
- open class func save(_ key: String, string: String, failure: ((NSError) -> Void)? = nil) -> Bool {
- return Static.instance.save(key, string: string, failure: failure)
+ open class func exists(_ key: String) throws -> Bool {
+ return try KeyClip.shared.exists(key)
}
- open class func save(_ key: String, dictionary: NSDictionary, failure: ((NSError) -> Void)? = nil) -> Bool {
- return Static.instance.save(key, dictionary: dictionary, failure: failure)
+ open class func save(data: Data, forKey key: String) throws {
+ return try KeyClip.shared.save(data: data, forKey: key)
}
- open class func load(_ key: String, failure: ((NSError) -> Void)? = nil) -> Data? {
- return Static.instance.load(key, failure: failure)
+ open class func save(string: String, forKey key: String) throws {
+ return try KeyClip.shared.save(string: string, forKey: key)
}
- open class func load(_ key: String, failure: ((NSError) -> Void)? = nil) -> NSDictionary? {
- return Static.instance.load(key, failure: failure)
+ open class func save(dictionary: [AnyHashable: Any], forKey key: String) throws {
+ return try KeyClip.shared.save(dictionary: dictionary, forKey: key)
}
- open class func load(_ key: String, failure: ((NSError) -> Void)? = nil) -> String? {
- return Static.instance.load(key, failure: failure)
+ open class func data(forKey key: String) throws -> Data? {
+ return try KeyClip.shared.data(forKey: key)
}
- open class func load(_ key: String, success: (NSDictionary) -> T, failure: ((NSError) -> Void)?) -> T? {
- return Static.instance.load(key, success: success, failure: failure)
+ open class func dictionary(forKey key: String) throws -> [AnyHashable: Any]? {
+ return try KeyClip.shared.dictionary(forKey: key)
}
- open class func load(_ key: String, success: (NSDictionary) -> T) -> T? {
- return Static.instance.load(key, success: success, failure: nil)
+ open class func string(forKey key: String) throws -> String? {
+ return try KeyClip.shared.string(forKey:key)
}
- open class func delete(_ key: String, failure: ((NSError) -> Void)? = nil) -> Bool {
- return Static.instance.delete(key, failure: failure)
+ open class func load(_ key: String, success: ([AnyHashable: Any]) -> T) throws -> T? {
+ return try KeyClip.shared.load(key, success: success)
}
- open class func clear(_ failure: ((NSError) -> Void)? = nil) -> Bool {
- return Static.instance.clear(failure)
+ @discardableResult
+ open class func delete(_ key: String) throws -> Bool {
+ return try KeyClip.shared.delete(key)
}
- open class func printError(_ printError: Bool) {
- Static.printError = printError
+ @discardableResult
+ open class func clear() throws -> Bool {
+ return try KeyClip.shared.clear()
}
// MARK: Debug Methods
diff --git a/KeyClip/Ring.swift b/KeyClip/Ring.swift
index 7af859b..d228aae 100644
--- a/KeyClip/Ring.swift
+++ b/KeyClip/Ring.swift
@@ -9,13 +9,63 @@
import Foundation
public extension KeyClip {
- public class Ring {
-
+ enum KeyClipError: Error, CustomDebugStringConvertible {
+ case stringEncoding
+ case dataLoading
+ case unhandledError(status: OSStatus)
+
+ // /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/Security.framework/Headers/SecBase.h
+ var message: String {
+ switch self {
+ case .stringEncoding:
+ return "Failed to encode string to utf8"
+
+ case .dataLoading:
+ return "Failed to load data from keychain"
+
+ case .unhandledError(let status):
+ #if os(iOS)
+ switch status {
+ case errSecUnimplemented:
+ return "Function or operation not implemented."
+ case errSecParam:
+ return "One or more parameters passed to a function where not valid."
+ case errSecAllocate:
+ return "Failed to allocate memory."
+ case errSecNotAvailable:
+ return "No keychain is available. You may need to restart your computer."
+ case errSecDuplicateItem:
+ return "The specified item already exists in the keychain."
+ case errSecItemNotFound:
+ return "The specified item could not be found in the keychain."
+ case errSecInteractionNotAllowed:
+ return "User interaction is not allowed."
+ case errSecDecode:
+ return "Unable to decode the provided data."
+ case errSecAuthFailed:
+ return "The user name or passphrase you entered is not correct."
+ case -25243: // errSecNoAccessForItem https://developer.apple.com/library/ios/samplecode/GenericKeychain/Listings/Classes_KeychainItemWrapper_m.html
+ return "Ignore the access group if running on the iPhone simulator."
+ default:
+ return "Refer to SecBase.h for description (status:\(status))"
+ }
+ #elseif os(OSX)
+ return "Refer to MacErrors.h for description (status:\(status))"
+ #endif
+ }
+ }
+
+ public var debugDescription: String {
+ return message
+ }
+ }
+
+ class Ring {
let accessGroup: String?
let service: String
let accessible: String
- // MARK: Initializer
+ // MARK: Init
init(accessGroup: String?, service: String, accessible: String) {
self.accessGroup = accessGroup
@@ -23,9 +73,9 @@ public extension KeyClip {
self.accessible = accessible
}
- // MARK: Public Methods
+ // MARK: Exists
- open func exists(_ key: String, failure: ((NSError) -> Void)? = nil) -> Bool {
+ open func exists(_ key: String) throws -> Bool {
var query: [String: AnyObject] = [
kSecAttrService as String : self.service as AnyObject,
kSecClass as String : kSecClassGenericPassword,
@@ -44,12 +94,13 @@ public extension KeyClip {
case errSecItemNotFound:
return false
default:
- self.failure(status: status, failure: failure)
- return false
+ throw KeyClipError.unhandledError(status: status)
}
}
+
+ // MARK: - Saving
- open func save(_ key: String, data: Data, failure: ((NSError) -> Void)? = nil) -> Bool {
+ open func save(data: Data, forKey key: String) throws {
var query: [String: AnyObject] = [
kSecAttrService as String : self.service as AnyObject,
kSecClass as String : kSecClassGenericPassword,
@@ -62,7 +113,7 @@ public extension KeyClip {
var status: OSStatus
- if self.exists(key, failure: failure) {
+ if try self.exists(key) {
status = SecItemUpdate(query as CFDictionary, [kSecValueData as String: data] as CFDictionary)
} else {
query[kSecAttrAccessible as String] = self.accessible as AnyObject?
@@ -70,33 +121,26 @@ public extension KeyClip {
status = SecItemAdd(query as CFDictionary, nil)
}
- switch status {
- case errSecSuccess:
- return true
- default:
- self.failure(status: status, failure: failure)
- return false
+ guard status == errSecSuccess else {
+ throw KeyClipError.unhandledError(status: status)
}
}
- open func save(_ key: String, string: String, failure: ((NSError) -> Void)? = nil) -> Bool {
- if let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false) {
- return self.save(key, data: data, failure: failure)
+ open func save(string: String, forKey key: String) throws {
+ guard let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false) else {
+ throw KeyClipError.stringEncoding
}
- return false
+
+ try save(data: data, forKey: key)
}
- open func save(_ key: String, dictionary: NSDictionary, failure: ((NSError) -> Void)? = nil) -> Bool {
- do {
- let data = try JSONSerialization.data(withJSONObject: dictionary, options: [])
- return self.save(key, data: data, failure: failure)
- } catch let error as NSError {
- self.failure(error: error, failure: failure)
- }
- return false
+ open func save(dictionary: [AnyHashable: Any], forKey key: String) throws {
+ try save(data: try JSONSerialization.data(withJSONObject: dictionary, options: []), forKey: key)
}
-
- open func load(_ key: String, failure: ((NSError) -> Void)? = nil) -> Data? {
+
+ // MARK: - Loading
+
+ open func data(forKey key: String) throws -> Data? {
var query: [String: AnyObject] = [
kSecAttrService as String : self.service as AnyObject,
kSecClass as String : kSecClassGenericPassword,
@@ -112,53 +156,56 @@ public extension KeyClip {
var result: AnyObject?
let status = withUnsafeMutablePointer(to: &result) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
- switch status {
- case errSecSuccess:
- if let data = result as? Data {
- return data
+ guard status == errSecSuccess else {
+ if status == errSecItemNotFound {
+ return nil
}
- return nil
- case errSecItemNotFound:
- return nil
- default:
- self.failure(status: status, failure: failure)
- return nil
+
+ throw KeyClipError.unhandledError(status: status)
}
- }
-
- open func load(_ key: String, failure: ((NSError) -> Void)? = nil) -> NSDictionary? {
- if let data: Data = self.load(key, failure: failure) {
- do {
- let json: Any = try JSONSerialization.jsonObject(with: data, options: [])
- return json as? NSDictionary
- } catch let error as NSError {
- self.failure(error: error, failure: failure)
- }
+
+ guard let data = result as? Data else {
+ throw KeyClipError.dataLoading
}
- return nil
+
+ return data
}
- open func load(_ key: String, failure: ((NSError) -> Void)? = nil) -> String? {
- if let data: Data = self.load(key, failure: failure) {
- if let string = NSString(data: data, encoding: String.Encoding.utf8.rawValue) {
- return string as String
- }
+ open func dictionary(forKey key: String) throws -> [AnyHashable: Any]? {
+ guard let data = try data(forKey: key) else {
+ return nil
}
- return nil
+
+ let json = try JSONSerialization.jsonObject(with: data, options: [])
+
+ return json as? [AnyHashable: Any]
}
- open func load(_ key: String, success: (NSDictionary) -> T, failure: ((NSError) -> Void)?) -> T? {
- if let dictionary: NSDictionary = self.load(key) {
- return success(dictionary)
+ open func string(forKey key: String) throws -> String? {
+ guard let data = try data(forKey: key) else {
+ return nil
+ }
+
+ guard let string = String(data: data, encoding: .utf8) else {
+ throw KeyClipError.stringEncoding
}
- return nil
+
+ return string
}
- open func load(_ key: String, success: (NSDictionary) -> T) -> T? {
- return self.load(key, success: success, failure: nil)
+ // MARK: - Loading and converting
+
+ open func load(_ key: String, success: ([AnyHashable: Any]) -> T) throws -> T? {
+ guard let dictionary = try dictionary(forKey: key) else {
+ return nil
+ }
+
+ return success(dictionary)
}
+
+ // MARK: - Deleting
- open func delete(_ key: String, failure: ((NSError) -> Void)? = nil) -> Bool {
+ open func delete(_ key: String) throws -> Bool {
var query: [String: AnyObject] = [
kSecAttrService as String : self.service as AnyObject,
kSecClass as String : kSecClassGenericPassword,
@@ -171,18 +218,18 @@ public extension KeyClip {
let status = SecItemDelete(query as CFDictionary)
- switch status {
- case errSecSuccess:
- return true
- case errSecItemNotFound:
- return false
- default:
- self.failure(status: status, failure: failure)
- return false
+ guard status == errSecSuccess else {
+ if status == errSecItemNotFound {
+ return false
+ }
+
+ throw KeyClipError.unhandledError(status: status)
}
+
+ return true
}
- open func clear(_ failure: ((NSError) -> Void)? = nil) -> Bool {
+ open func clear() throws -> Bool {
var query: [String: AnyObject] = [
kSecAttrService as String : self.service as AnyObject,
kSecClass as String : kSecClassGenericPassword ]
@@ -192,64 +239,16 @@ public extension KeyClip {
}
let status = SecItemDelete(query as CFDictionary)
-
- switch status {
- case errSecSuccess:
- return true
- case errSecItemNotFound:
- return false
- default:
- self.failure(status: status, failure: failure)
- return false
- }
- }
-
- // MARK: Private Methods
-
- fileprivate func failure(status: OSStatus, function: String = #function, line: Int = #line, failure: ((NSError) -> Void)?) {
- let userInfo = [ NSLocalizedDescriptionKey : statusMessage(status) ]
- self.failure(error: NSError(domain: "pw.aska.KeyClip", code: Int(status), userInfo: userInfo), function: function, line: line, failure: failure)
- }
-
- fileprivate func failure(error: NSError, function: String = #function, line: Int = #line, failure: ((NSError) -> Void)?) {
- failure?(error)
-
- if KeyClip.printError {
- NSLog("[KeyClip] function:\(function) line:\(line) \(error.debugDescription)")
- }
- }
-
- // /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/Security.framework/Headers/SecBase.h
- // swiftlint:disable:next cyclomatic_complexity
- fileprivate func statusMessage(_ status: OSStatus) -> String {
- #if os(iOS)
- switch status {
- case errSecUnimplemented:
- return "Function or operation not implemented."
- case errSecParam:
- return "One or more parameters passed to a function where not valid."
- case errSecAllocate:
- return "Failed to allocate memory."
- case errSecNotAvailable:
- return "No keychain is available. You may need to restart your computer."
- case errSecDuplicateItem:
- return "The specified item already exists in the keychain."
- case errSecItemNotFound:
- return "The specified item could not be found in the keychain."
- case errSecInteractionNotAllowed:
- return "User interaction is not allowed."
- case errSecDecode:
- return "Unable to decode the provided data."
- case errSecAuthFailed:
- return "The user name or passphrase you entered is not correct."
- case -25243: // errSecNoAccessForItem https://developer.apple.com/library/ios/samplecode/GenericKeychain/Listings/Classes_KeychainItemWrapper_m.html
- return "Ignore the access group if running on the iPhone simulator."
- default:
- return "Refer to SecBase.h for description (status:\(status))"
+
+ guard status == errSecSuccess else {
+ if status == errSecItemNotFound {
+ return false
}
- #elseif os(OSX)
- return "Refer to MacErrors.h for description (status:\(status))"
- #endif
+
+ throw KeyClipError.unhandledError(status: status)
+ }
+
+ return true
}
}
}
diff --git a/KeyClipTests/KeyClipTests.swift b/KeyClipTests/KeyClipTests.swift
index b5b1c15..86959dd 100644
--- a/KeyClipTests/KeyClipTests.swift
+++ b/KeyClipTests/KeyClipTests.swift
@@ -20,12 +20,12 @@ class Account {
let name: String
let password: String
- init(_ dictionary: NSDictionary) {
+ init(_ dictionary: [AnyHashable: Any]) {
self.name = dictionary[Constants.name] as! String
self.password = dictionary[Constants.password] as! String
}
- var dictionaryValue: [String: String] {
+ var dictionaryValue: [AnyHashable: String] {
return [Constants.name: name, Constants.password: password]
}
}
@@ -34,12 +34,13 @@ class KeyClipTests: XCTestCase {
override func setUp() {
super.setUp()
- let _ = KeyClip.clear()
- KeyClip.printError(true)
+
+ try! KeyClip.clear()
}
override func tearDown() {
- let _ = KeyClip.clear()
+ try! KeyClip.clear()
+
super.tearDown()
}
@@ -48,102 +49,125 @@ class KeyClipTests: XCTestCase {
let key2 = "testSaveLoadKey2"
let saveData = "data"
- XCTAssertTrue((KeyClip.load(key1) as String?) == nil)
- XCTAssertTrue((KeyClip.load(key2) as String?) == nil)
+ do {
+ XCTAssertTrue(try KeyClip.string(forKey: key1) == nil)
+ XCTAssertTrue(try KeyClip.string(forKey: key2) == nil)
- XCTAssertTrue(KeyClip.save(key1, string: saveData))
+ try KeyClip.save(string: saveData, forKey: key1)
- XCTAssertTrue((KeyClip.load(key1) as String?) != nil)
- XCTAssertTrue((KeyClip.load(key2) as String?) == nil)
+ XCTAssertFalse(try KeyClip.string(forKey: key1) == nil)
+ XCTAssertTrue(try KeyClip.string(forKey: key2) == nil)
- let loadData = KeyClip.load(key1) ?? ""
+ let loadData = try KeyClip.string(forKey: key1)
- XCTAssertEqual(loadData, saveData)
+ XCTAssertEqual(loadData, saveData)
+ } catch {
+ XCTFail("\(error)")
+ }
}
func testDictionary() {
let key1 = "testSaveLoadKey1"
let key2 = "testSaveLoadKey2"
let saveAccount = Account([Account.Constants.name: "aska", Account.Constants.password: "********"])
+
+ do {
+ XCTAssertTrue(try KeyClip.string(forKey: key1) == nil)
+ XCTAssertTrue(try KeyClip.string(forKey: key2) == nil)
- XCTAssertTrue((KeyClip.load(key1) as String?) == nil)
- XCTAssertTrue((KeyClip.load(key2) as String?) == nil)
-
- XCTAssertTrue(KeyClip.save(key1, dictionary: saveAccount.dictionaryValue as NSDictionary))
+ try KeyClip.save(dictionary: saveAccount.dictionaryValue, forKey: key1)
- XCTAssertTrue((KeyClip.load(key1) as String?) != nil)
- XCTAssertTrue((KeyClip.load(key2) as String?) == nil)
+ XCTAssertFalse(try KeyClip.string(forKey: key1) == nil)
+ XCTAssertTrue(try KeyClip.string(forKey: key2) == nil)
- let loadAccount = KeyClip.load(key1) { (dictionary) -> Account in
- return Account(dictionary)
- }
- XCTAssertEqual(loadAccount!.name, saveAccount.name)
+ let loadAccount = try KeyClip.load(key1) { (dictionary) -> Account in
+ return Account(dictionary)
+ }
+ XCTAssertEqual(loadAccount!.name, saveAccount.name)
- let ring = KeyClip.Builder().build()
- let loadAccount2 = ring.load(key1) { (dictionary) -> Account in
- return Account(dictionary)
- }
- XCTAssertEqual(loadAccount2!.name, saveAccount.name)
+ let ring = KeyClip.Builder().build()
+ let loadAccount2 = try ring.load(key1) { (dictionary) -> Account in
+ return Account(dictionary)
+ }
+ XCTAssertEqual(loadAccount2!.name, saveAccount.name)
- let success = { (dictionary) -> Account in
- return Account(dictionary)
+ let success = { (dictionary) -> Account in
+ return Account(dictionary)
+ }
+ let loadAccount3 = try ring.load(key1, success: success)
+ XCTAssertEqual(loadAccount3!.name, saveAccount.name)
+
+ try KeyClip.save(string: "dummy", forKey: key1)
+
+ do {
+ let _ = try KeyClip.dictionary(forKey: key1)
+
+ XCTFail("JSON parsing should throw error")
+ } catch {
+ // Do nothing
+ }
+ } catch {
+ XCTFail("\(error)")
}
- let loadAccount3 = ring.load(key1, success: success)
- XCTAssertEqual(loadAccount3!.name, saveAccount.name)
-
- XCTAssertTrue(KeyClip.save(key1, string: "dummy"))
- var hasError = false
- let data: NSDictionary? = KeyClip.load(key1, failure: { (error: NSError) in
- hasError = true
- })
-
- XCTAssertEqual(data, nil)
- XCTAssertTrue(hasError)
}
func testDelete() {
let key1 = "testDeleteKey1"
let key2 = "testDeleteKey2"
let saveData = "testDeleteData"
+
+ do {
+ try KeyClip.save(string: saveData, forKey: key1)
+ try KeyClip.save(string: saveData, forKey: key2)
- XCTAssertTrue(KeyClip.save(key1, string: saveData))
- XCTAssertTrue(KeyClip.save(key2, string: saveData))
+ XCTAssertFalse(try KeyClip.string(forKey: key1) == nil)
+ XCTAssertFalse(try KeyClip.string(forKey: key2) == nil)
- XCTAssertTrue((KeyClip.load(key1) as String?) != nil)
- XCTAssertTrue((KeyClip.load(key2) as String?) != nil)
+ XCTAssertTrue(try KeyClip.delete(key1))
- XCTAssertTrue(KeyClip.delete(key1))
-
- XCTAssertTrue((KeyClip.load(key1) as String?) == nil)
- XCTAssertTrue((KeyClip.load(key2) as String?) != nil)
+ XCTAssertTrue(try KeyClip.string(forKey: key1) == nil)
+ XCTAssertFalse(try KeyClip.string(forKey: key2) == nil)
+ } catch {
+ XCTFail("\(error)")
+ }
}
func testExists() {
let key1 = "testDeleteKey1"
let key2 = "testDeleteKey2"
let saveData = "testDeleteData"
+
+ do {
+ try KeyClip.save(string: saveData, forKey: key1)
+ try KeyClip.save(string: saveData, forKey: key2)
- XCTAssertTrue(KeyClip.save(key1, string: saveData))
- XCTAssertTrue(KeyClip.save(key2, string: saveData))
-
- XCTAssertTrue(KeyClip.exists(key1))
- XCTAssertTrue(KeyClip.exists(key2))
+ XCTAssertTrue(try KeyClip.exists(key1))
+ XCTAssertTrue(try KeyClip.exists(key2))
- XCTAssertTrue(KeyClip.delete(key1))
+ XCTAssertTrue(try KeyClip.delete(key1))
- XCTAssertTrue(!KeyClip.exists(key1))
- XCTAssertTrue(KeyClip.exists(key2))
+ XCTAssertFalse(try KeyClip.exists(key1))
+ XCTAssertTrue(try KeyClip.exists(key2))
+ } catch {
+ XCTFail("\(error)")
+ }
}
func testClear() {
let key = "testClearKey"
let saveData = "testClearData"
-
- XCTAssertTrue(KeyClip.save(key, string: saveData))
- XCTAssertTrue((KeyClip.load(key) as String?) != nil)
-
- XCTAssertTrue(KeyClip.clear())
- XCTAssertTrue((KeyClip.load(key) as String?) == nil)
+
+ do {
+ try KeyClip.save(string: saveData, forKey: key)
+
+ XCTAssertFalse(try KeyClip.string(forKey: key) == nil)
+
+ try KeyClip.clear()
+
+ XCTAssertTrue(try KeyClip.string(forKey: key) == nil)
+ } catch {
+ XCTFail("\(error)")
+ }
}
func testService() {
@@ -154,11 +178,15 @@ class KeyClipTests: XCTestCase {
let ring1 = KeyClip.Builder().service("Service1").build()
let ring2 = KeyClip.Builder().service("Service2").build()
- XCTAssertTrue(ring1.save(key, string: val1))
- XCTAssertTrue(ring2.save(key, string: val2))
+ do {
+ try ring1.save(string: val1, forKey: key)
+ try ring2.save(string: val2, forKey: key)
- XCTAssertTrue(ring1.load(key) == val1)
- XCTAssertTrue(ring2.load(key) == val2)
+ XCTAssertTrue(try ring1.string(forKey: key) == val1)
+ XCTAssertTrue(try ring2.string(forKey: key) == val2)
+ } catch {
+ XCTFail("\(error)")
+ }
XCTAssertEqual(ring1.service, "Service1")
XCTAssertEqual(ring2.service, "Service2")
@@ -169,10 +197,14 @@ class KeyClipTests: XCTestCase {
let val = "testSetServiceVal"
let ring = KeyClip.Builder().accessible(kSecAttrAccessibleAfterFirstUnlock as String).build()
+
+ do {
+ try ring.save(string: val, forKey: key)
- XCTAssertTrue(ring.save(key, string: val))
-
- XCTAssertTrue(ring.load(key) == val)
+ XCTAssertTrue(try ring.string(forKey: key) == val)
+ } catch {
+ XCTFail("\(error)")
+ }
XCTAssertEqual(ring.accessible, kSecAttrAccessibleAfterFirstUnlock as String)
@@ -187,27 +219,21 @@ class KeyClipTests: XCTestCase {
#if os(iOS)
let key = "testSetServiceKey"
let val1 = "testSetServiceVal1"
- let val2 = "testSetServiceVal2"
// kSecAttrAccessGroup is always "com.apple.token" on iOS 9 simulator's keychain
let defaultAccessGroup = KeyClip.defaultAccessGroup()
let ring1 = KeyClip.Builder().accessGroup(defaultAccessGroup).build()
- let ring2 = KeyClip.Builder()
- .accessGroup("test.dummy") // always failure
- .build()
- XCTAssertTrue(ring1.save(key, string: val1))
+ do {
+ try ring1.save(string: val1, forKey: key)
- XCTAssertFalse(ring2.save(key, string: val2))
-
- XCTAssertTrue(ring1.exists(key))
-
- XCTAssertTrue(ring1.load(key) == val1)
-
- XCTAssertNil(ring2.load(key) as String?)
+ XCTAssertTrue(try ring1.exists(key))
+ XCTAssertTrue(try ring1.string(forKey: key) == val1)
+ } catch {
+ XCTFail("\(error)")
+ }
XCTAssertEqual(ring1.accessGroup!, defaultAccessGroup)
- XCTAssertEqual(ring2.accessGroup!, "test.dummy")
#endif
}
@@ -219,20 +245,17 @@ class KeyClipTests: XCTestCase {
func testAccessGroupError() {
#if os(iOS)
- var errorCount = 0
let ring = KeyClip.Builder()
.accessGroup("test.dummy")
.build()
-
- let ret = ring.save("hoge", string: "bar") { error -> Void in
- errorCount += 1
- let status = error.code // OSStatus
- let defaultAccessGroup = KeyClip.defaultAccessGroup()
- NSLog("[KeyClip] Error status:\(status) App Identifier:\(defaultAccessGroup)")
+
+ do {
+ try ring.save(string: "bar", forKey: "foo")
+ } catch KeyClip.KeyClipError.unhandledError(let status) {
+ XCTAssertTrue(status == -34018)
+ } catch {
+ XCTFail("\(error)")
}
- XCTAssertFalse(ret)
-
- XCTAssertTrue(errorCount == 1)
#endif
}
}
diff --git a/TestApp iOS/AppDelegate.swift b/TestApp iOS/AppDelegate.swift
index c2b231a..ec6983a 100644
--- a/TestApp iOS/AppDelegate.swift
+++ b/TestApp iOS/AppDelegate.swift
@@ -14,7 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
- func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}