iOS Swift: UserDefaults vs Keychain ต่างกันยังไง
หลายคนที่เริ่มพัฒนา iOS Application มักจะคุ้นเคยกับการใช้ UserDefaults ในการเก็บข้อมูลภายในเครื่อง…
iOS Swift: UserDefaults vs Keychain ต่างกันยังไง

หลายคนที่เริ่มพัฒนา iOS Application มักจะคุ้นเคยกับการใช้ UserDefaults ในการเก็บข้อมูลภายในเครื่อง เพราะใช้งานง่ายและเขียนโค้ดเพียงไม่กี่บรรทัดก็สามารถบันทึกข้อมูลได้ทันที ไม่ว่าจะเป็น Theme ของแอป, Setting ต่าง ๆ หรือสถานะ Login
แต่เมื่อเริ่มต้องจัดเก็บข้อมูลที่มีความสำคัญมากขึ้น เช่น Access Token, Refresh Token หรือ Password หลายคนก็มักจะเริ่มได้ยินชื่อของ Keychain ผ่าน ๆ มาบ้าง และเกิดคำถามว่า ข้อมูลแบบไหนควรเก็บใน UserDefaults และข้อมูลแบบไหนควรเก็บใน Keychain
เพราะถึงแม้ทั้งสองอย่างจะใช้สำหรับ “เก็บข้อมูลในเครื่อง” เหมือนกัน แต่จริง ๆ แล้วจุดประสงค์ในการใช้งานแตกต่างกันค่อนข้างมาก
ในบทความนี้ เราจะมาดูกันว่า UserDefaults และ Keychain ต่างกันยังไง แต่ละตัวเหมาะกับการเก็บข้อมูลประเภทไหน และควรเลือกใช้อย่างไรใน iOS Application จริง
ถ้าขี้เกียจอ่านก็เลื่อนไปล่างสุดมี สรุป และ Git repo
UserDefaults
UserDefaults คือระบบจัดเก็บข้อมูลขนาดเล็กภายในเครื่องที่ Apple เตรียมไว้ให้สำหรับเก็บข้อมูลประเภท Settings ต่าง ๆ ของแอป โดยข้อมูลจะยังคงอยู่แม้ผู้ใช้จะปิดแอปหรือ Restart เครื่อง และจะหายไปเมื่อผู้ใช้ลบ Application ทิ้ง
- เหมาะกับข้อมูลทั่วไปที่ไม่ Sensitive
- ข้อมูลยังอยู่แม้ปิดแอปหรือ Restart เครื่อง
- ข้อมูลจะหายเมื่อผู้ใช้ลบแอปออกจากเครื่อง
จุดเด่นของ UserDefaults คือใช้งานง่ายมาก เหมาะสำหรับเก็บข้อมูลที่ไม่ซับซ้อนและไม่ใช่ข้อมูลสำคัญด้านความปลอดภัย เช่น
- Theme ของแอป (Dark / Light Mode)
- ภาษาที่ผู้ใช้เลือกภายในแอป (ไม่ได้เปลี่ยนจาก System Settings)
- สถานะเปิด Tutorial ครั้งแรก หรือการแสดงหน้าจอ Policy ให้ยอมรับเมื่อเข้าใช้งานครั้งแรก
- Setting ทั่วไปของแอป
ข้อมูลที่สามารถเก็บใน UserDefaults ได้จะเป็นข้อมูลประเภทพื้นฐาน (Property List Types) เช่น
-
String-Int-Bool-Double-Array-Dictionary-Data
ตัวอย่างการบันทึก (Set)
การบันทึกข้อมูลลงใน UserDefaults ใช้สำหรับเก็บค่าขนาดเล็กที่ต้องการใช้งานซ้ำภายในแอป เช่น theme, language หรือค่าการตั้งค่าต่าง ๆ ของผู้ใช้งาน โดยข้อมูลจะถูกจัดเก็บแบบ Key-Value
ทุกครั้งที่บันทึกข้อมูล จำเป็นต้องกำหนด Key เพื่อใช้เป็นตัวอ้างอิงในการอ่านค่ากลับมาในภายหลัง
UserDefaults.standard.set("dark", forKey: "theme") // String
UserDefaults.standard.set(18, forKey: "fontSize") // Int
UserDefaults.standard.set(true, forKey: "isFirstLaunch") // Bool
UserDefaults.standard.set(1.25, forKey: "playbackSpeed") // Double
UserDefaults.standard.set(["Swift", "iOS"], forKey: "favoriteTopics") // Array
UserDefaults.standard.set(["name": "Alice", "role": "Developer"],
forKey: "userProfile") // Dictionary
let data = "Hello".data(using: .utf8)
UserDefaults.standard.set(data, forKey: "rawData") // Data
ตัวอย่างการอ่านค่า (Read)
การอ่านค่ากลับมาใช้งาน โดยต้องใช้ Key เดียวกันกับตอนที่บันทึกข้อมูลไว้เสมอ เช่น หาก Save ค่าไว้ด้วย "theme" ตอนอ่านค่าก็ต้องใช้ Key นี้เช่นกัน ไม่เช่นนั้นจะไม่สามารถดึงข้อมูลเดิมกลับมาได้
สำหรับบางประเภทข้อมูล เช่น String หรือ Array ค่าที่อ่านกลับมาอาจเป็น Optional ได้ หากยังไม่เคยมีการบันทึกข้อมูลไว้ก่อน จึงควรตรวจสอบค่าก่อนนำไปใช้งาน
let theme: String? = UserDefaults.standard.string(forKey: "theme")
let fontSize: Int = UserDefaults.standard.integer(forKey: "fontSize")
let isFirstLaunch: Bool = UserDefaults.standard.bool(forKey: "isFirstLaunch")
let playbackSpeed: Double = UserDefaults.standard.double(forKey: "playbackSpeed")
let favoriteTopics: [String]? = UserDefaults.standard.stringArray(forKey: "favoriteTopics")
let userProfile: [String: Any]? = UserDefaults.standard.dictionary(forKey: "userProfile")
let rawData: Data? = UserDefaults.standard.data(forKey: "rawData")
ตัวอย่างการลบค่า (Remove)
การลบค่าก็เช่นกัน ต้องใช้ Key เดียวกันกับตอนที่บันทึกข้อมูลไว้เสมอ
// Remove String
UserDefaults.standard.removeObject(forKey: "theme")
// Remove Int
UserDefaults.standard.removeObject(forKey: "fontSize")
// Remove Bool
UserDefaults.standard.removeObject(forKey: "isFirstLaunch")
// Remove Double
UserDefaults.standard.removeObject(forKey: "playbackSpeed")
// Remove Array
UserDefaults.standard.removeObject(forKey: "favoriteTopics")
// Remove Dictionary
UserDefaults.standard.removeObject(forKey: "userProfile")
// Remove Data
UserDefaults.standard.removeObject(forKey: "rawData")
แม้ UserDefaults จะสะดวกและใช้งานง่าย แต่สิ่งสำคัญที่ต้องเข้าใจคือ ข้อมูลในนั้นไม่ได้ถูกออกแบบมาเพื่อเก็บข้อมูล Sensitive เช่น Password หรือ Access Token
ออกแบบ UserDefaultsManager
หลังจากเข้าใจพื้นฐานของ UserDefaults แล้ว สิ่งสำคัญต่อมาคือการออกแบบโครงสร้างการใช้งานให้เหมาะสมในการเรียกใช้งาน
หลายคนเริ่มต้นด้วยการเรียก UserDefaults.standard ตรง ๆ ตามจุดต่าง ๆ ของแอป ซึ่งอาจดูสะดวกในช่วงแรก แต่เมื่อโปรเจกต์เริ่มใหญ่ขึ้น วิธีนี้มักสร้างปัญหาตามมา เช่น
- String Key กระจัดกระจายทั่วโปรเจกต์ และเวลาเปลี่ยนชื่อ Key ทำให้ยากต่อการตามแก้ไขทีละจุด (บางคนบอก search แล้วใช้ replace ก็ได้ เอาที่สะดวกครับ)
- Logic การจัดเก็บข้อมูลกระจายหลายจุด เช่น บางหน้าบันทึกค่าเอง บางหน้าลบค่าเอง หรือบางหน้าอ่านค่าโดยตรง ทำให้พฤติกรรมไม่สอดคล้องกัน และเพิ่มโอกาสเกิด Bug จากการจัดการข้อมูลคนละรูปแบบ
- เวลาข้อมูลผิดหรือค่าไม่ตรงกัน จะตามหาสาเหตุได้ยาก เพราะไม่รู้ว่าข้อมูลถูกแก้ไขจากจุดไหนของระบบบ้าง
แนวทางที่นิยม คือสร้าง Layer กลางสำหรับจัดการ Preferences และ Storage แยกออกจาก Business Logic ให้ชัดเจน
UserDefaultsManagerProtocol และ UserDefaultsManager (Data Layer)
UserDefaultsManagerProtocol เป็น Protocol สำหรับกำหนดความสามารถของ storage layer โดย Protocol นี้ทำหน้าที่เป็น “Interface” หรือ “abstract” สำหรับอ่าน, เขียน และลบข้อมูล
ข้อสำคัญคือส่วนอื่นของแอปจะไม่ต้องรู้ว่าข้างในใช้ UserDefaults จริง หรืออาจเปลี่ยนไปใช้ storage แบบอื่นในอนาคตก็ได้
UserDefaultsManager คือ implementation จริงของ storage layer โดย Layer นี้มีหน้าที่คุยกับ UserDefaults ของ iOS โดยตรง
protocol UserDefaultsManagerProtocol {
func bool(forKey key: String) -> Bool
func string(forKey key: String) -> String?
func set(_ value: Bool, forKey key: String)
func set(_ value: String, forKey key: String)
func remove(forKey key: String)
}
final class UserDefaultsManager: UserDefaultsManagerProtocol {
private let userDefaults: UserDefaults
init(userDefaults: UserDefaults = .standard) {
self.userDefaults = userDefaults
}
func bool(forKey key: String) -> Bool {
userDefaults.bool(forKey: key)
}
func string(forKey key: String) -> String? {
userDefaults.string(forKey: key)
}
func set(_ value: Bool, forKey key: String) {
userDefaults.set(value, forKey: key)
}
func set(_ value: String, forKey key: String) {
userDefaults.set(value, forKey: key)
}
func remove(forKey key: String) {
userDefaults.removeObject(forKey: key)
}
}
จุดสำคัญคือเรา “ซ่อน” UserDefaults.standard ไว้ใน Manager อีกชั้นหนึ่ง ทำให้ส่วนอื่นของแอปไม่ต้องเรียก UserDefaults ตรง ๆ
AppPreferencesProtocol (Domain Layer)
AppPreferencesProtocol หลังจากมี storage layer แล้ว ขั้นต่อไปคือสร้าง Business Layer ซึ่ง Layer นี้สำคัญมาก เพราะเป็นจุดที่เราเริ่ม “แปลง” จาก storage key ให้กลายเป็น property ที่มีความหมายในเชิง business
AppPreferences ทำหน้าที่เชื่อมระหว่าง Business Logic กับ Storage Layer จุดสำคัญของ Layer นี้คือการรวม key string ไว้ในที่เดียว ช่วยลดปัญหา hard-coded string กระจายทั่วโปรเจกต์
จริง ๆ AppPreferences อยู่ฝั่ง Data Layer เพราะมันเป็น Implementation ของ AppPreferencesProtocol แต่เขียนรวมไว้ตรงนี้เพื่อให้เข้าใจง่าย ถ้าไม่ได้แคร์อะไรมากก็เอาไว้ใน Domain นั้นแหละ
protocol AppPreferencesProtocol {
var isLoggedIn: Bool { get set }
}
final class AppPreferences: AppPreferencesProtocol {
private enum Keys {
static let appTheme = "appTheme"
static let selectedLanguage = "selectedLanguage"
static let hasSeenTutorial = "hasSeenTutorial"
static let hasAcceptedPolicy = "hasAcceptedPolicy"
static let isLoggedIn = "isLoggedIn"
}
private let userDefaultsManager: UserDefaultsManagerProtocol
init(userDefaultsManager: UserDefaultsManagerProtocol = UserDefaultsManager()) {
self.userDefaultsManager = userDefaultsManager
}
var isLoggedIn: Bool {
get {
userDefaultsManager.bool(forKey: Keys.isLoggedIn)
}
set {
userDefaultsManager.set(newValue, forKey: Keys.isLoggedIn)
}
}
}
ViewModel (Presentation Logic / Business Logic)
- ViewModel ไม่จำเป็นต้องรู้ชื่อ Key
- ViewModel ไม่จำเป็นต้องรู้ว่า Store ที่เก็บคือ UserDefaults รึเปล่า
- ViewModel มีหน้าที่เรียกใช้อย่างเดียว
@MainActor
final class LoginViewModel {
private let appPreferences: AppPreferencesProtocol
init(appPreferences: AppPreferencesProtocol = AppPreferences()) {
self.appPreferences = appPreferences
}
func loginSuccess() {
appPreferences.isLoggedIn = true
}
func checkLoginStatus() -> Bool {
appPreferences.isLoggedIn
}
func logout() {
appPreferences.isLoggedIn = false
}
}
โครงสร้างแบบเข้าใจง่าย MVVM
Scenes/Login
- AuthRootView
- ViewModel
Services/Preferences
- AppPreferencesProtocol
- AppPreferences
Storages/UserDefaults
- UserDefaultsManagerProtocol
- UserDefaultsManager
Keychain
Keychain คือระบบ Secure Storage ของ Apple สำหรับจัดเก็บข้อมูลสำคัญ (Sensitive Data) ที่ต้องการความปลอดภัยสูง แตกต่างจาก UserDefaults ที่เหมาะกับการเก็บข้อมูลทั่วไปของแอป
Keychain จะให้ความสำคัญกับ “ความปลอดภัยของข้อมูล” เป็นหลัก ข้อมูลที่ถูกเก็บใน Keychain จะถูกเข้ารหัส (Encrypted) ภายในระบบของ iOS เพื่อช่วยป้องกันการเข้าถึงจากภายนอก
ภายใน Keychain ข้อมูลจะถูกจัดเก็บในรูปแบบ Data เป็นหลัก ดังนั้นหากต้องการเก็บข้อมูลประเภท String, Array, Dictionaryหรือ Model ต่าง ๆ จำเป็นต้องแปลงเป็น Data ก่อนเสมอ เช่น การใช้ String.data(using: .utf8) สำหรับแปลงข้อความเป็น Data ก่อนนำไปบันทึกใน Keychain ผ่าน kSecValueData
ตัวอย่างข้อมูลที่ควรเก็บใน Keychain เช่น
- Access Token
- Refresh Token
- Password
- API Key หรือ Secret ที่ได้รับจาก Backend Service เพื่อนำไปใช้ต่อ Third-party Service
- PIN Code หรือข้อมูล Authentication ภายในแอป
- ข้อมูลสำคัญที่เกี่ยวข้องกับตัวตนหรือบัญชีของผู้ใช้งาน เช่น เลขบัตรประชาชน เป็นต้น
ข้อมูลเหล่านี้ถือเป็น Sensitive Data เพราะหากรั่วไหล อาจส่งผลต่อความปลอดภัยของบัญชีผู้ใช้งานได้
Keychain Query คืออะไร
การทำงานของ Keychain จะอ้างอิงผ่าน Dictionary ที่เรียกว่า Query ซึ่งใช้สำหรับกำหนดรายละเอียดของข้อมูลที่ต้องการจัดการ เช่น ประเภทข้อมูล, Key ที่ใช้อ้างอิง, ข้อมูลที่ต้องการจัดเก็บ หรือเงื่อนไขในการค้นหา
kSecClass ใช้ระบุประเภทของข้อมูลที่จัดเก็บภายใน Keychain ค่าที่ใช้งานบ่อย
-
*kSecClassGenericPassword*ใช้เก็บข้อมูลทั่วไป เช่น Access Token, Refresh Token, Password หรือ API Key เป็นประเภทที่ใช้งานบ่อยที่สุดใน iOS App ทั่วไป
*kSecClassInternetPassword*ใช้เก็บ Password ที่เกี่ยวข้องกับ Internet Service หรือ Server*kSecClassCertificate*ใช้เก็บ Certificate ต่าง ๆ*kSecClassKey*ใช้เก็บ Public Key หรือ Private Key*kSecClassIdentity*ใช้เก็บข้อมูลประเภท Identity ที่รวม Certificate และ Private Key เข้าด้วยกัน
kSecAttrAccount ใช้เป็น Identifier หรือ Key สำหรับอ้างอิงข้อมูลภายใน Keychain
kSecValueData ใช้เก็บข้อมูลจริงภายใน Keychain โดยค่าจะอยู่ในรูปแบบ Data
kSecReturnData ใช้กำหนดว่าต้องการให้ Keychain คืนข้อมูลกลับมาในรูปแบบ Data หรือไม่ โดยมักใช้ตอน Read ข้อมูล
kSecMatchLimit ใช้กำหนดจำนวนข้อมูลที่ต้องการค้นหา
kSecMatchLimitOneคืนข้อมูลเพียง 1 รายการkSecMatchLimitAllคืนข้อมูลทั้งหมดที่ตรงกับเงื่อนไข
kSecAttrAccessible ใช้กำหนดระดับการเข้าถึงข้อมูล (Accessibility Level) ว่าข้อมูลจะสามารถถูกอ่านได้ในสถานะใดของเครื่อง
kSecAttrAccessibleWhenUnlockedอ่านข้อมูลได้เฉพาะตอนเครื่องถูกปลดล็อก
kSecAttrAccessibleWhenUnlockedThisDeviceOnlyอ่านข้อมูลได้เฉพาะตอนเครื่องถูกปลดล็อก แต่ข้อมูลจะอยู่เฉพาะเครื่องนี้ ไม่ข้ามไปเครื่องอื่นแม้ใช้ Apple ID เดียวกัน
-
kSecAttrAccessibleAfterFirstUnlockหลังจากผู้ใช้ปลดล็อกเครื่องครั้งแรกหลังเปิดเครื่อง แอปจะสามารถอ่านข้อมูลได้ต่อแม้เครื่องจะถูกล็อกอยู่
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly- อ่านข้อมูลได้หลังจากปลดล็อกเครื่องครั้งแรก และข้อมูลจะอยู่เฉพาะเครื่องนี้ ไม่ถูกย้ายไปเครื่องอื่น
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnlyอ่านข้อมูลได้เฉพาะตอนเครื่องถูกปลดล็อก และเครื่องต้องมีการตั้ง Passcode ไว้เท่านั้น หากผู้ใช้ปิด Passcode ข้อมูลใน Keychain จะถูกลบทันที อีกทั้งข้อมูลจะอยู่เฉพาะเครื่องนี้เท่านั้น
ข้อมูลบางประเภทใน Keychain สามารถคงอยู่ได้แม้ผู้ใช้ลบแอปออกจากเครื่องแล้วติดตั้งใหม่ แตกต่างจากUserDefaults ที่จะถูกลบทันทีเมื่อแอปถูกถอนการติดตั้ง อย่างไรก็ตาม
ตัวอย่างเช่น ในระบบ Login ของแอป เรามักจะได้รับ Access Token และ Refresh Token กลับมาหลังจากผู้ใช้เข้าสู่ระบบสำเร็จ ซึ่งข้อมูลเหล่านี้ไม่ควรถูกเก็บไว้ใน UserDefaults เพราะหากข้อมูลรั่วไหลอาจส่งผลต่อความปลอดภัยของบัญชีผู้ใช้ได้
เมื่อผู้ใช้ Logout ก็ควรลบการลบ
Access TokenและRefresh TokenออกจากKeychainทุกครั้ง
ตัวอย่างบันทึกข้อมูล (Set)
การบันทึกข้อมูลใน Keychain ใช้สำหรับจัดเก็บข้อมูลสำคัญ เช่น Access Token และ Refresh Tokenโดยทุกครั้งที่บันทึกข้อมูล จำเป็นต้องกำหนด Key เพื่อใช้เป็นตัวอ้างอิงในการอ่านหรือจัดการข้อมูลในภายหลัง
ในตัวอย่างนี้ String จะถูกแปลงเป็น Data ก่อนผ่าน value.data(using: .utf8) เนื่องจาก Keychain จะจัดเก็บข้อมูลผ่าน kSecValueData
หลังจากสร้าง query เรียบร้อยแล้ว จะใช้ SecItemAdd สำหรับเพิ่มข้อมูลใหม่เข้าไปใน Keychain
func saveToken(
key: String,
value: String
) {
guard let data = value.data(using: .utf8) else {
return
}
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data
]
SecItemAdd(query as CFDictionary, nil)
}
saveToken(
key: "accessToken",
value: "fake_token_12345_abc"
)
หากมีข้อมูลเดิมอยู่แล้ว การบันทึกข้อมูลซ้ำด้วย Key เดิมผ่าน SecItemAdd จะทำให้ระบบคืนค่า Error ประเภท errSecDuplicateItem กลับมา เนื่องจาก Keychain จะไม่เขียนทับข้อมูลเดิมให้อัตโนมัติ ดังนั้นในหลายกรณีจึงนิยมใช้ SecItemUpdate เพื่ออัปเดตข้อมูลแทน
ตัวอย่างการอ่านข้อมูล (Read)
การอ่านค่าใช้งานจาก Keychain จำเป็นต้องใช้ Key เดียวกันกับตอนที่บันทึกข้อมูลไว้ เช่น หาก Save Access Token ด้วย Key "access_token" ตอนอ่านค่าก็ต้องใช้ Key นี้เช่นกัน ไม่เช่นนั้นจะไม่สามารถดึงข้อมูลเดิมกลับมาได้
func readToken(
key: String
) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(
query as CFDictionary,
&result
)
guard
status == errSecSuccess,
let data: Data = result as? Data,
let token: String = String(data: data, encoding: .utf8)
else {
return nil
}
return token
}
if let accessToken: String = readToken(key: "accessToken") {
print("Access Token:", accessToken)
} else {
print("ไม่พบ Access Token")
}
ข้อมูลที่อ่านกลับมาจาก Keychain มักอยู่ในรูปแบบ Optional เพราะอาจยังไม่เคยมีการบันทึกข้อมูลไว้ก่อน
ตัวอย่างการอัปเดตข้อมูล (Update)
การอัปเดตข้อมูลใน Keychain ใช้เมื่อเราต้องการเปลี่ยนค่าของข้อมูลเดิม เช่น การได้รับ Access Token ใหม่หลัง Refresh Token สำเร็จ โดยระบบจะค้นหาข้อมูลจาก Key เดิม แล้วแทนที่ค่าภายในด้วยข้อมูลใหม่
SecItemUpdate จะค้นหารายการเดิมจาก query แล้วอัปเดตค่าตาม attributes ที่กำหนดไว้ ในตัวอย่างนี้คือการเปลี่ยนข้อมูลใน kSecValueData ให้เป็น Token ค่าใหม่
func updateToken(
key: String,
newValue: String
) {
guard let data: Data = newValue.data(using: .utf8) else {
return
}
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key
]
let attributes: [String: Any] = [
kSecValueData as String: data
]
let status: OSStatus = SecItemUpdate(
query as CFDictionary,
attributes as CFDictionary
)
if status == errSecSuccess {
print("Update token success")
} else {
print("Update token failed:", status)
}
}
การใช้ Update จะช่วยหลีกเลี่ยงปัญหา errSecDuplicateItem ที่อาจเกิดขึ้นเมื่อใช้ SecItemAdd ซ้ำด้วย Key เดิม แต่ถ้าไม่เคยมีข้อมูลนั้นอยู่ใน Keychain มาก่อน SecItemUpdate จะอัปเดตไม่สำเร็จ เพราะไม่มีรายการเดิมให้แก้ไข
ตัวอย่างการลบข้อมูล (Delete)
การลบข้อมูลใน Keychain ใช้เมื่อข้อมูลนั้นไม่จำเป็นต้องใช้งานต่อแล้ว เช่น ผู้ใช้ Logout ออกจากระบบ แอปก็ควรลบ Access Token และ Refresh Token ออกจาก Keychain ทันที เพื่อป้องกันการนำ Token เดิมกลับมาใช้งาน
SecItemDelete จะลบข้อมูลใน Keychain ตามเงื่อนไขที่ระบุไว้ใน query โดยในตัวอย่างนี้จะลบข้อมูลประเภท kSecClassGenericPassword ที่มี kSecAttrAccount ตรงกับ key ที่ส่งเข้ามา
func deleteToken(
key: String
) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key
]
let status: OSStatus = SecItemDelete(
query as CFDictionary
)
if status == errSecSuccess {
print("Delete token success")
} else {
print("Delete token failed:", status)
}
}
หากลบสำเร็จ status จะเป็น errSecSuccess แต่ถ้าไม่พบข้อมูลตาม Key ที่กำหนดไว้ การลบจะไม่สำเร็จและคืนค่า status อื่นกลับมาแทน เช่น ไม่มีข้อมูลอยู่ใน Keychain แล้ว
สรุป
UserDefaults และ Keychain เป็นระบบจัดเก็บข้อมูลภายในเครื่อง แต่ทั้งสองอย่างถูกออกแบบมาใช้งานคนละจุดประสงค์
UserDefaults เหมาะสำหรับข้อมูลทั่วไปของแอปที่ไม่เกี่ยวข้องกับความปลอดภัย เช่น Settings, Theme หรือ State ต่าง ๆ ของระบบ เพราะใช้งานง่ายและเข้าถึงข้อมูลได้สะดวก
Keychain ถูกออกแบบมาสำหรับจัดเก็บข้อมูลสำคัญ (Sensitive Data) โดยเฉพาะ เช่น Access Token, Refresh Token หรือ Password ซึ่งข้อมูลจะถูกเข้ารหัสและมีระบบควบคุมการเข้าถึงที่ปลอดภัยกว่า
สุดท้ายนี้ บทความนี้เป็นการสรุปจากประสบการณ์การพัฒนา iOS Application และความเข้าใจส่วนตัวของผมเอง เพื่อใช้เป็นการทบทวนความรู้ และแชร์แนวทางการเลือกใช้งาน UserDefaults และ Keychain
Github Repository
บทความอื่น ๆ
- Back to Basic Swift Foundation ที่ควรรู้
- แชร์การสร้าง Grocery App ด้วย SwiftUI + MVVM + Clean Architecture
- Swift Concurrency เปลี่ยน CallBack ให้กลายเป็น Code ที่อ่านง่ายด้วย Async/Await
- เปลี่ยน func Callback เดิมให้เป็น Async/Await ด้วย Continuation ใน Swift
- Structured Concurrency ใน Swift จัดการ Async Tasks อย่างเป็นระบบด้วย async/await
- สอน SwiftUI: Back to Basic SwiftUI Layout Foundation ที่ควรรู้ — PART 1
- สอน SwiftUI: Back to Basic SwiftUI Layout Foundation ที่ควรรู้ — PART 2
- สอน SwiftUI: Back to Basic SwiftUI Layout Foundation ที่ควรรู้ — PART 3
- iOS Swift: UserDefaults vs Keychain ต่างกันยังไง
메타데이터
- post_id
- df297dce3f18
- slug
- ios-swift-userdefaults-vs-keychain-ต่างกันยังไง-df297dce3f18
- url
- https://medium.com/@punyawat.codercamp/ios-swift-userdefaults-vs-keychain-%E0%B8%95%E0%B9%88%E0%B8%B2%E0%B8%87%E0%B8%81%E0%B8%B1%E0%B8%99%E0%B8%A2%E0%B8%B1%E0%B8%87%E0%B9%84%E0%B8%87-df297dce3f18
- canonical_url
- https://medium.com/@punyawat.codercamp/ios-swift-userdefaults-vs-keychain-%E0%B8%95%E0%B9%88%E0%B8%B2%E0%B8%87%E0%B8%81%E0%B8%B1%E0%B8%99%E0%B8%A2%E0%B8%B1%E0%B8%87%E0%B9%84%E0%B8%87-df297dce3f18
- author_url
- https://medium.com/@punyawat.codercamp
- status
- ok
- fetched_at
- 2026-06-09 15:37:30