2014-06-07 1 views
3

Pour diverses raisons, j'ai besoin d'associer un format numérique par défaut (un NSNumberFormatter) à des objets NSNumber. Je veux soutenir ce même pour les objets créés en dehors de mon contrôle, donc plutôt que de créer une sous-classe de NSNumber, je l'ai utilisé une catégorie et la ObjC associated object functionality pour y parvenir: Comment créer une catégorie avec une propriété pour les objets NSNumber

@interface NSNumber (defaultNumberFormat) 
@property (nonatomic,strong) NSNumberFormatter *defaultNumberFormat; 
@end 

@implementation NSNumber (defaultNumberFormat) 

@dynamic defaultNumberFormat; 

- (void)setDefaultNumberFormat:(NSNumberFormatter *)format { 
    [self willChangeValueForKey:@"defaultNumberFormat"]; 
    objc_setAssociatedObject(self, @selector(defaultNumberFormat), format, OBJC_ASSOCIATION_COPY_NONATOMIC); 
    [self didChangeValueForKey:@"defaultNumberFormat"]; 
} 

- (NSNumberFormatter *)defaultNumberFormat { 
    return objc_getAssociatedObject(self, @selector(defaultNumberFormat)); 
} 
@end 

Cela fonctionne bien sous 32 Compilation de bits, cependant sous iOS7 pour les cibles 64 bits, pour certaines valeurs, il se bloque avec EXC_BAD_ACCESS (code = EXC_I386_GPFLT) à l'appel obj_setAssociatedObject. Il s'avère que la raison est iOS utilise des pointeurs étiquetés pour les cibles 64 bits pour les objets sélectionnés avec de petites valeurs pour améliorer les performances (évite le besoin d'allocation de mémoire et de nettoyage ARC pour l'objet). Cela inclut certaines valeurs NSString, NSNumber et NSDate. Voir plus d'informations here et here.

Alors, comment obtenez-vous une catégorie avec une propriété pour NSNumber en mode 64 bits?

Répondre

1

Ma première tentative pour résoudre cela a essayé de gérer l'association manuellement par exception lorsqu'un objet pointeur étiqueté a été détecté. Cela fonctionne, mais n'était pas une solution pratique, puisque vous perdez tous les avantages d'ARC pour la valeur associée (ils ne sont pas nettoyés lorsque l'objet pointeur marqué n'est plus utilisé).

Au lieu de cela, une solution viable rend la lecture seule de la propriété, puis utilise un accesseur séparé pour transformer l'objet de pointeur marqué dans un objet normal avant d'appliquer l'objet associé:

@interface NSNumber (defaultNumberFormat) 
@property (nonatomic,strong,readonly) NSNumberFormatter *defaultNumberFormat; 
- (NSNumber *)applyDefaultNumberFormat:(NSNumberFormatter *)format; 
@end 

@implementation NSNumber (defaultNumberFormat) 

@dynamic defaultNumberFormat; 

- (NSNumber *)applyDefaultNumberFormat:(NSNumberFormatter *)format { 
    NSNumber *newNumber = self; 
#ifdef WORKAROUND_IOS_TAGGED_POINTER_ISSUE 
    unsigned long ptrValue = (unsigned long)self; 
    if (ptrValue & 0x1) { 
     // We have a non-aligned pointer - ie a tagged short-cut object stored inside the pointer. objc_setAssociatedObject() is broken for these object types (it will cause memory access faults). 
     // Transform ourselves into a non-tagged object. 
     newNumber = [NSDecimalNumber decimalNumberWithDecimal:[self decimalValue]]; 
     ptrValue = (unsigned long)newNumber; 
     if (ptrValue & 0x1 || ![newNumber isKindOfClass:[NSNumber class]]) { 
      NSLog(@"Failed to create a non-tagged NSNumber for number: %@ - hence default number format not set", [self description]); 
      return self; 
     } 
    } 
#endif 
    [newNumber willChangeValueForKey:@"defaultNumberFormat"]; 
    objc_setAssociatedObject(newNumber, @selector(defaultNumberFormat), format, OBJC_ASSOCIATION_COPY_NONATOMIC); 
    [newNumber didChangeValueForKey:@"defaultNumberFormat"]; 
    return newNumber; 
}  

- (NSNumberFormatter *)defaultNumberFormat { 
#ifdef WORKAROUND_IOS_TAGGED_POINTER_ISSUE 
    unsigned long ptrValue = (unsigned long)self; 
    if (ptrValue & 0x1) 
     return nil; 
#endif 
    return objc_getAssociatedObject(self, @selector(defaultNumberFormat)); 
} 

@end 
Questions connexes