Overview
SAMKeychain provides a simple migration path from other keychain wrapper libraries and older versions. This guide covers common migration scenarios and best practices.
Migrating from Other Libraries
From EMKeychain
SAMKeychain was originally inspired by EMKeychain (now discontinued). The API is similar but with some improvements:
EMKeychain (Old)
SAMKeychain (New)
// EMKeychain syntax
[EMKeychain setKeychainPassword:password
forService:service
account:account];
NSString *password = [EMKeychain keychainPasswordForService:service
account:account];
The main difference is simplified method names. Your existing keychain data remains accessible - just update the method calls.
From SDKeychain
SDKeychain is another discontinued library that SAMKeychain replaces:
SDKeychain (Old)
SAMKeychain (New)
// SDKeychain syntax
[SDKeychain storePassword:password
forService:service
account:account];
NSString *password = [SDKeychain passwordForService:service
account:account];
From Raw Security Framework
If you’re migrating from direct Security.framework usage, SAMKeychain dramatically simplifies your code:
Security.framework (Old)
SAMKeychain (New)
// Direct Security framework - verbose and error-prone
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding],
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked
};
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
if (status == errSecDuplicateItem) {
// Update existing item
NSDictionary *attributesToUpdate = @{
(__bridge id)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding]
};
status = SecItemUpdate((__bridge CFDictionaryRef)query,
(__bridge CFDictionaryRef)attributesToUpdate);
}
When migrating from direct Security framework usage, ensure your kSecAttrService and kSecAttrAccount values match exactly to access existing keychain items.
ARC Requirements
SAMKeychain requires Automatic Reference Counting (ARC) to be enabled in your project.
If Your Project Uses ARC
No action needed - SAMKeychain will work out of the box.
If Your Project Uses Manual Reference Counting (MRC)
You have two options:
Option 1: Enable ARC for SAMKeychain files only
In Xcode, select your target
Go to Build Phases > Compile Sources
Find SAMKeychain files (SAMKeychain.m, SAMKeychainQuery.m)
Add the -fobjc-arc compiler flag to each file
Option 2: Convert your entire project to ARC
Xcode provides an automated migration tool:
Edit > Convert > To Objective-C ARC…
Follow the migration assistant prompts
Review and test thoroughly after conversion
Most modern projects use ARC. Manual reference counting is only common in legacy codebases.
Version Updates
Updating from 1.x to Current Version
The current version maintains backward compatibility with 1.x releases. However, there are some improvements:
Error Handling
// Old: No error handling
NSString *password = [SAMKeychain passwordForService:service account:account];
// New: With error handling (recommended)
NSError *error = nil;
NSString *password = [SAMKeychain passwordForService:service
account:account
error:&error];
if (!password && error) {
NSLog(@"Failed to retrieve password: %@", error);
}
Swift Integration
The library now includes __attribute__((swift_error(none))) for better Swift integration:
// Modern Swift usage
if let password = SAMKeychain. password ( forService : "MyApp" ,
account : "user@example.com" ) {
// Use password
}
Always check for errors when performing keychain operations. The keychain can fail for various reasons (device locked, insufficient permissions, etc.).
Breaking Changes Checklist
When updating to newer versions:
✅ No breaking changes in the public API
✅ Error handling is additive (old methods still work)
✅ Existing keychain data remains accessible
⚠️ Default accessibility setting behavior may have changed (explicitly set accessibility type)
Code Migration Examples
Complete Migration Example
Here’s a complete before/after example:
// Old code using manual Security framework
- (void)savePassword:(NSString *)password forAccount:(NSString *)account {
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: @"MyApp",
(__bridge id)kSecAttrAccount: account,
};
// Try to delete existing
SecItemDelete((__bridge CFDictionaryRef)query);
// Add new item
NSMutableDictionary *newQuery = [query mutableCopy];
newQuery[(__bridge id)kSecValueData] = passwordData;
newQuery[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleWhenUnlocked;
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)newQuery, NULL);
if (status != errSecSuccess) {
NSLog(@"Error saving password: %d", (int)status);
}
}
- (NSString *)passwordForAccount:(NSString *)account {
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: @"MyApp",
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecReturnData: @YES,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne
};
CFDataRef dataRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&dataRef);
if (status == errSecSuccess && dataRef != NULL) {
NSData *data = (__bridge_transfer NSData *)dataRef;
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
return nil;
}
Lines of Code Reduction: 50+ lines → 20 lines 🎉
Accessibility Type Migration
Critical Security Update : The default accessibility type (NULL) is highly insecure. Explicitly set accessibility type during migration.
// Set accessibility type globally (recommended)
#if TARGET_OS_IPHONE
[SAMKeychain setAccessibilityType:kSecAttrAccessibleWhenUnlocked];
#endif
// Now all future passwords use this accessibility type
[SAMKeychain setPassword:password forService:service account:account];
Recommended Accessibility Types:
kSecAttrAccessibleWhenUnlocked - Most apps (requires device unlock)
kSecAttrAccessibleAfterFirstUnlock - Background apps that need access
kSecAttrAccessibleWhenUnlockedThisDeviceOnly - Sensitive data (no iCloud sync)
Testing Your Migration
After migrating, verify keychain operations:
- (void)testKeychainMigration {
NSString *testService = @"MigrationTest";
NSString *testAccount = @"test@example.com";
NSString *testPassword = @"SecurePassword123";
// 1. Save password
NSError *error = nil;
BOOL success = [SAMKeychain setPassword:testPassword
forService:testService
account:testAccount
error:&error];
NSAssert(success, @"Failed to save: %@", error);
// 2. Retrieve password
NSString *retrieved = [SAMKeychain passwordForService:testService
account:testAccount
error:&error];
NSAssert([retrieved isEqualToString:testPassword], @"Password mismatch");
// 3. Delete password
success = [SAMKeychain deletePasswordForService:testService
account:testAccount
error:&error];
NSAssert(success, @"Failed to delete: %@", error);
// 4. Verify deletion
retrieved = [SAMKeychain passwordForService:testService
account:testAccount
error:&error];
NSAssert(retrieved == nil, @"Password should be deleted");
NSLog(@"✅ Migration test passed!");
}
Next Steps
Troubleshooting Common issues and debugging tips
FAQ Frequently asked questions