Добавление элемента в цепочку ключей с помощью Swift
Я пытаюсь добавить элемент в keychain iOS с помощью Swift, но не могу понять, как правильно набирать литеры. Из сессии 709 WWDC 2013, учитывая следующий код Objective-C:
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);
Попытка сделать это в Swift следующим образом:
var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
var query: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: "MyService",
kSecAttrAccount: "Some account",
kSecValueData: secret
]
дает ошибку "Невозможно преобразовать тип выражения" Словарь "в" СловарьLiteralConvertible ".
Другим подходом, который я взял, было использование Swift и метода - setObject:forKey:
в словаре для добавления kSecClassGenericPassword с ключом kSecClass.
В Objective-C:
NSMutableDictionary *searchDictionary = [NSMutableDictionary dictionary];
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
В коде Objective-C CFTypeRef различных ключей класса элемента keychain соединяется с использованием id. В Swift documentation он упомянул, что Swift импортирует id как AnyObject. Однако, когда я попытался понизить kSecClass как AnyObject для метода, я получаю сообщение об ошибке "Тип" AnyObject "не соответствует NSCopying.
Любая помощь, будь то прямой ответ или какое-либо руководство о том, как взаимодействовать с типами Core Foundation, будет оценено.
РЕДАКТИРОВАТЬ 2
Это решение больше недействительно с Xcode 6 Beta 2. Если вы используете бета-версию 1, приведенный ниже код может работать.
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)
Чтобы использовать ключи атрибута Keychain Item в качестве ключей словаря, вам необходимо развернуть их, используя либо takeRetainedValue, либо takeUnretainedValue (при необходимости). Затем вы можете отправить их в NSCopying. Это потому, что они являются CFTypeRefs в заголовке, которые не все могут быть скопированы.
Как и в случае с Xcode 6 Beta 2, это приводит к сбою Xcode.
Ответы
Ответ 1
В xcode 6.0.1 вы должны это сделать!
let kSecClassValue = NSString(format: kSecClass)
let kSecAttrAccountValue = NSString(format: kSecAttrAccount)
let kSecValueDataValue = NSString(format: kSecValueData)
let kSecClassGenericPasswordValue = NSString(format: kSecClassGenericPassword)
let kSecAttrServiceValue = NSString(format: kSecAttrService)
let kSecMatchLimitValue = NSString(format: kSecMatchLimit)
let kSecReturnDataValue = NSString(format: kSecReturnData)
let kSecMatchLimitOneValue = NSString(format: kSecMatchLimitOne)
Ответ 2
Вам просто нужно опустить букву:
let dict = ["hi": "Pasan"] as NSDictionary
Теперь dict
является NSDictionary. Чтобы сделать изменчивым, он очень похож на Objective-C:
let mDict = dict.mutableCopy() as NSMutableDictionary
mDict["hola"] = "Ben"
Ответ 3
Возможно, с тех пор ситуация улучшилась. На Xcode 7 beta 4 никакое кастинг не требуется, кроме случаев, когда имеет дело с результатом AnyObject?
. В частности, работает следующее:
var query : [NSString : AnyObject] = [
kSecClass : kSecClassGenericPassword,
kSecAttrService : "MyAwesomeService",
kSecReturnAttributes : true, // return dictionary in result parameter
kSecReturnData : true // include the password value
]
var result : AnyObject?
let err = SecItemCopyMatching(query, &result)
if (err == errSecSuccess) {
// on success cast the result to a dictionary and extract the
// username and password from the dictionary.
if let result = result as ? [NSString : AnyObject],
let username = result[kSecAttrAccount] as? String,
let passdata = result[kSecValueData] as? NSData,
let password = NSString(data:passdata, encoding:NSUTF8StringEncoding) as? String {
return (username, password)
}
} else if (status == errSecItemNotFound) {
return nil;
} else {
// probably a program error,
// print and lookup err code (e.g., -50 = bad parameter)
}
Чтобы добавить ключ, если он отсутствует:
var query : [NSString : AnyObject] = [
kSecClass : kSecClassGenericPassword,
kSecAttrService : "MyAwesomeService",
kSecAttrLabel : "MyAwesomeService Password",
kSecAttrAccount : username,
kSecValueData : password.dataUsingEncoding(NSUTF8StringEncoding)!
]
let result = SecItemAdd(query, nil)
// check that result is errSecSuccess, etc...
Несколько замечаний: ваша первоначальная проблема, возможно, заключалась в том, что str.dataUsingEncoding
возвращает Необязательный. Добавление '!' или еще лучше, используя if let
для обработки нулевого возврата, скорее всего, сделает ваш код работать. Распечатка кода ошибки и поиск его в документах поможет много в изоляции проблемы (я получал err -50 = плохой параметр, пока не заметил проблему с моим kSecClass, ничего общего с типами данных или отбрасыванием!).
Ответ 4
Казалось, что это нормально, или, по крайней мере, у компилятора не было котят - UNTESTED за пределами этого
var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
var array1 = NSArray(objects:"\(kSecClassGenericPassword)", "MyService", "Some account", secret)
var array2 = NSArray(objects:"\(kSecClass)","\(kSecAttrService)", "\(kSecAttrAccount)","\(kSecValueData)")
let query = NSDictionary(objects: array1, forKeys: array2)
println(query)
let status = SecItemAdd(query as CFDictionaryRef, nil)
Кажется, хорошо работает в бета-версии 2
Ответ 5
Чтобы заставить это работать, вам нужно получить доступ к сохраненным значениям констант связки ключей. Например:
let kSecClassValue = kSecClass.takeRetainedValue() as NSString
let kSecAttrAccountValue = kSecAttrAccount.takeRetainedValue() as NSString
let kSecValueDataValue = kSecValueData.takeRetainedValue() as NSString
let kSecClassGenericPasswordValue = kSecClassGenericPassword.takeRetainedValue() as NSString
let kSecAttrServiceValue = kSecAttrService.takeRetainedValue() as NSString
let kSecMatchLimitValue = kSecMatchLimit.takeRetainedValue() as NSString
let kSecReturnDataValue = kSecReturnData.takeRetainedValue() as NSString
let kSecMatchLimitOneValue = kSecMatchLimitOne.takeRetainedValue() as NSString
Затем вы можете ссылаться на значения в MSMutableDictionary следующим образом:
var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
Я написал сообщение в блоге об этом:
http://rshelby.com/2014/08/using-swift-to-save-and-query-ios-keychain-in-xcode-beta-4/
Надеюсь, это поможет!
rshelby
Ответ 6
Swift 3
let kSecClassKey = String(kSecClass)
let kSecAttrAccountKey = String(kSecAttrAccount)
let kSecValueDataKey = String(kSecValueData)
let kSecClassGenericPasswordKey = String(kSecClassGenericPassword)
let kSecAttrServiceKey = String(kSecAttrService)
let kSecMatchLimitKey = String(kSecMatchLimit)
let kSecReturnDataKey = String(kSecReturnData)
let kSecMatchLimitOneKey = String(kSecMatchLimitOne)
вы также можете сделать это внутри самого словаря alá:
var query: [String: Any] = [
String(kSecClass): kSecClassGenericPassword,
String(kSecAttrService): "MyService",
String(kSecAttrAccount): "Some account",
String(kSecValueData): secret
]
однако, это более дорого для компилятора, тем более, что вы, вероятно, используете запрос в нескольких местах.
Ответ 7
более удобно использовать cocoa pods SSKeychain
+ (NSArray *)allAccounts;
+ (NSArray *)accountsForService:(NSString *)serviceName;
+ (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account;
+ (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account;
+ (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account;