Les Catégories en Objective-C
Les catégories en Objective-C, composées de fichiers d'en-tête (.h) et d'implémentation (.m), permettent d'injecter dynamiquement des méthodes dans une classe existante sans altérer son code source initial. Elles sont particulièrement utiles pour déclarer des méthodes privées, scinder des classes devenues trop volumineuses, ou exposer des API internes d'un framework.
Caractéristiques principales :
- Résolution à l'exécution (Runtime) : Les méthodes ne sont pas attachées directement à la classe hôte lors de la compilation, mais injectées via le runtime. C'est cette résolution tardive qui empêche l'ajout direct de nouvelles variables d'instance.
- Extension des classes système : Il est tout à fait possible d'ajouter des catégories aux classes fournies par le système (comme
NSStringouUIView).
Bien qu'il soit impossible d'ajouter des variables d'instance directement, il est possible de simuler des propriétés en utilisant les objets associés du runtime pour générer manuellement les accesseurs (getters et setters).
/*
Politiques d'association disponibles :
OBJC_ASSOCIATION_ASSIGN, OBJC_ASSOCIATION_COPY_NONATOMIC,
OBJC_ASSOCIATION_RETAIN_NONATOMIC, etc.
*/
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
static const char kVehicleModelKey;
@implementation Vehicle (Metadata)
- (void)setModelName:(NSString *)modelName {
objc_setAssociatedObject(self, &kVehicleModelKey, modelName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)modelName {
return objc_getAssociatedObject(self, &kVehicleModelKey);
}
@end
Règles de priorité et conflits : Une méthode définie dans une catégorie peut masquer une méthode de la classe originale. Si plusieurs catégories définissent une méthode avec le même nom, c'est l'implémentation de la dernière catégorie compilée qui sera utilisée. Attention, déclarer plusieurs catégories avec le même nom provoquera une erreur de compilation.
Voici un exemple courant où l'on étend une classe système pour faciliter la conversion de chaînes hexadécimales en couleurs :
@interface UIColor (HexString)
+ (UIColor *)colorFromHexString:(NSString *)hexString;
@end
@implementation UIColor (HexString)
+ (UIColor *)colorFromHexString:(NSString *)hexString {
unsigned int hexValue = 0;
NSScanner *scanner = [NSScanner scannerWithString:hexString];
if ([hexString hasPrefix:@"#"]) {
[scanner setScanLocation:1];
}
[scanner scanHexInt:&hexValue];
CGFloat r = ((hexValue >> 16) & 0xFF) / 255.0;
CGFloat g = ((hexValue >> 8) & 0xFF) / 255.0;
CGFloat b = (hexValue & 0xFF) / 255.0;
CGFloat a = ((hexValue >> 24) & 0xFF) / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}
@end
Les Extensions en Objective-C
Les extensions peuvent être vues comme des catégories anonymes. Contrairement aux catégories, elles sont résolues à la compilation. Elles sont généralement déclarées directement dans le fichier d'implémentation (.m) de la classe hôte, agissant comme une zone privée.
Utilisations et limites :
- Déclarer des propriétés, des méthodes et des variables d'instance strictement privées, invisibles pour les sous-classes et l'extérieur.
- Elles ne peuvent en aucun cas être appliquées aux classes du système.
Les Exetnsions en Swift
Swift abandonne le concept de catégories au profit d'un système d'extension unifié. Une extension permet d'ajouter des fonctionnalités à une classe, une structure, une énumération ou un protocole existant, sans même avoir besoin d'accéder à leur code source (ce qu'on appelle parfois la modélisation inverse). Les extensions Swift sont anonymes et ne permettent pas de redéfinir (override) des fonctionnalités existantes.
Une extension Swift permet de :
- Ajouter des propriétés calculées (instances et types).
- Définir de nouvelles méthodes d'instance et de type.
- Fournir de nouveaux initialiseurs.
- Définir des souscripteurs (subscripts).
- Définir et utiliser de nouveaux types imbriqués.
- Rendre un type existant conforme à un ou plusieurs protocoles.
Toute instance d'un type bénéficie des nouvelles fonctionnalités ajoutées par extension, y compris les instances créées avant la définition de l'extension elle-même. Comme en Objective-C, l'ajout de propriétés stockées nécessite l'utilisation d'objets associés, et les observateurs de propriétés ne sont pas supportés dans ce contexte.
import ObjectiveC
private var kIsLoadingKey: UInt8 = 0
extension UIViewController {
var isLoadingState: Bool {
get {
return (objc_getAssociatedObject(self, &kIsLoadingKey) as? Bool) ?? false
}
set {
objc_setAssociatedObject(self, &kIsLoadingKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
Syntaxe de base :
extension CustomType {
// Ajout de nouvelles fonctionnalités
}
extension CustomType: ProtocolA, ProtocolB {
// Implémentations requises par les protocoles
}
1. Ajout d'initialisateurs personnalisés :
extension CGSize {
init(ratio: CGFloat, baseWidth: CGFloat) {
let calculatedHeight = baseWidth / ratio
self.init(width: baseWidth, height: calculatedHeight)
}
}
2. Ajout de méthodes d'instance :
extension String {
func duplicated(times: Int) -> String {
var result = ""
for _ in 0..<times result="" return="" self=""></times>
3. Méthodes mutantes (mutating) :
extension Double {
mutating func halve() {
self = self / 2.0
}
}
var measurement = 10.0
measurement.halve()
// measurement vaut maintenant 5.0
4. Ajout de souscripteurs (subscripts) :
extension Array {
subscript(safe index: Int) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
5. Ajout de types imbriqués :
extension String {
enum ContentValidation {
case empty, valid, invalidFormat
}
var validationStatus: ContentValidation {
if self.isEmpty { return .empty }
let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
return emailPredicate.evaluate(with: self) ? .valid : .invalidFormat
}
}