iOS Keychain

Keychain是什么?它相当于一个轻量级的数据库,可以把数据存储在Keychain中,当移动端卸载后又重装,可以做到数据还原。无论是使用系统自带的还是第三方框架,都需要导入系统库:Security.framework

关于Keychain的文章,网上搜索一搜一大把,可以说全部都是废话,说是可以实现多个App之间实现数据共享,结果没有一篇文章有详细教程如何实现,更是大部分文章的代码完全都是一样的,也不知道是谁抄谁的。不过还是有一些文章是挺有质量的,至少让我知道了一个叫做SAMKeychain的好库,这个库拢共只有两对儿(.h和.m)文件,如下:

image.png
  • SAMKeychain.h


    image.png
  • SAMKeychainQuery.h

    image.png

简单的玩儿了玩儿,发现挺不错,于是自己改造了一下,让它变成自己的东西,主要是为了加深自己的理解和方便以后对Keychain的快速投入使用。

实话实说,最近几天只要是有时间都会看一下关于Keychain的文章,最后结合了几篇文章再加上自己的理解总结一下。

关于说到使用Keychain实现多个App之间共享数据的说法,我的测试结果是这样的,如果项目中,没有添加Keychain Sharing的话,或者说添加了Keychain Sharing,但是没有添加分组,就像下图这样:

image.png

那么保存的数据就只在当前项目的私有空间内,存储的数据所在group的名称是当前项目加载的开发证书的teamID加上当前项目的bundleID,就像这样:AB0CD12EFG.com.abcdefg.KeyChainTestDemo。下面是我用SAMKeychain改造之后存储的数据,可以简单看一下:

image.png

如果添加了Keychain Sharing,并且也添加了分组,像下图这样:

image.png

那么再次保存的数据将会被保存到新添加的这个分组中,之前添加的数据如果不删除会一直保留,虽然新分组的名称和默认的group的名称是一样的,但是性质确实不同的,因为只要添加了分组,其他App就可以通过这个组名来获取到当前App存储在这个分组中的所有数据,那么其他App是如果通过group名称获取数据的呢?重点!只要是同一个开发者账号下的App,需要全部都设置Keychain Sharing,且group名称必须相同,就能实现数据共享。在我测试当中,像下图这样:

image.png

无论我设置了多少个分组,存储数据和获取数据都只对第0个分组进行操作,我也不知道为什么??所以我的结论是:Keychain Sharing中,只有第0个分组有效。是不是很尴尬!话说我自己对这个结论都表示怀疑……

分享测试

下面是我的测试demo和针对SAMKeychain改造后的代码,有兴趣的可以看一下

  • 1、页面
image.png
  • 2、测试代码
#import "ViewController.h"
#import "LYKeychain.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UITextField *tfAccount;
@property (weak, nonatomic) IBOutlet UITextField *tfService;
@property (weak, nonatomic) IBOutlet UITextField *tfValue;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.view.backgroundColor = UIColor.whiteColor;
    
}

//写入数据
- (IBAction)writeToKeychainAction:(id)sender {
    
    BOOL isBol = [LYKeychain setValue:self.tfValue.text service:self.tfService.text account:self.tfAccount.text];
    NSLog(@"isBol = %@",isBol?@"yes":@"no");
    
}

//读取全部数据
- (IBAction)readAllCacheAction:(id)sender {
    
    NSArray *array = [LYKeychain allAccounts];
    NSLog(@"array = %@",array);
}

//删除全部数据
- (IBAction)deleteAllCacheAction:(id)sender {
    
    BOOL isBol = [LYKeychain deleteAllAccounts];
    NSLog(@"isBol = %@",isBol?@"yes":@"no");
    
}

//读取指定数据
- (IBAction)readCacheAction:(id)sender {
    
    NSString *cache = [LYKeychain valueWithService:self.tfService.text account:self.tfAccount.text];
    NSLog(@"cache = %@",cache);
    
}

//删除指定数据
- (IBAction)deleteCacheAction:(id)sender {
    
    BOOL isBol = [LYKeychain deleteValueWithService:self.tfService.text account:self.tfAccount.text];
    NSLog(@"isBol = %@",isBol?@"yes":@"no");
    
}

//取消键盘
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.view endEditing:YES];
}

@end
  • 3、改造后的工具
image.png
  • LYKeychain.h
#import 

NS_ASSUME_NONNULL_BEGIN

/**
 Error code specific to LYKeychain that can be returned in NSError objects.
 For codes returned by the operating system, refer to SecBase.h for your
 platform.
 */
typedef NS_ENUM(OSStatus, LYKeychainErrorCode) {
    /** Some of the arguments were invalid. */
    LYKeychainErrorBadArguments = -1001,
};

/** LYKeychain error domain */
extern NSString *const lyKeychainErrorDomain;

/** Account name. */
extern NSString *const lyKeychainAccountKey;

/**
 Time the item was created.
 
 The value will be a string.
 */
extern NSString *const lyKeychainCreatedAtKey;

/** Item class. */
extern NSString *const lyKeychainClassKey;

/** Item description. */
extern NSString *const lyKeychainDescriptionKey;

/** Item label. */
extern NSString *const lyKeychainLabelKey;

/**
 Time the item was last modified.
 
 The value will be a string.
 */
extern NSString *const lyKeychainLastModifiedKey;

/** Where the item was created. */
extern NSString *const lyKeychainWhereKey;

@interface LYKeychain : NSObject

/** 字符串类型存储 */
+ (BOOL)setValue:(NSString *)value service:(NSString *)service account:(NSString *)account;

/** Data类型存储 */
+ (BOOL)setValueData:(NSData *)value service:(NSString *)service account:(NSString *)account;

/** 删除缓存 */
+ (BOOL)deleteAllAccounts;
+ (BOOL)deleteValueWithService:(NSString *)service account:(NSString *)account;

/** 读取全部 */
+ (nullable NSArray *> *)allAccounts;
+ (nullable NSString *)valueWithService:(NSString *)service account:(NSString *)account;
+ (nullable NSData *)valueDataWithService:(NSString *)service account:(NSString *)account;

#pragma mark - Configuration

#if __IPHONE_4_0 && TARGET_OS_IPHONE
/**
 Returns the accessibility type for all future passwords saved to the Keychain.
 
 @return Returns the accessibility type.
 
 The return value will be `NULL` or one of the "Keychain Item Accessibility
 Constants" used for determining when a keychain item should be readable.
 
 @see setAccessibilityType
 */
+ (CFTypeRef)accessibilityType;

/**
 Sets the accessibility type for all future passwords saved to the Keychain.
 
 @param accessibilityType One of the "Keychain Item Accessibility Constants"
 used for determining when a keychain item should be readable.
 
 If the value is `NULL` (the default), the Keychain default will be used which
 is highly insecure. You really should use at least `kSecAttrAccessibleAfterFirstUnlock`
 for background applications or `kSecAttrAccessibleWhenUnlocked` for all
 other applications.
 
 @see accessibilityType
 */
+ (void)setAccessibilityType:(CFTypeRef)accessibilityType;
#endif

@end

NS_ASSUME_NONNULL_END
  • LYKeychain.m
#import "LYKeychain.h"
#import "LYKeychainQuery.h"

NSString *const lyKeychainErrorDomain = @"com.samsoffes.samkeychain";
NSString *const lyKeychainAccountKey = @"acct";
NSString *const lyKeychainCreatedAtKey = @"cdat";
NSString *const lyKeychainClassKey = @"labl";
NSString *const lyKeychainDescriptionKey = @"desc";
NSString *const lyKeychainLabelKey = @"labl";
NSString *const lyKeychainLastModifiedKey = @"mdat";
NSString *const lyKeychainWhereKey = @"svce";

#if __IPHONE_4_0 && TARGET_OS_IPHONE
static CFTypeRef LYKeychainAccessibilityType = NULL;
#endif

@implementation LYKeychain

+ (BOOL)setValue:(NSString *)value service:(NSString *)service account:(NSString *)account {
    return [self setValue:value service:service account:account error:nil];
}

+ (BOOL)setValue:(NSString *)value service:(NSString *)service account:(NSString *)account error:(NSError *__autoreleasing *)error {
    
    if (!value.length || !service.length || !account.length) {
        return NO;
    }
    
    LYKeychainQuery *query = [[LYKeychainQuery alloc] init];
    query.service = service;
    query.account = account;
    query.password = value;
    return [query save:error];
}

+ (BOOL)setValueData:(NSData *)value service:(NSString *)service account:(NSString *)account {
    return [self setValueData:value service:service account:account error:nil];
}

+ (BOOL)setValueData:(NSData *)value service:(NSString *)service account:(NSString *)account error:(NSError **)error {
    LYKeychainQuery *query = [[LYKeychainQuery alloc] init];
    query.service = service;
    query.account = account;
    query.passwordData = value;
    return [query save:error];
}

+ (BOOL)deleteAllAccounts {
    return [self deleteValueWithService:nil account:nil error:nil deleteAll:YES];
}

+ (BOOL)deleteValueWithService:(NSString *)service account:(NSString *)account {
    return [self deleteValueWithService:service account:account error:nil deleteAll:NO];
}

+ (BOOL)deleteValueWithService:(nullable NSString *)service account:(nullable NSString *)account error:(NSError *__autoreleasing *)error deleteAll:(BOOL)isAll{
    LYKeychainQuery *query = [[LYKeychainQuery alloc] init];
    
    //指定删除时执行此操作
    if (!isAll) {
        query.service = service;
        query.account = account;
    }
    
    return [query deleteItem:error deleteAll:isAll];
    
}

+ (nullable NSArray *)allAccounts {
    return [self service:nil account:nil error:nil class:NSArray.class];
}

+ (nullable NSString *)valueWithService:(NSString *)service account:(NSString *)account {
    return [self service:service account:account error:nil class:NSString.class];
}

+ (nullable NSData *)valueDataWithService:(NSString *)service account:(NSString *)account {
    return [self service:service account:account error:nil class:NSData.class];
}

+ (nullable id)service:(nullable NSString *)service account:(nullable NSString *)account error:(NSError *__autoreleasing *)error class:(Class)class {
    
    LYKeychainQuery *query = [[LYKeychainQuery alloc] init];
    if ([class isSubclassOfClass:NSArray.class]) {
        return [query fetchAll:error];
    }else if ([class isSubclassOfClass:NSString.class]){
        query.service = service;
        query.account = account;
        [query fetch:error];
        return query.password;
    }else if ([class isSubclassOfClass:NSData.class]){
        query.service = service;
        query.account = account;
        [query fetch:error];
        return query.passwordData;
    }
    return nil;
    
}

#if __IPHONE_4_0 && TARGET_OS_IPHONE
+ (CFTypeRef)accessibilityType {
    return LYKeychainAccessibilityType;
}

+ (void)setAccessibilityType:(CFTypeRef)accessibilityType {
    CFRetain(accessibilityType);
    if (LYKeychainAccessibilityType) {
        CFRelease(LYKeychainAccessibilityType);
    }
    LYKeychainAccessibilityType = accessibilityType;
}
#endif

@end
  • LYKeychainQuery.h
#if __has_feature(modules)
@import Foundation;
@import Security;
#else
#import 
#import 
#endif

NS_ASSUME_NONNULL_BEGIN

#if __IPHONE_7_0 || __MAC_10_9
// Keychain synchronization available at compile time
#define SAMKEYCHAIN_SYNCHRONIZATION_AVAILABLE 1
#endif

#if __IPHONE_3_0 || __MAC_10_9
// Keychain access group available at compile time
#define SAMKEYCHAIN_ACCESS_GROUP_AVAILABLE 1
#endif

#ifdef SAMKEYCHAIN_SYNCHRONIZATION_AVAILABLE
typedef NS_ENUM(NSUInteger, LYKeychainQuerySynchronizationMode) {
    LYKeychainQuerySynchronizationModeAny,
    LYKeychainQuerySynchronizationModeNo,
    LYKeychainQuerySynchronizationModeYes
};
#endif

@interface LYKeychainQuery : NSObject

/** kSecAttrAccount */
@property (nonatomic, copy, nullable) NSString *account;

/** kSecAttrService */
@property (nonatomic, copy, nullable) NSString *service;

/** kSecAttrLabel */
@property (nonatomic, copy, nullable) NSString *label;

#ifdef SAMKEYCHAIN_ACCESS_GROUP_AVAILABLE
/** kSecAttrAccessGroup (only used on iOS) */
@property (nonatomic, copy, nullable) NSString *accessGroup;
#endif

#ifdef SAMKEYCHAIN_SYNCHRONIZATION_AVAILABLE
/** kSecAttrSynchronizable */
@property (nonatomic) LYKeychainQuerySynchronizationMode synchronizationMode;
#endif

/** Root storage for password information */
@property (nonatomic, copy, nullable) NSData *passwordData;

/**
 This property automatically transitions between an object and the value of
 `passwordData` using NSKeyedArchiver and NSKeyedUnarchiver.
 */
@property (nonatomic, copy, nullable) id passwordObject;

/**
 Convenience accessor for setting and getting a password string. Passes through
 to `passwordData` using UTF-8 string encoding.
 */
@property (nonatomic, copy, nullable) NSString *password;

/**
 Save the receiver's attributes as a keychain item. Existing items with the
 given account, service, and access group will first be deleted.
 
 @param error Populated should an error occur.
 
 @return `YES` if saving was successful, `NO` otherwise.
 */
- (BOOL)save:(NSError **)error;

/**
 Delete keychain items that match the given account, service, and access group.
 
 @param error Populated should an error occur.
 
 @return `YES` if saving was successful, `NO` otherwise.
 */
- (BOOL)deleteItem:(NSError **)error deleteAll:(BOOL)isAll;

/**
 Fetch all keychain items that match the given account, service, and access
 group. The values of `password` and `passwordData` are ignored when fetching.
 
 @param error Populated should an error occur.
 
 @return An array of dictionaries that represent all matching keychain items or
 `nil` should an error occur.
 The order of the items is not determined.
 */
- (nullable NSArray *> *)fetchAll:(NSError **)error;

/**
 Fetch the keychain item that matches the given account, service, and access
 group. The `password` and `passwordData` properties will be populated unless
 an error occurs. The values of `password` and `passwordData` are ignored when
 fetching.
 
 @param error Populated should an error occur.
 
 @return `YES` if fetching was successful, `NO` otherwise.
 */
- (BOOL)fetch:(NSError **)error;

#ifdef SAMKEYCHAIN_SYNCHRONIZATION_AVAILABLE
/**
 Returns a boolean indicating if keychain synchronization is available on the device at runtime. The #define
 SAMKEYCHAIN_SYNCHRONIZATION_AVAILABLE is only for compile time. If you are checking for the presence of synchronization,
 you should use this method.
 
 @return A value indicating if keychain synchronization is available
 */
+ (BOOL)isSynchronizationAvailable;
#endif

@end

NS_ASSUME_NONNULL_END
  • LYKeychainQuery.m
#import "LYKeychainQuery.h"
#import "LYKeychain.h"

@implementation LYKeychainQuery

@synthesize account = _account;
@synthesize service = _service;
@synthesize label = _label;
@synthesize passwordData = _passwordData;

#ifdef SAMKEYCHAIN_ACCESS_GROUP_AVAILABLE
@synthesize accessGroup = _accessGroup;
#endif

#ifdef SAMKEYCHAIN_SYNCHRONIZATION_AVAILABLE
@synthesize synchronizationMode = _synchronizationMode;
#endif

#pragma mark - Public

- (BOOL)save:(NSError *__autoreleasing *)error {
    OSStatus status = LYKeychainErrorBadArguments;
    if (!self.service || !self.account || !self.passwordData) {
        if (error) {
            *error = [[self class] errorWithCode:status];
        }
        return NO;
    }
    NSMutableDictionary *query = nil;
    NSMutableDictionary * searchQuery = [self query];
    status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, nil);
    if (status == errSecSuccess) {//item already exists, update it!
        query = [[NSMutableDictionary alloc]init];
        [query setObject:self.passwordData forKey:(__bridge id)kSecValueData];
#if __IPHONE_4_0 && TARGET_OS_IPHONE
        CFTypeRef accessibilityType = [LYKeychain accessibilityType];
        if (accessibilityType) {
            [query setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible];
        }
#endif
        status = SecItemUpdate((__bridge CFDictionaryRef)(searchQuery), (__bridge CFDictionaryRef)(query));
    }else if(status == errSecItemNotFound){//item not found, create it!
        query = [self query];
        if (self.label) {
            [query setObject:self.label forKey:(__bridge id)kSecAttrLabel];
        }
        [query setObject:self.passwordData forKey:(__bridge id)kSecValueData];
#if __IPHONE_4_0 && TARGET_OS_IPHONE
        CFTypeRef accessibilityType = [LYKeychain accessibilityType];
        if (accessibilityType) {
            [query setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible];
        }
#endif
        status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
    }
    if (status != errSecSuccess && error != NULL) {
        *error = [[self class] errorWithCode:status];
    }
    return (status == errSecSuccess);
    
}

- (BOOL)deleteItem:(NSError *__autoreleasing  _Nullable *)error deleteAll:(BOOL)isAll {
    OSStatus status = LYKeychainErrorBadArguments;
    
    //指定删除时执行此操作
    if (!isAll) {
        if (!self.service || !self.account) {
            if (error) {
                *error = [[self class] errorWithCode:status];
            }
            return NO;
        }
    }
    
    NSMutableDictionary *query = [self query];
#if TARGET_OS_IPHONE
    status = SecItemDelete((__bridge CFDictionaryRef)query);
#else
    // On Mac OS, SecItemDelete will not delete a key created in a different
    // app, nor in a different version of the same app.
    //
    // To replicate the issue, save a password, change to the code and
    // rebuild the app, and then attempt to delete that password.
    //
    // This was true in OS X 10.6 and probably later versions as well.
    //
    // Work around it by using SecItemCopyMatching and SecKeychainItemDelete.
    CFTypeRef result = NULL;
    [query setObject:@YES forKey:(__bridge id)kSecReturnRef];
    status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
    if (status == errSecSuccess) {
        status = SecKeychainItemDelete((SecKeychainItemRef)result);
        CFRelease(result);
    }
#endif
    
    if (status != errSecSuccess && error != NULL) {
        *error = [[self class] errorWithCode:status];
    }
    
    return (status == errSecSuccess);
}

