Swiftを使用してiOSキーチェーンにアイテムを追加しようとしていますが、キャストを正しく入力する方法がわかりません。WWDC2013セッション709から、次の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
]
「式のタイプ「Dictionary」を「DictionaryLiteralConvertible」に変換できません」というエラーが発生します。
私が取った別のアプローチは、Swiftと- setObject:forKey:
キーkSecClassを使用してkSecClassGenericPasswordを追加する辞書のメソッド。
Objective-Cの場合:
NSMutableDictionary *searchDictionary = [NSMutableDictionary dictionary];
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
Objective-Cコードでは、さまざまなキーチェーンアイテムクラスキーのCFTypeRefがidを使用してブリッジされます。 Swift documentation )SwiftはidをAnyObjectとしてインポートすると記載されています。しかし、kSecClassをメソッドのAnyObjectとしてダウンキャストしようとしたとき、「Type'AnyObject 'がNSCopyingに準拠していない」というエラーが表示されます。
直接的な回答であろうと、Core Foundationタイプとの対話方法に関するガイダンスであろうと、どんな助けでもいただければ幸いです。
編集2
このソリューションは、Xcode 6 Beta 2以降は無効です。Beta1を使用している場合は、以下のコードが機能する可能性があります。
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)
キーチェーンアイテム属性キーをディクショナリキーとして使用するには、takeRetainedValueまたはtakeUnretainedValue(必要に応じて)を使用してそれらをアンラップする必要があります。次に、それらをNSCopyingにキャストできます。これは、ヘッダー内のCFTypeRefであり、すべてをコピーできるわけではないためです。
ただし、Xcode 6 Beta 2の時点では、これによりXcodeがクラッシュします。
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)
リテラルをダウンキャストする必要があります。
let dict = ["hi": "Pasan"] as NSDictionary
現在、dict
はNSDictionaryです。変更可能なものを作成するには、Objective-Cと非常によく似ています。
let mDict = dict.mutableCopy() as NSMutableDictionary
mDict["hola"] = "Ben"
おそらくそれ以来、状況は改善されています。 Xcode 7ベータ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
を使用してnilreturnを処理すると、コードが機能する可能性があります。エラーコードを印刷してドキュメントで調べると、問題を特定するのに大いに役立ちます(kSecClassの問題に気付くまで、データ型やキャストとは関係なく、エラー-50 =不正なパラメーターを取得していました!) 。
これはうまく機能しているようでした、または少なくともコンパイラには子猫がいませんでした-それを超えてテストされていません
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では正常に動作するようです
Swift
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)
辞書自体の中でそれを行うこともできます。
var query: [String: Any] = [
String(kSecClass): kSecClassGenericPassword,
String(kSecAttrService): "MyService",
String(kSecAttrAccount): "Some account",
String(kSecValueData): secret
]
ただし、これはコンパイラにとってよりコストがかかります。おそらく複数の場所でクエリを使用しているため、さらにコストがかかります。
これを機能させるには、代わりにキーチェーン定数の保持値にアクセスする必要があります。例えば:
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
カカオポッド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;