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;

Singleton — δομικό μοτίβο που περιγράφηκε από την GoF (Gang of Four) το 1994. Το μοτίβο λύνει δύο προβλήματα: περιορίζει τη δημιουργία στιγμιότυπου κλάσης σε ένα αντικείμενο και παρέχει καθολική πρόσβαση σε αυτό το αντικείμενο. Το Singleton είναι χρήσιμο για πόρους που πρέπει να είναι μοναδικοί: εργοστάσιο συνεδριών, κρυφή μνήμη εικόνων, διαχειριστής σύνδεσης βάσης δεδομένων, πελάτης Crashlytics ή Analytics.

Υλοποίηση Singleton απαιτεί ιδιωτικό κατασκευαστή (απαγορεύει εξωτερική δημιουργία), στατικό πεδίο με μοναδικό στιγμιότυπο και στατική μέθοδο πρόσβασης (shared, instance, getInstance). Οι πελάτες καλούν Singleton.shared.method() χωρίς να ανησυχούν για τη δημιουργία αντικειμένου. Το μοτίβο είναι δημοφιλές σε iOS και Android: URLSession.shared, UserDefaults.standard, FirebaseApp.sharedInstance — όλα είναι Singleton. Ωστόσο, η υπερβολική χρήση Singleton οδηγεί στο αντι-μοτίβο Global State.

Προβλήματα Singleton — κρυφές εξαρτήσεις (κλάσεις εξαρτώνται σιωπηρά από το αντικείμενο Singleton), δυσκολία δοκιμής (δεν μπορεί να αντικατασταθεί το στιγμιότυπο σε δοκιμή χωρίς επιπλέον προσπάθεια), παραβίαση της Single Responsibility Principle (το 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 {
    // Thread-safe 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 δηλώνει μια κλάση-singleton με τεμπέλικη αρχικοποίηση κατά την πρώτη πρόσβαση. Το 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 { /* ... */ }
}

Android SDK Singleton — πολλές υπηρεσίες συστήματος 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 objectObject declarationΑρχικοποιητής κλάσης ασφαλής για νήματαΝαι, κατά την πρώτη πρόσβαση
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 — ανοικτή κλάση με injectable ιδιότητα, όπου η δοκιμή ορίζει mock μέσω αντανάκλασης ή setter. Αυτή η προσέγγιση διατηρεί την απλότητα του Singleton, αλλά παρέχει εργαλεία για δοκιμή. Η Google συνιστά Hilt για Android, η Apple δεν επιβάλλει DI για iOS — η επιλογή εξαρτάται από την ομάδα.

Συχνές Ερωτήσεις

Είναι το Singleton αντι-μοτίβο;

Όχι, το Singleton είναι μοτίβο GoF, αλλά η συχνή λανθασμένη χρήση του το μετατρέπει σε αντι-μοτίβο Global State. Το Singleton δικαιολογείται για φυσικά μοναδικούς πόρους (οθόνη, εκτυπωτής, σύστημα αρχείων). Προβλήματα προκύπτουν όταν το Singleton χρησιμοποιείται για διαχείριση δεδομένων: κρυφές εξαρτήσεις, δυσκολία δοκιμής, παραβίαση της Single Responsibility Principle. Σύγχρονη εναλλακτική — DI με εύρος Singleton.

Πώς να δοκιμάσουμε κώδικα που χρησιμοποιεί Singleton;

Τρεις προσεγγίσεις: (1) μέσω πρωτοκόλλου — το Singleton υλοποιεί ένα πρωτόκολλο, οι δοκιμές αντικαθιστούν την υλοποίηση; (2) μέσω DI — το Singleton εισάγεται ως εξάρτηση μέσω κατασκευαστή; (3) μέσω μεθόδου reset — το Singleton έχει μέθοδο για επαναφορά κατάστασης σε δοκιμές (μόνο για δοκιμαστική έκδοση). Η πρώτη προσέγγιση είναι προτιμότερη, η τρίτη — επικίνδυνη για παραγωγή. Το Swift επιτρέπει την αντικατάσταση της shared ιδιότητας μέσω χειρισμών χρόνου εκτέλεσης σε δοκιμές.

Πώς διαφέρει το Kotlin object από το Java Singleton;

Kotlin object — γλωσσική κατασκευή που δημιουργεί Singleton σε επίπεδο bytecode. Σε αντίθεση με την υλοποίηση 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
  • Apple SDK — UIApplication.shared, UserDefaults.standard, FileManager.default
  • Android SDK — Retrofit, Room, SharedPreferences μέσω διαχειριστών Singleton
  • Εναλλακτικές — Dependency Injection για δοκιμάσιμο κώδικα

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης