Singleton — ما هو، مثيل واحد للفئة في iOS و Android

المؤلف: IT Sectr نُشر: 2026-02-17 وقت القراءة: 8 دق

Singleton — نمط إنشائي يضمن مثيلاً واحداً للفئة ويوفر نقطة وصول عامة إليها. Singleton يُستخدم على نطاق واسع في تطوير التطبيقات المحمولة للموارد المشتركة: عملاء الشبكة، قواعد البيانات، مديري الإعدادات. تم وصف النمط في كتاب GoF الكلاسيكي (1994) ولا يزال واحداً من أكثر الأنماط شهرة. المزيد على Refactoring Guru: Singleton.

النقاط الرئيسية

  • Singleton — يضمن مثيلاً واحداً للفئة لكل تطبيق
  • نقطة وصول عامة — خاصية ثابتة shared أو companion object
  • Thread safety — يتطلب المزامنة للعمل بشكل صحيح في البيئات متعددة الخيوط
  • النقد — Singleton يعقد الاختبار وينشئ تبعيات خفية
  • البدائل — Dependency Injection، Service Locator لاستبدال Singleton

ما هو Singleton: جوهر نمط المفرد؟

Singleton — نمط تصميم إنشائي وصفته GoF (Gang of Four) في 1994. يحل النمط مشكلتين: يقيد إنشاء مثيل الفئة بكائن واحد ويوفر وصولاً عاماً إلى هذا الكائن. Singleton مفيد للموارد التي يجب أن تكون فريدة: مصانع الجلسات، مخابئ الصور، مديري اتصال قواعد البيانات، عملاء Crashlytics أو Analytics.

تنفيذ Singleton يتطلب مُنشئاً خاصاً (يمنع الإنشاء الخارجي)، حقلاً ثابتاً بالمثيل الوحيد، وطريقة وصول ثابتة (shared، instance، getInstance). يستدعي العملاء Singleton.shared.method() دون القلق بشأن إنشاء الكائن. النمط شائع في iOS و Android: URLSession.shared، UserDefaults.standard، FirebaseApp.sharedInstance — كلها Singletons. ومع ذلك، الاستخدام المفرط لـ Singleton يؤدي إلى النمط المعاكس Global State.

مشاكل Singleton — تبعيات خفية (الفئات تعتمد ضمنياً على كائن Singleton)، تعقيد الاختبار (لا يمكن استبدال المثيل في الاختبارات دون جهد إضافي)، انتهاك مبدأ المسؤولية الواحدة (Singleton يدير كلاً من مثيله ومنطق الأعمال). تطوير التطبيقات المحمولة الحديث يفضل DI (Dagger، Hilt، Swinject) لإدارة المثيلات الفريدة — حاوية DI تنشئ الكائن مرة واحدة وتحقنه عبر المُنشئ.

Singleton في iOS باستخدام Swift: shared والخصائص الثابتة

Swift Singleton يُنفذ عبر خاصية ثابتة shared مع مُهيئ خاص. منذ Swift 3، التهيئة البطيئة للخصائص الثابتة مضمونة لتكون آمنة للخيوط — يضيف المترجم تلقائياً المزامنة عبر dispatch_once. يكفي تعريف static let shared = Class() وجعل init() خاصاً. Swift لا يتطلب مزامنة إضافية للوصول أحادي الخيط بعد التهيئة.

swift
final class NetworkManager {
    // Singleton آمن للخيوط
    static let shared = NetworkManager()

    private init() {
        URLSessionConfiguration.default.timeoutIntervalForRequest = 30
    }

    private var cache = NSCache<NSString, NSData>()

    func fetchData(from url: URL) async throws -> Data {
        let key = url.absoluteString as NSString
        if let cached = cache.object(forKey: key) {
            return cached as Data
        }
        let (data, _) = try await URLSession.shared.data(from: url)
        cache.setObject(data as NSData, forKey: key)
        return data
    }
}

// الاستخدام
let data = try await NetworkManager.shared.fetchData(from: url)

Apple Singleton — العديد من كائنات iOS SDK تستخدم Singleton: UIApplication.shared، UIScreen.main، FileManager.default، NotificationCenter.default، UserDefaults.standard. تستخدم Apple Singleton للخدمات الفريدة فيزيائياً (شاشة واحدة، تطبيق واحد). ينسخ المطورون هذا النمط لخدماتهم الخاصة. في SwiftUI، يتم استبدال الوصول العام إلى Singleton بـ Environment و @EnvironmentObject، مما يحسن قابلية الاختبار.

