NSUserDefaults هو مخزن بيانات من نوع مفتاح-قيمة في iOS وwatchOS وtvOS وmacOS، مصمم لحفظ إعدادات وتهيئات التطبيق. يتم تخزين البيانات في ملف plist داخل sandbox التطبيق ويتمت مزامنتها تلقائياً مع iCloud عبر NSUbiquitousKeyValueStore. وفقاً للتوثيق الرسمي من Apple Developer, 2025، يدعم NSUserDefaults تخزين الأنواع البدائية: String وInt وBool وFloat وDouble وData وDate وArray وDictionary. تمت إعادة تسمية الفئة إلى UserDefaults بدءاً من Swift 3، لكن اسمه في Objective-C NSUserDefaults لا يزال واسع الاستخدام في قاعدة الأكواد وتوثيق Apple.
الخلاصة
NSUserDefaults (UserDefaults في Swift) هي آلية Apple المدمجة لتخزين أزواج المفتاح-القيمة بتنسيق plist. وهي متاحة على جميع منصات Apple: iOS وiPadOS وwatchOS وtvOS وmacOS. الغرض الرئيسي منها هو حفظ تفضيلات المستخدم وحالة الواجهة وعلامات التشغيل الأول والخيارات المحددة وغيرها من البيانات البسيطة التي تبقى بعد إعادة تشغيل التطبيق.
كل تطبيق iOS لديه sandbox معزول، ويتم تخزين NSUserDefaults في دليل Library/Preferences داخل هذا sandbox في ملف يحمل اسم Bundle Identifier. يحتوي ملف plist على أزواج مفتاح-قيمة حيث المفتاح هو سلسلة نصية والقيمة هي أحد الأنواع المدعومة. حجم الملف غير محدود، لكن Apple توصي بتخزين الإعدادات فقط في UserDefaults، وليس كميات كبيرة من البيانات.
بدءاً من iOS 8، بدأ NSUserDefaults في دعم App Groups — تخزين مشترك بين تطبيقات لنفس المطور وإضافاتها (widgets وتطبيقات watchOS المصاحبة). يستخدم لذلك المُهيئ init?(suiteName:) مع معرف App Group. يتيح ذلك، على سبيل المثال، لعنصر واجهة Today قراءة الإعدادات من التطبيق الرئيسي دون تكرار منطق الحفظ.
مادياً، يتم تخزين NSUserDefaults في ملف plist ثنائي في المسار: {Sandbox}/Library/Preferences/com.example.myapp.plist. يستخدم الملف تنسيق plist الثنائي (NSPropertyListBinaryFormat_v1_0) للاكتناز وسرعة القراءة. في macOS قد يكون الملف بتنسيق XML للتوافق. على عكس SharedPreferences في Android، يمكن لملفات plist الخاصة بـ UserDefaults احتواء هياكل متداخلة عبر Dictionary وArray.
nملفات NSUserDefaults غير مشفرة افتراضياً. يتم تخزين البيانات بنص واضح ويمكن قراءتها عند الوصول المادي للجهاز أو عبر النسخ الاحتياطي. لتخزين البيانات الحساسة (كلمات المرور والرموز ومفاتيح التشفير)، توصي Apple بشدة باستخدام Keychain، الذي يشفر البيانات تلقائياً على مستوى نظام التشغيل.
يعمل NSUserDefaults وفق مبدأ التخزين المؤقت في الذاكرة مع مزامنة دورية على القرص. عند أول وصول إلى المثيل القياسي UserDefaults.standard، يقوم النظام بتحميل ملف plist إلى ذاكرة الوصول العشوائي (RAM) على شكل Dictionary. جميع القراءات اللاحقة تتم من الذاكرة. الكتابة تتم أيضاً في الذاكرة أولاً، بينما تتم المزامنة على القرص بشكل دوري في خلفية النظام.
تستخدم عمليات الكتابة طريقة set(_:forKey:) التي تقبل قيمة اختيارية من نوع Any?. يمكن أن تكون القيمة nil — وتستخدم لإزالة مفتاح. للكتابة الفورية على القرص، كانت تُستخدم سابقاً طريقة synchronize()، لكن بدءاً من iOS 7 وOS X 10.9 لم تعد ضرورية — يقوم النظام بمزامنة البيانات تلقائياً على فترات منتظمة. أعلنت Apple رسمياً أن synchronize() زائدة عن الحاجة في توثيقها.
يستخدم NSUserDefaults نظام سجلات (نطاقات) لتنظيم البحث عن القيم. عندما يطلب التطبيق قيمة بواسطة مفتاح، يتحقق UserDefaults بالتسلسل من النطاقات بترتيب معين: أولاً NSArgumentDomain (وسائط سطر الأوامر)، ثم نطاق التطبيق (Application)، ثم NSGlobalDomain (إعدادات النظام)، ثم النطاقات الخاصة باللغة وأخيراً NSRegistrationDomain (القيم الافتراضية المسجلة عبر register(defaults:)).
import Foundation
// مثال UserDefaults القياسي
let defaults = UserDefaults.standard
// كتابة القيم
defaults.set("آنا بيتروفا", forKey: "username")
defaults.set(28, forKey: "age")
defaults.set(true, forKey: "isLoggedIn")
// تسجيل القيم الافتراضية
defaults.register(defaults: [
"theme": "system",
"fontSize": 14
])
// القراءة مع إرجاع القيمة الافتراضية
let theme = defaults.string(forKey: "theme") ?? "system"
let fontSize = defaults.integer(forKey: "fontSize")
نطاق NSRegistrationDomain هو نطاق برمجي موجود فقط في الذاكرة ولا يُحفظ على القرص. يُستخدم لتعيين القيم الافتراضية التي تعمل حتى يقوم التطبيق بكتابة قيمته الخاصة في نطاق التطبيق. يتيح ذلك إنشاء نقطة تهيئة واحدة للإعدادات الافتراضية يمكن تغييرها مركزياً أثناء مرحلة التطوير.
يوفر NSUserDefaults مجموعة من الطرق المكتوبة (typed methods) لقراءة وكتابة البيانات: string(forKey:) وinteger(forKey:) وbool(forKey:) وfloat(forKey:) وdouble(forKey:) وdata(forKey:) وarray(forKey:) وdictionary(forKey:) وobject(forKey:). كل طريقة قراءة لها طريقة كتابة مقابلة set(_:forKey:) مع تحديد تلقائي لنوع القيمة المخزنة. إصدار Swift من UserDefaults يستخدم كتابة قوية (strong typing)، لكن إصدار Objective-C يقبل ويعيد id.
| طريقة القراءة (Swift) | نوع البيانات | القيمة الافتراضية |
|---|---|---|
| string(forKey:) | String? | nil |
| integer(forKey:) | Int | 0 |
| bool(forKey:) | Bool | false |
| float(forKey:) | Float | 0.0 |
| double(forKey:) | Double | 0.0 |
| data(forKey:) | Data? | nil |
طريقة synchronize() في NSUserDefaults تفرض كتابة جميع التغييرات من الذاكرة إلى القرص. في الإصدارات المبكرة من iOS، كان يجب استدعاء هذه الطريقة بعد كل كتابة لضمان حفظ البيانات. بدءاً من iOS 7، يقوم النظام بمزامنة UserDefaults تلقائياً في خلفية النظام، وأعلنت Apple رسمياً أن synchronize() زائدة عن الحاجة. استدعاء هذه الطريقة لا يسبب خطأ لكنه لا يوفر ضمانات إضافية للحفظ.
لمراقبة التغييرات، يوفر NSUserDefaults إشعار UserDefaults.didChangeNotification وطريقة مراقبة KVO addObserver(_:forKeyPath:options:context:). في SwiftUI، يتوفر Property Wrapper @AppStorage الذي يقوم بمزامنة قيمة في UserDefaults تلقائياً مع تحديث واجهة المستخدم. يدعم @AppStorage نفس الأنواع التي يدعمها UserDefaults وهو الطريقة المفضلة للعمل مع الإعدادات في تطبيقات SwiftUI.
// مراقبة التغييرات عبر KVO
class SettingsViewModel: NSObject {
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?
) {
guard let keyPath else { return }
print("تم تغيير المفتاح: \(keyPath)")
}
}
// SwiftUI - AppStorage
struct SettingsView: View {
@AppStorage("theme") private var theme: String = "system"
var body: some View {
Picker("السمة", selection: $theme) {
Text("النظام").tag("system")
Text("فاتح").tag("light")
Text("داكن").tag("dark")
}
}
}
للعمل مع App Groups (تخزين مشترك بين التطبيق وإضافاته)، يُستخدم المُهيئ UserDefaults(suiteName:) مع معرف App Group. على سبيل المثال، «group.com.example.myapp». البيانات المكتوبة في هذا المثيل يمكن الوصول إليها من التطبيق الرئيسي وعنصر الواجهة وتطبيق watchOS المصاحب والإضافات الأخرى التابعة لنفس App Group. يتم تخزين كل مثيل suite في ملف plist منفصل.
على الرغم من سهولته وبساطته، NSUserDefaults ليس حلاً شاملاً للتخزين لجميع أنواع البيانات في iOS. اعتماداً على الحجم والأهمية ومتطلبات الأمان، توفر Apple عدة بدائل، كل منها مُحسَّن لحالة استخدام محددة.
| الحل | متى يُستخدم | القيود |
|---|---|---|
| NSUserDefaults | إعدادات الواجهة والتهيئة | غير مناسب للبيانات الكبيرة أو الأسرار |
| Keychain | كلمات المرور والرموز ومفاتيح التشفير | أكثر تعقيداً في الاستخدام، أبطأ |
| CoreData | بيانات منظمة مع علاقات | مفرط لـ 10–20 إعداداً |
| FileManager | المستندات والصور والبيانات الثنائية | يتطلب إدارة يدوية للملفات |
| CloudKit | مزامنة سحابية بين الأجهزة | يتطلب حساب iCloud واتصال شبكة |
Keychain هو التخزين الآمن من Apple للبيانات السرية. على عكس NSUserDefaults، جميع البيانات في Keychain مشفرة على مستوى نظام التشغيل باستخدام تشفير الأجهزة من Secure Enclave على الأجهزة المتوافقة. يتم قفل Keychain وفتحه تلقائياً مع الجهاز ويدعم مشاركة الوصول بين تطبيقات نفس المطور عبر Keychain Access Groups.
العيب الرئيسي لـ Keychain هو تعقيد واجهة برمجته. لحفظ سلسلة نصية ببساطة، تحتاج إلى إنشاء استعلام SecItemAdd مع تحديد السمات: الفئة (kSecClassGenericPassword) والخدمة (kSecAttrService) والحساب (kSecAttrAccount) والبيانات الفعلية (kSecValueData). لتبسيط العمل مع Keychain، توجد أغلفة من طرف ثالث مثل KeychainAccess وSwiftKeychainWrapper التي توفر واجهة مفتاح-قيمة مريحة مشابهة لـ UserDefaults.
لننظر في مثال عملي: حفظ واستعادة حالة الإعداد (شاشات الترحيب) في تطبيق iOS باستخدام NSUserDefaults. عند التشغيل الأول، يرى المستخدم شاشات الإعداد؛ بعد إكمالها، يتم حفظ علامة في UserDefaults. في مرات التشغيل اللاحقة، يتم تخطي الإعداد. لـ SwiftUI يُستخدم @AppStorage؛ لـ UIKit يتم الوصول المباشر إلى UserDefaults.standard.
لننشئ OnboardingManager يغلف العمل مع UserDefaults لتخزين حالة الإعداد. يوفر المدير خاصية isOnboardingCompleted للتحقق من الحالة وطريقة markOnboardingCompleted لتعيين العلامة. تم استخراج مفتاح التخزين في ثابت لمنع الأخطاء الإملائية. لاختبار الوحدة، يستخدم المدير بروتوكول UserDefaultsProtocol مما يتيح استبدال التخزين الحقيقي بـ MockUserDefaults.
class OnboardingManager {
private let defaults: UserDefaults
private let hasSeenKey = "has_seen_onboarding"
init(defaults: UserDefaults = .standard) {
self.defaults = defaults
}
var isOnboardingCompleted: Bool {
defaults.bool(forKey: hasSeenKey)
}
func markOnboardingCompleted() {
defaults.set(true, forKey: hasSeenKey)
}
func resetOnboarding() {
defaults.removeObject(forKey: hasSeenKey)
}
}
// الاستخدام في التطبيق
let onboardingManager = OnboardingManager()
if !onboardingManager.isOnboardingCompleted {
showOnboarding()
} else {
showMainScreen()
}
لتخزين إعدادات أكثر تعقيداً، مثل كائن Profile منظم، يُوصى باستخدام بروتوكول Codable وJSONEncoder/JSONDecoder. يتم تسلسل الكائن إلى Data عبر JSONEncoder، وحفظه عبر set(_:forKey:)، وعند القراءة يتم إلغاء التسلسل من Data إلى الكائن عبر JSONDecoder. يسمح هذا النهج بتخزين هياكل معقدة في UserDefaults دون فقدان أمان الأنواع.
struct UserProfile: Codable {
let name: String
let age: Int
let preferences: [String: String]
}
extension UserDefaults {
func save<T: Codable>(_ value: T, forKey key: String) {
if let data = try? JSONEncoder().encode(value) {
set(data, forKey: key)
}
}
func load<T: Codable>(_ type: T.Type, forKey key: String) -> T? {
guard let data = data(forKey: key) else { return nil }
return try? JSONDecoder().decode(type, from: data)
}
}
// الاستخدام
let profile = UserProfile(name: "آنا", age: 28, preferences: ["theme": "dark"])
UserDefaults.standard.save(profile, forKey: "user_profile")
let loaded = UserDefaults.standard.load(UserProfile.self, forKey: "user_profile")
من المهم تذكر أن NSUserDefaults غير مصمم لتخزين كميات كبيرة من البيانات. توصي Apple بالحد من البيانات المخزنة إلى بضع عشرات من الكيلوبايتات. لتخزين الكائنات الكبيرة (الصور والمستندات والنماذج المسلسلة)، استخدم FileManager مع دليل Documents أو CoreData. بالإضافة إلى ذلك، لا يدعم UserDefaults إصدارة مخطط البيانات — عند تغيير هيكل نموذج Codable، قد تفشل إزالة تسلسل البيانات القديمة، ويجب معالجة ذلك في كود التطبيق.
الأسئلة الشائعة
كلاهما مخازن مفتاح-قيمة، لكن NSUserDefaults يدعم أنواعاً أكثر (Data وDate وArray وDictionary) ويتم مزامنته تلقائياً مع iCloud. يخزن SharedPreferences البيانات بتنسيق XML، بينما NSUserDefaults بتنسيق plist. NSUserDefaults لديه نظام نطاقات مع بحث متسلسل، بينما SharedPreferences يستخدم هيكلاً مسطحاً بسيطاً بأسماء الملفات.
لا، NSUserDefaults يخزن البيانات بنص واضح بدون تشفير. لكلمات المرور والرموز ومفاتيح التشفير، استخدم Keychain الذي يشفر البيانات على مستوى Secure Enclave. يدعم Keychain أيضاً سمات الوصول مثل المصادقة البيومترية (Face ID / Touch ID) قبل قراءة السر.
للمزامنة بين أجهزة مستخدم واحد، استخدم NSUbiquitousKeyValueStore — مخزن المفتاح-القيمة السحابي من iCloud. البيانات التي تُكتب في هذه الخدمة على جهاز واحد تظهر تلقائياً على جميع الأجهزة الأخرى بنفس حساب iCloud. السعة القصوى: 1 ميغابايت لكل تطبيق، 1024 مفتاحاً.
لحذف جميع البيانات، استدعِ طريقة removePersistentDomain(forName:) مع Bundle Identifier للتطبيق. لحذف قيم فردية، استخدم removeObject(forKey:). لإعادة تعيين كامل للإعدادات: UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!). جميع عمليات الحذف تُطبق فوراً على ذاكرة التخزين المؤقت.
Apple لا تضع حداً صارماً لحجم NSUserDefaults، لكن يُوصى بعدم تجاوز 100 كيلوبايت للحجم الإجمالي لجميع البيانات المخزنة. للأحجام الأكبر، استخدم CoreData أو FileManager. عند تخزين أكثر من 1 ميغابايت من البيانات، قد ينخفض أداء القراءة عند تشغيل التطبيق بشكل ملحوظ.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا