Les propriétés en programmation orientée objet

La façon la plus simple de donner à vos objets les infos dont ils ont besoin.

Les propriétés sont des valeurs associées aux classes, aux structures (structs), et aux énumérations. Elles permettent de stocker et de manipuler des données. Les propriétés peuvent être classées en différentes catégories selon leur comportement et leur utilisation.

1. Propriétés Stockées (Stored Properties)

Les propriétés stockées sont des propriétés qui stockent des valeurs et font partie de l'instance d'une classe ou d'une struct. Elles peuvent être variables (var) ou constantes (let).

struct Person {
    var name: String       // Propriété stockée variable
    let birthYear: Int     // Propriété stockée constante
}

var person1 = Person(name: "Alice", birthYear: 1990)
person1.name = "Bob"  // Autorisé car 'name' est une variable
// person1.birthYear = 1991  // Erreur car 'birthYear' est une constante

2. Propriétés Calculées (Computed Properties)

Les propriétés calculées ne stockent pas de valeur, mais calculent une valeur chaque fois qu'elles sont accédées. Elles sont définies en utilisant les mots-clés get et, optionnellement, set.

struct Rectangle {
    var width: Double
    var height: Double
    var area: Double {
        return width * height  // Propriété calculée pour l'aire
    }
    var perimeter: Double {
        get {
            return 2 * (width + height)
        }
        set(newPerimeter) {
            width = newPerimeter / 2 - height
        }
    }
}

var rect = Rectangle(width: 5.0, height: 10.0)
print(rect.area)  // Affiche : 50.0
rect.perimeter = 30.0
print(rect.width)  // Affiche : 5.0

3. Propriétés de Type (Type Properties)

Les propriétés de type sont associées à la classe ou à la struct elle-même, plutôt qu'à une instance particulière. Elles sont définies avec le mot-clé static pour les structs et les classes, et avec class pour les propriétés de classe variables (permettant l'override dans les sous-classes).

struct Circle {
    static var pi = 3.14159  // Propriété de type stockée
    var radius: Double
    var circumference: Double {
        return 2 * Circle.pi * radius  // Utilisation d'une propriété de type
    }
}

let circle = Circle(radius: 5.0)
print(circle.circumference)  // Affiche : 31.4159

Les classes peuvent aussi utiliser class au lieu de static pour permettre aux sous-classes de remplacer la propriété :

class SomeClass {
    class var description: String {
        return "Ceci est une classe de base"
    }
}

class SubClass: SomeClass {
    override class var description: String {
        return "Ceci est une sous-classe"
    }
}

print(SomeClass.description)  // Affiche : "Ceci est une classe de base"
print(SubClass.description)  // Affiche : "Ceci est une sous-classe"

4. Propriétés de Classe (Class-Only Properties)

Contrairement aux structs, les classes peuvent avoir des propriétés lazy, weak, et unowned, qui permettent un contrôle plus précis sur la gestion de la mémoire.

a. Propriétés paresseuses (Lazy Properties)

Les propriétés paresseuses (lazy) sont initialisées uniquement lorsque la propriété est accédée pour la première fois. Elles sont définies avec le mot-clé lazy.

class DataManager {
    lazy var data = loadData()  // Propriété paresseuse, initialisée à la première utilisation
    func loadData() -> [String] {
        // Simule une opération coûteuse
        return ["Data1", "Data2", "Data3"]
    }
}

let manager = DataManager()
// La méthode `loadData` n'est appelée que lorsque `data` est accédée
print(manager.data)  // Affiche : ["Data1", "Data2", "Data3"]

b. Propriétés faibles (Weak Properties)

Les propriétés faibles (weak) ne conservent pas une forte référence à l'objet auquel elles sont liées. Elles sont utilisées pour éviter les cycles de rétention et doivent toujours être optionnelles.

class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class Apartment {
    var number: Int
    weak var tenant: Person?  // Propriété faible pour éviter un cycle de rétention
    init(number: Int) {
        self.number = number
    }
}

var john: Person? = Person(name: "John")
var apartment: Apartment? = Apartment(number: 101)
apartment?.tenant = john
john = nil  // La référence à 'john' est libérée
print(apartment?.tenant?.name ?? "No tenant")  // Affiche : "No tenant"

c. Propriétés non possédées (Unowned Properties)

Les propriétés non possédées (unowned) sont semblables aux propriétés faibles, mais elles ne sont pas optionnelles. Elles supposent que la propriété ne sera jamais nil après avoir été initialisée.

class Customer {
    var name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
}

class CreditCard {
    var number: Int
    unowned var owner: Customer  // Propriété non possédée
    init(number: Int, owner: Customer) {
        self.number = number
        self.owner = owner
    }
}

var john: Customer? = Customer(name: "John")
john?.card = CreditCard(number: 123456789012, owner: john!)
print(john?.card?.owner.name ?? "No owner")  // Affiche : "John"

5. Observateurs de Propriété (Property Observers)

Les observateurs de propriété permettent d'exécuter du code avant ou après la modification d'une propriété. Il y a deux types d'observateurs : willSet (avant le changement) et didSet (après le changement).

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("À propos de définir totalSteps à \(newTotalSteps)")
        }
        didSet {
            print("Ajout de \(totalSteps - oldValue) pas")
        }
    }
}

let stepCounter = StepCounter()
stepCounter.totalSteps = 100  // Affiche "À propos de définir totalSteps à 100" puis "Ajout de 100 pas"
stepCounter.totalSteps = 200  // Affiche "À propos de définir totalSteps à 200" puis "Ajout de 100 pas"