Singleton في Android باستخدام Kotlin: companion object و object

Kotlin Singleton — الطريقة الأبسط: الكلمة المفتاحية object تعلن فئة مفردة مع تهيئة بطيئة عند أول وصول. Kotlin object آمن للخيوط ولا يتطلب مزامنة إضافية. إذا كان Singleton مع معاملات المُنشئ مطلوباً، يُستخدم companion object مع مفوض lazy. في Android، Singleton غالباً ما يكون ضرورياً لسياق Application والخدمات التي تُهيأ عبر Application.onCreate().

kotlin
// الخيار 1: object — Singleton بسيط بدون معاملات
object AppPreferences {
    private val prefs = Application.instance
        .getSharedPreferences("app", Context.MODE_PRIVATE)

    var isFirstLaunch: Boolean
        get() = prefs.getBoolean("first_launch", true)
        set(value) = prefs.edit { putBoolean("first_launch", value) }
}

// الخيار 2: companion object — Singleton مع معاملات
class ApiClient private constructor(baseUrl: String) {
    companion object {
        @Volatile
        private var instance: ApiClient? = null

        fun getInstance(baseUrl: String): ApiClient {
            return instance ?: this.synchronized {
                instance ?: ApiClient(baseUrl).also { instance = it }
            }
        }
    }

    fun request(endpoint: String): String { /* ... */ }
}

Singleton في Android SDK — العديد من خدمات النظام في Android تنفذ Singleton: context.getSystemService()، Room.databaseBuilder()، Retrofit.Builder(). تشمل الأمثلة SharedPreferences، MediaPlayer، AudioManager. في تطبيقات Android، يُستخدم Singleton غالباً للمستودعات والمديرين والمصانع. توصي Google باستبدال Singleton بـ DI (Hilt، Koin)، حيث تتم إدارة نطاق Singleton (Scope.Singleton أو @Singleton) بواسطة الحاوية بينما تبقى الفئة قابلة للاختبار.

Thread safety: dispatch_once و synchronized و lock

Thread safety — متطلب أساسي لـ Singleton في البيئات متعددة الخيوط. بدون مزامنة، يمكن لخيطين التحقق في وقت واحد من instance == null وإنشاء مثيلين. الحل هو القفل أثناء الإنشاء الأول والتحرير بعد التهيئة. في Swift، الخصائص الثابتة (static let) آمنة للخيوط افتراضياً. في Kotlin، object آمن للخيوط. لنمط Java في Kotlin، يُستخدم synchronized أو @Volatile + double-check locking.

اللغةالآليةالأمان للخيوطالتهيئة البطيئة
Swiftstatic letdispatch_once (تلقائي)نعم، عند أول وصول
Kotlin objectإعلان objectمُهيئ الفئة آمن للخيوطنعم، عند أول وصول
Kotlin companionsynchronized + @VolatileDouble-checked lockingنعم، عبر lazy أو synchronized
Javasynchronized + volatileDouble-checked lockingنعم، في getInstance()

Double-checked locking — نمط للتهيئة البطيئة لـ Singleton. الفحص الأول بدون مزامنة (سريع إذا كان المثيل موجوداً بالفعل)، والثاني داخل synchronized (الإنشاء بواسطة خيط واحد فقط). @Volatile يضمن رؤية التغييرات لجميع الخيوط. بدون volatile، قد يرى خيط آخر كائناً منشأً جزئياً. في Kotlin، مفوض lazy مع LazyThreadSafetyMode.SYNCHRONIZED ينفذ تلقائياً double-checked locking.

Singleton vs Dependency Injection: متى نستخدم

Dependency Injection — بديل لـ Singleton لإدارة المثيلات الفردية. حاوية DI (Dagger، Hilt، Koin، Swinject) تنشئ الكائن مرة واحدة في نطاق Singleton وتحقنه عبر المُنشئ. الفئة لا تعرف عن حالة Singleton — الحاوية تقرر. يصبح الكود قابلاً للاختبار: يتم استبدال وحدة DI بوحدة mock في الاختبارات. مزايا DI: تبعيات صريحة في المُنشئ، قابلية التجاوز، دورة حياة موحدة.