- (nullable NSArray *)fetchAll:(NSError *__autoreleasing *)error {
    NSMutableDictionary *query = [self query];
    [query setObject:@YES forKey:(__bridge id)kSecReturnAttributes];
    [query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
#if __IPHONE_4_0 && TARGET_OS_IPHONE
    CFTypeRef accessibilityType = [LYKeychain accessibilityType];
    if (accessibilityType) {
        [query setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible];
    }
#endif
    
    CFTypeRef result = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
    if (status != errSecSuccess && error != NULL) {
        *error = [[self class] errorWithCode:status];
        return nil;
    }
    
    return (__bridge_transfer NSArray *)result;
}

- (BOOL)fetch:(NSError *__autoreleasing *)error {
    OSStatus status = LYKeychainErrorBadArguments;
    if (!self.service || !self.account) {
        if (error) {
            *error = [[self class] errorWithCode:status];
        }
        return NO;
    }
    
    CFTypeRef result = NULL;
    NSMutableDictionary *query = [self query];
    [query setObject:@YES forKey:(__bridge id)kSecReturnData];
    [query setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
    status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
    
    if (status != errSecSuccess) {
        if (error) {
            *error = [[self class] errorWithCode:status];
        }
        return NO;
    }
    
    self.passwordData = (__bridge_transfer NSData *)result;
    return YES;
}

#pragma mark - Accessors

- (void)setPasswordObject:(id)object {
    self.passwordData = [NSKeyedArchiver archivedDataWithRootObject:object requiringSecureCoding:YES error:nil];
}

- (id)passwordObject {
    if ([self.passwordData length]) {
        return [NSKeyedUnarchiver unarchivedObjectOfClass:[NSData class] fromData:self.passwordData error:nil];
    }
    return nil;
}

- (void)setPassword:(NSString *)password {
    self.passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
}

- (NSString *)password {
    if ([self.passwordData length]) {
        return [[NSString alloc] initWithData:self.passwordData encoding:NSUTF8StringEncoding];
    }
    return nil;
}

#pragma mark - Synchronization Status

#ifdef SAMKEYCHAIN_SYNCHRONIZATION_AVAILABLE
+ (BOOL)isSynchronizationAvailable {
#if TARGET_OS_IPHONE
    // Apple suggested way to check for 7.0 at runtime
    // https://developer.apple.com/library/ios/documentation/userexperience/conceptual/transitionguide/SupportingEarlieriOS.html
    return floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1;
#else
    return floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_8_4;
#endif
}
#endif

#pragma mark - Private

- (NSMutableDictionary *)query {
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:3];
    [dictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    
    if (self.service) {
        [dictionary setObject:self.service forKey:(__bridge id)kSecAttrService];
    }
    
    if (self.account) {
        [dictionary setObject:self.account forKey:(__bridge id)kSecAttrAccount];
    }
    
#ifdef SAMKEYCHAIN_ACCESS_GROUP_AVAILABLE
#if !TARGET_IPHONE_SIMULATOR
    if (self.accessGroup) {
        [dictionary setObject:self.accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
    }
#endif
#endif
    
#ifdef SAMKEYCHAIN_SYNCHRONIZATION_AVAILABLE
    if ([[self class] isSynchronizationAvailable]) {
        id value;
        
        switch (self.synchronizationMode) {
            case LYKeychainQuerySynchronizationModeNo: {
                value = @NO;
                break;
            }
            case LYKeychainQuerySynchronizationModeYes: {
                value = @YES;
                break;
            }
            case LYKeychainQuerySynchronizationModeAny: {
                value = (__bridge id)(kSecAttrSynchronizableAny);
                break;
            }
        }
        
        [dictionary setObject:value forKey:(__bridge id)(kSecAttrSynchronizable)];
    }
#endif
    
    return dictionary;
}

+ (NSError *)errorWithCode:(OSStatus) code {
    static dispatch_once_t onceToken;
    static NSBundle *resourcesBundle = nil;
    dispatch_once(&onceToken, ^{
        NSURL *url = [[NSBundle bundleForClass:[LYKeychainQuery class]] URLForResource:@"LYKeychain" withExtension:@"bundle"];
        resourcesBundle = [NSBundle bundleWithURL:url];
    });
    
    NSString *message = nil;
    switch (code) {
        case errSecSuccess: return nil;
        case LYKeychainErrorBadArguments: message = NSLocalizedStringFromTableInBundle(@"LYKeychainErrorBadArguments", @"LYKeychain", resourcesBundle, nil); break;
            
#if TARGET_OS_IPHONE
        case errSecUnimplemented: {
            message = NSLocalizedStringFromTableInBundle(@"errSecUnimplemented", @"LYKeychain", resourcesBundle, nil);
            break;
        }
        case errSecParam: {
            message = NSLocalizedStringFromTableInBundle(@"errSecParam", @"LYKeychain", resourcesBundle, nil);
            break;
        }
        case errSecAllocate: {
            message = NSLocalizedStringFromTableInBundle(@"errSecAllocate", @"LYKeychain", resourcesBundle, nil);
            break;
        }
        case errSecNotAvailable: {
            message = NSLocalizedStringFromTableInBundle(@"errSecNotAvailable", @"LYKeychain", resourcesBundle, nil);
            break;
        }
        case errSecDuplicateItem: {
            message = NSLocalizedStringFromTableInBundle(@"errSecDuplicateItem", @"LYKeychain", resourcesBundle, nil);
            break;
        }
        case errSecItemNotFound: {
            message = NSLocalizedStringFromTableInBundle(@"errSecItemNotFound", @"LYKeychain", resourcesBundle, nil);
            break;
        }
        case errSecInteractionNotAllowed: {
            message = NSLocalizedStringFromTableInBundle(@"errSecInteractionNotAllowed", @"LYKeychain", resourcesBundle, nil);
            break;
        }
        case errSecDecode: {
            message = NSLocalizedStringFromTableInBundle(@"errSecDecode", @"LYKeychain", resourcesBundle, nil);
            break;
        }
        case errSecAuthFailed: {
            message = NSLocalizedStringFromTableInBundle(@"errSecAuthFailed", @"LYKeychain", resourcesBundle, nil);
            break;
        }
        default: {
            message = NSLocalizedStringFromTableInBundle(@"errSecDefault", @"LYKeychain", resourcesBundle, nil);
        }
#else
        default:
            message = (__bridge_transfer NSString *)SecCopyErrorMessageString(code, NULL);
#endif
    }
    
    NSDictionary *userInfo = nil;
    if (message) {
        userInfo = @{ NSLocalizedDescriptionKey : message };
    }
    return [NSError errorWithDomain:lyKeychainErrorDomain code:code userInfo:userInfo];
}

@end

所有代码,复制黏贴可运行

全剧终

你可能感兴趣的:(iOS Keychain)