I'm trying to add an item to the iOS keychain using Swift but can't figure out how to type cast properly. From WWDC 2013 session 709, given the following Objective-C code:
NSData *secret = [@"top secret" dataWithEncoding:NSUTF8StringEncoding];
NSDictionary *query = @{
(id)kSecClass: (id)kSecClassGenericPassword,
(id)kSecAttrService: @"myservice",
(id)kSecAttrAccount: @"account name here",
(id)kSecValueData: secret,
};
OSStatus = SecItemAdd((CFDictionaryRef)query, NULL);
Attempting to do it in Swift as follows:
var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
var query: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: "MyService",
kSecAttrAccount: "Some account",
kSecValueData: secret
]
yields the error "Cannot convert the expression's type 'Dictionary' to 'DictionaryLiteralConvertible'.
Another approach I took was to use Swift and the - setObject:forKey:
method on a Dictionary to add kSecClassGenericPassword with the key kSecClass.
In Objective-C:
NSMutableDictionary *searchDictionary = [NSMutableDictionary dictionary];
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
In the Objective-C code, the CFTypeRef of the various keychain item class keys are bridged over using id. In the Swift documentation it's mentioned that Swift imports id as AnyObject. However when I attempted to downcast kSecClass as AnyObject for the method, I get the error that "Type 'AnyObject' does not conform to NSCopying.
Any help, whether it's a direct answer or some guidance about how to interact with Core Foundation types would be appreciated.
EDIT 2
This solution is no longer valid as of Xcode 6 Beta 2. If you are using Beta 1 the code below may work.
var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let query = NSDictionary(objects: [kSecClassGenericPassword, "MyService", "Some account", secret], forKeys: [kSecClass,kSecAttrService, kSecAttrAccount, kSecValueData])
OSStatus status = SecItemAdd(query as CFDictionaryRef, NULL)
To use Keychain Item Attribute keys as dictionary keys you have to unwrap them by using either takeRetainedValue or takeUnretainedValue (as appropriate). Then you can cast them to NSCopying. This is because they are CFTypeRefs in the header, which aren't all copyable.
As of Xcode 6 Beta 2 however, this causes Xcode to crash.
You simply need to downcast the literal:
Now
dict
is an NSDictionary. To make a mutable one, it's very similar to Objective-C:In the xcode 6.0.1 you must do this!!
more convenient to use the cocoa pods SSKeychain
In order to get this to work, you need to access the retained values of the keychain constants instead. For example:
You can then reference the values in the MSMutableDictionary like so:
I wrote a blog post about it at: http://rshelby.com/2014/08/using-swift-to-save-and-query-ios-keychain-in-xcode-beta-4/
Hope this helps!
rshelby
Perhaps things have improved since. On Xcode 7 beta 4, no casting seems to be necessary except when dealing with the result
AnyObject?
. Specifically, the following seems to work:To add a key if it was missing:
A few things to point out: your initial problem might have been that
str.dataUsingEncoding
returns an Optional. Adding '!' or better yet, using anif let
to handle nil return, would likely make your code work. Printing out the error code and looking it up in the docs will help a lot in isolating the problem (I was getting err -50 = bad parameter, until I noticed a problem with my kSecClass, nothing to do with data types or casts!).Swift 3
you can also do it inside the dictionary itself alá:
however, this is more expensive for the compiler, even more so since you're probably using the query in multiple places.