متى يكون Singleton مبرراً — كائنات على مستوى النظام: Crashlytics، Analytics، Logging. هذه الخدمات تُهيأ مرة واحدة في AppDelegate/Application وتُستخدم في كل مكان. DI مبالغ فيه لها. Singleton أيضاً مناسب لمخابئ الصور (NSCache، Coil، Glide)، حيث الوصول العام مبرر بالأداء. لكل شيء آخر، DI هو المفضل: يجعل التبعيات مرئية، يبسط الاختبار وإعادة الهيكلة.

النهج المختلط — Singleton مع إمكانية التجاوز للاختبارات. في Swift، بروتوكول + خاصية ثابتة يمكن للاختبارات استبدالها (مثلاً عبر URLProtocol لـ URLSession). في Kotlin، فئة مفتوحة مع خاصية قابلة للحقن، حيث تضع الاختبارات mock عبر الانعكاس أو setter. هذا النهج يحافظ على بساطة Singleton لكنه يوفر إمكانيات الاختبار. توصي Google بـ Hilt لـ Android، Apple لا تفرض DI لـ iOS — الاختيار يعتمد على الفريق.

الأسئلة الشائعة

هل Singleton هو نمط معاكس؟

لا، Singleton هو نمط GoF، لكن استخدامه غير الصحيح المتكرر يحوله إلى النمط المعاكس Global State. Singleton مبرر للموارد الفريدة فيزيائياً (شاشة، طابعة، نظام ملفات). تظهر المشاكل عندما يُستخدم Singleton لإدارة البيانات: تبعيات خفية، تعقيد الاختبار، انتهاك مبدأ المسؤولية الواحدة. البديل الحديث هو DI مع نطاق Singleton.

كيف نختبر الكود الذي يستخدم Singleton؟

ثلاثة طرق: (1) عبر بروتوكول — Singleton ينفذ بروتوكولاً، الاختبارات تستبدل التنفيذ؛ (2) عبر DI — Singleton يُحقن كتبعية عبر المُنشئ؛ (3) عبر طريقة reset — Singleton لديه طريقة لإعادة تعيين الحالة في الاختبارات (فقط لبناءات الاختبار). الطريقة الأولى مفضلة، الثالثة خطيرة للإنتاج. Swift يسمح باستبدال الخاصية shared عبر التلاعب في وقت التشغيل في الاختبارات.

كيف يختلف Kotlin object عن Java Singleton؟

Kotlin object هو بناء لغوي ينشئ Singleton على مستوى البايت كود. على عكس تنفيذ Java بمُنشئ خاص و getInstance()، يضمن object الأمان للخيوط، التهيئة البطيئة، ويمنع الوراثة. Java Singleton يتطلب مزامنة يدوية (synchronized) و volatile للعمل بشكل صحيح في البيئات متعددة الخيوط. Kotlin object هو الطريقة الأكثر أماناً وإيجازاً في Android.

هل يمكن توريث Singleton؟

توريث Singleton يكسر النمط: إذا كان من الممكن توريث فئة Singleton، يمكن لفئة فرعية إنشاء مثيل ثانٍ، منتهكاً التفرد. في Swift، final class يمنع الوراثة. Kotlin object لا يمكن توريثه (object sealed). إذا كان Singleton مع تنوع مطلوباً، استخدم حاوية DI مع نطاق Singleton: تضمن مثيلاً واحداً وتدعم الوراثة عبر الواجهات.

كيف نمرر معاملات إلى Singleton في Android؟

تُمرر المعاملات عبر init(context: Application) أو getInstance(param). Kotlin object لا يقبل معاملات — استخدم companion object مع طريقة مصنع getInstance(param). Hilt يحل المشكلة: @Singleton + @Inject constructor(context: Application) — حاوية DI تحقن سياق Application تلقائياً. لعميل Retrofit، تُمرر المعاملات (baseUrl، interceptors) عبر builder في وحدة DI.

ملخص

  • Singleton — نمط بمثيل واحد ووصول عام
  • Swift shared — static let مع أمان للخيوط مضمون من المترجم
  • Kotlin object — تهيئة بطيئة بدون كود إضافي
  • Thread safety — double-checked locking لـ Java، تلقائي لـ Swift/Kotlin
  • SDK Apple — UIApplication.shared، UserDefaults.standard، FileManager.default
  • SDK Android — Retrofit، Room، SharedPreferences عبر مديري Singleton
  • البدائل — Dependency Injection لكود قابل للاختبار

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا